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