1 /*
2  * Copyright (C) 2009 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.settingslib.TwoTargetPreference.ICON_SIZE_MEDIUM;
20 
21 import android.accessibilityservice.AccessibilityServiceInfo;
22 import android.accessibilityservice.AccessibilityShortcutInfo;
23 import android.app.admin.DevicePolicyManager;
24 import android.app.settings.SettingsEnums;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.content.pm.ServiceInfo;
31 import android.graphics.drawable.Drawable;
32 import android.hardware.display.ColorDisplayManager;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.view.accessibility.AccessibilityManager;
41 
42 import androidx.annotation.VisibleForTesting;
43 import androidx.core.content.ContextCompat;
44 import androidx.preference.Preference;
45 import androidx.preference.PreferenceCategory;
46 import androidx.preference.SwitchPreference;
47 
48 import com.android.internal.accessibility.AccessibilityShortcutController;
49 import com.android.internal.content.PackageMonitor;
50 import com.android.settings.R;
51 import com.android.settings.Utils;
52 import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
53 import com.android.settings.dashboard.DashboardFragment;
54 import com.android.settings.display.DarkUIPreferenceController;
55 import com.android.settings.search.BaseSearchIndexProvider;
56 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
57 import com.android.settingslib.RestrictedLockUtilsInternal;
58 import com.android.settingslib.RestrictedPreference;
59 import com.android.settingslib.accessibility.AccessibilityUtils;
60 import com.android.settingslib.search.SearchIndexable;
61 
62 import java.util.ArrayList;
63 import java.util.Collection;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 
68 /** Activity with the accessibility settings. */
69 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
70 public class AccessibilitySettings extends DashboardFragment {
71 
72     private static final String TAG = "AccessibilitySettings";
73 
74     // Index of the first preference in a preference category.
75     private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1;
76 
77     // Preference categories
78     private static final String CATEGORY_SCREEN_READER = "screen_reader_category";
79     private static final String CATEGORY_AUDIO_AND_CAPTIONS = "audio_and_captions_category";
80     private static final String CATEGORY_DISPLAY = "display_category";
81     private static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category";
82     private static final String CATEGORY_EXPERIMENTAL = "experimental_category";
83     private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category";
84 
85     private static final String[] CATEGORIES = new String[] {
86             CATEGORY_SCREEN_READER, CATEGORY_AUDIO_AND_CAPTIONS, CATEGORY_DISPLAY,
87             CATEGORY_INTERACTION_CONTROL, CATEGORY_EXPERIMENTAL, CATEGORY_DOWNLOADED_SERVICES
88     };
89 
90     // Preferences
91     private static final String TOGGLE_INVERSION_PREFERENCE =
92             "toggle_inversion_preference";
93     private static final String TOGGLE_LARGE_POINTER_ICON =
94             "toggle_large_pointer_icon";
95     private static final String TOGGLE_DISABLE_ANIMATIONS = "toggle_disable_animations";
96     private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
97             "magnification_preference_screen";
98     private static final String DISPLAY_DALTONIZER_PREFERENCE_SCREEN =
99             "daltonizer_preference";
100 
101     // Extras passed to sub-fragments.
102     static final String EXTRA_PREFERENCE_KEY = "preference_key";
103     static final String EXTRA_CHECKED = "checked";
104     static final String EXTRA_TITLE = "title";
105     static final String EXTRA_TITLE_RES = "title_res";
106     static final String EXTRA_RESOLVE_INFO = "resolve_info";
107     static final String EXTRA_SUMMARY = "summary";
108     static final String EXTRA_SETTINGS_TITLE = "settings_title";
109     static final String EXTRA_COMPONENT_NAME = "component_name";
110     static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
111     static final String EXTRA_VIDEO_RAW_RESOURCE_ID = "video_resource";
112     static final String EXTRA_LAUNCHED_FROM_SUW = "from_suw";
113     static final String EXTRA_ANIMATED_IMAGE_RES = "animated_image_res";
114     static final String EXTRA_HTML_DESCRIPTION = "html_description";
115 
116     // Timeout before we update the services if packages are added/removed
117     // since the AccessibilityManagerService has to do that processing first
118     // to generate the AccessibilityServiceInfo we need for proper
119     // presentation.
120     private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
121 
122     private final Handler mHandler = new Handler();
123 
124     private final Runnable mUpdateRunnable = new Runnable() {
125         @Override
126         public void run() {
127             if (getActivity() != null) {
128                 updateServicePreferences();
129             }
130         }
131     };
132 
133     private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
134         @Override
135         public void onPackageAdded(String packageName, int uid) {
136             sendUpdate();
137         }
138 
139         @Override
140         public void onPackageAppeared(String packageName, int reason) {
141             sendUpdate();
142         }
143 
144         @Override
145         public void onPackageDisappeared(String packageName, int reason) {
146             sendUpdate();
147         }
148 
149         @Override
150         public void onPackageRemoved(String packageName, int uid) {
151             sendUpdate();
152         }
153 
154         private void sendUpdate() {
155             mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_SERVICES_MILLIS);
156         }
157     };
158 
159     private final SettingsContentObserver mSettingsContentObserver;
160 
161     private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
162             new ArrayMap<>();
163     private final Map<Preference, PreferenceCategory> mServicePreferenceToPreferenceCategoryMap =
164             new ArrayMap<>();
165     private final Map<ComponentName, PreferenceCategory> mPreBundledServiceComponentToCategoryMap =
166             new ArrayMap<>();
167 
168     private SwitchPreference mToggleLargePointerIconPreference;
169     private SwitchPreference mToggleDisableAnimationsPreference;
170     private Preference mDisplayMagnificationPreferenceScreen;
171     private Preference mDisplayDaltonizerPreferenceScreen;
172     private Preference mToggleInversionPreference;
173 
174     /**
175      * Check if the color transforms are color accelerated. Some transforms are experimental only
176      * on non-accelerated platforms due to the performance implications.
177      *
178      * @param context The current context
179      */
isColorTransformAccelerated(Context context)180     public static boolean isColorTransformAccelerated(Context context) {
181         return context.getResources()
182                 .getBoolean(com.android.internal.R.bool.config_setColorTransformAccelerated);
183     }
184 
AccessibilitySettings()185     public AccessibilitySettings() {
186         // Observe changes to anything that the shortcut can toggle, so we can reflect updates
187         final Collection<AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> features =
188                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values();
189         final List<String> shortcutFeatureKeys = new ArrayList<>(features.size());
190         for (AccessibilityShortcutController.ToggleableFrameworkFeatureInfo feature : features) {
191             shortcutFeatureKeys.add(feature.getSettingKey());
192         }
193 
194         // Observe changes from accessibility selection menu
195         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
196         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
197         mSettingsContentObserver = new SettingsContentObserver(mHandler, shortcutFeatureKeys) {
198             @Override
199             public void onChange(boolean selfChange, Uri uri) {
200                 updateAllPreferences();
201             }
202         };
203     }
204 
205     @Override
getMetricsCategory()206     public int getMetricsCategory() {
207         return SettingsEnums.ACCESSIBILITY;
208     }
209 
210     @Override
getHelpResource()211     public int getHelpResource() {
212         return R.string.help_uri_accessibility;
213     }
214 
215     @Override
onCreate(Bundle icicle)216     public void onCreate(Bundle icicle) {
217         super.onCreate(icicle);
218         initializeAllPreferences();
219     }
220 
221     @Override
onAttach(Context context)222     public void onAttach(Context context) {
223         super.onAttach(context);
224         use(DarkUIPreferenceController.class).setParentFragment(this);
225         use(AccessibilityHearingAidPreferenceController.class)
226                 .setFragmentManager(getFragmentManager());
227     }
228 
229     @Override
onStart()230     public void onStart() {
231         super.onStart();
232         updateAllPreferences();
233 
234         mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
235         mSettingsContentObserver.register(getContentResolver());
236     }
237 
238     @Override
onStop()239     public void onStop() {
240         mSettingsPackageMonitor.unregister();
241         mSettingsContentObserver.unregister(getContentResolver());
242         super.onStop();
243     }
244 
245     @Override
getPreferenceScreenResId()246     protected int getPreferenceScreenResId() {
247         return R.xml.accessibility_settings;
248     }
249 
250     @Override
getLogTag()251     protected String getLogTag() {
252         return TAG;
253     }
254 
255     /**
256      * Returns the summary for the current state of this accessibilityService.
257      *
258      * @param context A valid context
259      * @param info The accessibilityService's info
260      * @param serviceEnabled Whether the accessibility service is enabled.
261      * @return The service summary
262      */
263     @VisibleForTesting
getServiceSummary(Context context, AccessibilityServiceInfo info, boolean serviceEnabled)264     static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info,
265             boolean serviceEnabled) {
266         if (serviceEnabled && info.crashed) {
267             return context.getText(R.string.accessibility_summary_state_stopped);
268         }
269 
270         final CharSequence serviceState;
271         final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info);
272         if (fragmentType == AccessibilityServiceFragmentType.INVISIBLE_TOGGLE) {
273             final ComponentName componentName = new ComponentName(
274                     info.getResolveInfo().serviceInfo.packageName,
275                     info.getResolveInfo().serviceInfo.name);
276             final boolean shortcutEnabled = AccessibilityUtil.getUserShortcutTypesFromSettings(
277                     context, componentName) != AccessibilityUtil.UserShortcutType.EMPTY;
278             serviceState = shortcutEnabled
279                     ? context.getText(R.string.accessibility_summary_shortcut_enabled)
280                     : context.getText(R.string.accessibility_summary_shortcut_disabled);
281         } else {
282             serviceState = serviceEnabled
283                     ? context.getText(R.string.accessibility_summary_state_enabled)
284                     : context.getText(R.string.accessibility_summary_state_disabled);
285         }
286 
287         final CharSequence serviceSummary = info.loadSummary(context.getPackageManager());
288         final String stateSummaryCombo = context.getString(
289                 R.string.preference_summary_default_combination,
290                 serviceState, serviceSummary);
291 
292         return TextUtils.isEmpty(serviceSummary) ? serviceState : stateSummaryCombo;
293     }
294 
295     /**
296      * Returns the description for the current state of this accessibilityService.
297      *
298      * @param context A valid context
299      * @param info The accessibilityService's info
300      * @param serviceEnabled Whether the accessibility service is enabled.
301      * @return The service description
302      */
303     @VisibleForTesting
getServiceDescription(Context context, AccessibilityServiceInfo info, boolean serviceEnabled)304     static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info,
305             boolean serviceEnabled) {
306         if (serviceEnabled && info.crashed) {
307             return context.getText(R.string.accessibility_description_state_stopped);
308         }
309 
310         return info.loadDescription(context.getPackageManager());
311     }
312 
isRampingRingerEnabled(final Context context)313     static boolean isRampingRingerEnabled(final Context context) {
314         return Settings.Global.getInt(
315                 context.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
316     }
317 
initializeAllPreferences()318     private void initializeAllPreferences() {
319         for (int i = 0; i < CATEGORIES.length; i++) {
320             PreferenceCategory prefCategory = findPreference(CATEGORIES[i]);
321             mCategoryToPrefCategoryMap.put(CATEGORIES[i], prefCategory);
322         }
323 
324         // Display inversion.
325         mToggleInversionPreference = findPreference(TOGGLE_INVERSION_PREFERENCE);
326 
327         // Large pointer icon.
328         mToggleLargePointerIconPreference = findPreference(TOGGLE_LARGE_POINTER_ICON);
329 
330         mToggleDisableAnimationsPreference = findPreference(TOGGLE_DISABLE_ANIMATIONS);
331 
332         // Display magnification.
333         mDisplayMagnificationPreferenceScreen = findPreference(
334                 DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN);
335 
336         // Display color adjustments.
337         mDisplayDaltonizerPreferenceScreen = findPreference(DISPLAY_DALTONIZER_PREFERENCE_SCREEN);
338     }
339 
updateAllPreferences()340     private void updateAllPreferences() {
341         updateSystemPreferences();
342         updateServicePreferences();
343     }
344 
updateServicePreferences()345     protected void updateServicePreferences() {
346         // Since services category is auto generated we have to do a pass
347         // to generate it since services can come and go and then based on
348         // the global accessibility state to decided whether it is enabled.
349         final ArrayList<Preference> servicePreferences =
350                 new ArrayList<>(mServicePreferenceToPreferenceCategoryMap.keySet());
351         for (int i = 0; i < servicePreferences.size(); i++) {
352             Preference service = servicePreferences.get(i);
353             PreferenceCategory category = mServicePreferenceToPreferenceCategoryMap.get(service);
354             category.removePreference(service);
355         }
356 
357         initializePreBundledServicesMapFromArray(CATEGORY_SCREEN_READER,
358                 R.array.config_preinstalled_screen_reader_services);
359         initializePreBundledServicesMapFromArray(CATEGORY_AUDIO_AND_CAPTIONS,
360                 R.array.config_preinstalled_audio_and_caption_services);
361         initializePreBundledServicesMapFromArray(CATEGORY_DISPLAY,
362                 R.array.config_preinstalled_display_services);
363         initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL,
364                 R.array.config_preinstalled_interaction_control_services);
365 
366         final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList(
367                 getPrefContext());
368 
369         final PreferenceCategory downloadedServicesCategory =
370                 mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES);
371 
372         for (int i = 0, count = preferenceList.size(); i < count; ++i) {
373             final RestrictedPreference preference = preferenceList.get(i);
374             final ComponentName componentName = preference.getExtras().getParcelable(
375                     EXTRA_COMPONENT_NAME);
376             PreferenceCategory prefCategory = downloadedServicesCategory;
377             // Set the appropriate category if the service comes pre-installed.
378             if (mPreBundledServiceComponentToCategoryMap.containsKey(componentName)) {
379                 prefCategory = mPreBundledServiceComponentToCategoryMap.get(componentName);
380             }
381             prefCategory.addPreference(preference);
382             mServicePreferenceToPreferenceCategoryMap.put(preference, prefCategory);
383         }
384 
385         // Update the order of all the category according to the order defined in xml file.
386         updateCategoryOrderFromArray(CATEGORY_SCREEN_READER,
387             R.array.config_order_screen_reader_services);
388         updateCategoryOrderFromArray(CATEGORY_AUDIO_AND_CAPTIONS,
389             R.array.config_order_audio_and_caption_services);
390         updateCategoryOrderFromArray(CATEGORY_INTERACTION_CONTROL,
391             R.array.config_order_interaction_control_services);
392         updateCategoryOrderFromArray(CATEGORY_DISPLAY,
393             R.array.config_order_display_services);
394 
395         // Need to check each time when updateServicePreferences() called.
396         if (downloadedServicesCategory.getPreferenceCount() == 0) {
397             getPreferenceScreen().removePreference(downloadedServicesCategory);
398         } else {
399             getPreferenceScreen().addPreference(downloadedServicesCategory);
400         }
401     }
402 
getInstalledAccessibilityList(Context context)403     private List<RestrictedPreference> getInstalledAccessibilityList(Context context) {
404         final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context);
405         final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);
406 
407         final List<AccessibilityShortcutInfo> installedShortcutList =
408                 a11yManager.getInstalledAccessibilityShortcutListAsUser(context,
409                         UserHandle.myUserId());
410 
411         // Remove duplicate item here, new a ArrayList to copy unmodifiable list result
412         // (getInstalledAccessibilityServiceList).
413         final List<AccessibilityServiceInfo> installedServiceList = new ArrayList<>(
414                 a11yManager.getInstalledAccessibilityServiceList());
415         installedServiceList.removeIf(
416                 target -> containsTargetNameInList(installedShortcutList, target));
417 
418         final List<RestrictedPreference> activityList =
419                 preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList);
420 
421         final List<RestrictedPreference> serviceList =
422                 preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList);
423 
424         final List<RestrictedPreference> preferenceList = new ArrayList<>();
425         preferenceList.addAll(activityList);
426         preferenceList.addAll(serviceList);
427 
428         return preferenceList;
429     }
430 
containsTargetNameInList(List<AccessibilityShortcutInfo> shortcutInfos, AccessibilityServiceInfo targetServiceInfo)431     private boolean containsTargetNameInList(List<AccessibilityShortcutInfo> shortcutInfos,
432             AccessibilityServiceInfo targetServiceInfo) {
433         final ServiceInfo serviceInfo = targetServiceInfo.getResolveInfo().serviceInfo;
434         final String servicePackageName = serviceInfo.packageName;
435         final CharSequence serviceLabel = serviceInfo.loadLabel(getPackageManager());
436 
437         for (int i = 0, count = shortcutInfos.size(); i < count; ++i) {
438             final ActivityInfo activityInfo = shortcutInfos.get(i).getActivityInfo();
439             final String activityPackageName = activityInfo.packageName;
440             final CharSequence activityLabel = activityInfo.loadLabel(getPackageManager());
441             if (servicePackageName.equals(activityPackageName)
442                     && serviceLabel.equals(activityLabel)) {
443                 return true;
444             }
445         }
446         return false;
447     }
448 
initializePreBundledServicesMapFromArray(String categoryKey, int key)449     private void initializePreBundledServicesMapFromArray(String categoryKey, int key) {
450         String[] services = getResources().getStringArray(key);
451         PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
452         for (int i = 0; i < services.length; i++) {
453             ComponentName component = ComponentName.unflattenFromString(services[i]);
454             mPreBundledServiceComponentToCategoryMap.put(component, category);
455         }
456     }
457 
458     /**
459      * Update the order of preferences in the category by matching their preference
460      * key with the string array of preference order which is defined in the xml.
461      *
462      * @param categoryKey The key of the category need to update the order
463      * @param key The key of the string array which defines the order of category
464      */
updateCategoryOrderFromArray(String categoryKey, int key)465     private void updateCategoryOrderFromArray(String categoryKey, int key) {
466         String[] services = getResources().getStringArray(key);
467         PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
468         int preferenceCount = category.getPreferenceCount();
469         int serviceLength = services.length;
470         for (int preferenceIndex = 0; preferenceIndex < preferenceCount; preferenceIndex++) {
471             for (int serviceIndex = 0; serviceIndex < serviceLength; serviceIndex++) {
472                 if (category.getPreference(preferenceIndex).getKey()
473                         .equals(services[serviceIndex])) {
474                     category.getPreference(preferenceIndex).setOrder(serviceIndex);
475                     break;
476                 }
477             }
478         }
479     }
480 
updateSystemPreferences()481     protected void updateSystemPreferences() {
482         // Move color inversion and color correction preferences to Display category if device
483         // supports HWC hardware-accelerated color transform.
484         if (ColorDisplayManager.isColorTransformAccelerated(getContext())) {
485             PreferenceCategory experimentalCategory =
486                     mCategoryToPrefCategoryMap.get(CATEGORY_EXPERIMENTAL);
487             PreferenceCategory displayCategory =
488                     mCategoryToPrefCategoryMap.get(CATEGORY_DISPLAY);
489             experimentalCategory.removePreference(mToggleInversionPreference);
490             experimentalCategory.removePreference(mDisplayDaltonizerPreferenceScreen);
491             mDisplayMagnificationPreferenceScreen.setSummary(
492                     ToggleScreenMagnificationPreferenceFragment.getServiceSummary(getContext()));
493             mDisplayDaltonizerPreferenceScreen.setOrder(
494                     mDisplayMagnificationPreferenceScreen.getOrder() + 1);
495             mDisplayDaltonizerPreferenceScreen.setSummary(AccessibilityUtil.getSummary(
496                     getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED));
497             mToggleInversionPreference.setOrder(
498                     mDisplayDaltonizerPreferenceScreen.getOrder() + 1);
499             mToggleLargePointerIconPreference.setOrder(
500                     mToggleInversionPreference.getOrder() + 1);
501             mToggleDisableAnimationsPreference.setOrder(
502                     mToggleLargePointerIconPreference.getOrder() + 1);
503             mToggleInversionPreference.setSummary(AccessibilityUtil.getSummary(
504                     getContext(), Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED));
505             displayCategory.addPreference(mToggleInversionPreference);
506             displayCategory.addPreference(mDisplayDaltonizerPreferenceScreen);
507         }
508     }
509 
510     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
511             new BaseSearchIndexProvider(R.xml.accessibility_settings);
512 
513     /**
514      * This class helps setup RestrictedPreference.
515      */
516     @VisibleForTesting
517     static class RestrictedPreferenceHelper {
518         private final Context mContext;
519         private final DevicePolicyManager mDpm;
520         private final PackageManager mPm;
521 
RestrictedPreferenceHelper(Context context)522         RestrictedPreferenceHelper(Context context) {
523             mContext = context;
524             mDpm = context.getSystemService(DevicePolicyManager.class);
525             mPm = context.getPackageManager();
526         }
527 
528         /**
529          * Creates the list of {@link RestrictedPreference} with the installedServices arguments.
530          *
531          * @param installedServices The list of {@link AccessibilityServiceInfo}s of the
532          *                          installed accessibility services
533          * @return The list of {@link RestrictedPreference}
534          */
createAccessibilityServicePreferenceList( List<AccessibilityServiceInfo> installedServices)535         List<RestrictedPreference> createAccessibilityServicePreferenceList(
536                 List<AccessibilityServiceInfo> installedServices) {
537 
538             final Set<ComponentName> enabledServices =
539                     AccessibilityUtils.getEnabledServicesFromSettings(mContext);
540             final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
541                     UserHandle.myUserId());
542             final int installedServicesSize = installedServices.size();
543 
544             final List<RestrictedPreference> preferenceList = new ArrayList<>(
545                     installedServicesSize);
546 
547             for (int i = 0; i < installedServicesSize; ++i) {
548                 final AccessibilityServiceInfo info = installedServices.get(i);
549                 final ResolveInfo resolveInfo = info.getResolveInfo();
550                 final String packageName = resolveInfo.serviceInfo.packageName;
551                 final ComponentName componentName = new ComponentName(packageName,
552                         resolveInfo.serviceInfo.name);
553 
554                 final String key = componentName.flattenToString();
555                 final CharSequence title = resolveInfo.loadLabel(mPm);
556                 final boolean serviceEnabled = enabledServices.contains(componentName);
557                 final CharSequence summary = getServiceSummary(mContext, info, serviceEnabled);
558                 final String fragment = getAccessibilityServiceFragmentTypeName(info);
559 
560                 Drawable icon = resolveInfo.loadIcon(mPm);
561                 if (resolveInfo.getIconResource() == 0) {
562                     icon = ContextCompat.getDrawable(mContext,
563                             R.drawable.ic_accessibility_generic);
564                 }
565 
566                 final RestrictedPreference preference = createRestrictedPreference(key, title,
567                         summary, icon, fragment);
568 
569                 // permittedServices null means all accessibility services are allowed.
570                 final boolean serviceAllowed =
571                         permittedServices == null || permittedServices.contains(packageName);
572 
573                 setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed,
574                         serviceEnabled);
575 
576                 final String prefKey = preference.getKey();
577                 final int imageRes = info.getAnimatedImageRes();
578                 final CharSequence description = getServiceDescription(mContext, info,
579                         serviceEnabled);
580                 final String htmlDescription = info.loadHtmlDescription(mPm);
581                 final String settingsClassName = info.getSettingsActivityName();
582 
583                 putBasicExtras(preference, prefKey, title, description, imageRes, htmlDescription,
584                         componentName);
585                 putServiceExtras(preference, resolveInfo, serviceEnabled);
586                 putSettingsExtras(preference, packageName, settingsClassName);
587 
588                 preferenceList.add(preference);
589             }
590             return preferenceList;
591         }
592 
593         /**
594          * Create the list of {@link RestrictedPreference} with the installedShortcuts arguments.
595          *
596          * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the
597          *                           installed accessibility shortcuts
598          * @return The list of {@link RestrictedPreference}
599          */
createAccessibilityActivityPreferenceList( List<AccessibilityShortcutInfo> installedShortcuts)600         List<RestrictedPreference> createAccessibilityActivityPreferenceList(
601                 List<AccessibilityShortcutInfo> installedShortcuts) {
602             final Set<ComponentName> enabledServices =
603                     AccessibilityUtils.getEnabledServicesFromSettings(mContext);
604             final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
605                     UserHandle.myUserId());
606 
607             final int installedShortcutsSize = installedShortcuts.size();
608             final List<RestrictedPreference> preferenceList = new ArrayList<>(
609                     installedShortcutsSize);
610 
611             for (int i = 0; i < installedShortcutsSize; ++i) {
612                 final AccessibilityShortcutInfo info = installedShortcuts.get(i);
613                 final ActivityInfo activityInfo = info.getActivityInfo();
614                 final ComponentName componentName = info.getComponentName();
615 
616                 final String key = componentName.flattenToString();
617                 final CharSequence title = activityInfo.loadLabel(mPm);
618                 final String summary = info.loadSummary(mPm);
619                 final String fragment =
620                         LaunchAccessibilityActivityPreferenceFragment.class.getName();
621 
622                 Drawable icon = activityInfo.loadIcon(mPm);
623                 if (activityInfo.getIconResource() == 0) {
624                     icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic);
625                 }
626 
627                 final RestrictedPreference preference = createRestrictedPreference(key, title,
628                         summary, icon, fragment);
629 
630                 final String packageName = componentName.getPackageName();
631                 // permittedServices null means all accessibility services are allowed.
632                 final boolean serviceAllowed =
633                         permittedServices == null || permittedServices.contains(packageName);
634                 final boolean serviceEnabled = enabledServices.contains(componentName);
635 
636                 setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed,
637                         serviceEnabled);
638 
639                 final String prefKey = preference.getKey();
640                 final String description = info.loadDescription(mPm);
641                 final int imageRes = info.getAnimatedImageRes();
642                 final String htmlDescription = info.loadHtmlDescription(mPm);
643                 final String settingsClassName = info.getSettingsActivityName();
644 
645                 putBasicExtras(preference, prefKey, title, description, imageRes, htmlDescription,
646                         componentName);
647                 putSettingsExtras(preference, packageName, settingsClassName);
648 
649                 preferenceList.add(preference);
650             }
651             return preferenceList;
652         }
653 
getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info)654         private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) {
655             // Shorten the name to avoid exceeding 100 characters in one line.
656             final String volumeShortcutToggleAccessibilityServicePreferenceFragment =
657                     VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName();
658 
659             switch (AccessibilityUtil.getAccessibilityServiceFragmentType(info)) {
660                 case AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE:
661                     return volumeShortcutToggleAccessibilityServicePreferenceFragment;
662                 case AccessibilityServiceFragmentType.INVISIBLE_TOGGLE:
663                     return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName();
664                 case AccessibilityServiceFragmentType.TOGGLE:
665                     return ToggleAccessibilityServicePreferenceFragment.class.getName();
666                 default:
667                     // impossible status
668                     throw new AssertionError();
669             }
670         }
671 
createRestrictedPreference(String key, CharSequence title, CharSequence summary, Drawable icon, String fragment)672         private RestrictedPreference createRestrictedPreference(String key, CharSequence title,
673                 CharSequence summary, Drawable icon, String fragment) {
674             final RestrictedPreference preference = new RestrictedPreference(mContext);
675 
676             preference.setKey(key);
677             preference.setTitle(title);
678             preference.setSummary(summary);
679             Utils.setSafeIcon(preference, icon);
680             preference.setFragment(fragment);
681             preference.setIconSize(ICON_SIZE_MEDIUM);
682             preference.setPersistent(false); // Disable SharedPreferences.
683             preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX);
684 
685             return preference;
686         }
687 
setRestrictedPreferenceEnabled(RestrictedPreference preference, String packageName, boolean serviceAllowed, boolean serviceEnabled)688         private void setRestrictedPreferenceEnabled(RestrictedPreference preference,
689                 String packageName, boolean serviceAllowed, boolean serviceEnabled) {
690             if (serviceAllowed || serviceEnabled) {
691                 preference.setEnabled(true);
692             } else {
693                 // Disable accessibility service that are not permitted.
694                 final EnforcedAdmin admin =
695                         RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
696                                 mContext, packageName, UserHandle.myUserId());
697                 if (admin != null) {
698                     preference.setDisabledByAdmin(admin);
699                 } else {
700                     preference.setEnabled(false);
701                 }
702             }
703         }
704 
705         /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */
putBasicExtras(RestrictedPreference preference, String prefKey, CharSequence title, CharSequence summary, int imageRes, String htmlDescription, ComponentName componentName)706         private void putBasicExtras(RestrictedPreference preference, String prefKey,
707                 CharSequence title, CharSequence summary, int imageRes, String htmlDescription,
708                 ComponentName componentName) {
709             final Bundle extras = preference.getExtras();
710             extras.putString(EXTRA_PREFERENCE_KEY, prefKey);
711             extras.putCharSequence(EXTRA_TITLE, title);
712             extras.putCharSequence(EXTRA_SUMMARY, summary);
713             extras.putParcelable(EXTRA_COMPONENT_NAME, componentName);
714             extras.putInt(EXTRA_ANIMATED_IMAGE_RES, imageRes);
715             extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
716         }
717 
718         /**
719          * Puts the service extras into {@link RestrictedPreference}'s getExtras().
720          *
721          * Called by {@link AccessibilityServiceInfo} for now.
722          */
putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, Boolean serviceEnabled)723         private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo,
724                 Boolean serviceEnabled) {
725             final Bundle extras = preference.getExtras();
726 
727             extras.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo);
728             extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
729         }
730 
731         /**
732          * Puts the settings extras into {@link RestrictedPreference}'s getExtras().
733          *
734          * Called when settings UI is needed.
735          */
putSettingsExtras(RestrictedPreference preference, String packageName, String settingsClassName)736         private void putSettingsExtras(RestrictedPreference preference, String packageName,
737                 String settingsClassName) {
738             final Bundle extras = preference.getExtras();
739 
740             if (!TextUtils.isEmpty(settingsClassName)) {
741                 extras.putString(EXTRA_SETTINGS_TITLE,
742                         mContext.getText(R.string.accessibility_menu_item_settings).toString());
743                 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
744                         new ComponentName(packageName, settingsClassName).flattenToString());
745             }
746         }
747     }
748 }
749