• 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.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
20 
21 import android.app.Dialog;
22 import android.app.settings.SettingsEnums;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.pm.ResolveInfo;
29 import android.graphics.drawable.Drawable;
30 import android.icu.text.CaseMap;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.text.Html;
37 import android.text.TextUtils;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.view.accessibility.AccessibilityManager;
42 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
43 import android.widget.CheckBox;
44 import android.widget.ImageView;
45 import android.widget.Switch;
46 
47 import androidx.annotation.VisibleForTesting;
48 import androidx.preference.Preference;
49 import androidx.preference.PreferenceCategory;
50 import androidx.preference.PreferenceScreen;
51 
52 import com.android.settings.R;
53 import com.android.settings.SettingsActivity;
54 import com.android.settings.SettingsPreferenceFragment;
55 import com.android.settings.accessibility.AccessibilityDialogUtils.DialogType;
56 import com.android.settings.accessibility.AccessibilityUtil.UserShortcutType;
57 import com.android.settings.utils.LocaleUtils;
58 import com.android.settings.widget.SettingsMainSwitchBar;
59 import com.android.settings.widget.SettingsMainSwitchPreference;
60 import com.android.settingslib.HelpUtils;
61 import com.android.settingslib.accessibility.AccessibilityUtils;
62 import com.android.settingslib.widget.IllustrationPreference;
63 import com.android.settingslib.widget.OnMainSwitchChangeListener;
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 
71 /**
72  * Base class for accessibility fragments with toggle, shortcut, some helper functions
73  * and dialog management.
74  */
75 public abstract class ToggleFeaturePreferenceFragment extends SettingsPreferenceFragment
76         implements ShortcutPreference.OnClickCallback, OnMainSwitchChangeListener {
77 
78     protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
79     protected ShortcutPreference mShortcutPreference;
80     protected Preference mSettingsPreference;
81     protected String mPreferenceKey;
82 
83     protected CharSequence mSettingsTitle;
84     protected Intent mSettingsIntent;
85     // The mComponentName maybe null, such as Magnify
86     protected ComponentName mComponentName;
87     protected CharSequence mPackageName;
88     protected Uri mImageUri;
89     private CharSequence mDescription;
90     protected CharSequence mHtmlDescription;
91 
92     private static final String DRAWABLE_FOLDER = "drawable";
93     protected static final String KEY_USE_SERVICE_PREFERENCE = "use_service";
94     public static final String KEY_GENERAL_CATEGORY = "general_categories";
95     protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description";
96     private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
97     protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type";
98     protected static final String KEY_ANIMATED_IMAGE = "animated_image";
99 
100     private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
101     private SettingsContentObserver mSettingsContentObserver;
102 
103     private CheckBox mSoftwareTypeCheckBox;
104     private CheckBox mHardwareTypeCheckBox;
105 
106     public static final int NOT_SET = -1;
107     // Save user's shortcutType value when savedInstance has value (e.g. device rotated).
108     protected int mSavedCheckBoxValue = NOT_SET;
109 
110     // For html description of accessibility service, must follow the rule, such as
111     // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
112     private static final String IMG_PREFIX = "R.drawable.";
113 
114     private ImageView mImageGetterCacheView;
115 
116     private final Html.ImageGetter mImageGetter = (String str) -> {
117         if (str != null && str.startsWith(IMG_PREFIX)) {
118             final String fileName = str.substring(IMG_PREFIX.length());
119             return getDrawableFromUri(Uri.parse(
120                     ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
121                             + mComponentName.getPackageName() + "/" + DRAWABLE_FOLDER + "/"
122                             + fileName));
123         }
124         return null;
125     };
126 
127     @Override
onCreate(Bundle savedInstanceState)128     public void onCreate(Bundle savedInstanceState) {
129         super.onCreate(savedInstanceState);
130 
131         // Restore the user shortcut type.
132         if (savedInstanceState != null && savedInstanceState.containsKey(
133                 KEY_SAVED_USER_SHORTCUT_TYPE)) {
134             mSavedCheckBoxValue = savedInstanceState.getInt(KEY_SAVED_USER_SHORTCUT_TYPE, NOT_SET);
135         }
136 
137         setupDefaultShortcutIfNecessary(getPrefContext());
138         final int resId = getPreferenceScreenResId();
139         if (resId <= 0) {
140             final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
141                     getPrefContext());
142             setPreferenceScreen(preferenceScreen);
143         }
144 
145         final List<String> shortcutFeatureKeys = new ArrayList<>();
146         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
147         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
148         mSettingsContentObserver = new SettingsContentObserver(new Handler(), shortcutFeatureKeys) {
149             @Override
150             public void onChange(boolean selfChange, Uri uri) {
151                 updateShortcutPreferenceData();
152                 updateShortcutPreference();
153             }
154         };
155     }
156 
157     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)158     public View onCreateView(LayoutInflater inflater, ViewGroup container,
159             Bundle savedInstanceState) {
160         // Need to be called as early as possible. Protected variables will be assigned here.
161         onProcessArguments(getArguments());
162 
163         initAnimatedImagePreference();
164         initToggleServiceSwitchPreference();
165         initGeneralCategory();
166         initShortcutPreference();
167         initSettingsPreference();
168         initHtmlTextPreference();
169         initFooterPreference();
170 
171         installActionBarToggleSwitch();
172 
173         updateToggleServiceTitle(mToggleServiceSwitchPreference);
174 
175         mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
176             removeDialog(DialogEnums.EDIT_SHORTCUT);
177             mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
178         };
179         return super.onCreateView(inflater, container, savedInstanceState);
180     }
181 
182     @Override
onViewCreated(View view, Bundle savedInstanceState)183     public void onViewCreated(View view, Bundle savedInstanceState) {
184         super.onViewCreated(view, savedInstanceState);
185 
186         final SettingsActivity activity = (SettingsActivity) getActivity();
187         final SettingsMainSwitchBar switchBar = activity.getSwitchBar();
188         switchBar.hide();
189 
190         updatePreferenceOrder();
191     }
192 
193     @Override
onResume()194     public void onResume() {
195         super.onResume();
196 
197         final AccessibilityManager am = getPrefContext().getSystemService(
198                 AccessibilityManager.class);
199         am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
200         mSettingsContentObserver.register(getContentResolver());
201         updateShortcutPreferenceData();
202         updateShortcutPreference();
203     }
204 
205     @Override
onPause()206     public void onPause() {
207         final AccessibilityManager am = getPrefContext().getSystemService(
208                 AccessibilityManager.class);
209         am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
210         mSettingsContentObserver.unregister(getContentResolver());
211         super.onPause();
212     }
213 
214     @Override
onSaveInstanceState(Bundle outState)215     public void onSaveInstanceState(Bundle outState) {
216         final int value = getShortcutTypeCheckBoxValue();
217         if (value != NOT_SET) {
218             outState.putInt(KEY_SAVED_USER_SHORTCUT_TYPE, value);
219         }
220         super.onSaveInstanceState(outState);
221     }
222 
223     @Override
onCreateDialog(int dialogId)224     public Dialog onCreateDialog(int dialogId) {
225         Dialog dialog;
226         switch (dialogId) {
227             case DialogEnums.EDIT_SHORTCUT:
228                 final CharSequence dialogTitle = getPrefContext().getString(
229                         R.string.accessibility_shortcut_title, mPackageName);
230                 final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
231                         ? DialogType.EDIT_SHORTCUT_GENERIC_SUW : DialogType.EDIT_SHORTCUT_GENERIC;
232                 dialog = AccessibilityDialogUtils.showEditShortcutDialog(
233                         getPrefContext(), dialogType, dialogTitle,
234                         this::callOnAlertDialogCheckboxClicked);
235                 setupEditShortcutDialog(dialog);
236                 return dialog;
237             case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
238                 dialog = AccessibilityGestureNavigationTutorial
239                         .createAccessibilityTutorialDialog(getPrefContext(),
240                                 getUserShortcutTypes());
241                 dialog.setCanceledOnTouchOutside(false);
242                 return dialog;
243             default:
244                 throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
245         }
246     }
247 
248     @Override
getDialogMetricsCategory(int dialogId)249     public int getDialogMetricsCategory(int dialogId) {
250         switch (dialogId) {
251             case DialogEnums.EDIT_SHORTCUT:
252                 return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT;
253             case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
254                 return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL;
255             default:
256                 return SettingsEnums.ACTION_UNKNOWN;
257         }
258     }
259 
260     @Override
getMetricsCategory()261     public int getMetricsCategory() {
262         return SettingsEnums.ACCESSIBILITY_SERVICE;
263     }
264 
265     @Override
getHelpResource()266     public int getHelpResource() {
267         return 0;
268     }
269 
270     @Override
onDestroyView()271     public void onDestroyView() {
272         super.onDestroyView();
273         removeActionBarToggleSwitch();
274     }
275 
276     @Override
onSwitchChanged(Switch switchView, boolean isChecked)277     public void onSwitchChanged(Switch switchView, boolean isChecked) {
278         onPreferenceToggled(mPreferenceKey, isChecked);
279     }
280 
281     /**
282      * Returns the shortcut type list which has been checked by user.
283      */
getUserShortcutTypes()284     abstract int getUserShortcutTypes();
285 
updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference)286     protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
287         switchPreference.setTitle(R.string.accessibility_service_primary_switch_title);
288     }
289 
onPreferenceToggled(String preferenceKey, boolean enabled)290     protected abstract void onPreferenceToggled(String preferenceKey, boolean enabled);
291 
onInstallSwitchPreferenceToggleSwitch()292     protected void onInstallSwitchPreferenceToggleSwitch() {
293         // Implement this to set a checked listener.
294         updateSwitchBarToggleSwitch();
295         mToggleServiceSwitchPreference.addOnSwitchChangeListener(this);
296     }
297 
onRemoveSwitchPreferenceToggleSwitch()298     protected void onRemoveSwitchPreferenceToggleSwitch() {
299         // Implement this to reset a checked listener.
300     }
301 
updateSwitchBarToggleSwitch()302     protected void updateSwitchBarToggleSwitch() {
303         // Implement this to update the state of switch.
304     }
305 
installActionBarToggleSwitch()306     private void installActionBarToggleSwitch() {
307         onInstallSwitchPreferenceToggleSwitch();
308     }
309 
removeActionBarToggleSwitch()310     private void removeActionBarToggleSwitch() {
311         mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
312         onRemoveSwitchPreferenceToggleSwitch();
313     }
314 
setTitle(String title)315     public void setTitle(String title) {
316         getActivity().setTitle(title);
317     }
318 
onProcessArguments(Bundle arguments)319     protected void onProcessArguments(Bundle arguments) {
320         // Key.
321         mPreferenceKey = arguments.getString(AccessibilitySettings.EXTRA_PREFERENCE_KEY);
322 
323         // Title.
324         if (arguments.containsKey(AccessibilitySettings.EXTRA_RESOLVE_INFO)) {
325             ResolveInfo info = arguments.getParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO);
326             getActivity().setTitle(info.loadLabel(getPackageManager()).toString());
327         } else if (arguments.containsKey(AccessibilitySettings.EXTRA_TITLE)) {
328             setTitle(arguments.getString(AccessibilitySettings.EXTRA_TITLE));
329         }
330 
331         // Summary.
332         if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) {
333             mDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_SUMMARY);
334         }
335 
336         // Settings html description.
337         if (arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) {
338             mHtmlDescription = arguments.getCharSequence(
339                     AccessibilitySettings.EXTRA_HTML_DESCRIPTION);
340         }
341     }
342 
343     /** Customizes the order by preference key. */
getPreferenceOrderList()344     protected List<String> getPreferenceOrderList() {
345         final List<String> lists = new ArrayList<>();
346         lists.add(KEY_ANIMATED_IMAGE);
347         lists.add(KEY_USE_SERVICE_PREFERENCE);
348         lists.add(KEY_GENERAL_CATEGORY);
349         lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
350         return lists;
351     }
352 
updatePreferenceOrder()353     private void updatePreferenceOrder() {
354         final List<String> lists = getPreferenceOrderList();
355 
356         final PreferenceScreen preferenceScreen = getPreferenceScreen();
357         preferenceScreen.setOrderingAsAdded(false);
358 
359         final int size = lists.size();
360         for (int i = 0; i < size; i++) {
361             final Preference preference = preferenceScreen.findPreference(lists.get(i));
362             if (preference != null) {
363                 preference.setOrder(i);
364             }
365         }
366     }
367 
getDrawableFromUri(Uri imageUri)368     private Drawable getDrawableFromUri(Uri imageUri) {
369         if (mImageGetterCacheView == null) {
370             mImageGetterCacheView = new ImageView(getPrefContext());
371         }
372 
373         mImageGetterCacheView.setAdjustViewBounds(true);
374         mImageGetterCacheView.setImageURI(imageUri);
375 
376         if (mImageGetterCacheView.getDrawable() == null) {
377             return null;
378         }
379 
380         final Drawable drawable =
381                 mImageGetterCacheView.getDrawable().mutate().getConstantState().newDrawable();
382         mImageGetterCacheView.setImageURI(null);
383         final int imageWidth = drawable.getIntrinsicWidth();
384         final int imageHeight = drawable.getIntrinsicHeight();
385         final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2;
386         if ((imageWidth > AccessibilityUtil.getScreenWidthPixels(getPrefContext()))
387                 || (imageHeight > screenHalfHeight)) {
388             return null;
389         }
390 
391         drawable.setBounds(/* left= */0, /* top= */0, drawable.getIntrinsicWidth(),
392                 drawable.getIntrinsicHeight());
393 
394         return drawable;
395     }
396 
initAnimatedImagePreference()397     private void initAnimatedImagePreference() {
398         if (mImageUri == null) {
399             return;
400         }
401 
402         final IllustrationPreference illustrationPreference =
403                 new IllustrationPreference(getPrefContext());
404         illustrationPreference.setImageUri(mImageUri);
405         illustrationPreference.setSelectable(false);
406         illustrationPreference.setKey(KEY_ANIMATED_IMAGE);
407 
408         getPreferenceScreen().addPreference(illustrationPreference);
409     }
410 
initToggleServiceSwitchPreference()411     private void initToggleServiceSwitchPreference() {
412         mToggleServiceSwitchPreference = new SettingsMainSwitchPreference(getPrefContext());
413         mToggleServiceSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE);
414         if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
415             final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED);
416             mToggleServiceSwitchPreference.setChecked(enabled);
417         }
418 
419         getPreferenceScreen().addPreference(mToggleServiceSwitchPreference);
420     }
421 
initGeneralCategory()422     private void initGeneralCategory() {
423         final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext());
424         generalCategory.setKey(KEY_GENERAL_CATEGORY);
425         generalCategory.setTitle(R.string.accessibility_screen_option);
426 
427         getPreferenceScreen().addPreference(generalCategory);
428     }
429 
initShortcutPreference()430     protected void initShortcutPreference() {
431         // Initial the shortcut preference.
432         mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null);
433         mShortcutPreference.setPersistent(false);
434         mShortcutPreference.setKey(getShortcutPreferenceKey());
435         mShortcutPreference.setOnClickCallback(this);
436 
437         final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName);
438         mShortcutPreference.setTitle(title);
439 
440         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
441         generalCategory.addPreference(mShortcutPreference);
442     }
443 
initSettingsPreference()444     protected void initSettingsPreference() {
445         if (mSettingsTitle == null || mSettingsIntent == null) {
446             return;
447         }
448 
449         // Show the "Settings" menu as if it were a preference screen.
450         mSettingsPreference = new Preference(getPrefContext());
451         mSettingsPreference.setTitle(mSettingsTitle);
452         mSettingsPreference.setIconSpaceReserved(false);
453         mSettingsPreference.setIntent(mSettingsIntent);
454 
455         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
456         generalCategory.addPreference(mSettingsPreference);
457     }
458 
initHtmlTextPreference()459     private void initHtmlTextPreference() {
460         if (TextUtils.isEmpty(mHtmlDescription)) {
461             return;
462         }
463         final PreferenceScreen screen = getPreferenceScreen();
464         final CharSequence htmlDescription = Html.fromHtml(mHtmlDescription.toString(),
465                 Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null);
466         final String iconContentDescription =
467                 getString(R.string.accessibility_introduction_title, mPackageName);
468 
469         final AccessibilityFooterPreference htmlFooterPreference =
470                 new AccessibilityFooterPreference(screen.getContext());
471         htmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE);
472         htmlFooterPreference.setSummary(htmlDescription);
473         htmlFooterPreference.setContentDescription(
474                 generateFooterContentDescription(htmlDescription));
475 
476         // Only framework tools support help link
477         if (getHelpResource() != 0) {
478             htmlFooterPreference.setLearnMoreAction(view -> {
479                 final Intent helpIntent = HelpUtils.getHelpIntent(
480                         getContext(), getContext().getString(getHelpResource()),
481                         getContext().getClass().getName());
482                 view.startActivityForResult(helpIntent, 0);
483             });
484 
485             final String learnMoreContentDescription = getPrefContext().getString(
486                     R.string.footer_learn_more_content_description, mPackageName);
487             htmlFooterPreference.setLearnMoreContentDescription(learnMoreContentDescription);
488             htmlFooterPreference.setLinkEnabled(true);
489         } else {
490             htmlFooterPreference.setLinkEnabled(false);
491         }
492         screen.addPreference(htmlFooterPreference);
493     }
494 
initFooterPreference()495     private void initFooterPreference() {
496         if (!TextUtils.isEmpty(mDescription)) {
497             createFooterPreference(getPreferenceScreen(), mDescription,
498                     getString(R.string.accessibility_introduction_title, mPackageName));
499         }
500 
501         if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) {
502             final CharSequence defaultDescription =
503                     getText(R.string.accessibility_service_default_description);
504             createFooterPreference(getPreferenceScreen(), defaultDescription,
505                     getString(R.string.accessibility_introduction_title, mPackageName));
506         }
507     }
508 
509 
510     /**
511      * Creates {@link AccessibilityFooterPreference} and append into {@link PreferenceScreen}
512      *
513      * @param screen The preference screen to add the footer preference
514      * @param summary The summary of the preference summary.
515      * @param iconContentDescription The content description of icon in the footer.
516      */
517     @VisibleForTesting
createFooterPreference(PreferenceScreen screen, CharSequence summary, String iconContentDescription)518     void createFooterPreference(PreferenceScreen screen, CharSequence summary,
519             String iconContentDescription) {
520         final AccessibilityFooterPreference footerPreference =
521                 new AccessibilityFooterPreference(screen.getContext());
522         footerPreference.setSummary(summary);
523         footerPreference.setContentDescription(
524                 generateFooterContentDescription(summary));
525 
526         // Only framework tools support help link
527         if (getHelpResource() != 0) {
528             footerPreference.setLearnMoreAction(view -> {
529                 final Intent helpIntent = HelpUtils.getHelpIntent(
530                         getContext(), getContext().getString(getHelpResource()),
531                         getContext().getClass().getName());
532                 view.startActivityForResult(helpIntent, 0);
533             });
534 
535             final String learnMoreContentDescription = getPrefContext().getString(
536                     R.string.footer_learn_more_content_description, mPackageName);
537             footerPreference.setLearnMoreContentDescription(learnMoreContentDescription);
538         }
539         screen.addPreference(footerPreference);
540     }
541 
generateFooterContentDescription(CharSequence footerContent)542     private CharSequence generateFooterContentDescription(CharSequence footerContent) {
543         final StringBuffer sb = new StringBuffer();
544         sb.append(getPrefContext().getString(
545                 R.string.accessibility_introduction_title, mPackageName))
546                 .append("\n\n")
547                 .append(footerContent);
548         return sb;
549     }
550     @VisibleForTesting
setupEditShortcutDialog(Dialog dialog)551     void setupEditShortcutDialog(Dialog dialog) {
552         final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut);
553         mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox);
554         setDialogTextAreaClickListener(dialogSoftwareView, mSoftwareTypeCheckBox);
555 
556         final View dialogHardwareView = dialog.findViewById(R.id.hardware_shortcut);
557         mHardwareTypeCheckBox = dialogHardwareView.findViewById(R.id.checkbox);
558         setDialogTextAreaClickListener(dialogHardwareView, mHardwareTypeCheckBox);
559 
560         updateEditShortcutDialogCheckBox();
561     }
562 
setDialogTextAreaClickListener(View dialogView, CheckBox checkBox)563     private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) {
564         final View dialogTextArea = dialogView.findViewById(R.id.container);
565         dialogTextArea.setOnClickListener(v -> checkBox.toggle());
566     }
567 
updateEditShortcutDialogCheckBox()568     private void updateEditShortcutDialogCheckBox() {
569         // If it is during onConfigChanged process then restore the value, or get the saved value
570         // when shortcutPreference is checked.
571         int value = restoreOnConfigChangedValue();
572         if (value == NOT_SET) {
573             final int lastNonEmptyUserShortcutType = PreferredShortcuts.retrieveUserShortcutType(
574                     getPrefContext(), mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
575             value = mShortcutPreference.isChecked() ? lastNonEmptyUserShortcutType
576                     : UserShortcutType.EMPTY;
577         }
578 
579         mSoftwareTypeCheckBox.setChecked(
580                 hasShortcutType(value, UserShortcutType.SOFTWARE));
581         mHardwareTypeCheckBox.setChecked(
582                 hasShortcutType(value, UserShortcutType.HARDWARE));
583     }
584 
restoreOnConfigChangedValue()585     private int restoreOnConfigChangedValue() {
586         final int savedValue = mSavedCheckBoxValue;
587         mSavedCheckBoxValue = NOT_SET;
588         return savedValue;
589     }
590 
hasShortcutType(int value, @UserShortcutType int type)591     private boolean hasShortcutType(int value, @UserShortcutType int type) {
592         return (value & type) == type;
593     }
594 
595     /**
596      * Returns accumulated {@link UserShortcutType} checkbox value or {@code NOT_SET} if checkboxes
597      * did not exist.
598      */
getShortcutTypeCheckBoxValue()599     protected int getShortcutTypeCheckBoxValue() {
600         if (mSoftwareTypeCheckBox == null || mHardwareTypeCheckBox == null) {
601             return NOT_SET;
602         }
603 
604         int value = UserShortcutType.EMPTY;
605         if (mSoftwareTypeCheckBox.isChecked()) {
606             value |= UserShortcutType.SOFTWARE;
607         }
608         if (mHardwareTypeCheckBox.isChecked()) {
609             value |= UserShortcutType.HARDWARE;
610         }
611         return value;
612     }
613 
getShortcutTypeSummary(Context context)614     protected CharSequence getShortcutTypeSummary(Context context) {
615         if (!mShortcutPreference.isSettingsEditable()) {
616             return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware);
617         }
618 
619         if (!mShortcutPreference.isChecked()) {
620             return context.getText(R.string.switch_off_text);
621         }
622 
623         final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(context,
624                 mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
625 
626         final List<CharSequence> list = new ArrayList<>();
627         final CharSequence softwareTitle = context.getText(
628                 R.string.accessibility_shortcut_edit_summary_software);
629 
630         if (hasShortcutType(shortcutTypes, UserShortcutType.SOFTWARE)) {
631             list.add(softwareTitle);
632         }
633         if (hasShortcutType(shortcutTypes, UserShortcutType.HARDWARE)) {
634             final CharSequence hardwareTitle = context.getText(
635                     R.string.accessibility_shortcut_hardware_keyword);
636             list.add(hardwareTitle);
637         }
638 
639         // Show software shortcut if first time to use.
640         if (list.isEmpty()) {
641             list.add(softwareTitle);
642         }
643 
644         return CaseMap.toTitle().wholeString().noLowercase().apply(Locale.getDefault(), /* iter= */
645                 null, LocaleUtils.getConcatenatedString(list));
646     }
647 
648     /**
649      * This method will be invoked when a button in the edit shortcut dialog is clicked.
650      *
651      * @param dialog The dialog that received the click
652      * @param which  The button that was clicked
653      */
callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which)654     protected void callOnAlertDialogCheckboxClicked(DialogInterface dialog, int which) {
655         if (mComponentName == null) {
656             return;
657         }
658 
659         final int value = getShortcutTypeCheckBoxValue();
660 
661         saveNonEmptyUserShortcutType(value);
662         AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), value, mComponentName);
663         AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), ~value, mComponentName);
664         mShortcutPreference.setChecked(value != UserShortcutType.EMPTY);
665         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
666     }
667 
updateShortcutPreferenceData()668     protected void updateShortcutPreferenceData() {
669         if (mComponentName == null) {
670             return;
671         }
672 
673         final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
674                 getPrefContext(), mComponentName);
675         if (shortcutTypes != UserShortcutType.EMPTY) {
676             final PreferredShortcut shortcut = new PreferredShortcut(
677                     mComponentName.flattenToString(), shortcutTypes);
678             PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
679         }
680     }
681 
updateShortcutPreference()682     protected void updateShortcutPreference() {
683         if (mComponentName == null) {
684             return;
685         }
686 
687         final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
688                 mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
689         mShortcutPreference.setChecked(
690                 AccessibilityUtil.hasValuesInSettings(getPrefContext(), shortcutTypes,
691                         mComponentName));
692         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
693     }
694 
getShortcutPreferenceKey()695     protected String getShortcutPreferenceKey() {
696         return KEY_SHORTCUT_PREFERENCE;
697     }
698 
699     @Override
onToggleClicked(ShortcutPreference preference)700     public void onToggleClicked(ShortcutPreference preference) {
701         if (mComponentName == null) {
702             return;
703         }
704 
705         final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(getPrefContext(),
706                 mComponentName.flattenToString(), UserShortcutType.SOFTWARE);
707         if (preference.isChecked()) {
708             AccessibilityUtil.optInAllValuesToSettings(getPrefContext(), shortcutTypes,
709                     mComponentName);
710             showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
711         } else {
712             AccessibilityUtil.optOutAllValuesFromSettings(getPrefContext(), shortcutTypes,
713                     mComponentName);
714         }
715         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
716     }
717 
718     @Override
onSettingsClicked(ShortcutPreference preference)719     public void onSettingsClicked(ShortcutPreference preference) {
720         showDialog(DialogEnums.EDIT_SHORTCUT);
721     }
722 
723     /**
724      * Setups a configurable default if the setting has never been set.
725      */
setupDefaultShortcutIfNecessary(Context context)726     private static void setupDefaultShortcutIfNecessary(Context context) {
727         final String targetKey = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
728         String targetString = Settings.Secure.getString(context.getContentResolver(), targetKey);
729         if (!TextUtils.isEmpty(targetString)) {
730             // The shortcut setting has been set
731             return;
732         }
733 
734         // AccessibilityManager#getAccessibilityShortcutTargets may not return correct shortcut
735         // targets during boot. Needs to read settings directly here.
736         targetString = AccessibilityUtils.getShortcutTargetServiceComponentNameString(context,
737                 UserHandle.myUserId());
738         if (TextUtils.isEmpty(targetString)) {
739             // No configurable default accessibility service
740             return;
741         }
742 
743         // Only fallback to default accessibility service when setting is never updated.
744         final ComponentName shortcutName = ComponentName.unflattenFromString(targetString);
745         if (shortcutName != null) {
746             Settings.Secure.putString(context.getContentResolver(), targetKey,
747                     shortcutName.flattenToString());
748         }
749     }
750 
751     @VisibleForTesting
saveNonEmptyUserShortcutType(int type)752     void saveNonEmptyUserShortcutType(int type) {
753         if (type == UserShortcutType.EMPTY) {
754             return;
755         }
756 
757         final PreferredShortcut shortcut = new PreferredShortcut(
758                 mComponentName.flattenToString(), type);
759         PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
760     }
761 }
762