• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.accessibility;
18 
19 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
20 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
21 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
22 import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
23 import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
24 import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList;
25 
26 import android.app.Dialog;
27 import android.app.settings.SettingsEnums;
28 import android.content.ComponentName;
29 import android.content.ContentResolver;
30 import android.content.Context;
31 import android.content.pm.PackageManager;
32 import android.icu.text.MessageFormat;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.provider.DeviceConfig;
36 import android.provider.Settings;
37 import android.text.TextUtils;
38 import android.view.InputDevice;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.accessibility.AccessibilityManager;
43 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
44 
45 import androidx.annotation.NonNull;
46 import androidx.annotation.Nullable;
47 import androidx.annotation.StringRes;
48 import androidx.core.util.Preconditions;
49 import androidx.preference.Preference;
50 import androidx.preference.PreferenceCategory;
51 import androidx.preference.SwitchPreferenceCompat;
52 
53 import com.android.internal.accessibility.util.ShortcutUtils;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.server.accessibility.Flags;
56 import com.android.settings.DialogCreatable;
57 import com.android.settings.R;
58 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
59 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
60 import com.android.settings.search.BaseSearchIndexProvider;
61 import com.android.settingslib.core.AbstractPreferenceController;
62 import com.android.settingslib.search.Indexable;
63 import com.android.settingslib.search.SearchIndexable;
64 import com.android.settingslib.search.SearchIndexableRaw;
65 import com.android.settingslib.widget.IllustrationPreference;
66 import com.android.settingslib.widget.SettingsThemeHelper;
67 
68 import com.google.android.setupcompat.util.WizardManagerHelper;
69 import com.google.android.setupdesign.util.ThemeHelper;
70 
71 import java.util.ArrayList;
72 import java.util.List;
73 import java.util.Set;
74 import java.util.stream.Stream;
75 
76 /**
77  * Fragment that shows the actual UI for providing basic magnification accessibility service setup
78  * and does not have toggle bar to turn on service to use.
79  */
80 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
81 public class ToggleScreenMagnificationPreferenceFragment extends
82         ToggleFeaturePreferenceFragment implements DialogHelper {
83 
84     private static final String TAG =
85             ToggleScreenMagnificationPreferenceFragment.class.getSimpleName();
86     @VisibleForTesting
87     static final String KEY_MAGNIFICATION_SHORTCUT_PREFERENCE = "magnification_shortcut_preference";
88     private static final char COMPONENT_NAME_SEPARATOR = ':';
89     private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
90             new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
91 
92     // TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util.
93     private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
94     @Nullable
95     private DialogCreatable mMagnificationModeDialogDelegate;
96     @Nullable
97     private DialogCreatable mMagnificationCursorFollowingModeDialogDelegate;
98 
99     @Nullable
100     MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController;
101 
102     private boolean mInSetupWizard;
103 
104     @VisibleForTesting
setMagnificationModeDialogDelegate(@onNull DialogCreatable delegate)105     public void setMagnificationModeDialogDelegate(@NonNull DialogCreatable delegate) {
106         mMagnificationModeDialogDelegate = delegate;
107     }
108 
109     @VisibleForTesting
setMagnificationCursorFollowingModeDialogDelegate( @onNull DialogCreatable delegate)110     public void setMagnificationCursorFollowingModeDialogDelegate(
111             @NonNull DialogCreatable delegate) {
112         mMagnificationCursorFollowingModeDialogDelegate = delegate;
113     }
114 
115     @Override
onCreate(@ullable Bundle savedInstanceState)116     public void onCreate(@Nullable Bundle savedInstanceState) {
117         super.onCreate(savedInstanceState);
118         getActivity().setTitle(R.string.accessibility_screen_magnification_title);
119         mInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
120     }
121 
122     @NonNull
123     @Override
onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @NonNull Bundle savedInstanceState)124     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
125             @NonNull Bundle savedInstanceState) {
126         mFeatureName = getString(R.string.accessibility_screen_magnification_title);
127         final boolean useExpressiveTheme = WizardManagerHelper.isAnySetupWizard(getIntent())
128                 ? ThemeHelper.shouldApplyGlifExpressiveStyle(getPrefContext())
129                 : SettingsThemeHelper.isExpressiveTheme(getPrefContext());
130         mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
131                 .authority(getPrefContext().getPackageName())
132                 .appendPath(String.valueOf(useExpressiveTheme
133                         ? R.raw.accessibility_magnification_banner_expressive
134                         : R.raw.a11y_magnification_banner))
135                 .build();
136         mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
137             mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
138         };
139 
140         final View view = super.onCreateView(inflater, container, savedInstanceState);
141         updateFooterPreference();
142         return view;
143     }
144 
updateFooterPreference()145     private void updateFooterPreference() {
146         final String title = getPrefContext().getString(
147                 R.string.accessibility_screen_magnification_about_title);
148         final String learnMoreText = getPrefContext().getString(
149                 R.string.accessibility_screen_magnification_footer_learn_more_content_description);
150         mFooterPreferenceController.setIntroductionTitle(title);
151         mFooterPreferenceController.setupHelpLink(getHelpResource(), learnMoreText);
152         mFooterPreferenceController.displayPreference(getPreferenceScreen());
153     }
154 
155     @Override
onResume()156     public void onResume() {
157         super.onResume();
158         final IllustrationPreference illustrationPreference =
159                 getPreferenceScreen().findPreference(KEY_ANIMATED_IMAGE);
160         if (illustrationPreference != null) {
161             illustrationPreference.applyDynamicColor();
162         }
163 
164         final AccessibilityManager am = getPrefContext().getSystemService(
165                 AccessibilityManager.class);
166         am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
167     }
168 
169     @Override
getPreferenceScreenResId()170     protected int getPreferenceScreenResId() {
171         // TODO(b/171272809): Add back when controllers move to static type
172         return 0;
173     }
174 
175     @Override
getLogTag()176     protected String getLogTag() {
177         return TAG;
178     }
179 
180     @Override
onPause()181     public void onPause() {
182         final AccessibilityManager am = getPrefContext().getSystemService(
183                 AccessibilityManager.class);
184         am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
185 
186         super.onPause();
187     }
188 
189     @NonNull
190     @Override
onCreateDialog(int dialogId)191     public Dialog onCreateDialog(int dialogId) {
192         switch (dialogId) {
193             case DialogEnums.DIALOG_MAGNIFICATION_MODE:
194             case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
195                 return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
196                         .onCreateDialog(dialogId);
197             case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
198                 return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
199                         .onCreateDialog(dialogId);
200             case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
201                 return AccessibilityShortcutsTutorial
202                         .showAccessibilityGestureTutorialDialog(getPrefContext());
203             default:
204                 return super.onCreateDialog(dialogId);
205         }
206     }
207 
isWindowMagnificationSupported(Context context)208     private static boolean isWindowMagnificationSupported(Context context) {
209         return context.getResources().getBoolean(
210                 com.android.internal.R.bool.config_magnification_area)
211                 && context.getPackageManager().hasSystemFeature(
212                 PackageManager.FEATURE_WINDOW_MAGNIFICATION);
213     }
214 
isMagnificationCursorFollowingModeDialogSupported()215     private static boolean isMagnificationCursorFollowingModeDialogSupported() {
216         return com.android.settings.accessibility.Flags.enableMagnificationCursorFollowingDialog()
217                 && hasMouse();
218     }
219 
220     @Override
initSettingsPreference()221     protected void initSettingsPreference() {
222         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
223         if (isWindowMagnificationSupported(getContext())) {
224             // LINT.IfChange(preference_list)
225             addMagnificationModeSetting(generalCategory);
226             addFollowTypingSetting(generalCategory);
227             addOneFingerPanningSetting(generalCategory);
228             addAlwaysOnSetting(generalCategory);
229             addJoystickSetting(generalCategory);
230             // LINT.ThenChange(:search_data)
231         }
232         addCursorFollowingSetting(generalCategory);
233         addFeedbackSetting(generalCategory);
234     }
235 
236     @Override
onProcessArguments(Bundle arguments)237     protected void onProcessArguments(Bundle arguments) {
238         Context context = getContext();
239 
240         if (!arguments.containsKey(AccessibilitySettings.EXTRA_PREFERENCE_KEY)) {
241             arguments.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
242                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
243         }
244 
245         if (!arguments.containsKey(AccessibilitySettings.EXTRA_INTRO)) {
246             arguments.putCharSequence(AccessibilitySettings.EXTRA_INTRO,
247                     context.getString(R.string.accessibility_screen_magnification_intro_text));
248         }
249 
250         if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)
251                 && !Flags.enableMagnificationOneFingerPanningGesture()) {
252             String summary = "";
253             boolean hasTouchscreen = hasTouchscreen();
254             if (Flags.enableMagnificationKeyboardControl() && hasHardKeyboard()) {
255                 // Include the keyboard summary when a keyboard is plugged in.
256                 final String meta = context.getString(R.string.modifier_keys_meta);
257                 final String alt = context.getString(R.string.modifier_keys_alt);
258                 summary += MessageFormat.format(
259                         context.getString(
260                                 R.string.accessibility_screen_magnification_keyboard_summary,
261                                 meta, alt, meta, alt),
262                         new Object[]{1, 2, 3, 4});
263                 if (hasTouchscreen) {
264                     // Add a newline before the touchscreen text.
265                     summary += "<br/><br/>";
266                 }
267 
268             }
269             if (hasTouchscreen || TextUtils.isEmpty(summary)) {
270                 // Always show the touchscreen summary if there is no summary yet, even if the
271                 // touchscreen is missing.
272                 // If the keyboard summary is present and there is no touchscreen, then we can
273                 // ignore the touchscreen summary.
274                 summary += MessageFormat.format(
275                         context.getString(R.string.accessibility_screen_magnification_summary),
276                         new Object[]{1, 2, 3, 4, 5});
277             }
278             arguments.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary);
279         }
280 
281         super.onProcessArguments(arguments);
282     }
283 
createMagnificationModePreference(Context context)284     private static Preference createMagnificationModePreference(Context context) {
285         final Preference pref = new Preference(context);
286         pref.setTitle(R.string.accessibility_magnification_mode_title);
287         pref.setKey(MagnificationModePreferenceController.PREF_KEY);
288         pref.setPersistent(false);
289         return pref;
290     }
291 
addMagnificationModeSetting(PreferenceCategory generalCategory)292     private void addMagnificationModeSetting(PreferenceCategory generalCategory) {
293         mSettingsPreference = createMagnificationModePreference(getPrefContext());
294         generalCategory.addPreference(mSettingsPreference);
295 
296         final MagnificationModePreferenceController magnificationModePreferenceController =
297                 new MagnificationModePreferenceController(Preconditions.checkNotNull(getContext()),
298                         MagnificationModePreferenceController.PREF_KEY);
299         magnificationModePreferenceController.setDialogHelper(/* dialogHelper= */this);
300         mMagnificationModeDialogDelegate = magnificationModePreferenceController;
301         getSettingsLifecycle().addObserver(magnificationModePreferenceController);
302         magnificationModePreferenceController.displayPreference(getPreferenceScreen());
303         addPreferenceController(magnificationModePreferenceController);
304     }
305 
createCursorFollowingPreference(Context context)306     private static Preference createCursorFollowingPreference(Context context) {
307         final Preference pref = new Preference(context);
308         pref.setTitle(R.string.accessibility_magnification_cursor_following_title);
309         pref.setKey(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
310         pref.setPersistent(false);
311         return pref;
312     }
313 
addCursorFollowingSetting(PreferenceCategory generalCategory)314     private void addCursorFollowingSetting(PreferenceCategory generalCategory) {
315         if (!isMagnificationCursorFollowingModeDialogSupported()) {
316             return;
317         }
318 
319         generalCategory.addPreference(createCursorFollowingPreference(getPrefContext()));
320 
321         final MagnificationCursorFollowingModePreferenceController controller =
322                 new MagnificationCursorFollowingModePreferenceController(
323                         getContext(),
324                         MagnificationCursorFollowingModePreferenceController.PREF_KEY);
325         controller.setDialogHelper(/* dialogHelper= */this);
326         mMagnificationCursorFollowingModeDialogDelegate = controller;
327         controller.displayPreference(getPreferenceScreen());
328         addPreferenceController(controller);
329     }
330 
createFollowTypingPreference(Context context)331     private static Preference createFollowTypingPreference(Context context) {
332         final Preference pref = new SwitchPreferenceCompat(context);
333         pref.setTitle(R.string.accessibility_screen_magnification_follow_typing_title);
334         pref.setSummary(R.string.accessibility_screen_magnification_follow_typing_summary);
335         pref.setKey(MagnificationFollowTypingPreferenceController.PREF_KEY);
336         return pref;
337     }
338 
addFollowTypingSetting(PreferenceCategory generalCategory)339     private void addFollowTypingSetting(PreferenceCategory generalCategory) {
340         generalCategory.addPreference(createFollowTypingPreference(getPrefContext()));
341 
342         var followTypingPreferenceController = new MagnificationFollowTypingPreferenceController(
343                 getContext(), MagnificationFollowTypingPreferenceController.PREF_KEY);
344         followTypingPreferenceController.setInSetupWizard(mInSetupWizard);
345         followTypingPreferenceController.displayPreference(getPreferenceScreen());
346         addPreferenceController(followTypingPreferenceController);
347     }
348 
isAlwaysOnSupported(Context context)349     private static boolean isAlwaysOnSupported(Context context) {
350         final boolean defaultValue = context.getResources().getBoolean(
351                 com.android.internal.R.bool.config_magnification_always_on_enabled);
352 
353         return DeviceConfig.getBoolean(
354                 DeviceConfig.NAMESPACE_WINDOW_MANAGER,
355                 "AlwaysOnMagnifier__enable_always_on_magnifier",
356                 defaultValue
357         );
358     }
359 
createAlwaysOnPreference(Context context)360     private static Preference createAlwaysOnPreference(Context context) {
361         final Preference pref = new SwitchPreferenceCompat(context);
362         pref.setTitle(R.string.accessibility_screen_magnification_always_on_title);
363         pref.setSummary(R.string.accessibility_screen_magnification_always_on_summary);
364         pref.setKey(MagnificationAlwaysOnPreferenceController.PREF_KEY);
365         return pref;
366     }
367 
addAlwaysOnSetting(PreferenceCategory generalCategory)368     private void addAlwaysOnSetting(PreferenceCategory generalCategory) {
369         if (!isAlwaysOnSupported(getContext())) {
370             return;
371         }
372 
373         final Preference pref = createAlwaysOnPreference(getPrefContext());
374         generalCategory.addPreference(pref);
375 
376         var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController(
377                 getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY);
378         alwaysOnPreferenceController.setInSetupWizard(mInSetupWizard);
379         getSettingsLifecycle().addObserver(alwaysOnPreferenceController);
380         alwaysOnPreferenceController.displayPreference(getPreferenceScreen());
381         addPreferenceController(alwaysOnPreferenceController);
382     }
383 
createOneFingerPanningPreference(Context context)384     private static Preference createOneFingerPanningPreference(Context context) {
385         final Preference pref = new SwitchPreferenceCompat(context);
386         pref.setTitle(R.string.accessibility_magnification_one_finger_panning_title);
387         pref.setKey(MagnificationOneFingerPanningPreferenceController.PREF_KEY);
388         return pref;
389     }
390 
isOneFingerPanningSupported()391     private static boolean isOneFingerPanningSupported() {
392         return Flags.enableMagnificationOneFingerPanningGesture();
393     }
394 
addOneFingerPanningSetting(PreferenceCategory generalCategory)395     private void addOneFingerPanningSetting(PreferenceCategory generalCategory) {
396         if (!isOneFingerPanningSupported()) {
397             return;
398         }
399 
400         final Preference pref = createOneFingerPanningPreference(getPrefContext());
401         generalCategory.addPreference(pref);
402 
403         mOneFingerPanningPreferenceController =
404                 new MagnificationOneFingerPanningPreferenceController(getContext());
405         mOneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard);
406         getSettingsLifecycle().addObserver(mOneFingerPanningPreferenceController);
407         mOneFingerPanningPreferenceController.displayPreference(getPreferenceScreen());
408         addPreferenceController(mOneFingerPanningPreferenceController);
409     }
410 
createJoystickPreference(Context context)411     private static Preference createJoystickPreference(Context context) {
412         final Preference pref = new SwitchPreferenceCompat(context);
413         pref.setTitle(R.string.accessibility_screen_magnification_joystick_title);
414         pref.setSummary(R.string.accessibility_screen_magnification_joystick_summary);
415         pref.setKey(MagnificationJoystickPreferenceController.PREF_KEY);
416         return pref;
417     }
418 
createFeedbackPreference(Context context)419     private static Preference createFeedbackPreference(Context context) {
420         final Preference pref = new Preference(context);
421         pref.setTitle(R.string.accessibility_feedback_title);
422         pref.setSummary(R.string.accessibility_feedback_summary);
423         pref.setKey(MagnificationFeedbackPreferenceController.PREF_KEY);
424         return pref;
425     }
426 
isJoystickSupported()427     private static boolean isJoystickSupported() {
428         return DeviceConfig.getBoolean(
429                 DeviceConfig.NAMESPACE_WINDOW_MANAGER,
430                 "MagnificationJoystick__enable_magnification_joystick",
431                 false);
432     }
433 
addJoystickSetting(PreferenceCategory generalCategory)434     private void addJoystickSetting(PreferenceCategory generalCategory) {
435         if (!isJoystickSupported()) {
436             return;
437         }
438 
439         final Preference pref = createJoystickPreference(getPrefContext());
440         generalCategory.addPreference(pref);
441 
442         MagnificationJoystickPreferenceController joystickPreferenceController =
443                 new MagnificationJoystickPreferenceController(
444                         getContext(),
445                         MagnificationJoystickPreferenceController.PREF_KEY
446                 );
447         joystickPreferenceController.setInSetupWizard(mInSetupWizard);
448         joystickPreferenceController.displayPreference(getPreferenceScreen());
449         addPreferenceController(joystickPreferenceController);
450     }
451 
addFeedbackSetting(PreferenceCategory generalCategory)452     private void addFeedbackSetting(PreferenceCategory generalCategory) {
453         if (!Flags.enableLowVisionHats()) {
454             return;
455         }
456 
457         final Preference feedbackPreference = createFeedbackPreference(getPrefContext());
458         generalCategory.addPreference(feedbackPreference);
459 
460         final MagnificationFeedbackPreferenceController magnificationFeedbackPreferenceController =
461                 new MagnificationFeedbackPreferenceController(getContext(), this,
462                         MagnificationFeedbackPreferenceController.PREF_KEY);
463         magnificationFeedbackPreferenceController.setInSetupWizard(mInSetupWizard);
464         magnificationFeedbackPreferenceController.displayPreference(getPreferenceScreen());
465         addPreferenceController(magnificationFeedbackPreferenceController);
466     }
467 
468     @Override
showDialog(int dialogId)469     public void showDialog(int dialogId) {
470         super.showDialog(dialogId);
471     }
472 
473     @Override
registerKeysToObserverCallback( AccessibilitySettingsContentObserver contentObserver)474     protected void registerKeysToObserverCallback(
475             AccessibilitySettingsContentObserver contentObserver) {
476         super.registerKeysToObserverCallback(contentObserver);
477 
478         var keysToObserve = List.of(
479                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED,
480                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
481                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED
482         );
483         contentObserver.registerKeysToObserverCallback(keysToObserve,
484                 key -> updatePreferencesState());
485 
486         if (Flags.enableMagnificationOneFingerPanningGesture()) {
487             contentObserver.registerKeysToObserverCallback(
488                     List.of(Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED),
489                     key -> updateHtmlTextPreference());
490         }
491     }
492 
updatePreferencesState()493     private void updatePreferencesState() {
494         final List<AbstractPreferenceController> controllers = new ArrayList<>();
495         getPreferenceControllers().forEach(controllers::addAll);
496         controllers.forEach(controller -> controller.updateState(
497                 findPreference(controller.getPreferenceKey())));
498     }
499 
500     @Override
getCurrentHtmlDescription()501     CharSequence getCurrentHtmlDescription() {
502         CharSequence origin = super.getCurrentHtmlDescription();
503         if (!TextUtils.isEmpty(origin)) {
504             // If in ToggleFeaturePreferenceFragment we already have a fixed html description, we
505             // should use the fixed one, otherwise we'll dynamically decide the description.
506             return origin;
507         }
508 
509         Context context = getContext();
510         if (mOneFingerPanningPreferenceController != null && context != null) {
511             @StringRes int resId = mOneFingerPanningPreferenceController.isChecked()
512                     ? R.string.accessibility_screen_magnification_summary_one_finger_panning_on
513                     : R.string.accessibility_screen_magnification_summary_one_finger_panning_off;
514             return MessageFormat.format(context.getString(resId), new Object[]{1, 2, 3, 4, 5});
515         }
516         return "";
517     }
518 
519     @Override
getShortcutFeatureSettingsKeys()520     protected List<String> getShortcutFeatureSettingsKeys() {
521         final List<String> shortcutKeys = super.getShortcutFeatureSettingsKeys();
522         shortcutKeys.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
523         return shortcutKeys;
524     }
525 
526     @Override
getShortcutTypeSummary(Context context)527     protected CharSequence getShortcutTypeSummary(Context context) {
528         if (!mShortcutPreference.isChecked()) {
529             return context.getText(R.string.switch_off_text);
530         }
531 
532         return getShortcutSummaryList(context,
533                 PreferredShortcuts.retrieveUserShortcutType(context,
534                         MAGNIFICATION_CONTROLLER_NAME));
535     }
536 
537     @Override
getHelpResource()538     public int getHelpResource() {
539         return R.string.help_url_magnification;
540     }
541 
542     @Override
getMetricsCategory()543     public int getMetricsCategory() {
544         // TODO: Distinguish between magnification modes
545         return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION;
546     }
547 
548     @Override
getDialogMetricsCategory(int dialogId)549     public int getDialogMetricsCategory(int dialogId) {
550         switch (dialogId) {
551             case DialogEnums.DIALOG_MAGNIFICATION_MODE:
552             case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
553                 return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
554                         .getDialogMetricsCategory(dialogId);
555             case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
556                 return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
557                         .getDialogMetricsCategory(dialogId);
558             case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
559                 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
560             case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
561                 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON;
562             default:
563                 return super.getDialogMetricsCategory(dialogId);
564         }
565     }
566 
567     @Override
getUserShortcutTypes()568     int getUserShortcutTypes() {
569         return ShortcutUtils.getEnabledShortcutTypes(
570                 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME);
571     }
572 
573     @Override
getTileComponentName()574     ComponentName getTileComponentName() {
575         return null;
576     }
577 
578     @Override
onPreferenceToggled(String preferenceKey, boolean enabled)579     protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
580         if (enabled && TextUtils.equals(
581                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
582                 preferenceKey)) {
583             showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
584         }
585         Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? ON : OFF);
586     }
587 
588     @Override
onInstallSwitchPreferenceToggleSwitch()589     protected void onInstallSwitchPreferenceToggleSwitch() {
590         mToggleServiceSwitchPreference.setVisible(false);
591     }
592 
593     @Override
onToggleClicked(@onNull ShortcutPreference preference)594     public void onToggleClicked(@NonNull ShortcutPreference preference) {
595         final int shortcutTypes = getUserPreferredShortcutTypes();
596         getPrefContext().getSystemService(AccessibilityManager.class).enableShortcutsForTargets(
597                 preference.isChecked(), shortcutTypes,
598                 Set.of(MAGNIFICATION_CONTROLLER_NAME), getPrefContext().getUserId()
599         );
600         if (preference.isChecked()) {
601             showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
602         }
603         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
604     }
605 
606     @Override
onSettingsClicked(@onNull ShortcutPreference preference)607     public void onSettingsClicked(@NonNull ShortcutPreference preference) {
608         EditShortcutsPreferenceFragment.showEditShortcutScreen(
609                 requireContext(),
610                 getMetricsCategory(),
611                 getShortcutTitle(),
612                 MAGNIFICATION_COMPONENT_NAME,
613                 getIntent());
614     }
615 
616     @Override
updateShortcutPreferenceData()617     protected void updateShortcutPreferenceData() {
618         final int shortcutTypes = ShortcutUtils.getEnabledShortcutTypes(
619                 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME);
620         if (shortcutTypes != DEFAULT) {
621             final PreferredShortcut shortcut = new PreferredShortcut(
622                     MAGNIFICATION_CONTROLLER_NAME, shortcutTypes);
623             PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
624         }
625     }
626 
627     @Override
initShortcutPreference()628     protected void initShortcutPreference() {
629         mShortcutPreference = new ShortcutPreference(getPrefContext(), null);
630         mShortcutPreference.setPersistent(false);
631         mShortcutPreference.setKey(getShortcutPreferenceKey());
632         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
633         mShortcutPreference.setOnClickCallback(this);
634         mShortcutPreference.setTitle(getShortcutTitle());
635 
636         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
637         generalCategory.addPreference(mShortcutPreference);
638     }
639 
640     @Override
getShortcutPreferenceKey()641     protected String getShortcutPreferenceKey() {
642         return KEY_MAGNIFICATION_SHORTCUT_PREFERENCE;
643     }
644 
645     @Override
getShortcutTitle()646     protected CharSequence getShortcutTitle() {
647         return getText(R.string.accessibility_screen_magnification_shortcut_title);
648     }
649 
650     @Override
updateShortcutPreference()651     protected void updateShortcutPreference() {
652         mShortcutPreference.setChecked(getUserShortcutTypes() != DEFAULT);
653         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
654     }
655 
656     /**
657      * Gets the service summary of magnification.
658      *
659      * @param context The current context.
660      */
661     @NonNull
getServiceSummary(@onNull Context context)662     public static CharSequence getServiceSummary(@NonNull Context context) {
663         // Get the user shortcut type from settings provider.
664         final int userShortcutType = ShortcutUtils.getEnabledShortcutTypes(
665                 context, MAGNIFICATION_CONTROLLER_NAME);
666         final CharSequence featureState =
667                 (userShortcutType != DEFAULT)
668                         ? context.getText(R.string.accessibility_summary_shortcut_enabled)
669                         : context.getText(R.string.generic_accessibility_feature_shortcut_off);
670         final CharSequence featureSummary = context.getText(R.string.magnification_feature_summary);
671         return context.getString(
672                 com.android.settingslib.R.string.preference_summary_default_combination,
673                 featureState, featureSummary);
674     }
675 
676     @Override
getUserPreferredShortcutTypes()677     protected int getUserPreferredShortcutTypes() {
678         return PreferredShortcuts.retrieveUserShortcutType(
679                 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME);
680     }
681 
682     /**
683      * Returns if a mouse is attached.
684      */
hasMouse()685     private static boolean hasMouse() {
686         final int[] devices = InputDevice.getDeviceIds();
687         for (int i = 0; i < devices.length; i++) {
688             InputDevice device = InputDevice.getDevice(devices[i]);
689             if (device != null && (device.getSources() & InputDevice.SOURCE_MOUSE)
690                     == InputDevice.SOURCE_MOUSE) {
691                 return true;
692             }
693         }
694         return false;
695     }
696 
hasHardKeyboard()697     private boolean hasHardKeyboard() {
698         final int[] devices = InputDevice.getDeviceIds();
699         for (int i = 0; i < devices.length; i++) {
700             InputDevice device = InputDevice.getDevice(devices[i]);
701             if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
702                 continue;
703             }
704 
705             return true;
706         }
707         return false;
708     }
709 
hasTouchscreen()710     private boolean hasTouchscreen() {
711         return getPackageManager()
712                 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
713                 || getPackageManager().hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
714     }
715 
716     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
717             new BaseSearchIndexProvider() {
718                 // LINT.IfChange(search_data)
719                 @NonNull
720                 @Override
721                 public List<SearchIndexableRaw> getRawDataToIndex(@NonNull Context context,
722                         boolean enabled) {
723                     final List<SearchIndexableRaw> rawData =
724                             super.getRawDataToIndex(context, enabled);
725 
726                     if (!com.android.settings.accessibility.Flags.fixA11ySettingsSearch()) {
727                         return rawData;
728                     }
729 
730                     // Add all preferences to search raw data so that they are included in
731                     // indexing, which happens infrequently. Irrelevant preferences should be
732                     // hidden from the live returned search results by `getNonIndexableKeys`,
733                     // which is called every time a search occurs. This allows for dynamic search
734                     // entries that hide or show depending on current device state.
735                     rawData.add(createShortcutPreferenceSearchData(context));
736                     Stream.of(
737                                     createMagnificationModePreference(context),
738                                     createFollowTypingPreference(context),
739                                     createOneFingerPanningPreference(context),
740                                     createAlwaysOnPreference(context),
741                                     createJoystickPreference(context),
742                                     createCursorFollowingPreference(context),
743                                     createFeedbackPreference(context)
744                             )
745                             .forEach(pref ->
746                                     rawData.add(createPreferenceSearchData(context, pref)));
747                     return rawData;
748                 }
749 
750                 @NonNull
751                 @Override
752                 public List<String> getNonIndexableKeys(@NonNull Context context) {
753                     final List<String> niks = super.getNonIndexableKeys(context);
754 
755                     if (!com.android.settings.accessibility.Flags.fixA11ySettingsSearch()) {
756                         return niks;
757                     }
758 
759                     if (!isWindowMagnificationSupported(context)) {
760                         niks.add(MagnificationModePreferenceController.PREF_KEY);
761                         niks.add(MagnificationFollowTypingPreferenceController.PREF_KEY);
762                         niks.add(MagnificationOneFingerPanningPreferenceController.PREF_KEY);
763                         niks.add(MagnificationAlwaysOnPreferenceController.PREF_KEY);
764                         niks.add(MagnificationJoystickPreferenceController.PREF_KEY);
765                     } else {
766                         if (!isAlwaysOnSupported(context)
767                                 // This preference's title "Keep on while switching apps" does not
768                                 // mention magnification so it may confuse users who search a term
769                                 // like "Keep on".
770                                 // So we hide it if the user has no magnification shortcut enabled.
771                                 || ShortcutUtils.getEnabledShortcutTypes(
772                                         context, MAGNIFICATION_CONTROLLER_NAME) == DEFAULT) {
773                             niks.add(MagnificationAlwaysOnPreferenceController.PREF_KEY);
774                         }
775                         if (!isOneFingerPanningSupported()) {
776                             niks.add(MagnificationOneFingerPanningPreferenceController.PREF_KEY);
777                         }
778                         if (!isJoystickSupported()) {
779                             niks.add(MagnificationJoystickPreferenceController.PREF_KEY);
780                         }
781                     }
782 
783                     if (!isMagnificationCursorFollowingModeDialogSupported()) {
784                         niks.add(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
785                     }
786 
787                     if (!Flags.enableLowVisionHats()) {
788                         niks.add(MagnificationFeedbackPreferenceController.PREF_KEY);
789                     }
790 
791                     return niks;
792                 }
793                 // LINT.ThenChange(:preference_list)
794 
795                 private SearchIndexableRaw createPreferenceSearchData(
796                         Context context, Preference pref) {
797                     final SearchIndexableRaw raw = new SearchIndexableRaw(context);
798                     raw.key = pref.getKey();
799                     raw.title = pref.getTitle().toString();
800                     return raw;
801                 }
802 
803                 private SearchIndexableRaw createShortcutPreferenceSearchData(Context context) {
804                     final SearchIndexableRaw raw = new SearchIndexableRaw(context);
805                     raw.key = KEY_MAGNIFICATION_SHORTCUT_PREFERENCE;
806                     raw.title = context.getString(
807                             R.string.accessibility_screen_magnification_shortcut_title);
808                     return raw;
809                 }
810             };
811 }
812