From 46369353edb534fff3d7fe91c4999e64ba44538c Mon Sep 17 00:00:00 2001 From: Isaac Chai Date: Wed, 21 Feb 2024 14:54:32 +0000 Subject: [PATCH 1/2] Adding Settings preference for single finger panning feature Test: Locally tested on device + MagnificationOneFingerPanningPreferenceControllerTest Bug: 282039824 Change-Id: I1d1a649060cba862c8f333e6e76184fade2dcdce --- res/values/strings.xml | 8 + ...nOneFingerPanningPreferenceController.java | 102 ++++++++++ ...ScreenMagnificationPreferenceFragment.java | 19 ++ ...FingerPanningPreferenceControllerTest.java | 187 ++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 7ae9074242a..8b0cd67d331 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4760,6 +4760,14 @@ Cancel Magnification settings + + One-finger panning + + Move the magnification area by dragging one finger. + + Move the magnification area by dragging two fingers. Magnify with shortcut diff --git a/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java b/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java new file mode 100644 index 00000000000..a2ce948994d --- /dev/null +++ b/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceController.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; + +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; +import androidx.preference.TwoStatePreference; + +import com.android.server.accessibility.Flags; +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +public class MagnificationOneFingerPanningPreferenceController + extends TogglePreferenceController { + static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED; + + @Nullable + private TwoStatePreference mSwitchPreference; + + @VisibleForTesting + final boolean mDefaultValue; + + public MagnificationOneFingerPanningPreferenceController(Context context) { + super(context, PREF_KEY); + boolean defaultValue; + try { + defaultValue = context.getResources().getBoolean( + com.android.internal.R.bool.config_enable_a11y_magnification_single_panning); + } catch (Resources.NotFoundException e) { + defaultValue = false; + } + mDefaultValue = defaultValue; + } + + @Override + public int getAvailabilityStatus() { + return (Flags.enableMagnificationOneFingerPanningGesture()) + ? AVAILABLE : DISABLED_FOR_USER; + } + + @Override + public boolean isChecked() { + return Settings.Secure.getInt( + mContext.getContentResolver(), + PREF_KEY, + (mDefaultValue) ? ON : OFF) == ON; + } + + @Override + public boolean setChecked(boolean isChecked) { + var toReturn = Settings.Secure.putInt(mContext.getContentResolver(), + PREF_KEY, + (isChecked ? ON : OFF)); + if (mSwitchPreference != null) { + refreshSummary(mSwitchPreference); + } + return toReturn; + } + + @Override + public CharSequence getSummary() { + return (isChecked()) + ? mContext.getString( + R.string.accessibility_magnification_one_finger_panning_summary_on) + : mContext.getString( + R.string.accessibility_magnification_one_finger_panning_summary_off); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_accessibility; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mSwitchPreference = screen.findPreference(getPreferenceKey()); + refreshSummary(mSwitchPreference); + } +} diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index d9baa03d177..985d45d89c2 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -200,6 +200,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); generalCategory.addPreference(mSettingsPreference); + addOneFingerPanningSetting(generalCategory); final MagnificationModePreferenceController magnificationModePreferenceController = new MagnificationModePreferenceController(getContext(), MagnificationModePreferenceController.PREF_KEY); @@ -283,6 +284,24 @@ public class ToggleScreenMagnificationPreferenceFragment extends addPreferenceController(alwaysOnPreferenceController); } + private void addOneFingerPanningSetting(PreferenceCategory generalCategory) { + if (!Flags.enableMagnificationOneFingerPanningGesture()) { + return; + } + + var oneFingerPanningPreference = new SwitchPreferenceCompat(getPrefContext()); + oneFingerPanningPreference.setTitle( + R.string.accessibility_magnification_one_finger_panning_title); + oneFingerPanningPreference.setKey( + MagnificationOneFingerPanningPreferenceController.PREF_KEY); + generalCategory.addPreference(oneFingerPanningPreference); + + var oneFingerPanningPreferenceController = + new MagnificationOneFingerPanningPreferenceController(getContext()); + oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen()); + addPreferenceController(oneFingerPanningPreferenceController); + } + private void addJoystickSetting(PreferenceCategory generalCategory) { if (!DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_WINDOW_MANAGER, diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java new file mode 100644 index 00000000000..326a7a04a20 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationOneFingerPanningPreferenceControllerTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.server.accessibility.Flags.enableMagnificationOneFingerPanningGesture; +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_FOR_USER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.server.accessibility.Flags; +import com.android.settings.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class MagnificationOneFingerPanningPreferenceControllerTest { + private static final String ONE_FINGER_PANNING_KEY = + Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED; + + @Rule public final SetFlagsRule mSetFlagsRule = + new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + + private final Context mContext = ApplicationProvider.getApplicationContext(); + private final SwitchPreference mSwitchPreference = spy(new SwitchPreference(mContext)); + private final MagnificationOneFingerPanningPreferenceController mController = + new MagnificationOneFingerPanningPreferenceController(mContext); + + private PreferenceScreen mScreen; + + @Before + public void setUp() { + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + mScreen = preferenceManager.createPreferenceScreen(mContext); + mSwitchPreference.setKey(MagnificationOneFingerPanningPreferenceController.PREF_KEY); + mScreen.addPreference(mSwitchPreference); + mController.displayPreference(mScreen); + } + + @After + public void cleanup() { + // Can't use resetToDefaults as it NPE with + // "Cannot invoke "android.content.IContentProvider.call" + Settings.Secure.putInt( + mContext.getContentResolver(), + MagnificationOneFingerPanningPreferenceController.PREF_KEY, + (mController.mDefaultValue) ? ON : OFF); + } + + @Test + public void displayPreference_defaultState_correctSummarySet() { + assertThat(mSwitchPreference.getSummary()) + .isEqualTo(mContext.getString( + R.string.accessibility_magnification_one_finger_panning_summary_off)); + } + + @Test + public void getAvailabilityStatus_defaultState_disabled() { + int status = mController.getAvailabilityStatus(); + + assertThat(status).isEqualTo(DISABLED_FOR_USER); + } + + @Test + public void getAvailabilityStatus_featureFlagEnabled_enabled() { + enableFlag(); + + int status = mController.getAvailabilityStatus(); + + assertThat(status).isEqualTo(AVAILABLE); + } + + @Test + public void isChecked_defaultState_returnFalse() { + assertThat(mController.isChecked()).isFalse(); + assertThat(mSwitchPreference.isChecked()).isFalse(); + } + + @Test + public void isChecked_settingsEnabled_returnTrue() { + Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, ON); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_settingsDisabled_returnTrue() { + Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, OFF); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void setChecked_enabled_enabledSummarySet() { + mController.setChecked(true); + + assertThat(mSwitchPreference.getSummary()).isEqualTo(enabledSummary()); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void setChecked_disabled_disabledSummarySet() { + mController.setChecked(false); + + assertThat(mController.isChecked()).isFalse(); + assertThat(mSwitchPreference.getSummary()).isEqualTo(disabledSummary()); + } + + @Test + public void getSummary_disable_disableSummaryTextUsed() { + Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, OFF); + + var summary = mController.getSummary(); + + assertThat(summary).isEqualTo(disabledSummary()); + } + + @Test + public void getSummary_enable_enabledSummaryTextUsed() { + Settings.Secure.putInt(mContext.getContentResolver(), ONE_FINGER_PANNING_KEY, ON); + + var summary = mController.getSummary(); + + assertThat(summary).isEqualTo(enabledSummary()); + } + + @Test + public void performClick_switchDefaultState_shouldReturnTrue() { + enableFlag(); + + mSwitchPreference.performClick(); + + verify(mSwitchPreference).setChecked(true); + assertThat(mController.isChecked()).isTrue(); + assertThat(mSwitchPreference.isChecked()).isTrue(); + } + + private void enableFlag() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE); + assertThat(enableMagnificationOneFingerPanningGesture()).isTrue(); + // This ensures that preference change listeners are added correctly. + mController.displayPreference(mScreen); + } + + private String enabledSummary() { + return mContext.getString( + R.string.accessibility_magnification_one_finger_panning_summary_on); + } + + private String disabledSummary() { + return mContext.getString( + R.string.accessibility_magnification_one_finger_panning_summary_off); + } +} From 8ca4f47292847ac48d7aacd09c7c64e99868324f Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Tue, 5 Mar 2024 16:28:33 +0000 Subject: [PATCH 2/2] Return the correct position for the profile tab The existing code returns the fixed tab values, which doesn't work now that the ordering is determined by the user manager's ordering. Bug: 302278487 Test: manual Test: atest ProfileSelectFragmentTest Change-Id: I87b393b8e12e21dc1b38ee75bb43ca9133785c81 --- .../ProfileSelectFragment.java | 18 +++++++++++- .../ProfileSelectFragmentTest.java | 29 +++++++++++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java index 8279588cd1d..53a55542848 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java @@ -230,7 +230,8 @@ public abstract class ProfileSelectFragment extends DashboardFragment { if (bundle != null) { final int extraTab = bundle.getInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, -1); if (extraTab != -1) { - return ((ViewPagerAdapter) mViewPager.getAdapter()).getTabForPosition(extraTab); + return ((ViewPagerAdapter) mViewPager.getAdapter()) + .getPositionForProfileTab(extraTab); } final int userId = bundle.getInt(EXTRA_USER_ID, UserHandle.SYSTEM.getIdentifier()); final boolean isWorkProfile = UserManager.get(activity).isManagedProfile(userId); @@ -410,7 +411,22 @@ public abstract class ProfileSelectFragment extends DashboardFragment { } @ProfileType int profileType = mChildFragments[position].getArguments().getInt(EXTRA_PROFILE); + return profileTypeToTab(profileType); + } + private int getPositionForProfileTab(int profileTab) { + for (int i = 0; i < mChildFragments.length; ++i) { + Bundle arguments = mChildFragments[i].getArguments(); + if (arguments != null + && profileTypeToTab(arguments.getInt(EXTRA_PROFILE)) == profileTab) { + return i; + } + } + Log.e(TAG, "position requested for an unknown profile tab " + profileTab); + return 0; + } + + private int profileTypeToTab(@ProfileType int profileType) { if (profileType == ProfileType.WORK) { return WORK_TAB; } diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java index 17e0d1ca63c..b6af0f908cc 100644 --- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectFragmentTest.java @@ -118,7 +118,9 @@ public class ProfileSelectFragmentTest { profileSelectFragment.setViewPager(viewPager); mFragmentManager.beginTransaction().add(profileSelectFragment, "tag"); - assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(WORK_TAB); + // The expected position '2' comes from the order in which fragments are added in + // TestProfileSelectFragment#getFragments() + assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(2); } @Test @@ -136,7 +138,9 @@ public class ProfileSelectFragmentTest { profileSelectFragment.setViewPager(viewPager); mFragmentManager.beginTransaction().add(profileSelectFragment, "tag"); - assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(PRIVATE_TAB); + // The expected position '1' comes from the order in which fragments are added in + // TestProfileSelectFragment#getFragments() + assertThat(mFragment.getTabId(mActivity, bundle)).isEqualTo(1); } @Test @@ -343,10 +347,25 @@ public class ProfileSelectFragmentTest { @Override public Fragment[] getFragments() { + Fragment personalFragment = new SettingsPreferenceFragmentTest.TestFragment(); + Bundle personalBundle = new Bundle(); + personalBundle.putInt(EXTRA_PROFILE, ProfileType.PERSONAL); + personalFragment.setArguments(personalBundle); + + Fragment workFragment = new SettingsPreferenceFragmentTest.TestFragment(); + Bundle workBundle = new Bundle(); + workBundle.putInt(EXTRA_PROFILE, ProfileType.WORK); + workFragment.setArguments(workBundle); + + Fragment privateFragment = new SettingsPreferenceFragmentTest.TestFragment(); + Bundle privateBundle = new Bundle(); + privateBundle.putInt(EXTRA_PROFILE, ProfileType.PRIVATE); + privateFragment.setArguments(privateBundle); + return new Fragment[]{ - new SettingsPreferenceFragmentTest.TestFragment(), //0 - new SettingsPreferenceFragmentTest.TestFragment(), - new SettingsPreferenceFragmentTest.TestFragment() + personalFragment, //0 + privateFragment, + workFragment }; } }