From 20d1a7d1b66ff4870990758882cea0e89484ab24 Mon Sep 17 00:00:00 2001 From: Yongshun Liu Date: Wed, 12 Feb 2025 10:28:11 +0000 Subject: [PATCH] a11y: Add cursor following mode dialog This a pure UI change that adds a new magnification cursor following mode dialog behind a flag. The framework support will be added separately later. There are 3 modes as the following: - continuous mode - center mode - edge mode It also renames magnification mode dialog xml file for general purpose within accessibility. NO_IFTTT=linter not working Bug: b/388335935 Flag: com.android.settings.accessibility.enable_magnification_cursor_following_dialog Test: SettingsRoboTests:com.android.settings.accessibility.ToggleScreenMagnificationPreferenceFragmentTest && SettingsRoboTests:com.android.settings.accessibility.MagnificationModePreferenceControllerTest && SettingsRoboTests:com.android.settings.accessibility.MagnificationCursorFollowingModePreferenceControllerTest Change-Id: If2672186faf7443cc210d79630b1ea4f3808d7e4 --- ...er.xml => accessibility_dialog_header.xml} | 2 +- res/values/strings.xml | 10 + .../AccessibilityDialogUtils.java | 5 + ...rsorFollowingModePreferenceController.java | 221 ++++++++++++++++++ ...MagnificationModePreferenceController.java | 8 +- ...ScreenMagnificationPreferenceFragment.java | 55 +++++ ...FollowingModePreferenceControllerTest.java | 200 ++++++++++++++++ ...enMagnificationPreferenceFragmentTest.java | 23 +- 8 files changed, 520 insertions(+), 4 deletions(-) rename res/layout/{accessibility_magnification_mode_header.xml => accessibility_dialog_header.xml} (92%) create mode 100644 src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java diff --git a/res/layout/accessibility_magnification_mode_header.xml b/res/layout/accessibility_dialog_header.xml similarity index 92% rename from res/layout/accessibility_magnification_mode_header.xml rename to res/layout/accessibility_dialog_header.xml index e4765535f27..ace8b239d0e 100644 --- a/res/layout/accessibility_magnification_mode_header.xml +++ b/res/layout/accessibility_dialog_header.xml @@ -21,9 +21,9 @@ android:padding="?android:attr/dialogPreferredPadding"> diff --git a/res/values/strings.xml b/res/values/strings.xml index eecbd79a502..1a904ade6c9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5217,6 +5217,16 @@ Magnification Magnification shortcut + + Cursor following + + Choose how Magnification follows your cursor. + + Move screen continuously as mouse moves + + Move screen keeping mouse at center of screen + + Move screen when mouse touches edges of screen Magnify typing diff --git a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java index c89b8d7122a..dc4900861a2 100644 --- a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java +++ b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java @@ -104,6 +104,11 @@ public class AccessibilityDialogUtils { * screen / Switch between full and partial screen > Save. */ int DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING = 1011; + + /** + * OPEN: Settings > Accessibility > Magnification > Cursor following. + */ + int DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE = 1012; } /** diff --git a/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java b/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java new file mode 100644 index 00000000000..d217ead007f --- /dev/null +++ b/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2025 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 android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.DialogInterface; +import android.provider.Settings; +import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.core.util.Preconditions; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.DialogCreatable; +import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; +import com.android.settings.core.BasePreferenceController; + +import java.util.ArrayList; +import java.util.List; + +/** + * Controller that shows the magnification cursor following mode and the preference click behavior. + */ +public class MagnificationCursorFollowingModePreferenceController extends + BasePreferenceController implements DialogCreatable { + static final String PREF_KEY = + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE; + + private static final String TAG = + MagnificationCursorFollowingModePreferenceController.class.getSimpleName(); + + private final List mModeList = new ArrayList<>(); + @Nullable + private DialogHelper mDialogHelper; + @VisibleForTesting + @Nullable + ListView mModeListView; + @Nullable + private Preference mModePreference; + + public MagnificationCursorFollowingModePreferenceController(@NonNull Context context, + @NonNull String preferenceKey) { + super(context, preferenceKey); + initModeList(); + } + + public void setDialogHelper(@NonNull DialogHelper dialogHelper) { + mDialogHelper = dialogHelper; + } + + private void initModeList() { + mModeList.add(new ModeInfo(mContext.getString( + R.string.accessibility_magnification_cursor_following_continuous), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS)); + mModeList.add(new ModeInfo( + mContext.getString(R.string.accessibility_magnification_cursor_following_center), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER)); + mModeList.add(new ModeInfo( + mContext.getString(R.string.accessibility_magnification_cursor_following_edge), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE)); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @NonNull + @Override + public CharSequence getSummary() { + return getCursorFollowingModeSummary(getCurrentMagnificationCursorFollowingMode()); + } + + @Override + public void displayPreference(@NonNull PreferenceScreen screen) { + super.displayPreference(screen); + mModePreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public boolean handlePreferenceTreeClick(@NonNull Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) || mModePreference == null) { + return super.handlePreferenceTreeClick(preference); + } + + Preconditions.checkNotNull(mDialogHelper).showDialog( + DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE); + return true; + } + + @NonNull + @Override + public Dialog onCreateDialog(int dialogId) { + Preconditions.checkArgument( + dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE, + "This only handles cursor following mode dialog"); + return createMagnificationCursorFollowingModeDialog(); + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + Preconditions.checkArgument( + dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE, + "This only handles cursor following mode dialog"); + return SettingsEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING; + } + + @NonNull + private Dialog createMagnificationCursorFollowingModeDialog() { + mModeListView = AccessibilityDialogUtils.createSingleChoiceListView(mContext, mModeList, + /* itemListener= */null); + final View headerView = LayoutInflater.from(mContext).inflate( + R.layout.accessibility_dialog_header, mModeListView, + /* attachToRoot= */false); + final TextView textView = Preconditions.checkNotNull(headerView.findViewById( + R.id.accessibility_dialog_header_text_view)); + textView.setText( + mContext.getString(R.string.accessibility_magnification_cursor_following_header)); + textView.setVisibility(View.VISIBLE); + mModeListView.addHeaderView(headerView, /* data= */null, /* isSelectable= */false); + final int selectionIndex = computeSelectionIndex(); + if (selectionIndex != AdapterView.INVALID_POSITION) { + mModeListView.setItemChecked(selectionIndex, /* value= */true); + } + final CharSequence title = mContext.getString( + R.string.accessibility_magnification_cursor_following_title); + final CharSequence positiveBtnText = mContext.getString(R.string.save); + final CharSequence negativeBtnText = mContext.getString(R.string.cancel); + return AccessibilityDialogUtils.createCustomDialog(mContext, title, mModeListView, + positiveBtnText, + this::onMagnificationCursorFollowingModeDialogPositiveButtonClicked, + negativeBtnText, /* negativeListener= */null); + } + + void onMagnificationCursorFollowingModeDialogPositiveButtonClicked( + DialogInterface dialogInterface, int which) { + ListView listView = Preconditions.checkNotNull(mModeListView); + final int selectionIndex = listView.getCheckedItemPosition(); + if (selectionIndex == AdapterView.INVALID_POSITION) { + Log.w(TAG, "Selected positive button with INVALID_POSITION index"); + return; + } + ModeInfo cursorFollowingMode = (ModeInfo) listView.getItemAtPosition(selectionIndex); + if (cursorFollowingMode != null) { + Preconditions.checkNotNull(mModePreference).setSummary( + getCursorFollowingModeSummary(cursorFollowingMode.mMode)); + Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY, + cursorFollowingMode.mMode); + } + } + + private int computeSelectionIndex() { + ListView listView = Preconditions.checkNotNull(mModeListView); + @AccessibilityMagnificationCursorFollowingMode + final int currentMode = getCurrentMagnificationCursorFollowingMode(); + for (int i = 0; i < listView.getCount(); i++) { + final ModeInfo mode = (ModeInfo) listView.getItemAtPosition(i); + if (mode != null && mode.mMode == currentMode) { + return i; + } + } + return AdapterView.INVALID_POSITION; + } + + @NonNull + private CharSequence getCursorFollowingModeSummary( + @AccessibilityMagnificationCursorFollowingMode int cursorFollowingMode) { + int stringId = switch (cursorFollowingMode) { + case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER -> + R.string.accessibility_magnification_cursor_following_center; + case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE -> + R.string.accessibility_magnification_cursor_following_edge; + default -> + R.string.accessibility_magnification_cursor_following_continuous; + }; + return mContext.getString(stringId); + } + + private @AccessibilityMagnificationCursorFollowingMode int + getCurrentMagnificationCursorFollowingMode() { + return Settings.Secure.getInt(mContext.getContentResolver(), PREF_KEY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS); + } + + static class ModeInfo extends ItemInfoArrayAdapter.ItemInfo { + @AccessibilityMagnificationCursorFollowingMode + public final int mMode; + + ModeInfo(@NonNull CharSequence title, + @AccessibilityMagnificationCursorFollowingMode int mode) { + super(title, /* summary= */null, /* drawableId= */null); + mMode = mode; + } + } +} diff --git a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java index 71ea4c23958..93cb23b7e2a 100644 --- a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java +++ b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java @@ -176,8 +176,12 @@ public class MagnificationModePreferenceController extends BasePreferenceControl mContext, mModeInfos, this::onMagnificationModeSelected); final View headerView = LayoutInflater.from(mContext).inflate( - R.layout.accessibility_magnification_mode_header, - getMagnificationModesListView(), /* attachToRoot= */false); + R.layout.accessibility_dialog_header, getMagnificationModesListView(), + /* attachToRoot= */false); + final TextView textView = Preconditions.checkNotNull(headerView.findViewById( + R.id.accessibility_dialog_header_text_view)); + textView.setText( + mContext.getString(R.string.accessibility_magnification_area_settings_message)); getMagnificationModesListView().addHeaderView(headerView, /* data= */null, /* isSelectable= */false); diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index 8b525079bef..71c95c0c7bf 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -93,6 +93,8 @@ public class ToggleScreenMagnificationPreferenceFragment extends private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; @Nullable private DialogCreatable mMagnificationModeDialogDelegate; + @Nullable + private DialogCreatable mMagnificationCursorFollowingModeDialogDelegate; @Nullable MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController; @@ -104,6 +106,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends mMagnificationModeDialogDelegate = delegate; } + @VisibleForTesting + public void setMagnificationCursorFollowingModeDialogDelegate( + @NonNull DialogCreatable delegate) { + mMagnificationCursorFollowingModeDialogDelegate = delegate; + } + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -186,6 +194,9 @@ public class ToggleScreenMagnificationPreferenceFragment extends case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING: return Preconditions.checkNotNull(mMagnificationModeDialogDelegate) .onCreateDialog(dialogId); + case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE: + return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate) + .onCreateDialog(dialogId); case DialogEnums.GESTURE_NAVIGATION_TUTORIAL: return AccessibilityShortcutsTutorial .showAccessibilityGestureTutorialDialog(getPrefContext()); @@ -201,6 +212,11 @@ public class ToggleScreenMagnificationPreferenceFragment extends PackageManager.FEATURE_WINDOW_MAGNIFICATION); } + private static boolean isMagnificationCursorFollowingModeDialogSupported() { + // TODO(b/398066000): Hide the setting when no pointer device exists for most form factors. + return com.android.settings.accessibility.Flags.enableMagnificationCursorFollowingDialog(); + } + @Override protected void initSettingsPreference() { final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); @@ -213,6 +229,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends addJoystickSetting(generalCategory); // LINT.ThenChange(:search_data) } + addCursorFollowingSetting(generalCategory); addFeedbackSetting(generalCategory); } @@ -286,6 +303,31 @@ public class ToggleScreenMagnificationPreferenceFragment extends addPreferenceController(magnificationModePreferenceController); } + private static Preference createCursorFollowingPreference(Context context) { + final Preference pref = new Preference(context); + pref.setTitle(R.string.accessibility_magnification_cursor_following_title); + pref.setKey(MagnificationCursorFollowingModePreferenceController.PREF_KEY); + pref.setPersistent(false); + return pref; + } + + private void addCursorFollowingSetting(PreferenceCategory generalCategory) { + if (!isMagnificationCursorFollowingModeDialogSupported()) { + return; + } + + generalCategory.addPreference(createCursorFollowingPreference(getPrefContext())); + + final MagnificationCursorFollowingModePreferenceController controller = + new MagnificationCursorFollowingModePreferenceController( + getContext(), + MagnificationCursorFollowingModePreferenceController.PREF_KEY); + controller.setDialogHelper(/* dialogHelper= */this); + mMagnificationCursorFollowingModeDialogDelegate = controller; + controller.displayPreference(getPreferenceScreen()); + addPreferenceController(controller); + } + private static Preference createFollowTypingPreference(Context context) { final Preference pref = new SwitchPreferenceCompat(context); pref.setTitle(R.string.accessibility_screen_magnification_follow_typing_title); @@ -510,6 +552,9 @@ public class ToggleScreenMagnificationPreferenceFragment extends case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING: return Preconditions.checkNotNull(mMagnificationModeDialogDelegate) .getDialogMetricsCategory(dialogId); + case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE: + return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate) + .getDialogMetricsCategory(dialogId); case DialogEnums.GESTURE_NAVIGATION_TUTORIAL: return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION; case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL: @@ -667,6 +712,11 @@ public class ToggleScreenMagnificationPreferenceFragment extends return rawData; } + // Add all preferences to search raw data so that they are included in + // indexing, which happens infrequently. Irrelevant preferences should be + // hidden from the live returned search results by `getNonIndexableKeys`, + // which is called every time a search occurs. This allows for dynamic search + // entries that hide or show depending on current device state. rawData.add(createShortcutPreferenceSearchData(context)); Stream.of( createMagnificationModePreference(context), @@ -674,6 +724,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends createOneFingerPanningPreference(context), createAlwaysOnPreference(context), createJoystickPreference(context), + createCursorFollowingPreference(context), createFeedbackPreference(context) ) .forEach(pref -> @@ -714,6 +765,10 @@ public class ToggleScreenMagnificationPreferenceFragment extends } } + if (!isMagnificationCursorFollowingModeDialogSupported()) { + niks.add(MagnificationCursorFollowingModePreferenceController.PREF_KEY); + } + if (!Flags.enableLowVisionHats()) { niks.add(MagnificationFeedbackPreferenceController.PREF_KEY); } diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java new file mode 100644 index 00000000000..42efdfe6784 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2025 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.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.provider.Settings; +import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode; +import android.text.TextUtils; +import android.widget.AdapterView; +import android.widget.ListView; + +import androidx.annotation.NonNull; +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.DialogCreatable; +import com.android.settings.R; +import com.android.settings.accessibility.MagnificationCursorFollowingModePreferenceController.ModeInfo; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link MagnificationCursorFollowingModePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class MagnificationCursorFollowingModePreferenceControllerTest { + private static final String PREF_KEY = + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE; + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + @Spy + private TestDialogHelper mDialogHelper = new TestDialogHelper(); + + private PreferenceScreen mScreen; + private Context mContext; + private MagnificationCursorFollowingModePreferenceController mController; + private Preference mModePreference; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat); + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + mScreen = preferenceManager.createPreferenceScreen(mContext); + mModePreference = new Preference(mContext); + mModePreference.setKey(PREF_KEY); + mScreen.addPreference(mModePreference); + mController = new MagnificationCursorFollowingModePreferenceController(mContext, PREF_KEY); + mController.setDialogHelper(mDialogHelper); + mDialogHelper.setDialogDelegate(mController); + showPreferenceOnTheScreen(); + } + + private void showPreferenceOnTheScreen() { + mController.displayPreference(mScreen); + } + + @AccessibilityMagnificationCursorFollowingMode + private int getCheckedModeFromDialog() { + final ListView listView = mController.mModeListView; + assertThat(listView).isNotNull(); + + final int checkedPosition = listView.getCheckedItemPosition(); + assertWithMessage("No mode is checked").that(checkedPosition) + .isNotEqualTo(AdapterView.INVALID_POSITION); + + final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(checkedPosition); + return modeInfo.mMode; + } + + private void performItemClickWith(@AccessibilityMagnificationCursorFollowingMode int mode) { + final ListView listView = mController.mModeListView; + assertThat(listView).isNotNull(); + + int modeIndex = AdapterView.NO_ID; + for (int i = 0; i < listView.getAdapter().getCount(); i++) { + final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(i); + if (modeInfo != null && modeInfo.mMode == mode) { + modeIndex = i; + break; + } + } + assertWithMessage("The mode could not be found").that(modeIndex) + .isNotEqualTo(AdapterView.NO_ID); + + listView.performItemClick(listView.getChildAt(modeIndex), modeIndex, modeIndex); + } + + @Test + public void clickPreference_defaultMode_selectionIsDefault() { + mController.handlePreferenceTreeClick(mModePreference); + + assertThat(getCheckedModeFromDialog()).isEqualTo( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS); + } + + @Test + public void clickPreference_nonDefaultMode_selectionIsExpected() { + Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER); + + mController.handlePreferenceTreeClick(mModePreference); + + assertThat(getCheckedModeFromDialog()).isEqualTo( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER); + } + + @Test + public void selectItemInDialog_selectionIsExpected() { + mController.handlePreferenceTreeClick(mModePreference); + + performItemClickWith( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE); + + assertThat(getCheckedModeFromDialog()).isEqualTo( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE); + } + + @Test + public void selectItemInDialog_dismissWithoutSave_selectionNotPersists() { + mController.handlePreferenceTreeClick(mModePreference); + + performItemClickWith( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE); + + showPreferenceOnTheScreen(); + + mController.handlePreferenceTreeClick(mModePreference); + + assertThat(getCheckedModeFromDialog()).isEqualTo( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS); + assertThat(TextUtils.equals(mController.getSummary(), mContext.getString( + R.string.accessibility_magnification_cursor_following_continuous))).isTrue(); + } + + @Test + public void selectItemInDialog_saveAndDismiss_selectionPersists() { + mController.handlePreferenceTreeClick(mModePreference); + + performItemClickWith( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE); + mController.onMagnificationCursorFollowingModeDialogPositiveButtonClicked( + mDialogHelper.getDialog(), DialogInterface.BUTTON_POSITIVE); + + showPreferenceOnTheScreen(); + + mController.handlePreferenceTreeClick(mModePreference); + + assertThat(getCheckedModeFromDialog()).isEqualTo( + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE); + assertThat(TextUtils.equals(mController.getSummary(), mContext.getString( + R.string.accessibility_magnification_cursor_following_edge))).isTrue(); + } + + private static class TestDialogHelper implements DialogHelper { + private DialogCreatable mDialogDelegate; + private Dialog mDialog; + + @Override + public void showDialog(int dialogId) { + mDialog = mDialogDelegate.onCreateDialog(dialogId); + } + + public void setDialogDelegate(@NonNull DialogCreatable delegate) { + mDialogDelegate = delegate; + } + + public Dialog getDialog() { + return mDialog; + } + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java index 3c136f04356..6407c081aea 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java @@ -613,6 +613,24 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { verify(dialogDelegate).getDialogMetricsCategory(dialogId); } + @Test + @EnableFlags(com.android.settings.accessibility.Flags + .FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG) + public void onCreateDialog_setCursorFollowingModeDialogDelegate_invokeDialogDelegate() { + ToggleScreenMagnificationPreferenceFragment fragment = + mFragController.create( + R.id.main_content, /* bundle= */ null).start().resume().get(); + final DialogCreatable dialogDelegate = mock(DialogCreatable.class, RETURNS_DEEP_STUBS); + final int dialogId = DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE; + when(dialogDelegate.getDialogMetricsCategory(anyInt())).thenReturn(dialogId); + fragment.setMagnificationCursorFollowingModeDialogDelegate(dialogDelegate); + + fragment.onCreateDialog(dialogId); + fragment.getDialogMetricsCategory(dialogId); + verify(dialogDelegate).onCreateDialog(dialogId); + verify(dialogDelegate).getDialogMetricsCategory(dialogId); + } + @Test public void getMetricsCategory_returnsCorrectCategory() { ToggleScreenMagnificationPreferenceFragment fragment = @@ -826,6 +844,7 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { MagnificationOneFingerPanningPreferenceController.PREF_KEY, MagnificationAlwaysOnPreferenceController.PREF_KEY, MagnificationJoystickPreferenceController.PREF_KEY, + MagnificationCursorFollowingModePreferenceController.PREF_KEY, MagnificationFeedbackPreferenceController.PREF_KEY); final List rawData = ToggleScreenMagnificationPreferenceFragment @@ -881,7 +900,9 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { @EnableFlags({ com.android.settings.accessibility.Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH, Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE, - Flags.FLAG_ENABLE_LOW_VISION_HATS}) + Flags.FLAG_ENABLE_LOW_VISION_HATS, + com.android.settings.accessibility.Flags + .FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG}) public void getNonIndexableKeys_hasShortcutAndAllFeaturesEnabled_allItemsSearchable() { mShadowAccessibilityManager.setAccessibilityShortcutTargets( TRIPLETAP, List.of(MAGNIFICATION_CONTROLLER_NAME));