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.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
22 import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
23 import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
24 
25 import android.app.Dialog;
26 import android.app.settings.SettingsEnums;
27 import android.content.ComponentName;
28 import android.content.ContentResolver;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.pm.PackageManager;
32 import android.icu.text.CaseMap;
33 import android.icu.text.MessageFormat;
34 import android.net.Uri;
35 import android.os.Bundle;
36 import android.os.UserHandle;
37 import android.provider.DeviceConfig;
38 import android.provider.Settings;
39 import android.text.TextUtils;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.view.accessibility.AccessibilityManager;
44 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
45 import android.widget.CheckBox;
46 
47 import androidx.annotation.Nullable;
48 import androidx.preference.Preference;
49 import androidx.preference.PreferenceCategory;
50 import androidx.preference.SwitchPreferenceCompat;
51 import androidx.preference.TwoStatePreference;
52 
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.server.accessibility.Flags;
55 import com.android.settings.DialogCreatable;
56 import com.android.settings.R;
57 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
58 import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
59 import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
60 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
61 import com.android.settings.utils.LocaleUtils;
62 import com.android.settingslib.core.AbstractPreferenceController;
63 import com.android.settingslib.widget.IllustrationPreference;
64 
65 import com.google.android.setupcompat.util.WizardManagerHelper;
66 
67 import java.util.ArrayList;
68 import java.util.List;
69 import java.util.Locale;
70 import java.util.Set;
71 import java.util.StringJoiner;
72 
73 /**
74  * Fragment that shows the actual UI for providing basic magnification accessibility service setup
75  * and does not have toggle bar to turn on service to use.
76  */
77 public class ToggleScreenMagnificationPreferenceFragment extends
78         ToggleFeaturePreferenceFragment implements
79         MagnificationModePreferenceController.DialogHelper {
80 
81     private static final String TAG = "ToggleScreenMagnificationPreferenceFragment";
82     private static final char COMPONENT_NAME_SEPARATOR = ':';
83     private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
84             new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
85 
86     // TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util.
87     private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
88     private CheckBox mSoftwareTypeCheckBox;
89     private CheckBox mHardwareTypeCheckBox;
90     private CheckBox mTripleTapTypeCheckBox;
91     @Nullable private CheckBox mTwoFingerTripleTapTypeCheckBox;
92     private DialogCreatable mDialogDelegate;
93 
94     private boolean mInSetupWizard;
95 
96     @Override
onCreate(Bundle savedInstanceState)97     public void onCreate(Bundle savedInstanceState) {
98         super.onCreate(savedInstanceState);
99         getActivity().setTitle(R.string.accessibility_screen_magnification_title);
100         mInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
101     }
102 
103     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)104     public View onCreateView(LayoutInflater inflater, ViewGroup container,
105             Bundle savedInstanceState) {
106         mPackageName = getString(R.string.accessibility_screen_magnification_title);
107         mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
108                 .authority(getPrefContext().getPackageName())
109                 .appendPath(String.valueOf(R.raw.a11y_magnification_banner))
110                 .build();
111         mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
112             removeDialog(DialogEnums.EDIT_SHORTCUT);
113             mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
114         };
115 
116         final View view = super.onCreateView(inflater, container, savedInstanceState);
117         updateFooterPreference();
118         return view;
119     }
120 
updateFooterPreference()121     private void updateFooterPreference() {
122         final String title = getPrefContext().getString(
123                 R.string.accessibility_screen_magnification_about_title);
124         final String learnMoreText = getPrefContext().getString(
125                 R.string.accessibility_screen_magnification_footer_learn_more_content_description);
126         mFooterPreferenceController.setIntroductionTitle(title);
127         mFooterPreferenceController.setupHelpLink(getHelpResource(), learnMoreText);
128         mFooterPreferenceController.displayPreference(getPreferenceScreen());
129     }
130 
131     @Override
onResume()132     public void onResume() {
133         super.onResume();
134         final IllustrationPreference illustrationPreference =
135                 getPreferenceScreen().findPreference(KEY_ANIMATED_IMAGE);
136         if (illustrationPreference != null) {
137             illustrationPreference.applyDynamicColor();
138         }
139 
140         final AccessibilityManager am = getPrefContext().getSystemService(
141                 AccessibilityManager.class);
142         am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
143     }
144 
145     @Override
getPreferenceScreenResId()146     protected int getPreferenceScreenResId() {
147         // TODO(b/171272809): Add back when controllers move to static type
148         return 0;
149     }
150 
151     @Override
getLogTag()152     protected String getLogTag() {
153         return TAG;
154     }
155 
156     @Override
onPause()157     public void onPause() {
158         final AccessibilityManager am = getPrefContext().getSystemService(
159                 AccessibilityManager.class);
160         am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
161 
162         super.onPause();
163     }
164 
165     @Override
onCreateDialog(int dialogId)166     public Dialog onCreateDialog(int dialogId) {
167         if (mDialogDelegate != null) {
168             mDialog = mDialogDelegate.onCreateDialog(dialogId);
169             if (mDialog != null) {
170                 return mDialog;
171             }
172         }
173         switch (dialogId) {
174             case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
175                 return AccessibilityShortcutsTutorial
176                         .showAccessibilityGestureTutorialDialog(getPrefContext());
177             case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
178                 final CharSequence dialogTitle = getShortcutTitle();
179                 final int dialogType = mInSetupWizard
180                         ? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW
181                         : DialogType.EDIT_SHORTCUT_MAGNIFICATION;
182                 mDialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(),
183                         dialogType, dialogTitle, this::callOnAlertDialogCheckboxClicked);
184                 setupMagnificationEditShortcutDialog(mDialog);
185                 return mDialog;
186             default:
187                 return super.onCreateDialog(dialogId);
188         }
189     }
190 
191     @Override
initSettingsPreference()192     protected void initSettingsPreference() {
193         // If the device doesn't support window magnification feature, it should hide the
194         // settings preference.
195         final boolean supportWindowMagnification =
196                 getContext().getResources().getBoolean(
197                         com.android.internal.R.bool.config_magnification_area)
198                         && getContext().getPackageManager().hasSystemFeature(
199                         PackageManager.FEATURE_WINDOW_MAGNIFICATION);
200         if (!supportWindowMagnification) {
201             return;
202         }
203         mSettingsPreference = new Preference(getPrefContext());
204         mSettingsPreference.setTitle(R.string.accessibility_magnification_mode_title);
205         mSettingsPreference.setKey(MagnificationModePreferenceController.PREF_KEY);
206         mSettingsPreference.setPersistent(false);
207 
208         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
209         generalCategory.addPreference(mSettingsPreference);
210 
211         final MagnificationModePreferenceController magnificationModePreferenceController =
212                 new MagnificationModePreferenceController(getContext(),
213                         MagnificationModePreferenceController.PREF_KEY);
214         magnificationModePreferenceController.setDialogHelper(this);
215         getSettingsLifecycle().addObserver(magnificationModePreferenceController);
216         magnificationModePreferenceController.displayPreference(getPreferenceScreen());
217         addPreferenceController(magnificationModePreferenceController);
218 
219         addFollowTypingSetting(generalCategory);
220         addOneFingerPanningSetting(generalCategory);
221         addAlwaysOnSetting(generalCategory);
222         addJoystickSetting(generalCategory);
223     }
224 
225     @Override
onProcessArguments(Bundle arguments)226     protected void onProcessArguments(Bundle arguments) {
227         Context context = getContext();
228 
229         if (!arguments.containsKey(AccessibilitySettings.EXTRA_PREFERENCE_KEY)) {
230             arguments.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
231                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
232         }
233 
234         if (!arguments.containsKey(AccessibilitySettings.EXTRA_INTRO)) {
235             arguments.putCharSequence(AccessibilitySettings.EXTRA_INTRO,
236                     context.getString(R.string.accessibility_screen_magnification_intro_text));
237         }
238 
239         if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) {
240             String summary = MessageFormat.format(
241                     context.getString(R.string.accessibility_screen_magnification_summary),
242                             new Object[]{1, 2, 3, 4, 5});
243             arguments.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary);
244         }
245 
246         super.onProcessArguments(arguments);
247     }
248 
addFollowTypingSetting(PreferenceCategory generalCategory)249     private void addFollowTypingSetting(PreferenceCategory generalCategory) {
250         var followingTypingSwitchPreference = new SwitchPreferenceCompat(getPrefContext());
251         followingTypingSwitchPreference.setTitle(
252                 R.string.accessibility_screen_magnification_follow_typing_title);
253         followingTypingSwitchPreference.setSummary(
254                 R.string.accessibility_screen_magnification_follow_typing_summary);
255         followingTypingSwitchPreference.setKey(
256                 MagnificationFollowTypingPreferenceController.PREF_KEY);
257         generalCategory.addPreference(followingTypingSwitchPreference);
258 
259         var followTypingPreferenceController = new MagnificationFollowTypingPreferenceController(
260                 getContext(), MagnificationFollowTypingPreferenceController.PREF_KEY);
261         followTypingPreferenceController.setInSetupWizard(mInSetupWizard);
262         followTypingPreferenceController.displayPreference(getPreferenceScreen());
263         addPreferenceController(followTypingPreferenceController);
264     }
265 
isAlwaysOnSettingEnabled()266     private boolean isAlwaysOnSettingEnabled() {
267         final boolean defaultValue = getContext().getResources().getBoolean(
268                 com.android.internal.R.bool.config_magnification_always_on_enabled);
269 
270         return DeviceConfig.getBoolean(
271                 DeviceConfig.NAMESPACE_WINDOW_MANAGER,
272                 "AlwaysOnMagnifier__enable_always_on_magnifier",
273                 defaultValue
274         );
275     }
276 
addAlwaysOnSetting(PreferenceCategory generalCategory)277     private void addAlwaysOnSetting(PreferenceCategory generalCategory) {
278         if (!isAlwaysOnSettingEnabled()) {
279             return;
280         }
281 
282         var alwaysOnPreference = new SwitchPreferenceCompat(getPrefContext());
283         alwaysOnPreference.setTitle(
284                 R.string.accessibility_screen_magnification_always_on_title);
285         alwaysOnPreference.setSummary(
286                 R.string.accessibility_screen_magnification_always_on_summary);
287         alwaysOnPreference.setKey(
288                 MagnificationAlwaysOnPreferenceController.PREF_KEY);
289         generalCategory.addPreference(alwaysOnPreference);
290 
291         var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController(
292                 getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY);
293         alwaysOnPreferenceController.setInSetupWizard(mInSetupWizard);
294         getSettingsLifecycle().addObserver(alwaysOnPreferenceController);
295         alwaysOnPreferenceController.displayPreference(getPreferenceScreen());
296         addPreferenceController(alwaysOnPreferenceController);
297     }
298 
addOneFingerPanningSetting(PreferenceCategory generalCategory)299     private void addOneFingerPanningSetting(PreferenceCategory generalCategory) {
300         if (!Flags.enableMagnificationOneFingerPanningGesture()) {
301             return;
302         }
303 
304         var oneFingerPanningPreference = new SwitchPreferenceCompat(getPrefContext());
305         oneFingerPanningPreference.setTitle(
306                 R.string.accessibility_magnification_one_finger_panning_title);
307         oneFingerPanningPreference.setKey(
308                 MagnificationOneFingerPanningPreferenceController.PREF_KEY);
309         generalCategory.addPreference(oneFingerPanningPreference);
310 
311         var oneFingerPanningPreferenceController =
312                 new MagnificationOneFingerPanningPreferenceController(getContext());
313         oneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard);
314         getSettingsLifecycle().addObserver(oneFingerPanningPreferenceController);
315         oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen());
316         addPreferenceController(oneFingerPanningPreferenceController);
317     }
318 
addJoystickSetting(PreferenceCategory generalCategory)319     private void addJoystickSetting(PreferenceCategory generalCategory) {
320         if (!DeviceConfig.getBoolean(
321                 DeviceConfig.NAMESPACE_WINDOW_MANAGER,
322                 "MagnificationJoystick__enable_magnification_joystick",
323                 false
324         )) {
325             return;
326         }
327 
328         TwoStatePreference joystickPreference = new SwitchPreferenceCompat(getPrefContext());
329         joystickPreference.setTitle(
330                 R.string.accessibility_screen_magnification_joystick_title);
331         joystickPreference.setSummary(
332                 R.string.accessibility_screen_magnification_joystick_summary);
333         joystickPreference.setKey(
334                 MagnificationJoystickPreferenceController.PREF_KEY);
335         generalCategory.addPreference(joystickPreference);
336 
337         MagnificationJoystickPreferenceController joystickPreferenceController =
338                 new MagnificationJoystickPreferenceController(
339                         getContext(),
340                         MagnificationJoystickPreferenceController.PREF_KEY
341                 );
342         joystickPreferenceController.setInSetupWizard(mInSetupWizard);
343         joystickPreferenceController.displayPreference(getPreferenceScreen());
344         addPreferenceController(joystickPreferenceController);
345     }
346 
347     @Override
showDialog(int dialogId)348     public void showDialog(int dialogId) {
349         super.showDialog(dialogId);
350     }
351 
352     @Override
setDialogDelegate(DialogCreatable delegate)353     public void setDialogDelegate(DialogCreatable delegate) {
354         mDialogDelegate = delegate;
355     }
356 
357     @Override
getShortcutTypeCheckBoxValue()358     protected int getShortcutTypeCheckBoxValue() {
359         if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
360             return NOT_SET;
361         }
362 
363         int value = UserShortcutType.EMPTY;
364         if (mSoftwareTypeCheckBox.isChecked()) {
365             value |= UserShortcutType.SOFTWARE;
366         }
367         if (mHardwareTypeCheckBox.isChecked()) {
368             value |= UserShortcutType.HARDWARE;
369         }
370         if (mTripleTapTypeCheckBox.isChecked()) {
371             value |= UserShortcutType.TRIPLETAP;
372         }
373         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
374             if (mTwoFingerTripleTapTypeCheckBox.isChecked()) {
375                 value |= UserShortcutType.TWOFINGER_DOUBLETAP;
376             }
377         }
378         return value;
379     }
380 
381     @VisibleForTesting
setupMagnificationEditShortcutDialog(Dialog dialog)382     void setupMagnificationEditShortcutDialog(Dialog dialog) {
383         final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
384         mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
385         setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
386 
387         final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut);
388         mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
389         setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
390 
391         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
392             final View dialogTwoFignerTripleTapView =
393                     dialog.findViewById(R.id.two_finger_triple_tap_shortcut);
394             mTwoFingerTripleTapTypeCheckBox = dialogTwoFignerTripleTapView.findViewById(
395                     R.id.checkbox);
396             setDialogTextAreaClickListener(
397                     dialogTwoFignerTripleTapView, mTwoFingerTripleTapTypeCheckBox);
398         }
399 
400         final View dialogTripleTapView = dialog.findViewById(R.id.triple_tap_shortcut);
401         mTripleTapTypeCheckBox = dialogTripleTapView.findViewById(R.id.checkbox);
402         setDialogTextAreaClickListener(dialogTripleTapView, mTripleTapTypeCheckBox);
403 
404         final View advancedView = dialog.findViewById(R.id.advanced_shortcut);
405         if (mTripleTapTypeCheckBox.isChecked()) {
406             advancedView.setVisibility(View.GONE);
407             dialogTripleTapView.setVisibility(View.VISIBLE);
408         }
409 
410         updateMagnificationEditShortcutDialogCheckBox();
411     }
412 
setDialogTextAreaClickListener(View dialogView, CheckBox checkBox)413     private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
414         final View dialogTextArea = dialogView.findViewById(R.id.container);
415         dialogTextArea.setOnClickListener(v -> checkBox.toggle());
416     }
417 
updateMagnificationEditShortcutDialogCheckBox()418     private void updateMagnificationEditShortcutDialogCheckBox() {
419         // If it is during onConfigChanged process then restore the value, or get the saved value
420         // when shortcutPreference is checked.
421         int value = restoreOnConfigChangedValue();
422         if (value == NOT_SET) {
423             final int lastNonEmptyUserShortcutType = getUserPreferredShortcutTypes();
424             value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
425                     : UserShortcutType.EMPTY;
426         }
427 
428         mSoftwareTypeCheckBox.setChecked(
429                 hasShortcutType(value, UserShortcutType.SOFTWARE));
430         mHardwareTypeCheckBox.setChecked(
431                 hasShortcutType(value, UserShortcutType.HARDWARE));
432         mTripleTapTypeCheckBox.setChecked(
433                 hasShortcutType(value, UserShortcutType.TRIPLETAP));
434         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
435             mTwoFingerTripleTapTypeCheckBox.setChecked(
436                     hasShortcutType(value, UserShortcutType.TWOFINGER_DOUBLETAP));
437         }
438     }
439 
restoreOnConfigChangedValue()440     private int restoreOnConfigChangedValue() {
441         final int savedValue = mSavedCheckBoxValue;
442         mSavedCheckBoxValue = NOT_SET;
443         return savedValue;
444     }
445 
hasShortcutType(int value, @UserShortcutType int type)446     private boolean hasShortcutType(int value, @UserShortcutType int type) {
447         return (value & type) == type;
448     }
449 
getSoftwareShortcutTypeSummary(Context context)450     private static CharSequence getSoftwareShortcutTypeSummary(Context context) {
451         int resId;
452         if (AccessibilityUtil.isFloatingMenuEnabled(context)) {
453             resId = R.string.accessibility_shortcut_edit_summary_software;
454         } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) {
455             resId = R.string.accessibility_shortcut_edit_summary_software_gesture;
456         } else {
457             resId = R.string.accessibility_shortcut_edit_summary_software;
458         }
459         return context.getText(resId);
460     }
461 
462     @Override
registerKeysToObserverCallback( AccessibilitySettingsContentObserver contentObserver)463     protected void registerKeysToObserverCallback(
464             AccessibilitySettingsContentObserver contentObserver) {
465         super.registerKeysToObserverCallback(contentObserver);
466 
467         var keysToObserve = List.of(
468             Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED,
469             Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
470             Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED
471         );
472         contentObserver.registerKeysToObserverCallback(keysToObserve,
473                 key -> updatePreferencesState());
474     }
475 
updatePreferencesState()476     private void updatePreferencesState() {
477         final List<AbstractPreferenceController> controllers = new ArrayList<>();
478         getPreferenceControllers().forEach(controllers::addAll);
479         controllers.forEach(controller -> controller.updateState(
480                 findPreference(controller.getPreferenceKey())));
481     }
482 
483     @Override
getShortcutFeatureSettingsKeys()484     protected List<String> getShortcutFeatureSettingsKeys() {
485         final List<String> shortcutKeys = super.getShortcutFeatureSettingsKeys();
486         shortcutKeys.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
487         return shortcutKeys;
488     }
489 
490     @Override
getShortcutTypeSummary(Context context)491     protected CharSequence getShortcutTypeSummary(Context context) {
492         if (!mShortcutPreference.isChecked()) {
493             return context.getText(R.string.switch_off_text);
494         }
495 
496         final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
497                 MAGNIFICATION_CONTROLLER_NAME);
498 
499         // LINT.IfChange(shortcut_type_ui_order)
500         final List<CharSequence> list = new ArrayList<>();
501         if (android.view.accessibility.Flags.a11yQsShortcut()) {
502             if (hasShortcutType(shortcutTypes, UserShortcutType.QUICK_SETTINGS)) {
503                 final CharSequence qsTitle = context.getText(
504                         R.string.accessibility_feature_shortcut_setting_summary_quick_settings);
505                 list.add(qsTitle);
506             }
507         }
508         if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
509             list.add(getSoftwareShortcutTypeSummary(context));
510         }
511         if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
512             final CharSequence hardwareTitle = context.getText(
513                     R.string.accessibility_shortcut_hardware_keyword);
514             list.add(hardwareTitle);
515         }
516         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
517             if (hasShortcutType(shortcutTypes, UserShortcutType.TWOFINGER_DOUBLETAP)) {
518                 final CharSequence twoFingerDoubleTapTitle = context.getString(
519                         R.string.accessibility_shortcut_two_finger_double_tap_keyword, 2);
520                 list.add(twoFingerDoubleTapTitle);
521             }
522         }
523         if (hasShortcutType(shortcutTypes, UserShortcutType.TRIPLETAP)) {
524             final CharSequence tripleTapTitle = context.getText(
525                     R.string.accessibility_shortcut_triple_tap_keyword);
526             list.add(tripleTapTitle);
527         }
528         // LINT.ThenChange(/res/xml/accessibility_edit_shortcuts.xml:shortcut_type_ui_order)
529 
530         // Show software shortcut if first time to use.
531         if (list.isEmpty()) {
532             list.add(getSoftwareShortcutTypeSummary(context));
533         }
534 
535         return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
536                 null, LocaleUtils.getConcatenatedString(list));
537     }
538 
539     @Override
callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which)540     protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
541         final int value = getShortcutTypeCheckBoxValue();
542 
543         saveNonEmptyUserShortcutType(value);
544         optInAllMagnificationValuesToSettings(getPrefContext(), value);
545         optOutAllMagnificationValuesFromSettings(getPrefContext(), ~value);
546         mShortcutPreference.setChecked(value != UserShortcutType.EMPTY);
547         mShortcutPreference.setSummary(
548                 getShortcutTypeSummary(getPrefContext()));
549 
550         if (mHardwareTypeCheckBox.isChecked()) {
551             AccessibilityUtil.skipVolumeShortcutDialogTimeoutRestriction(getPrefContext());
552         }
553     }
554 
555     @Override
getHelpResource()556     public int getHelpResource() {
557         return R.string.help_url_magnification;
558     }
559 
560     @Override
getMetricsCategory()561     public int getMetricsCategory() {
562         // TODO: Distinguish between magnification modes
563         return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION;
564     }
565 
566     @Override
getDialogMetricsCategory(int dialogId)567     public int getDialogMetricsCategory(int dialogId) {
568         if (mDialogDelegate != null) {
569             final int category = mDialogDelegate.getDialogMetricsCategory(dialogId);
570             if (category != 0) {
571                 return category;
572             }
573         }
574 
575         switch (dialogId) {
576             case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
577                 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
578             case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
579                 return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON;
580             case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
581                 return SettingsEnums.DIALOG_MAGNIFICATION_EDIT_SHORTCUT;
582             default:
583                 return super.getDialogMetricsCategory(dialogId);
584         }
585     }
586 
587     @Override
getUserShortcutTypes()588     int getUserShortcutTypes() {
589         return getUserShortcutTypeFromSettings(getPrefContext());
590     }
591 
592     @Override
getTileComponentName()593     ComponentName getTileComponentName() {
594         return null;
595     }
596 
597     @Override
getTileTooltipContent(@uickSettingsTooltipType int type)598     CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) {
599         return null;
600     }
601 
602     @Override
onPreferenceToggled(String preferenceKey, boolean enabled)603     protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
604         if (enabled && TextUtils.equals(
605                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
606                 preferenceKey)) {
607             showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
608         }
609         Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? ON : OFF);
610     }
611 
612     @Override
onInstallSwitchPreferenceToggleSwitch()613     protected void onInstallSwitchPreferenceToggleSwitch() {
614         mToggleServiceSwitchPreference.setVisible(false);
615     }
616 
617     @Override
onToggleClicked(ShortcutPreference preference)618     public void onToggleClicked(ShortcutPreference preference) {
619         final int shortcutTypes = getUserPreferredShortcutTypes();
620         if (preference.isChecked()) {
621             optInAllMagnificationValuesToSettings(getPrefContext(), shortcutTypes);
622             showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
623         } else {
624             optOutAllMagnificationValuesFromSettings(getPrefContext(), shortcutTypes);
625         }
626         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
627     }
628 
629     @Override
onSettingsClicked(ShortcutPreference preference)630     public void onSettingsClicked(ShortcutPreference preference) {
631         if (com.android.settings.accessibility.Flags.editShortcutsInFullScreen()) {
632             EditShortcutsPreferenceFragment.showEditShortcutScreen(
633                     requireContext(),
634                     getMetricsCategory(),
635                     getShortcutTitle(),
636                     MAGNIFICATION_COMPONENT_NAME,
637                     getIntent());
638         } else {
639             showDialog(DialogEnums.MAGNIFICATION_EDIT_SHORTCUT);
640         }
641     }
642 
643     @Override
updateShortcutPreferenceData()644     protected void updateShortcutPreferenceData() {
645         final int shortcutTypes = getUserShortcutTypeFromSettings(getPrefContext());
646         if (shortcutTypes != UserShortcutType.EMPTY) {
647             final PreferredShortcut shortcut = new PreferredShortcut(
648                     MAGNIFICATION_CONTROLLER_NAME, shortcutTypes);
649             PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
650         }
651     }
652 
653     @Override
initShortcutPreference()654     protected void initShortcutPreference() {
655         mShortcutPreference = new ShortcutPreference(getPrefContext(), null);
656         mShortcutPreference.setPersistent(false);
657         mShortcutPreference.setKey(getShortcutPreferenceKey());
658         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
659         mShortcutPreference.setOnClickCallback(this);
660         mShortcutPreference.setTitle(getShortcutTitle());
661 
662         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
663         generalCategory.addPreference(mShortcutPreference);
664     }
665 
666     @Override
getShortcutTitle()667     protected CharSequence getShortcutTitle() {
668         return getText(R.string.accessibility_screen_magnification_shortcut_title);
669     }
670 
671     @Override
updateShortcutPreference()672     protected void updateShortcutPreference() {
673         final int shortcutTypes = getUserPreferredShortcutTypes();
674         mShortcutPreference.setChecked(
675                 hasMagnificationValuesInSettings(getPrefContext(), shortcutTypes));
676         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
677     }
678 
679     @VisibleForTesting
saveNonEmptyUserShortcutType(int type)680     void saveNonEmptyUserShortcutType(int type) {
681         if (type == UserShortcutType.EMPTY) {
682             return;
683         }
684 
685         final PreferredShortcut shortcut = new PreferredShortcut(
686                 MAGNIFICATION_CONTROLLER_NAME, type);
687         PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
688     }
689 
690     @VisibleForTesting
optInAllMagnificationValuesToSettings(Context context, int shortcutTypes)691     static void optInAllMagnificationValuesToSettings(Context context, int shortcutTypes) {
692         if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
693             optInMagnificationValueToSettings(context, UserShortcutType.SOFTWARE);
694         }
695         if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
696             optInMagnificationValueToSettings(context, UserShortcutType.HARDWARE);
697         }
698         if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
699             optInMagnificationValueToSettings(context, UserShortcutType.TRIPLETAP);
700         }
701         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
702             if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP)
703                     == UserShortcutType.TWOFINGER_DOUBLETAP)) {
704                 optInMagnificationValueToSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP);
705             }
706         }
707         if (android.view.accessibility.Flags.a11yQsShortcut()) {
708             if (((shortcutTypes & UserShortcutType.QUICK_SETTINGS)
709                     == UserShortcutType.QUICK_SETTINGS)) {
710                 optInMagnificationValueToSettings(context, UserShortcutType.QUICK_SETTINGS);
711             }
712         }
713     }
714 
optInMagnificationValueToSettings( Context context, @UserShortcutType int shortcutType)715     private static void optInMagnificationValueToSettings(
716             Context context, @UserShortcutType int shortcutType) {
717         if (android.view.accessibility.Flags.a11yQsShortcut()) {
718             AccessibilityManager a11yManager = context.getSystemService(AccessibilityManager.class);
719             if (a11yManager != null) {
720                 a11yManager.enableShortcutsForTargets(
721                         /* enable= */ true,
722                         shortcutType,
723                         Set.of(MAGNIFICATION_CONTROLLER_NAME),
724                         UserHandle.myUserId()
725                 );
726             }
727             return;
728         }
729 
730         if (shortcutType == UserShortcutType.TRIPLETAP) {
731             Settings.Secure.putInt(context.getContentResolver(),
732                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, ON);
733             return;
734         }
735 
736         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
737             if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
738                 Settings.Secure.putInt(
739                         context.getContentResolver(),
740                         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
741                         ON);
742                 return;
743             }
744         }
745 
746         if (hasMagnificationValueInSettings(context, shortcutType)) {
747             return;
748         }
749 
750         final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType);
751         final String targetString = Settings.Secure.getString(context.getContentResolver(),
752                 targetKey);
753         final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
754 
755         if (!TextUtils.isEmpty(targetString)) {
756             joiner.add(targetString);
757         }
758         joiner.add(MAGNIFICATION_CONTROLLER_NAME);
759 
760         Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
761         // The size setting defaults to unknown. If the user has ever manually changed the size
762         // before, we do not automatically change it.
763         if (shortcutType == UserShortcutType.SOFTWARE
764                 && Settings.Secure.getInt(context.getContentResolver(),
765                 Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
766                 FloatingMenuSizePreferenceController.Size.UNKNOWN)
767                 == FloatingMenuSizePreferenceController.Size.UNKNOWN) {
768             Settings.Secure.putInt(context.getContentResolver(),
769                     Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
770                     FloatingMenuSizePreferenceController.Size.LARGE);
771         }
772     }
773 
774     @VisibleForTesting
optOutAllMagnificationValuesFromSettings(Context context, int shortcutTypes)775     static void optOutAllMagnificationValuesFromSettings(Context context,
776             int shortcutTypes) {
777         if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
778             optOutMagnificationValueFromSettings(context, UserShortcutType.SOFTWARE);
779         }
780         if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
781             optOutMagnificationValueFromSettings(context, UserShortcutType.HARDWARE);
782         }
783         if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
784             optOutMagnificationValueFromSettings(context, UserShortcutType.TRIPLETAP);
785         }
786         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
787             if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP)
788                     == UserShortcutType.TWOFINGER_DOUBLETAP)) {
789                 optOutMagnificationValueFromSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP);
790             }
791         }
792         if (android.view.accessibility.Flags.a11yQsShortcut()) {
793             if (((shortcutTypes & UserShortcutType.QUICK_SETTINGS)
794                     == UserShortcutType.QUICK_SETTINGS)) {
795                 optOutMagnificationValueFromSettings(context, UserShortcutType.QUICK_SETTINGS);
796             }
797         }
798     }
799 
optOutMagnificationValueFromSettings(Context context, @UserShortcutType int shortcutType)800     private static void optOutMagnificationValueFromSettings(Context context,
801             @UserShortcutType int shortcutType) {
802         if (android.view.accessibility.Flags.a11yQsShortcut()) {
803             AccessibilityManager a11yManager = context.getSystemService(AccessibilityManager.class);
804             if (a11yManager != null) {
805                 a11yManager.enableShortcutsForTargets(
806                         /* enable= */ false,
807                         shortcutType,
808                         Set.of(MAGNIFICATION_CONTROLLER_NAME),
809                         UserHandle.myUserId()
810                 );
811             }
812             return;
813         }
814 
815         if (shortcutType == UserShortcutType.TRIPLETAP) {
816             Settings.Secure.putInt(context.getContentResolver(),
817                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF);
818             return;
819         }
820 
821         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
822             if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
823                 Settings.Secure.putInt(
824                         context.getContentResolver(),
825                         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
826                         OFF);
827                 return;
828             }
829         }
830 
831         final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType);
832         final String targetString = Settings.Secure.getString(context.getContentResolver(),
833                 targetKey);
834 
835         if (TextUtils.isEmpty(targetString)) {
836             return;
837         }
838 
839         final StringJoiner joiner = new StringJoiner(String.valueOf(COMPONENT_NAME_SEPARATOR));
840 
841         sStringColonSplitter.setString(targetString);
842         while (sStringColonSplitter.hasNext()) {
843             final String name = sStringColonSplitter.next();
844             if (TextUtils.isEmpty(name) || MAGNIFICATION_CONTROLLER_NAME.equals(name)) {
845                 continue;
846             }
847             joiner.add(name);
848         }
849 
850         Settings.Secure.putString(context.getContentResolver(), targetKey, joiner.toString());
851     }
852 
853     @VisibleForTesting
hasMagnificationValuesInSettings(Context context, int shortcutTypes)854     static boolean hasMagnificationValuesInSettings(Context context, int shortcutTypes) {
855         boolean exist = false;
856 
857         if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) {
858             exist = hasMagnificationValueInSettings(context, UserShortcutType.SOFTWARE);
859         }
860         if (((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE)) {
861             exist |= hasMagnificationValueInSettings(context, UserShortcutType.HARDWARE);
862         }
863         if (((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP)) {
864             exist |= hasMagnificationValueInSettings(context, UserShortcutType.TRIPLETAP);
865         }
866         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
867             if (((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP)
868                     == UserShortcutType.TWOFINGER_DOUBLETAP)) {
869                 exist |= hasMagnificationValueInSettings(context,
870                         UserShortcutType.TWOFINGER_DOUBLETAP);
871             }
872         }
873         return exist;
874     }
875 
hasMagnificationValueInSettings(Context context, @UserShortcutType int shortcutType)876     private static boolean hasMagnificationValueInSettings(Context context,
877             @UserShortcutType int shortcutType) {
878         if (shortcutType == UserShortcutType.TRIPLETAP) {
879             return Settings.Secure.getInt(context.getContentResolver(),
880                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, OFF) == ON;
881         }
882 
883         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
884             if (shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
885                 return Settings.Secure.getInt(context.getContentResolver(),
886                         Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
887                         OFF) == ON;
888             }
889         }
890 
891         final String targetKey = AccessibilityUtil.convertKeyFromSettings(shortcutType);
892         final String targetString = Settings.Secure.getString(context.getContentResolver(),
893                 targetKey);
894 
895         if (TextUtils.isEmpty(targetString)) {
896             return false;
897         }
898 
899         sStringColonSplitter.setString(targetString);
900         while (sStringColonSplitter.hasNext()) {
901             final String name = sStringColonSplitter.next();
902             if (MAGNIFICATION_CONTROLLER_NAME.equals(name)) {
903                 return true;
904             }
905         }
906         return false;
907     }
908 
getUserShortcutTypeFromSettings(Context context)909     private static int getUserShortcutTypeFromSettings(Context context) {
910         int shortcutTypes = UserShortcutType.EMPTY;
911         if (hasMagnificationValuesInSettings(context, UserShortcutType.SOFTWARE)) {
912             shortcutTypes |= UserShortcutType.SOFTWARE;
913         }
914         if (hasMagnificationValuesInSettings(context, UserShortcutType.HARDWARE)) {
915             shortcutTypes |= UserShortcutType.HARDWARE;
916         }
917         if (hasMagnificationValuesInSettings(context, UserShortcutType.TRIPLETAP)) {
918             shortcutTypes |= UserShortcutType.TRIPLETAP;
919         }
920         if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
921             if (hasMagnificationValuesInSettings(context, UserShortcutType.TWOFINGER_DOUBLETAP)) {
922                 shortcutTypes |= UserShortcutType.TWOFINGER_DOUBLETAP;
923             }
924         }
925         return shortcutTypes;
926     }
927 
928     /**
929      * Gets the service summary of magnification.
930      *
931      * @param context The current context.
932      */
getServiceSummary(Context context)933     public static CharSequence getServiceSummary(Context context) {
934         // Get the user shortcut type from settings provider.
935         final int userShortcutType = getUserShortcutTypeFromSettings(context);
936         final CharSequence featureState =
937                 (userShortcutType != AccessibilityUtil.UserShortcutType.EMPTY)
938                 ? context.getText(R.string.accessibility_summary_shortcut_enabled)
939                 : context.getText(R.string.generic_accessibility_feature_shortcut_off);
940         final CharSequence featureSummary = context.getText(R.string.magnification_feature_summary);
941         return context.getString(R.string.preference_summary_default_combination,
942                 featureState, featureSummary);
943     }
944 
945     @Override
getUserPreferredShortcutTypes()946     protected int getUserPreferredShortcutTypes() {
947         return PreferredShortcuts.retrieveUserShortcutType(
948                 getPrefContext(), MAGNIFICATION_CONTROLLER_NAME);
949     }
950 }
951