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/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/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); + } +} 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 }; } }