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