• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.widget.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.Color;
32 import android.graphics.drawable.Drawable;
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 
47 import com.android.internal.accessibility.AccessibilityShortcutController;
48 import com.android.internal.content.PackageMonitor;
49 import com.android.settings.R;
50 import com.android.settings.Utils;
51 import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType;
52 import com.android.settings.dashboard.DashboardFragment;
53 import com.android.settings.overlay.FeatureFactory;
54 import com.android.settings.search.BaseSearchIndexProvider;
55 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
56 import com.android.settingslib.RestrictedLockUtilsInternal;
57 import com.android.settingslib.RestrictedPreference;
58 import com.android.settingslib.accessibility.AccessibilityUtils;
59 import com.android.settingslib.search.SearchIndexable;
60 import com.android.settingslib.search.SearchIndexableRaw;
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_CAPTIONS = "captions_category";
80     private static final String CATEGORY_AUDIO = "audio_category";
81     private static final String CATEGORY_DISPLAY = "display_category";
82     private static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_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_CAPTIONS, CATEGORY_AUDIO, CATEGORY_DISPLAY,
87             CATEGORY_INTERACTION_CONTROL, CATEGORY_DOWNLOADED_SERVICES
88     };
89 
90     // Extras passed to sub-fragments.
91     static final String EXTRA_PREFERENCE_KEY = "preference_key";
92     static final String EXTRA_CHECKED = "checked";
93     static final String EXTRA_TITLE = "title";
94     static final String EXTRA_TITLE_RES = "title_res";
95     static final String EXTRA_RESOLVE_INFO = "resolve_info";
96     static final String EXTRA_SUMMARY = "summary";
97     static final String EXTRA_SETTINGS_TITLE = "settings_title";
98     static final String EXTRA_COMPONENT_NAME = "component_name";
99     static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
100     static final String EXTRA_VIDEO_RAW_RESOURCE_ID = "video_resource";
101     static final String EXTRA_LAUNCHED_FROM_SUW = "from_suw";
102     static final String EXTRA_ANIMATED_IMAGE_RES = "animated_image_res";
103     static final String EXTRA_HTML_DESCRIPTION = "html_description";
104 
105     // Timeout before we update the services if packages are added/removed
106     // since the AccessibilityManagerService has to do that processing first
107     // to generate the AccessibilityServiceInfo we need for proper
108     // presentation.
109     private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
110 
111     private final Handler mHandler = new Handler();
112 
113     private final Runnable mUpdateRunnable = new Runnable() {
114         @Override
115         public void run() {
116             if (getActivity() != null) {
117                 onContentChanged();
118             }
119         }
120     };
121 
122     private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
123         @Override
124         public void onPackageAdded(String packageName, int uid) {
125             sendUpdate();
126         }
127 
128         @Override
129         public void onPackageAppeared(String packageName, int reason) {
130             sendUpdate();
131         }
132 
133         @Override
134         public void onPackageDisappeared(String packageName, int reason) {
135             sendUpdate();
136         }
137 
138         @Override
139         public void onPackageRemoved(String packageName, int uid) {
140             sendUpdate();
141         }
142 
143         private void sendUpdate() {
144             mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_SERVICES_MILLIS);
145         }
146     };
147 
148     @VisibleForTesting
149     final SettingsContentObserver mSettingsContentObserver;
150 
151     private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap =
152             new ArrayMap<>();
153     private final Map<Preference, PreferenceCategory> mServicePreferenceToPreferenceCategoryMap =
154             new ArrayMap<>();
155     private final Map<ComponentName, PreferenceCategory> mPreBundledServiceComponentToCategoryMap =
156             new ArrayMap<>();
157 
158     private boolean mNeedPreferencesUpdate = false;
159     private boolean mIsForeground = true;
160 
AccessibilitySettings()161     public AccessibilitySettings() {
162         // Observe changes to anything that the shortcut can toggle, so we can reflect updates
163         final Collection<AccessibilityShortcutController.ToggleableFrameworkFeatureInfo> features =
164                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values();
165         final List<String> shortcutFeatureKeys = new ArrayList<>(features.size());
166         for (AccessibilityShortcutController.ToggleableFrameworkFeatureInfo feature : features) {
167             shortcutFeatureKeys.add(feature.getSettingKey());
168         }
169 
170         // Observe changes from accessibility selection menu
171         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
172         shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
173         mSettingsContentObserver = new SettingsContentObserver(mHandler, shortcutFeatureKeys) {
174             @Override
175             public void onChange(boolean selfChange, Uri uri) {
176                 onContentChanged();
177             }
178         };
179     }
180 
181     @Override
getMetricsCategory()182     public int getMetricsCategory() {
183         return SettingsEnums.ACCESSIBILITY;
184     }
185 
186     @Override
getHelpResource()187     public int getHelpResource() {
188         return R.string.help_uri_accessibility;
189     }
190 
191     @Override
onAttach(Context context)192     public void onAttach(Context context) {
193         super.onAttach(context);
194         use(AccessibilityHearingAidPreferenceController.class)
195                 .setFragmentManager(getFragmentManager());
196     }
197 
198     @Override
onCreate(Bundle icicle)199     public void onCreate(Bundle icicle) {
200         super.onCreate(icicle);
201         initializeAllPreferences();
202         updateAllPreferences();
203         registerContentMonitors();
204     }
205 
206     @Override
onStart()207     public void onStart() {
208         if (mNeedPreferencesUpdate) {
209             updateAllPreferences();
210             mNeedPreferencesUpdate = false;
211         }
212         mIsForeground = true;
213         super.onStart();
214     }
215 
216     @Override
onStop()217     public void onStop() {
218         mIsForeground = false;
219         super.onStop();
220     }
221 
222     @Override
onDestroy()223     public void onDestroy() {
224         unregisterContentMonitors();
225         super.onDestroy();
226     }
227 
228     @Override
getPreferenceScreenResId()229     protected int getPreferenceScreenResId() {
230         return R.xml.accessibility_settings;
231     }
232 
233     @Override
getLogTag()234     protected String getLogTag() {
235         return TAG;
236     }
237 
238     /**
239      * Returns the summary for the current state of this accessibilityService.
240      *
241      * @param context        A valid context
242      * @param info           The accessibilityService's info
243      * @param serviceEnabled Whether the accessibility service is enabled.
244      * @return The service summary
245      */
246     @VisibleForTesting
getServiceSummary(Context context, AccessibilityServiceInfo info, boolean serviceEnabled)247     static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info,
248             boolean serviceEnabled) {
249         if (serviceEnabled && info.crashed) {
250             return context.getText(R.string.accessibility_summary_state_stopped);
251         }
252 
253         final CharSequence serviceState;
254         final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info);
255         if (fragmentType == AccessibilityServiceFragmentType.INVISIBLE_TOGGLE) {
256             final ComponentName componentName = new ComponentName(
257                     info.getResolveInfo().serviceInfo.packageName,
258                     info.getResolveInfo().serviceInfo.name);
259             final boolean shortcutEnabled = AccessibilityUtil.getUserShortcutTypesFromSettings(
260                     context, componentName) != AccessibilityUtil.UserShortcutType.EMPTY;
261             serviceState = shortcutEnabled
262                     ? context.getText(R.string.accessibility_summary_shortcut_enabled)
263                     : context.getText(R.string.accessibility_summary_shortcut_disabled);
264         } else {
265             serviceState = serviceEnabled
266                     ? context.getText(R.string.accessibility_summary_state_enabled)
267                     : context.getText(R.string.accessibility_summary_state_disabled);
268         }
269 
270         final CharSequence serviceSummary = info.loadSummary(context.getPackageManager());
271         final String stateSummaryCombo = context.getString(
272                 R.string.preference_summary_default_combination,
273                 serviceState, serviceSummary);
274 
275         return TextUtils.isEmpty(serviceSummary) ? serviceState : stateSummaryCombo;
276     }
277 
278     /**
279      * Returns the description for the current state of this accessibilityService.
280      *
281      * @param context        A valid context
282      * @param info           The accessibilityService's info
283      * @param serviceEnabled Whether the accessibility service is enabled.
284      * @return The service description
285      */
286     @VisibleForTesting
getServiceDescription(Context context, AccessibilityServiceInfo info, boolean serviceEnabled)287     static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info,
288             boolean serviceEnabled) {
289         if (serviceEnabled && info.crashed) {
290             return context.getText(R.string.accessibility_description_state_stopped);
291         }
292 
293         return info.loadDescription(context.getPackageManager());
294     }
295 
isRampingRingerEnabled(final Context context)296     static boolean isRampingRingerEnabled(final Context context) {
297         return Settings.Global.getInt(
298                 context.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
299     }
300 
301     @VisibleForTesting
onContentChanged()302     void onContentChanged() {
303         // If the fragment is visible then update preferences immediately, else set the flag then
304         // wait for the fragment to show up to update preferences.
305         if (mIsForeground) {
306             updateAllPreferences();
307         } else {
308             mNeedPreferencesUpdate = true;
309         }
310     }
311 
initializeAllPreferences()312     private void initializeAllPreferences() {
313         for (int i = 0; i < CATEGORIES.length; i++) {
314             PreferenceCategory prefCategory = findPreference(CATEGORIES[i]);
315             mCategoryToPrefCategoryMap.put(CATEGORIES[i], prefCategory);
316         }
317     }
318 
319     @VisibleForTesting
updateAllPreferences()320     void updateAllPreferences() {
321         updateSystemPreferences();
322         updateServicePreferences();
323     }
324 
registerContentMonitors()325     private void registerContentMonitors() {
326         final Context context = getActivity();
327 
328         mSettingsPackageMonitor.register(context, context.getMainLooper(), /* externalStorage= */
329                 false);
330         mSettingsContentObserver.register(getContentResolver());
331     }
332 
unregisterContentMonitors()333     private void unregisterContentMonitors() {
334         mSettingsPackageMonitor.unregister();
335         mSettingsContentObserver.unregister(getContentResolver());
336     }
337 
updateServicePreferences()338     protected void updateServicePreferences() {
339         // Since services category is auto generated we have to do a pass
340         // to generate it since services can come and go and then based on
341         // the global accessibility state to decided whether it is enabled.
342         final ArrayList<Preference> servicePreferences =
343                 new ArrayList<>(mServicePreferenceToPreferenceCategoryMap.keySet());
344         for (int i = 0; i < servicePreferences.size(); i++) {
345             Preference service = servicePreferences.get(i);
346             PreferenceCategory category = mServicePreferenceToPreferenceCategoryMap.get(service);
347             category.removePreference(service);
348         }
349 
350         initializePreBundledServicesMapFromArray(CATEGORY_SCREEN_READER,
351                 R.array.config_preinstalled_screen_reader_services);
352         initializePreBundledServicesMapFromArray(CATEGORY_CAPTIONS,
353                 R.array.config_preinstalled_captions_services);
354         initializePreBundledServicesMapFromArray(CATEGORY_AUDIO,
355                 R.array.config_preinstalled_audio_services);
356         initializePreBundledServicesMapFromArray(CATEGORY_DISPLAY,
357                 R.array.config_preinstalled_display_services);
358         initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL,
359                 R.array.config_preinstalled_interaction_control_services);
360 
361         final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList(
362                 getPrefContext());
363 
364         final PreferenceCategory downloadedServicesCategory =
365                 mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES);
366 
367         for (int i = 0, count = preferenceList.size(); i < count; ++i) {
368             final RestrictedPreference preference = preferenceList.get(i);
369             final ComponentName componentName = preference.getExtras().getParcelable(
370                     EXTRA_COMPONENT_NAME);
371             PreferenceCategory prefCategory = downloadedServicesCategory;
372             // Set the appropriate category if the service comes pre-installed.
373             if (mPreBundledServiceComponentToCategoryMap.containsKey(componentName)) {
374                 prefCategory = mPreBundledServiceComponentToCategoryMap.get(componentName);
375             }
376             prefCategory.addPreference(preference);
377             mServicePreferenceToPreferenceCategoryMap.put(preference, prefCategory);
378         }
379 
380         // Update the order of all the category according to the order defined in xml file.
381         updateCategoryOrderFromArray(CATEGORY_SCREEN_READER,
382                 R.array.config_order_screen_reader_services);
383         updateCategoryOrderFromArray(CATEGORY_CAPTIONS,
384                 R.array.config_order_captions_services);
385         updateCategoryOrderFromArray(CATEGORY_AUDIO,
386                 R.array.config_order_audio_services);
387         updateCategoryOrderFromArray(CATEGORY_INTERACTION_CONTROL,
388                 R.array.config_order_interaction_control_services);
389         updateCategoryOrderFromArray(CATEGORY_DISPLAY,
390                 R.array.config_order_display_services);
391 
392         // Need to check each time when updateServicePreferences() called.
393         if (downloadedServicesCategory.getPreferenceCount() == 0) {
394             getPreferenceScreen().removePreference(downloadedServicesCategory);
395         } else {
396             getPreferenceScreen().addPreference(downloadedServicesCategory);
397         }
398 
399         // Hide screen reader category if it is empty.
400         updatePreferenceCategoryVisibility(CATEGORY_SCREEN_READER);
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 
481     /**
482      * Updates the visibility of a category according to its child preference count.
483      *
484      * @param categoryKey The key of the category which needs to check
485      */
updatePreferenceCategoryVisibility(String categoryKey)486     private void updatePreferenceCategoryVisibility(String categoryKey) {
487         final PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey);
488         category.setVisible(category.getPreferenceCount() != 0);
489     }
490 
491     /**
492      * Updates preferences related to system configurations.
493      */
updateSystemPreferences()494     protected void updateSystemPreferences() {
495         // Do nothing.
496     }
497 
498     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
499             new BaseSearchIndexProvider(R.xml.accessibility_settings) {
500                 @Override
501                 public List<SearchIndexableRaw> getRawDataToIndex(Context context,
502                         boolean enabled) {
503                     return FeatureFactory.getFactory(context)
504                             .getAccessibilitySearchFeatureProvider().getSearchIndexableRawData(
505                                     context);
506                 }
507             };
508 
509     /**
510      * This class helps setup RestrictedPreference.
511      */
512     @VisibleForTesting
513     static class RestrictedPreferenceHelper {
514         private final Context mContext;
515         private final DevicePolicyManager mDpm;
516         private final PackageManager mPm;
517 
RestrictedPreferenceHelper(Context context)518         RestrictedPreferenceHelper(Context context) {
519             mContext = context;
520             mDpm = context.getSystemService(DevicePolicyManager.class);
521             mPm = context.getPackageManager();
522         }
523 
524         /**
525          * Creates the list of {@link RestrictedPreference} with the installedServices arguments.
526          *
527          * @param installedServices The list of {@link AccessibilityServiceInfo}s of the
528          *                          installed accessibility services
529          * @return The list of {@link RestrictedPreference}
530          */
531         @VisibleForTesting
createAccessibilityServicePreferenceList( List<AccessibilityServiceInfo> installedServices)532         List<RestrictedPreference> createAccessibilityServicePreferenceList(
533                 List<AccessibilityServiceInfo> installedServices) {
534 
535             final Set<ComponentName> enabledServices =
536                     AccessibilityUtils.getEnabledServicesFromSettings(mContext);
537             final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
538                     UserHandle.myUserId());
539             final int installedServicesSize = installedServices.size();
540 
541             final List<RestrictedPreference> preferenceList = new ArrayList<>(
542                     installedServicesSize);
543 
544             for (int i = 0; i < installedServicesSize; ++i) {
545                 final AccessibilityServiceInfo info = installedServices.get(i);
546                 final ResolveInfo resolveInfo = info.getResolveInfo();
547                 final String packageName = resolveInfo.serviceInfo.packageName;
548                 final ComponentName componentName = new ComponentName(packageName,
549                         resolveInfo.serviceInfo.name);
550 
551                 final String key = componentName.flattenToString();
552                 final CharSequence title = resolveInfo.loadLabel(mPm);
553                 final boolean serviceEnabled = enabledServices.contains(componentName);
554                 final CharSequence summary = getServiceSummary(mContext, info, serviceEnabled);
555                 final String fragment = getAccessibilityServiceFragmentTypeName(info);
556 
557                 Drawable icon = resolveInfo.loadIcon(mPm);
558                 if (resolveInfo.getIconResource() == 0) {
559                     icon = ContextCompat.getDrawable(mContext,
560                             R.drawable.ic_accessibility_generic);
561                 }
562 
563                 final RestrictedPreference preference = createRestrictedPreference(key, title,
564                         summary, icon, fragment);
565 
566                 // permittedServices null means all accessibility services are allowed.
567                 final boolean serviceAllowed =
568                         permittedServices == null || permittedServices.contains(packageName);
569 
570                 setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed,
571                         serviceEnabled);
572                 final String prefKey = preference.getKey();
573                 final int imageRes = info.getAnimatedImageRes();
574                 final CharSequence description = getServiceDescription(mContext, info,
575                         serviceEnabled);
576                 final String htmlDescription = info.loadHtmlDescription(mPm);
577                 final String settingsClassName = info.getSettingsActivityName();
578 
579                 putBasicExtras(preference, prefKey, title, description, imageRes, htmlDescription,
580                         componentName);
581                 putServiceExtras(preference, resolveInfo, serviceEnabled);
582                 putSettingsExtras(preference, packageName, settingsClassName);
583 
584                 preferenceList.add(preference);
585             }
586             return preferenceList;
587         }
588 
589         /**
590          * Create the list of {@link RestrictedPreference} with the installedShortcuts arguments.
591          *
592          * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the
593          *                           installed accessibility shortcuts
594          * @return The list of {@link RestrictedPreference}
595          */
596         @VisibleForTesting
createAccessibilityActivityPreferenceList( List<AccessibilityShortcutInfo> installedShortcuts)597         List<RestrictedPreference> createAccessibilityActivityPreferenceList(
598                 List<AccessibilityShortcutInfo> installedShortcuts) {
599             final Set<ComponentName> enabledServices =
600                     AccessibilityUtils.getEnabledServicesFromSettings(mContext);
601             final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
602                     UserHandle.myUserId());
603 
604             final int installedShortcutsSize = installedShortcuts.size();
605             final List<RestrictedPreference> preferenceList = new ArrayList<>(
606                     installedShortcutsSize);
607 
608             for (int i = 0; i < installedShortcutsSize; ++i) {
609                 final AccessibilityShortcutInfo info = installedShortcuts.get(i);
610                 final ActivityInfo activityInfo = info.getActivityInfo();
611                 final ComponentName componentName = info.getComponentName();
612 
613                 final String key = componentName.flattenToString();
614                 final CharSequence title = activityInfo.loadLabel(mPm);
615                 final String summary = info.loadSummary(mPm);
616                 final String fragment =
617                         LaunchAccessibilityActivityPreferenceFragment.class.getName();
618 
619                 Drawable icon = activityInfo.loadIcon(mPm);
620                 if (activityInfo.getIconResource() == 0) {
621                     icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic);
622                 }
623 
624                 final RestrictedPreference preference = createRestrictedPreference(key, title,
625                         summary, icon, fragment);
626 
627                 final String packageName = componentName.getPackageName();
628                 // permittedServices null means all accessibility services are allowed.
629                 final boolean serviceAllowed =
630                         permittedServices == null || permittedServices.contains(packageName);
631                 final boolean serviceEnabled = enabledServices.contains(componentName);
632 
633                 setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed,
634                         serviceEnabled);
635 
636                 final String prefKey = preference.getKey();
637                 final String description = info.loadDescription(mPm);
638                 final int imageRes = info.getAnimatedImageRes();
639                 final String htmlDescription = info.loadHtmlDescription(mPm);
640                 final String settingsClassName = info.getSettingsActivityName();
641 
642                 putBasicExtras(preference, prefKey, title, description, imageRes, htmlDescription,
643                         componentName);
644                 putSettingsExtras(preference, packageName, settingsClassName);
645 
646                 preferenceList.add(preference);
647             }
648             return preferenceList;
649         }
650 
getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info)651         private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) {
652             // Shorten the name to avoid exceeding 100 characters in one line.
653             final String volumeShortcutToggleAccessibilityServicePreferenceFragment =
654                     VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName();
655 
656             switch (AccessibilityUtil.getAccessibilityServiceFragmentType(info)) {
657                 case AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE:
658                     return volumeShortcutToggleAccessibilityServicePreferenceFragment;
659                 case AccessibilityServiceFragmentType.INVISIBLE_TOGGLE:
660                     return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName();
661                 case AccessibilityServiceFragmentType.TOGGLE:
662                     return ToggleAccessibilityServicePreferenceFragment.class.getName();
663                 default:
664                     // impossible status
665                     throw new AssertionError();
666             }
667         }
668 
createRestrictedPreference(String key, CharSequence title, CharSequence summary, Drawable icon, String fragment)669         private RestrictedPreference createRestrictedPreference(String key, CharSequence title,
670                 CharSequence summary, Drawable icon, String fragment) {
671             final RestrictedPreference preference = new RestrictedPreference(mContext);
672 
673             preference.setKey(key);
674             preference.setTitle(title);
675             preference.setSummary(summary);
676             preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE));
677             preference.setFragment(fragment);
678             preference.setIconSize(ICON_SIZE_MEDIUM);
679             preference.setPersistent(false); // Disable SharedPreferences.
680             preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX);
681 
682             return preference;
683         }
684 
setRestrictedPreferenceEnabled(RestrictedPreference preference, String packageName, boolean serviceAllowed, boolean serviceEnabled)685         private void setRestrictedPreferenceEnabled(RestrictedPreference preference,
686                 String packageName, boolean serviceAllowed, boolean serviceEnabled) {
687             if (serviceAllowed || serviceEnabled) {
688                 preference.setEnabled(true);
689             } else {
690                 // Disable accessibility service that are not permitted.
691                 final EnforcedAdmin admin =
692                         RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
693                                 mContext, packageName, UserHandle.myUserId());
694                 if (admin != null) {
695                     preference.setDisabledByAdmin(admin);
696                 } else {
697                     preference.setEnabled(false);
698                 }
699             }
700         }
701 
702         /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */
putBasicExtras(RestrictedPreference preference, String prefKey, CharSequence title, CharSequence summary, int imageRes, String htmlDescription, ComponentName componentName)703         private void putBasicExtras(RestrictedPreference preference, String prefKey,
704                 CharSequence title, CharSequence summary, int imageRes, String htmlDescription,
705                 ComponentName componentName) {
706             final Bundle extras = preference.getExtras();
707             extras.putString(EXTRA_PREFERENCE_KEY, prefKey);
708             extras.putCharSequence(EXTRA_TITLE, title);
709             extras.putCharSequence(EXTRA_SUMMARY, summary);
710             extras.putParcelable(EXTRA_COMPONENT_NAME, componentName);
711             extras.putInt(EXTRA_ANIMATED_IMAGE_RES, imageRes);
712             extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
713         }
714 
715         /**
716          * Puts the service extras into {@link RestrictedPreference}'s getExtras().
717          *
718          * Called by {@link AccessibilityServiceInfo} for now.
719          */
putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, Boolean serviceEnabled)720         private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo,
721                 Boolean serviceEnabled) {
722             final Bundle extras = preference.getExtras();
723 
724             extras.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo);
725             extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
726         }
727 
728         /**
729          * Puts the settings extras into {@link RestrictedPreference}'s getExtras().
730          *
731          * Called when settings UI is needed.
732          */
putSettingsExtras(RestrictedPreference preference, String packageName, String settingsClassName)733         private void putSettingsExtras(RestrictedPreference preference, String packageName,
734                 String settingsClassName) {
735             final Bundle extras = preference.getExtras();
736 
737             if (!TextUtils.isEmpty(settingsClassName)) {
738                 extras.putString(EXTRA_SETTINGS_TITLE,
739                         mContext.getText(R.string.accessibility_menu_item_settings).toString());
740                 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
741                         new ComponentName(packageName, settingsClassName).flattenToString());
742             }
743         }
744     }
745 }
746