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 android.accessibilityservice.AccessibilityServiceInfo; 20 import android.accessibilityservice.AccessibilityShortcutInfo; 21 import android.app.settings.SettingsEnums; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.pm.ServiceInfo; 25 import android.hardware.input.InputManager; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.UserHandle; 29 import android.provider.Settings; 30 import android.text.TextUtils; 31 import android.util.ArrayMap; 32 import android.util.Pair; 33 import android.view.InputDevice; 34 import android.view.accessibility.AccessibilityManager; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.preference.Preference; 39 import androidx.preference.PreferenceCategory; 40 41 import com.android.internal.accessibility.AccessibilityShortcutController; 42 import com.android.internal.accessibility.util.AccessibilityUtils; 43 import com.android.internal.content.PackageMonitor; 44 import com.android.settings.R; 45 import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType; 46 import com.android.settings.dashboard.DashboardFragment; 47 import com.android.settings.development.Enable16kUtils; 48 import com.android.settings.inputmethod.PhysicalKeyboardFragment; 49 import com.android.settings.overlay.FeatureFactory; 50 import com.android.settings.search.BaseSearchIndexProvider; 51 import com.android.settingslib.RestrictedPreference; 52 import com.android.settingslib.core.AbstractPreferenceController; 53 import com.android.settingslib.search.SearchIndexable; 54 import com.android.settingslib.search.SearchIndexableRaw; 55 56 import java.util.ArrayList; 57 import java.util.Collection; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.stream.Collectors; 62 63 /** Activity with the accessibility settings. */ 64 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) 65 public class AccessibilitySettings extends DashboardFragment implements 66 InputManager.InputDeviceListener { 67 68 private static final String TAG = "AccessibilitySettings"; 69 70 // Preference categories 71 private static final String CATEGORY_SCREEN_READER = "screen_reader_category"; 72 private static final String CATEGORY_CAPTIONS = "captions_category"; 73 private static final String CATEGORY_AUDIO = "audio_category"; 74 private static final String CATEGORY_SPEECH = "speech_category"; 75 private static final String CATEGORY_DISPLAY = "display_category"; 76 private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category"; 77 private static final String CATEGORY_KEYBOARD_OPTIONS = "physical_keyboard_options_category"; 78 @VisibleForTesting 79 static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category"; 80 81 private static final String[] CATEGORIES = new String[]{ 82 CATEGORY_SCREEN_READER, CATEGORY_CAPTIONS, CATEGORY_AUDIO, CATEGORY_DISPLAY, 83 CATEGORY_SPEECH, CATEGORY_INTERACTION_CONTROL, 84 CATEGORY_KEYBOARD_OPTIONS, CATEGORY_DOWNLOADED_SERVICES 85 }; 86 87 // Extras passed to sub-fragments. 88 static final String EXTRA_PREFERENCE_KEY = "preference_key"; 89 static final String EXTRA_CHECKED = "checked"; 90 static final String EXTRA_TITLE = "title"; 91 static final String EXTRA_RESOLVE_INFO = "resolve_info"; 92 static final String EXTRA_SUMMARY = "summary"; 93 static final String EXTRA_INTRO = "intro"; 94 static final String EXTRA_SETTINGS_TITLE = "settings_title"; 95 static final String EXTRA_COMPONENT_NAME = "component_name"; 96 static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name"; 97 static final String EXTRA_TILE_SERVICE_COMPONENT_NAME = "tile_service_component_name"; 98 static final String EXTRA_LAUNCHED_FROM_SUW = "from_suw"; 99 static final String EXTRA_ANIMATED_IMAGE_RES = "animated_image_res"; 100 static final String EXTRA_HTML_DESCRIPTION = "html_description"; 101 static final String EXTRA_TIME_FOR_LOGGING = "start_time_to_log_a11y_tool"; 102 static final String EXTRA_METRICS_CATEGORY = "metrics_category"; 103 104 public static final String VOICE_ACCESS_SERVICE = "android.apps.accessibility.voiceaccess"; 105 106 // Timeout before we update the services if packages are added/removed 107 // since the AccessibilityManagerService has to do that processing first 108 // to generate the AccessibilityServiceInfo we need for proper 109 // presentation. 110 private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000; 111 112 private final Handler mHandler = new Handler(); 113 114 private final Runnable mUpdateRunnable = new Runnable() { 115 @Override 116 public void run() { 117 if (getActivity() != null) { 118 onContentChanged(); 119 } 120 } 121 }; 122 123 private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() { 124 @Override 125 public void onPackageAdded(String packageName, int uid) { 126 sendUpdate(); 127 } 128 129 @Override 130 public void onPackageModified(@NonNull String packageName) { 131 sendUpdate(); 132 } 133 134 @Override 135 public void onPackageAppeared(String packageName, int reason) { 136 sendUpdate(); 137 } 138 139 @Override 140 public void onPackageDisappeared(String packageName, int reason) { 141 sendUpdate(); 142 } 143 144 @Override 145 public void onPackageRemoved(String packageName, int uid) { 146 sendUpdate(); 147 } 148 149 private void sendUpdate() { 150 mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_SERVICES_MILLIS); 151 } 152 }; 153 154 @VisibleForTesting 155 final AccessibilitySettingsContentObserver mSettingsContentObserver; 156 157 private final Map<String, PreferenceCategory> mCategoryToPrefCategoryMap = 158 new ArrayMap<>(); 159 @VisibleForTesting 160 final Map<Preference, PreferenceCategory> mServicePreferenceToPreferenceCategoryMap = 161 new ArrayMap<>(); 162 private final Map<ComponentName, PreferenceCategory> mPreBundledServiceComponentToCategoryMap = 163 new ArrayMap<>(); 164 165 private boolean mNeedPreferencesUpdate = false; 166 private boolean mIsForeground = true; 167 AccessibilitySettings()168 public AccessibilitySettings() { 169 // Observe changes to anything that the shortcut can toggle, so we can reflect updates 170 final Collection<AccessibilityShortcutController.FrameworkFeatureInfo> features = 171 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap().values(); 172 final List<String> shortcutFeatureKeys = new ArrayList<>(features.size()); 173 for (AccessibilityShortcutController.FrameworkFeatureInfo feature : features) { 174 final String key = feature.getSettingKey(); 175 if (key != null) { 176 shortcutFeatureKeys.add(key); 177 } 178 } 179 180 // Observe changes from accessibility selection menu 181 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); 182 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); 183 if (android.view.accessibility.Flags.a11yQsShortcut()) { 184 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_QS_TARGETS); 185 } 186 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_STICKY_KEYS); 187 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SLOW_KEYS); 188 shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS); 189 mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler); 190 mSettingsContentObserver.registerKeysToObserverCallback(shortcutFeatureKeys, 191 key -> onContentChanged()); 192 } 193 194 @Override getMetricsCategory()195 public int getMetricsCategory() { 196 return SettingsEnums.ACCESSIBILITY; 197 } 198 199 @Override getHelpResource()200 public int getHelpResource() { 201 return R.string.help_uri_accessibility; 202 } 203 204 @Override onAttach(Context context)205 public void onAttach(Context context) { 206 super.onAttach(context); 207 use(AccessibilityHearingAidPreferenceController.class) 208 .setFragmentManager(getFragmentManager()); 209 } 210 211 @Override onCreate(Bundle icicle)212 public void onCreate(Bundle icicle) { 213 super.onCreate(icicle); 214 initializeAllPreferences(); 215 updateAllPreferences(); 216 mNeedPreferencesUpdate = false; 217 registerContentMonitors(); 218 registerInputDeviceListener(); 219 } 220 221 @Override onStart()222 public void onStart() { 223 super.onStart(); 224 mIsForeground = true; 225 } 226 227 @Override onResume()228 public void onResume() { 229 super.onResume(); 230 if (mNeedPreferencesUpdate) { 231 updateAllPreferences(); 232 mNeedPreferencesUpdate = false; 233 } 234 } 235 236 @Override onPause()237 public void onPause() { 238 super.onPause(); 239 mNeedPreferencesUpdate = true; 240 } 241 242 @Override onStop()243 public void onStop() { 244 mIsForeground = false; 245 super.onStop(); 246 } 247 248 @Override onDestroy()249 public void onDestroy() { 250 unregisterContentMonitors(); 251 unRegisterInputDeviceListener(); 252 super.onDestroy(); 253 } 254 255 @Override getPreferenceScreenResId()256 protected int getPreferenceScreenResId() { 257 return R.xml.accessibility_settings; 258 } 259 260 @Override getLogTag()261 protected String getLogTag() { 262 return TAG; 263 } 264 265 /** 266 * Returns the summary for the current state of this accessibilityService. 267 * 268 * @param context A valid context 269 * @param info The accessibilityService's info 270 * @param serviceEnabled Whether the accessibility service is enabled. 271 * @return The service summary 272 */ getServiceSummary(Context context, AccessibilityServiceInfo info, boolean serviceEnabled)273 public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, 274 boolean serviceEnabled) { 275 if (serviceEnabled && info.crashed) { 276 return context.getText(R.string.accessibility_summary_state_stopped); 277 } 278 279 final CharSequence serviceState; 280 final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info); 281 if (fragmentType == AccessibilityServiceFragmentType.INVISIBLE_TOGGLE) { 282 final ComponentName componentName = new ComponentName( 283 info.getResolveInfo().serviceInfo.packageName, 284 info.getResolveInfo().serviceInfo.name); 285 final boolean shortcutEnabled = AccessibilityUtil.getUserShortcutTypesFromSettings( 286 context, componentName) != AccessibilityUtil.UserShortcutType.EMPTY; 287 serviceState = shortcutEnabled 288 ? context.getText(R.string.accessibility_summary_shortcut_enabled) 289 : context.getText(R.string.generic_accessibility_feature_shortcut_off); 290 } else { 291 serviceState = serviceEnabled 292 ? context.getText(R.string.generic_accessibility_service_on) 293 : context.getText(R.string.generic_accessibility_service_off); 294 } 295 296 final CharSequence serviceSummary = info.loadSummary(context.getPackageManager()); 297 final String stateSummaryCombo = context.getString( 298 R.string.preference_summary_default_combination, 299 serviceState, serviceSummary); 300 301 return TextUtils.isEmpty(serviceSummary) ? serviceState : stateSummaryCombo; 302 } 303 304 /** 305 * Returns the description for the current state of this accessibilityService. 306 * 307 * @param context A valid context 308 * @param info The accessibilityService's info 309 * @param serviceEnabled Whether the accessibility service is enabled. 310 * @return The service description 311 */ getServiceDescription(Context context, AccessibilityServiceInfo info, boolean serviceEnabled)312 public static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info, 313 boolean serviceEnabled) { 314 if (serviceEnabled && info.crashed) { 315 return context.getText(R.string.accessibility_description_state_stopped); 316 } 317 318 return info.loadDescription(context.getPackageManager()); 319 } 320 321 @VisibleForTesting onContentChanged()322 void onContentChanged() { 323 // If the fragment is visible then update preferences immediately, else set the flag then 324 // wait for the fragment to show up to update preferences. 325 if (mIsForeground) { 326 updateAllPreferences(); 327 } else { 328 mNeedPreferencesUpdate = true; 329 } 330 } 331 initializeAllPreferences()332 private void initializeAllPreferences() { 333 for (int i = 0; i < CATEGORIES.length; i++) { 334 PreferenceCategory prefCategory = findPreference(CATEGORIES[i]); 335 mCategoryToPrefCategoryMap.put(CATEGORIES[i], prefCategory); 336 } 337 } 338 339 @VisibleForTesting updateAllPreferences()340 void updateAllPreferences() { 341 updateServicePreferences(); 342 updatePreferencesState(); 343 updateSystemPreferences(); 344 } 345 registerContentMonitors()346 private void registerContentMonitors() { 347 final Context context = getActivity(); 348 349 mSettingsPackageMonitor.register(context, context.getMainLooper(), /* externalStorage= */ 350 false); 351 mSettingsContentObserver.register(getContentResolver()); 352 } 353 registerInputDeviceListener()354 private void registerInputDeviceListener() { 355 InputManager mIm = getSystemService(InputManager.class); 356 if (mIm == null) { 357 return; 358 } 359 mIm.registerInputDeviceListener(this, null); 360 } 361 unRegisterInputDeviceListener()362 private void unRegisterInputDeviceListener() { 363 InputManager mIm = getSystemService(InputManager.class); 364 if (mIm == null) { 365 return; 366 } 367 mIm.unregisterInputDeviceListener(this); 368 } 369 unregisterContentMonitors()370 private void unregisterContentMonitors() { 371 mSettingsPackageMonitor.unregister(); 372 mSettingsContentObserver.unregister(getContentResolver()); 373 } 374 updateServicePreferences()375 protected void updateServicePreferences() { 376 // Since services category is auto generated we have to do a pass 377 // to generate it since services can come and go and then based on 378 // the global accessibility state to decided whether it is enabled. 379 final ArrayList<Preference> servicePreferences = 380 new ArrayList<>(mServicePreferenceToPreferenceCategoryMap.keySet()); 381 for (int i = 0; i < servicePreferences.size(); i++) { 382 Preference service = servicePreferences.get(i); 383 PreferenceCategory category = mServicePreferenceToPreferenceCategoryMap.get(service); 384 category.removePreference(service); 385 } 386 387 initializePreBundledServicesMapFromArray(CATEGORY_SCREEN_READER, 388 R.array.config_preinstalled_screen_reader_services); 389 initializePreBundledServicesMapFromArray(CATEGORY_CAPTIONS, 390 R.array.config_preinstalled_captions_services); 391 initializePreBundledServicesMapFromArray(CATEGORY_AUDIO, 392 R.array.config_preinstalled_audio_services); 393 initializePreBundledServicesMapFromArray(CATEGORY_DISPLAY, 394 R.array.config_preinstalled_display_services); 395 initializePreBundledServicesMapFromArray(CATEGORY_SPEECH, 396 R.array.config_preinstalled_speech_services); 397 initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL, 398 R.array.config_preinstalled_interaction_control_services); 399 400 // ACCESSIBILITY_MENU_IN_SYSTEM is a default pre-bundled interaction control service. 401 // If the device opts out of including this service then this is a no-op. 402 mPreBundledServiceComponentToCategoryMap.put( 403 AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM, 404 mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL)); 405 406 final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList( 407 getPrefContext()); 408 409 final PreferenceCategory downloadedServicesCategory = 410 mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES); 411 412 for (int i = 0, count = preferenceList.size(); i < count; ++i) { 413 final RestrictedPreference preference = preferenceList.get(i); 414 final ComponentName componentName = preference.getExtras().getParcelable( 415 EXTRA_COMPONENT_NAME); 416 PreferenceCategory prefCategory = downloadedServicesCategory; 417 // Set the appropriate category if the service comes pre-installed. 418 if (mPreBundledServiceComponentToCategoryMap.containsKey(componentName)) { 419 prefCategory = mPreBundledServiceComponentToCategoryMap.get(componentName); 420 } 421 prefCategory.addPreference(preference); 422 mServicePreferenceToPreferenceCategoryMap.put(preference, prefCategory); 423 } 424 425 // Update the order of all the category according to the order defined in xml file. 426 updateCategoryOrderFromArray(CATEGORY_SCREEN_READER, 427 R.array.config_order_screen_reader_services); 428 updateCategoryOrderFromArray(CATEGORY_CAPTIONS, 429 R.array.config_order_captions_services); 430 updateCategoryOrderFromArray(CATEGORY_AUDIO, 431 R.array.config_order_audio_services); 432 updateCategoryOrderFromArray(CATEGORY_INTERACTION_CONTROL, 433 R.array.config_order_interaction_control_services); 434 updateCategoryOrderFromArray(CATEGORY_DISPLAY, 435 R.array.config_order_display_services); 436 updateCategoryOrderFromArray(CATEGORY_SPEECH, 437 R.array.config_order_speech_services); 438 439 // Need to check each time when updateServicePreferences() called. 440 if (downloadedServicesCategory.getPreferenceCount() == 0) { 441 getPreferenceScreen().removePreference(downloadedServicesCategory); 442 } else { 443 getPreferenceScreen().addPreference(downloadedServicesCategory); 444 } 445 446 // Hide category if it is empty. 447 updatePreferenceCategoryVisibility(CATEGORY_SCREEN_READER); 448 updatePreferenceCategoryVisibility(CATEGORY_SPEECH); 449 updatePreferenceCategoryVisibility(CATEGORY_KEYBOARD_OPTIONS); 450 } 451 getInstalledAccessibilityList(Context context)452 private List<RestrictedPreference> getInstalledAccessibilityList(Context context) { 453 final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context); 454 final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context); 455 456 final List<AccessibilityShortcutInfo> installedShortcutList = 457 a11yManager.getInstalledAccessibilityShortcutListAsUser(context, 458 UserHandle.myUserId()); 459 final List<AccessibilityActivityPreference> activityList = 460 preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList); 461 final Set<Pair<String, CharSequence>> packageLabelPairs = 462 activityList.stream() 463 .map(a11yActivityPref -> new Pair<>( 464 a11yActivityPref.getPackageName(), a11yActivityPref.getLabel()) 465 ).collect(Collectors.toSet()); 466 467 // Remove duplicate item here, new a ArrayList to copy unmodifiable list result 468 // (getInstalledAccessibilityServiceList). 469 final List<AccessibilityServiceInfo> installedServiceList = new ArrayList<>( 470 a11yManager.getInstalledAccessibilityServiceList()); 471 if (!packageLabelPairs.isEmpty()) { 472 installedServiceList.removeIf( 473 target -> containsPackageAndLabelInList(packageLabelPairs, target)); 474 } 475 final List<RestrictedPreference> serviceList = 476 preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList); 477 478 final List<RestrictedPreference> preferenceList = new ArrayList<>(); 479 preferenceList.addAll(activityList); 480 preferenceList.addAll(serviceList); 481 482 return preferenceList; 483 } 484 containsPackageAndLabelInList( Set<Pair<String, CharSequence>> packageLabelPairs, AccessibilityServiceInfo targetServiceInfo)485 private boolean containsPackageAndLabelInList( 486 Set<Pair<String, CharSequence>> packageLabelPairs, 487 AccessibilityServiceInfo targetServiceInfo) { 488 final ServiceInfo serviceInfo = targetServiceInfo.getResolveInfo().serviceInfo; 489 final String servicePackageName = serviceInfo.packageName; 490 final CharSequence serviceLabel = serviceInfo.loadLabel(getPackageManager()); 491 492 return packageLabelPairs.contains(new Pair<>(servicePackageName, serviceLabel)); 493 } 494 initializePreBundledServicesMapFromArray(String categoryKey, int key)495 private void initializePreBundledServicesMapFromArray(String categoryKey, int key) { 496 String[] services = getResources().getStringArray(key); 497 PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey); 498 for (int i = 0; i < services.length; i++) { 499 // TODO(b/335443194) Voice access is not available in 16kB mode. 500 if (services[i].contains(VOICE_ACCESS_SERVICE) 501 && Enable16kUtils.isPageAgnosticModeOn(getContext())) { 502 continue; 503 } 504 ComponentName component = ComponentName.unflattenFromString(services[i]); 505 mPreBundledServiceComponentToCategoryMap.put(component, category); 506 } 507 } 508 509 /** 510 * Update the order of preferences in the category by matching their preference 511 * key with the string array of preference order which is defined in the xml. 512 * 513 * @param categoryKey The key of the category need to update the order 514 * @param key The key of the string array which defines the order of category 515 */ updateCategoryOrderFromArray(String categoryKey, int key)516 private void updateCategoryOrderFromArray(String categoryKey, int key) { 517 String[] services = getResources().getStringArray(key); 518 PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey); 519 int preferenceCount = category.getPreferenceCount(); 520 int serviceLength = services.length; 521 for (int preferenceIndex = 0; preferenceIndex < preferenceCount; preferenceIndex++) { 522 for (int serviceIndex = 0; serviceIndex < serviceLength; serviceIndex++) { 523 if (category.getPreference(preferenceIndex).getKey() 524 .equals(services[serviceIndex])) { 525 category.getPreference(preferenceIndex).setOrder(serviceIndex); 526 break; 527 } 528 } 529 } 530 } 531 532 /** 533 * Updates the visibility of a category according to its child preference count. 534 * 535 * @param categoryKey The key of the category which needs to check 536 */ updatePreferenceCategoryVisibility(String categoryKey)537 private void updatePreferenceCategoryVisibility(String categoryKey) { 538 final PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey); 539 category.setVisible(category.getPreferenceCount() != 0); 540 } 541 542 /** 543 * Updates preferences related to system configurations. 544 */ updateSystemPreferences()545 protected void updateSystemPreferences() { 546 updateKeyboardPreferencesVisibility(); 547 } 548 updatePreferencesState()549 private void updatePreferencesState() { 550 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 551 getPreferenceControllers().forEach(controllers::addAll); 552 controllers.forEach(controller -> controller.updateState( 553 findPreference(controller.getPreferenceKey()))); 554 } 555 updateKeyboardPreferencesVisibility()556 private void updateKeyboardPreferencesVisibility() { 557 if (!mCategoryToPrefCategoryMap.containsKey(CATEGORY_KEYBOARD_OPTIONS)) { 558 return; 559 } 560 boolean isVisible = isAnyHardKeyboardsExist() 561 && isAnyKeyboardPreferenceAvailable(); 562 mCategoryToPrefCategoryMap.get(CATEGORY_KEYBOARD_OPTIONS).setVisible( 563 isVisible); 564 if (isVisible) { 565 //set summary here. 566 findPreference(KeyboardBounceKeyPreferenceController.PREF_KEY).setSummary( 567 getContext().getString(R.string.bounce_keys_summary, 568 PhysicalKeyboardFragment.BOUNCE_KEYS_THRESHOLD)); 569 findPreference(KeyboardSlowKeyPreferenceController.PREF_KEY).setSummary( 570 getContext().getString(R.string.slow_keys_summary, 571 PhysicalKeyboardFragment.SLOW_KEYS_THRESHOLD)); 572 } 573 } 574 isAnyHardKeyboardsExist()575 private boolean isAnyHardKeyboardsExist() { 576 for (int deviceId : InputDevice.getDeviceIds()) { 577 final InputDevice device = InputDevice.getDevice(deviceId); 578 if (device != null && !device.isVirtual() && device.isFullKeyboard()) { 579 return true; 580 } 581 } 582 return false; 583 } 584 isAnyKeyboardPreferenceAvailable()585 private boolean isAnyKeyboardPreferenceAvailable() { 586 for (List<AbstractPreferenceController> controllerList : getPreferenceControllers()) { 587 for (AbstractPreferenceController controller : controllerList) { 588 if (controller.getPreferenceKey().equals( 589 KeyboardBounceKeyPreferenceController.PREF_KEY) 590 || controller.getPreferenceKey().equals( 591 KeyboardSlowKeyPreferenceController.PREF_KEY) 592 || controller.getPreferenceKey().equals( 593 KeyboardStickyKeyPreferenceController.PREF_KEY)) { 594 if (controller.isAvailable()) { 595 return true; 596 } 597 } 598 } 599 } 600 return false; 601 } 602 603 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 604 new BaseSearchIndexProvider(R.xml.accessibility_settings) { 605 @Override 606 public List<SearchIndexableRaw> getRawDataToIndex(Context context, 607 boolean enabled) { 608 return FeatureFactory.getFeatureFactory() 609 .getAccessibilitySearchFeatureProvider().getSearchIndexableRawData( 610 context); 611 } 612 }; 613 614 @Override onInputDeviceAdded(int deviceId)615 public void onInputDeviceAdded(int deviceId) {} 616 617 @Override onInputDeviceRemoved(int deviceId)618 public void onInputDeviceRemoved(int deviceId) {} 619 620 @Override onInputDeviceChanged(int deviceId)621 public void onInputDeviceChanged(int deviceId) { 622 mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_SERVICES_MILLIS); 623 } 624 } 625