• 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.common.ShortcutConstants.UserShortcutType.DEFAULT;
20 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
21 import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
22 import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList;
23 
24 import android.app.Dialog;
25 import android.app.settings.SettingsEnums;
26 import android.content.ComponentName;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.DialogInterface;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.content.pm.ServiceInfo;
34 import android.graphics.drawable.Drawable;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.provider.Settings;
39 import android.service.quicksettings.TileService;
40 import android.text.Html;
41 import android.text.TextUtils;
42 import android.view.LayoutInflater;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.view.accessibility.AccessibilityManager;
46 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
47 import android.widget.CompoundButton;
48 import android.widget.CompoundButton.OnCheckedChangeListener;
49 import android.widget.ImageView;
50 
51 import androidx.annotation.NonNull;
52 import androidx.annotation.Nullable;
53 import androidx.annotation.VisibleForTesting;
54 import androidx.preference.Preference;
55 import androidx.preference.PreferenceCategory;
56 import androidx.preference.PreferenceScreen;
57 import androidx.preference.PreferenceViewHolder;
58 import androidx.recyclerview.widget.RecyclerView;
59 
60 import com.android.internal.accessibility.common.ShortcutConstants;
61 import com.android.internal.accessibility.util.ShortcutUtils;
62 import com.android.settings.R;
63 import com.android.settings.SettingsActivity;
64 import com.android.settings.accessibility.actionbar.FeedbackMenuController;
65 import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
66 import com.android.settings.dashboard.DashboardFragment;
67 import com.android.settings.flags.Flags;
68 import com.android.settings.widget.SettingsMainSwitchBar;
69 import com.android.settings.widget.SettingsMainSwitchPreference;
70 import com.android.settingslib.utils.ThreadUtils;
71 import com.android.settingslib.widget.IllustrationPreference;
72 import com.android.settingslib.widget.TopIntroPreference;
73 
74 import com.google.android.setupcompat.util.WizardManagerHelper;
75 import com.google.android.setupdesign.util.ThemeHelper;
76 
77 import java.util.ArrayList;
78 import java.util.List;
79 import java.util.Set;
80 
81 /**
82  * Base class for accessibility fragments with toggle, shortcut, some helper functions
83  * and dialog management.
84  */
85 public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
86         implements ShortcutPreference.OnClickCallback, OnCheckedChangeListener {
87 
88     public static final String KEY_GENERAL_CATEGORY = "general_categories";
89     public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
90     protected static final String KEY_TOP_INTRO_PREFERENCE = "top_intro";
91     protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description";
92     protected static final String KEY_ANIMATED_IMAGE = "animated_image";
93     // For html description of accessibility service, must follow the rule, such as
94     // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
95     private static final String IMG_PREFIX = "R.drawable.";
96     private static final String DRAWABLE_FOLDER = "drawable";
97 
98     protected TopIntroPreference mTopIntroPreference;
99     protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
100     protected ShortcutPreference mShortcutPreference;
101     protected Preference mSettingsPreference;
102     @Nullable protected AccessibilityFooterPreference mHtmlFooterPreference;
103     protected AccessibilityFooterPreferenceController mFooterPreferenceController;
104     protected String mPreferenceKey;
105     protected Dialog mDialog;
106     protected CharSequence mSettingsTitle;
107     protected Intent mSettingsIntent;
108     // The mComponentName maybe null, such as Magnify
109     protected ComponentName mComponentName;
110     protected CharSequence mFeatureName;
111     protected Uri mImageUri;
112     protected CharSequence mHtmlDescription;
113     protected CharSequence mTopIntroTitle;
114     private CharSequence mDescription;
115     private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
116     private AccessibilitySettingsContentObserver mSettingsContentObserver;
117     private ImageView mImageGetterCacheView;
118     protected final Html.ImageGetter mImageGetter = (String str) -> {
119         if (str != null && str.startsWith(IMG_PREFIX)) {
120             final String fileName = str.substring(IMG_PREFIX.length());
121             return getDrawableFromUri(Uri.parse(
122                     ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
123                             + mComponentName.getPackageName() + "/" + DRAWABLE_FOLDER + "/"
124                             + fileName));
125         }
126         return null;
127     };
128 
129     @Override
onCreate(Bundle savedInstanceState)130     public void onCreate(Bundle savedInstanceState) {
131         super.onCreate(savedInstanceState);
132 
133         onProcessArguments(getArguments());
134         final int resId = getPreferenceScreenResId();
135         if (resId <= 0) {
136             final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
137                     getPrefContext());
138             setPreferenceScreen(preferenceScreen);
139         }
140 
141         mSettingsContentObserver = new AccessibilitySettingsContentObserver(new Handler());
142         registerKeysToObserverCallback(mSettingsContentObserver);
143 
144         FeedbackMenuController.init(this, getFeedbackCategory());
145     }
146 
registerKeysToObserverCallback( AccessibilitySettingsContentObserver contentObserver)147     protected void registerKeysToObserverCallback(
148             AccessibilitySettingsContentObserver contentObserver) {
149         final List<String> shortcutFeatureKeys = getShortcutFeatureSettingsKeys();
150 
151         contentObserver.registerKeysToObserverCallback(shortcutFeatureKeys, key -> {
152             updateShortcutPreferenceData();
153             updateShortcutPreference();
154         });
155     }
156 
getShortcutFeatureSettingsKeys()157     protected List<String> getShortcutFeatureSettingsKeys() {
158         final List<String> shortcutFeatureKeys = new ArrayList<>();
159         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
160         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
161         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS);
162         return shortcutFeatureKeys;
163     }
164 
165     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)166     public View onCreateView(LayoutInflater inflater, ViewGroup container,
167             Bundle savedInstanceState) {
168         initTopIntroPreference();
169         initAnimatedImagePreference();
170         initToggleServiceSwitchPreference();
171         initGeneralCategory();
172         initShortcutPreference();
173         initSettingsPreference();
174         initAppInfoPreference();
175         initHtmlTextPreference();
176         initFooterPreference();
177 
178         installActionBarToggleSwitch();
179 
180         updateToggleServiceTitle(mToggleServiceSwitchPreference);
181 
182         mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> {
183             mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
184         };
185 
186         updatePreferenceOrder();
187         return super.onCreateView(inflater, container, savedInstanceState);
188     }
189 
190     @Override
onCreateDialog(int dialogId)191     public Dialog onCreateDialog(int dialogId) {
192         switch (dialogId) {
193             case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
194                 if (isAnySetupWizard()) {
195                     mDialog = AccessibilityShortcutsTutorial
196                             .createAccessibilityTutorialDialogForSetupWizard(
197                                     getPrefContext(), getUserPreferredShortcutTypes(),
198                                     this::callOnTutorialDialogButtonClicked, mFeatureName);
199                 } else {
200                     mDialog = AccessibilityShortcutsTutorial
201                             .createAccessibilityTutorialDialog(
202                                     getPrefContext(), getUserPreferredShortcutTypes(),
203                                     this::callOnTutorialDialogButtonClicked, mFeatureName);
204                 }
205                 mDialog.setCanceledOnTouchOutside(false);
206                 return mDialog;
207             default:
208                 throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
209         }
210     }
211 
212     @Override
onViewCreated(View view, Bundle savedInstanceState)213     public void onViewCreated(View view, Bundle savedInstanceState) {
214         super.onViewCreated(view, savedInstanceState);
215 
216         final SettingsActivity settingsActivity = (SettingsActivity) getActivity();
217         final SettingsMainSwitchBar switchBar = settingsActivity.getSwitchBar();
218         switchBar.hide();
219 
220         writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(getContext());
221     }
222 
223     @Override
onResume()224     public void onResume() {
225         super.onResume();
226 
227         final AccessibilityManager am = getPrefContext().getSystemService(
228                 AccessibilityManager.class);
229         am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
230         mSettingsContentObserver.register(getContentResolver());
231         updateShortcutPreferenceData();
232         updateShortcutPreference();
233     }
234 
235     @Override
onPause()236     public void onPause() {
237         final AccessibilityManager am = getPrefContext().getSystemService(
238                 AccessibilityManager.class);
239         am.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
240         mSettingsContentObserver.unregister(getContentResolver());
241         super.onPause();
242     }
243 
244     @Override
onDestroyView()245     public void onDestroyView() {
246         super.onDestroyView();
247         removeActionBarToggleSwitch();
248     }
249 
250     @Override
getDialogMetricsCategory(int dialogId)251     public int getDialogMetricsCategory(int dialogId) {
252         switch (dialogId) {
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     /**
266      * Returns the category of the feedback page.
267      *
268      * <p>By default, this method returns {@link SettingsEnums#PAGE_UNKNOWN}. This indicates that
269      * the feedback category is unknown, and the absence of a feedback menu.
270      *
271      * @return The feedback category, which is {@link SettingsEnums#PAGE_UNKNOWN} by default.
272      */
getFeedbackCategory()273     protected int getFeedbackCategory() {
274         return SettingsEnums.PAGE_UNKNOWN;
275     }
276 
277     @Override
getHelpResource()278     public int getHelpResource() {
279         return 0;
280     }
281 
282     @Override
onCheckedChanged(CompoundButton buttonView, boolean isChecked)283     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
284         onPreferenceToggled(mPreferenceKey, isChecked);
285     }
286 
287     /**
288      * Returns the shortcut type list which has been checked by user.
289      */
getUserShortcutTypes()290     abstract int getUserShortcutTypes();
291 
292     /** Returns the accessibility tile component name. */
getTileComponentName()293     abstract ComponentName getTileComponentName();
294 
updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference)295     protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
296         final CharSequence title =
297                 getString(R.string.accessibility_service_primary_switch_title, mFeatureName);
298         switchPreference.setTitle(title);
299     }
300 
getUseServicePreferenceKey()301     protected String getUseServicePreferenceKey() {
302         return "use_service";
303     }
304 
getShortcutTitle()305     protected CharSequence getShortcutTitle() {
306         return getString(R.string.accessibility_shortcut_title, mFeatureName);
307     }
308 
309     @VisibleForTesting
getContentDescriptionForAnimatedIllustration()310     CharSequence getContentDescriptionForAnimatedIllustration() {
311         return getString(R.string.accessibility_illustration_content_description, mFeatureName);
312     }
313 
onPreferenceToggled(String preferenceKey, boolean enabled)314     protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
315     }
316 
onInstallSwitchPreferenceToggleSwitch()317     protected void onInstallSwitchPreferenceToggleSwitch() {
318         // Implement this to set a checked listener.
319         updateSwitchBarToggleSwitch();
320         mToggleServiceSwitchPreference.addOnSwitchChangeListener(this);
321     }
322 
onRemoveSwitchPreferenceToggleSwitch()323     protected void onRemoveSwitchPreferenceToggleSwitch() {
324         // Implement this to reset a checked listener.
325     }
326 
updateSwitchBarToggleSwitch()327     protected void updateSwitchBarToggleSwitch() {
328         // Implement this to update the state of switch.
329     }
330 
setTitle(String title)331     public void setTitle(String title) {
332         getActivity().setTitle(title);
333     }
334 
onProcessArguments(@ullable Bundle arguments)335     protected void onProcessArguments(@Nullable Bundle arguments) {
336         if (arguments == null) {
337             return;
338         }
339         // Key.
340         mPreferenceKey = arguments.getString(AccessibilitySettings.EXTRA_PREFERENCE_KEY);
341 
342         // Title.
343         if (arguments.containsKey(AccessibilitySettings.EXTRA_RESOLVE_INFO)) {
344             ResolveInfo info = arguments.getParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO);
345             getActivity().setTitle(info.loadLabel(getPackageManager()).toString());
346         } else if (arguments.containsKey(AccessibilitySettings.EXTRA_TITLE)) {
347             setTitle(arguments.getString(AccessibilitySettings.EXTRA_TITLE));
348         }
349 
350         // Summary.
351         if (arguments.containsKey(AccessibilitySettings.EXTRA_SUMMARY)) {
352             mDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_SUMMARY);
353         }
354 
355         // Settings html description.
356         if (arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) {
357             mHtmlDescription = arguments.getCharSequence(
358                     AccessibilitySettings.EXTRA_HTML_DESCRIPTION);
359         }
360 
361         // Intro.
362         if (arguments.containsKey(AccessibilitySettings.EXTRA_INTRO)) {
363             mTopIntroTitle = arguments.getCharSequence(AccessibilitySettings.EXTRA_INTRO);
364         }
365     }
366 
installActionBarToggleSwitch()367     private void installActionBarToggleSwitch() {
368         onInstallSwitchPreferenceToggleSwitch();
369     }
370 
removeActionBarToggleSwitch()371     private void removeActionBarToggleSwitch() {
372         mToggleServiceSwitchPreference.setOnPreferenceClickListener(null);
373         onRemoveSwitchPreferenceToggleSwitch();
374     }
375 
updatePreferenceOrder()376     private void updatePreferenceOrder() {
377         final List<String> lists = getPreferenceOrderList();
378 
379         final PreferenceScreen preferenceScreen = getPreferenceScreen();
380         preferenceScreen.setOrderingAsAdded(false);
381 
382         final int size = lists.size();
383         for (int i = 0; i < size; i++) {
384             final Preference preference = preferenceScreen.findPreference(lists.get(i));
385             if (preference != null) {
386                 preference.setOrder(i);
387             }
388         }
389     }
390 
391     /** Customizes the order by preference key. */
getPreferenceOrderList()392     protected List<String> getPreferenceOrderList() {
393         final List<String> lists = new ArrayList<>();
394         lists.add(KEY_TOP_INTRO_PREFERENCE);
395         lists.add(KEY_ANIMATED_IMAGE);
396         lists.add(getUseServicePreferenceKey());
397         lists.add(KEY_GENERAL_CATEGORY);
398         lists.add(KEY_HTML_DESCRIPTION_PREFERENCE);
399         return lists;
400     }
401 
getDrawableFromUri(Uri imageUri)402     private Drawable getDrawableFromUri(Uri imageUri) {
403         if (mImageGetterCacheView == null) {
404             mImageGetterCacheView = new ImageView(getPrefContext());
405         }
406 
407         mImageGetterCacheView.setAdjustViewBounds(true);
408         mImageGetterCacheView.setImageURI(imageUri);
409 
410         if (mImageGetterCacheView.getDrawable() == null) {
411             return null;
412         }
413 
414         final Drawable drawable =
415                 mImageGetterCacheView.getDrawable().mutate().getConstantState().newDrawable();
416         mImageGetterCacheView.setImageURI(null);
417         final int imageWidth = drawable.getIntrinsicWidth();
418         final int imageHeight = drawable.getIntrinsicHeight();
419         final int screenHalfHeight = AccessibilityUtil.getScreenHeightPixels(getPrefContext()) / 2;
420         if ((imageWidth > AccessibilityUtil.getScreenWidthPixels(getPrefContext()))
421                 || (imageHeight > screenHalfHeight)) {
422             return null;
423         }
424 
425         drawable.setBounds(/* left= */0, /* top= */0, drawable.getIntrinsicWidth(),
426                 drawable.getIntrinsicHeight());
427 
428         return drawable;
429     }
initAnimatedImagePreference()430     private void initAnimatedImagePreference() {
431         initAnimatedImagePreference(mImageUri, new IllustrationPreference(getPrefContext()) {
432             @Override
433             public void onBindViewHolder(PreferenceViewHolder holder) {
434                 super.onBindViewHolder(holder);
435                 if (ThemeHelper.shouldApplyGlifExpressiveStyle(getContext())
436                         && isAnySetupWizard()) {
437                     View illustrationFrame = holder.findViewById(R.id.illustration_frame);
438                     final ViewGroup.LayoutParams lp = illustrationFrame.getLayoutParams();
439                     lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
440                     illustrationFrame.setLayoutParams(lp);
441                 }
442             }
443         });
444     }
445 
446     @VisibleForTesting
initAnimatedImagePreference( @ullable Uri imageUri, @NonNull IllustrationPreference preference)447     void initAnimatedImagePreference(
448             @Nullable Uri imageUri,
449             @NonNull IllustrationPreference preference) {
450         if (imageUri == null) {
451             return;
452         }
453 
454         final int displayHalfHeight =
455                 AccessibilityUtil.getDisplayBounds(getPrefContext()).height() / 2;
456         preference.setImageUri(imageUri);
457         preference.setSelectable(false);
458         preference.setMaxHeight(displayHalfHeight);
459         preference.setKey(KEY_ANIMATED_IMAGE);
460         preference.setOnBindListener(view -> {
461             // isAnimatable is decided in
462             // {@link IllustrationPreference#onBindViewHolder(PreferenceViewHolder)}. Therefore, we
463             // wait until the view is bond to set the content description for it.
464             // The content description is added for an animation illustration only. Since the static
465             // images are decorative.
466             ThreadUtils.getUiThreadHandler().post(() -> {
467                 if (preference.isAnimatable()) {
468                     preference.setContentDescription(
469                             getContentDescriptionForAnimatedIllustration());
470                 }
471             });
472         });
473         getPreferenceScreen().addPreference(preference);
474     }
475 
476     @VisibleForTesting
initTopIntroPreference()477     void initTopIntroPreference() {
478         if (TextUtils.isEmpty(mTopIntroTitle)) {
479             return;
480         }
481         mTopIntroPreference = new TopIntroPreference(getPrefContext());
482         mTopIntroPreference.setKey(KEY_TOP_INTRO_PREFERENCE);
483         mTopIntroPreference.setTitle(mTopIntroTitle);
484         getPreferenceScreen().addPreference(mTopIntroPreference);
485     }
486 
initToggleServiceSwitchPreference()487     private void initToggleServiceSwitchPreference() {
488         mToggleServiceSwitchPreference = new SettingsMainSwitchPreference(getPrefContext());
489         mToggleServiceSwitchPreference.setKey(getUseServicePreferenceKey());
490         if (getArguments() != null
491                 && getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) {
492             final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED);
493             mToggleServiceSwitchPreference.setChecked(enabled);
494         }
495 
496         getPreferenceScreen().addPreference(mToggleServiceSwitchPreference);
497     }
498 
initGeneralCategory()499     private void initGeneralCategory() {
500         final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext());
501         generalCategory.setKey(KEY_GENERAL_CATEGORY);
502         generalCategory.setTitle(R.string.accessibility_screen_option);
503 
504         getPreferenceScreen().addPreference(generalCategory);
505     }
506 
initShortcutPreference()507     protected void initShortcutPreference() {
508         // Initial the shortcut preference.
509         mShortcutPreference = new ShortcutPreference(getPrefContext(), /* attrs= */ null);
510         mShortcutPreference.setPersistent(false);
511         mShortcutPreference.setKey(getShortcutPreferenceKey());
512         mShortcutPreference.setOnClickCallback(this);
513         mShortcutPreference.setTitle(getShortcutTitle());
514 
515         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
516         generalCategory.addPreference(mShortcutPreference);
517     }
518 
initSettingsPreference()519     protected void initSettingsPreference() {
520         if (mSettingsTitle == null || mSettingsIntent == null) {
521             return;
522         }
523 
524         // Show the "Settings" menu as if it were a preference screen.
525         mSettingsPreference = new Preference(getPrefContext());
526         mSettingsPreference.setTitle(mSettingsTitle);
527         mSettingsPreference.setIconSpaceReserved(false);
528         mSettingsPreference.setIntent(mSettingsIntent);
529 
530         final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
531         generalCategory.addPreference(mSettingsPreference);
532     }
533 
534     @VisibleForTesting
535     @Nullable
createAppInfoPreference()536     Preference createAppInfoPreference() {
537         if (!Flags.accessibilityShowAppInfoButton()) {
538             return null;
539         }
540         // App Info is not available in Setup Wizard.
541         if (isAnySetupWizard()) {
542             return null;
543         }
544         // Only show the button for pages with valid component package names.
545         if (mComponentName == null) {
546             return null;
547         }
548         final String packageName = mComponentName.getPackageName();
549         final PackageManager packageManager = getPrefContext().getPackageManager();
550         if (!packageManager.isPackageAvailable(packageName)) {
551             return null;
552         }
553 
554         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
555         intent.setPackage(getContext().getPackageName());
556         intent.setData(Uri.parse("package:" + packageName));
557 
558         final Preference appInfoPreference = new Preference(getPrefContext());
559         appInfoPreference.setTitle(getString(R.string.application_info_label));
560         appInfoPreference.setIconSpaceReserved(false);
561         appInfoPreference.setIntent(intent);
562         return appInfoPreference;
563     }
564 
initAppInfoPreference()565     private void initAppInfoPreference() {
566         final Preference appInfoPreference = createAppInfoPreference();
567         if (appInfoPreference != null) {
568             final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
569             generalCategory.addPreference(appInfoPreference);
570         }
571     }
572 
initHtmlTextPreference()573     private void initHtmlTextPreference() {
574         if (TextUtils.isEmpty(getCurrentHtmlDescription())) {
575             return;
576         }
577         final PreferenceScreen screen = getPreferenceScreen();
578 
579         mHtmlFooterPreference =
580                 new AccessibilityFooterPreference(screen.getContext());
581         mHtmlFooterPreference.setKey(KEY_HTML_DESCRIPTION_PREFERENCE);
582         updateHtmlTextPreference();
583         screen.addPreference(mHtmlFooterPreference);
584 
585         // TODO(b/171272809): Migrate to DashboardFragment.
586         final String title = getString(R.string.accessibility_introduction_title, mFeatureName);
587         mFooterPreferenceController = new AccessibilityFooterPreferenceController(
588                 screen.getContext(), mHtmlFooterPreference.getKey());
589         mFooterPreferenceController.setIntroductionTitle(title);
590         mFooterPreferenceController.displayPreference(screen);
591     }
592 
updateHtmlTextPreference()593     protected void updateHtmlTextPreference() {
594         if (mHtmlFooterPreference == null) {
595             return;
596         }
597 
598         String description = getCurrentHtmlDescription().toString();
599         final CharSequence htmlDescription = Html.fromHtml(description,
600                 Html.FROM_HTML_MODE_COMPACT, mImageGetter, /* tagHandler= */ null);
601         mHtmlFooterPreference.setSummary(htmlDescription);
602     }
603 
getCurrentHtmlDescription()604     CharSequence getCurrentHtmlDescription() {
605         return mHtmlDescription;
606     }
607 
initFooterPreference()608     private void initFooterPreference() {
609         if (!TextUtils.isEmpty(mDescription)) {
610             createFooterPreference(getPreferenceScreen(), mDescription,
611                     getString(R.string.accessibility_introduction_title, mFeatureName));
612         }
613     }
614 
615     /**
616      * Creates {@link AccessibilityFooterPreference} and append into {@link PreferenceScreen}
617      *
618      * @param screen The preference screen to add the footer preference
619      * @param summary The summary of the preference summary
620      * @param introductionTitle The title of introduction in the footer
621      */
622     @VisibleForTesting
createFooterPreference(PreferenceScreen screen, CharSequence summary, String introductionTitle)623     void createFooterPreference(PreferenceScreen screen, CharSequence summary,
624             String introductionTitle) {
625         final AccessibilityFooterPreference footerPreference =
626                 new AccessibilityFooterPreference(screen.getContext());
627         footerPreference.setSummary(summary);
628         screen.addPreference(footerPreference);
629 
630         mFooterPreferenceController = new AccessibilityFooterPreferenceController(
631                 screen.getContext(), footerPreference.getKey());
632         mFooterPreferenceController.setIntroductionTitle(introductionTitle);
633         mFooterPreferenceController.displayPreference(screen);
634     }
635 
getShortcutTypeSummary(Context context)636     protected CharSequence getShortcutTypeSummary(Context context) {
637         if (!mShortcutPreference.isSettingsEditable()) {
638             return context.getText(R.string.accessibility_shortcut_edit_dialog_title_hardware);
639         }
640 
641         if (!mShortcutPreference.isChecked()) {
642             return context.getText(R.string.accessibility_shortcut_state_off);
643         }
644 
645         final int shortcutTypes = PreferredShortcuts.retrieveUserShortcutType(
646                 context, mComponentName.flattenToString(), getDefaultShortcutTypes());
647         return getShortcutSummaryList(context, shortcutTypes);
648     }
649 
650     /**
651      * This method will be invoked when a button in the tutorial dialog is clicked.
652      *
653      * @param dialog The dialog that received the click
654      * @param which  The button that was clicked
655      */
callOnTutorialDialogButtonClicked(DialogInterface dialog, int which)656     private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) {
657         dialog.dismiss();
658     }
659 
updateShortcutPreferenceData()660     protected void updateShortcutPreferenceData() {
661         if (mComponentName == null) {
662             return;
663         }
664 
665         final int shortcutTypes = AccessibilityUtil.getUserShortcutTypesFromSettings(
666                 getPrefContext(), mComponentName);
667         if (shortcutTypes != DEFAULT) {
668             final PreferredShortcut shortcut = new PreferredShortcut(
669                     mComponentName.flattenToString(), shortcutTypes);
670             PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut);
671         }
672     }
673 
updateShortcutPreference()674     protected void updateShortcutPreference() {
675         if (mComponentName == null) {
676             return;
677         }
678 
679         final int shortcutTypes = getUserPreferredShortcutTypes();
680         mShortcutPreference.setChecked(
681                 ShortcutUtils.isShortcutContained(
682                         getPrefContext(), shortcutTypes, mComponentName.flattenToString()));
683         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
684     }
685 
getShortcutPreferenceKey()686     protected String getShortcutPreferenceKey() {
687         return KEY_SHORTCUT_PREFERENCE;
688     }
689 
690     @Override
onToggleClicked(ShortcutPreference preference)691     public void onToggleClicked(ShortcutPreference preference) {
692         if (mComponentName == null) {
693             return;
694         }
695 
696         final int shortcutTypes = getUserPreferredShortcutTypes();
697         final boolean isChecked = preference.isChecked();
698         getPrefContext().getSystemService(AccessibilityManager.class).enableShortcutsForTargets(
699                 isChecked, shortcutTypes,
700                 Set.of(mComponentName.flattenToString()), getPrefContext().getUserId());
701         if (isChecked) {
702             showDialog(DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL);
703         }
704         mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext()));
705     }
706 
707     @Override
onSettingsClicked(ShortcutPreference preference)708     public void onSettingsClicked(ShortcutPreference preference) {
709         EditShortcutsPreferenceFragment.showEditShortcutScreen(
710                 requireContext(), getMetricsCategory(), getShortcutTitle(),
711                 mComponentName, getIntent());
712     }
713 
714     /**
715      * Setups {@link com.android.internal.R.string#config_defaultAccessibilityService} into
716      * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE} if that settings key has never
717      * been set and only write the key when user enter into corresponding page.
718      */
719     @VisibleForTesting
writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(Context context)720     void writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(Context context) {
721         if (mComponentName == null) {
722             return;
723         }
724 
725         // It might be shortened form (with a leading '.'). Need to unflatten back to ComponentName
726         // first, or it will encounter errors when getting service from
727         // `ACCESSIBILITY_SHORTCUT_TARGET_SERVICE`.
728         final ComponentName configDefaultService = ComponentName.unflattenFromString(
729                 getString(com.android.internal.R.string.config_defaultAccessibilityService));
730 
731         if (!mComponentName.equals(configDefaultService)) {
732             return;
733         }
734 
735         final String targetKey = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
736         final String targetString = Settings.Secure.getString(context.getContentResolver(),
737                 targetKey);
738 
739         // By intentional, we only need to write the config string when the Settings key has never
740         // been set (== null). Empty string also means someone already wrote it before, so we need
741         // to respect the value.
742         if (targetString == null) {
743             Settings.Secure.putString(context.getContentResolver(), targetKey,
744                     configDefaultService.flattenToString());
745         }
746     }
747 
748     /** Returns user visible name of the tile by given {@link ComponentName}. */
loadTileLabel(Context context, ComponentName componentName)749     protected CharSequence loadTileLabel(Context context, ComponentName componentName) {
750         final PackageManager packageManager = context.getPackageManager();
751         final Intent queryIntent = new Intent(TileService.ACTION_QS_TILE);
752         final List<ResolveInfo> resolveInfos =
753                 packageManager.queryIntentServices(queryIntent, PackageManager.GET_META_DATA);
754         for (ResolveInfo info : resolveInfos) {
755             final ServiceInfo serviceInfo = info.serviceInfo;
756             if (TextUtils.equals(componentName.getPackageName(), serviceInfo.packageName)
757                     && TextUtils.equals(componentName.getClassName(), serviceInfo.name)) {
758                 return serviceInfo.loadLabel(packageManager);
759             }
760         }
761         return null;
762     }
763 
764     @VisibleForTesting
isAnySetupWizard()765     boolean isAnySetupWizard() {
766         return WizardManagerHelper.isAnySetupWizard(getIntent());
767     }
768 
769     /**
770      * Returns the default preferred shortcut types when the user doesn't have a preferred shortcut
771      * types
772      */
773     @ShortcutConstants.UserShortcutType
getDefaultShortcutTypes()774     protected int getDefaultShortcutTypes() {
775         return SOFTWARE;
776     }
777 
778     /**
779      * Returns the user preferred shortcut types or the default shortcut types if not set
780      */
781     @ShortcutConstants.UserShortcutType
getUserPreferredShortcutTypes()782     protected int getUserPreferredShortcutTypes() {
783         return PreferredShortcuts.retrieveUserShortcutType(
784                 getPrefContext(), mComponentName.flattenToString(), getDefaultShortcutTypes());
785     }
786 
787     @Override
onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)788     public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
789             Bundle savedInstanceState) {
790         RecyclerView recyclerView =
791                 super.onCreateRecyclerView(inflater, parent, savedInstanceState);
792         return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView);
793     }
794 }
795