1 /* 2 * Copyright (C) 2016 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.inputmethod; 18 19 import android.app.Activity; 20 import android.app.settings.SettingsEnums; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.hardware.input.InputDeviceIdentifier; 25 import android.hardware.input.InputManager; 26 import android.hardware.input.InputSettings; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.UserHandle; 31 import android.provider.SearchIndexableResource; 32 import android.provider.Settings.Secure; 33 import android.text.TextUtils; 34 import android.util.FeatureFlagUtils; 35 import android.view.InputDevice; 36 import android.view.inputmethod.InputMethodManager; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.preference.Preference; 41 import androidx.preference.Preference.OnPreferenceChangeListener; 42 import androidx.preference.PreferenceCategory; 43 import androidx.preference.PreferenceScreen; 44 import androidx.preference.TwoStatePreference; 45 46 import com.android.internal.util.Preconditions; 47 import com.android.settings.R; 48 import com.android.settings.core.SubSettingLauncher; 49 import com.android.settings.dashboard.DashboardFragment; 50 import com.android.settings.keyboard.Flags; 51 import com.android.settings.overlay.FeatureFactory; 52 import com.android.settings.search.BaseSearchIndexProvider; 53 import com.android.settingslib.search.SearchIndexable; 54 import com.android.settingslib.utils.ThreadUtils; 55 56 import java.text.Collator; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.List; 60 import java.util.Objects; 61 62 // TODO(b/327638540): Update implementation of preference here and reuse key preferences and 63 // controllers between here and A11y Setting page. 64 @SearchIndexable 65 public final class PhysicalKeyboardFragment extends DashboardFragment 66 implements InputManager.InputDeviceListener { 67 68 private static final String KEYBOARD_OPTIONS_CATEGORY = "keyboard_options_category"; 69 private static final String KEYBOARD_A11Y_CATEGORY = "keyboard_a11y_category"; 70 private static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys"; 71 private static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys"; 72 private static final String ACCESSIBILITY_STICKY_KEYS = "accessibility_sticky_keys"; 73 private static final String ACCESSIBILITY_MOUSE_KEYS = "accessibility_mouse_keys"; 74 private static final String ACCESSIBILITY_PHYSICAL_KEYBOARD_A11Y = "physical_keyboard_a11y"; 75 private static final String KEYBOARD_SHORTCUTS_HELPER = "keyboard_shortcuts_helper"; 76 private static final String MODIFIER_KEYS_SETTINGS = "modifier_keys_settings"; 77 private static final String EXTRA_AUTO_SELECTION = "auto_selection"; 78 public static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier"; 79 private static final String TAG = "KeyboardAndTouchA11yFragment"; 80 private static final Uri sVirtualKeyboardSettingsUri = Secure.getUriFor( 81 Secure.SHOW_IME_WITH_HARD_KEYBOARD); 82 private static final Uri sAccessibilityBounceKeysUri = Secure.getUriFor( 83 Secure.ACCESSIBILITY_BOUNCE_KEYS); 84 private static final Uri sAccessibilitySlowKeysUri = Secure.getUriFor( 85 Secure.ACCESSIBILITY_SLOW_KEYS); 86 private static final Uri sAccessibilityStickyKeysUri = Secure.getUriFor( 87 Secure.ACCESSIBILITY_STICKY_KEYS); 88 private static final Uri sAccessibilityMouseKeysUri = Secure.getUriFor( 89 Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED); 90 public static final int BOUNCE_KEYS_THRESHOLD = 500; 91 public static final int SLOW_KEYS_THRESHOLD = 500; 92 93 @NonNull 94 private final ArrayList<HardKeyboardDeviceInfo> mLastHardKeyboards = new ArrayList<>(); 95 96 private InputManager mIm; 97 private InputMethodManager mImm; 98 private InputDeviceIdentifier mAutoInputDeviceIdentifier; 99 private KeyboardSettingsFeatureProvider mFeatureProvider; 100 @NonNull 101 private PreferenceCategory mKeyboardAssistanceCategory; 102 @Nullable 103 private PreferenceCategory mKeyboardA11yCategory = null; 104 @Nullable 105 private TwoStatePreference mAccessibilityBounceKeys = null; 106 @Nullable 107 private TwoStatePreference mAccessibilitySlowKeys = null; 108 @Nullable 109 private TwoStatePreference mAccessibilityStickyKeys = null; 110 @Nullable 111 private TwoStatePreference mAccessibilityMouseKeys = null; 112 private boolean mSupportsFirmwareUpdate; 113 114 @Override getLogTag()115 protected String getLogTag() { 116 return TAG; 117 } 118 119 @Override getPreferenceScreenResId()120 protected int getPreferenceScreenResId() { 121 return R.xml.physical_keyboard_settings; 122 } 123 124 @Override onSaveInstanceState(Bundle outState)125 public void onSaveInstanceState(Bundle outState) { 126 outState.putParcelable(EXTRA_AUTO_SELECTION, mAutoInputDeviceIdentifier); 127 super.onSaveInstanceState(outState); 128 } 129 130 @Override onCreatePreferences(Bundle bundle, String s)131 public void onCreatePreferences(Bundle bundle, String s) { 132 super.onCreatePreferences(bundle, s); 133 Activity activity = Preconditions.checkNotNull(getActivity()); 134 addPreferencesFromResource(R.xml.physical_keyboard_settings); 135 mIm = Preconditions.checkNotNull(activity.getSystemService(InputManager.class)); 136 mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class)); 137 mKeyboardAssistanceCategory = Preconditions.checkNotNull( 138 findPreference(KEYBOARD_OPTIONS_CATEGORY)); 139 140 mKeyboardA11yCategory = Objects.requireNonNull(findPreference(KEYBOARD_A11Y_CATEGORY)); 141 mAccessibilityBounceKeys = Objects.requireNonNull( 142 mKeyboardA11yCategory.findPreference(ACCESSIBILITY_BOUNCE_KEYS)); 143 mAccessibilityBounceKeys.setSummary( 144 getContext().getString(R.string.bounce_keys_summary, BOUNCE_KEYS_THRESHOLD)); 145 mAccessibilitySlowKeys = Objects.requireNonNull( 146 mKeyboardA11yCategory.findPreference(ACCESSIBILITY_SLOW_KEYS)); 147 mAccessibilitySlowKeys.setSummary( 148 getContext().getString(R.string.slow_keys_summary, SLOW_KEYS_THRESHOLD)); 149 mAccessibilityStickyKeys = Objects.requireNonNull( 150 mKeyboardA11yCategory.findPreference(ACCESSIBILITY_STICKY_KEYS)); 151 mAccessibilityMouseKeys = Objects.requireNonNull( 152 mKeyboardA11yCategory.findPreference(ACCESSIBILITY_MOUSE_KEYS)); 153 154 FeatureFactory featureFactory = FeatureFactory.getFeatureFactory(); 155 mMetricsFeatureProvider = featureFactory.getMetricsFeatureProvider(); 156 mFeatureProvider = featureFactory.getKeyboardSettingsFeatureProvider(); 157 mSupportsFirmwareUpdate = mFeatureProvider.supportsFirmwareUpdate(); 158 if (mSupportsFirmwareUpdate) { 159 mFeatureProvider.registerKeyboardInformationCategory(getPreferenceScreen()); 160 } 161 boolean isModifierKeySettingsEnabled = FeatureFlagUtils 162 .isEnabled(getContext(), FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); 163 boolean isKeyboardAndTouchpadA11yNewPageEnabled = 164 Flags.keyboardAndTouchpadA11yNewPageEnabled(); 165 if (!isModifierKeySettingsEnabled) { 166 mKeyboardAssistanceCategory.removePreference(findPreference(MODIFIER_KEYS_SETTINGS)); 167 } 168 if (!isKeyboardAndTouchpadA11yNewPageEnabled) { 169 mKeyboardAssistanceCategory.removePreference( 170 findPreference(ACCESSIBILITY_PHYSICAL_KEYBOARD_A11Y)); 171 } 172 if (isKeyboardAndTouchpadA11yNewPageEnabled) { 173 mKeyboardA11yCategory.removePreference(mAccessibilityBounceKeys); 174 } 175 if (isKeyboardAndTouchpadA11yNewPageEnabled) { 176 mKeyboardA11yCategory.removePreference(mAccessibilitySlowKeys); 177 } 178 if (isKeyboardAndTouchpadA11yNewPageEnabled) { 179 mKeyboardA11yCategory.removePreference(mAccessibilityStickyKeys); 180 } 181 if (!InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled() 182 || isKeyboardAndTouchpadA11yNewPageEnabled) { 183 mKeyboardA11yCategory.removePreference(mAccessibilityMouseKeys); 184 } 185 if (isKeyboardAndTouchpadA11yNewPageEnabled) { 186 mKeyboardA11yCategory.setVisible(false); 187 } 188 InputDeviceIdentifier inputDeviceIdentifier = activity.getIntent().getParcelableExtra( 189 EXTRA_INPUT_DEVICE_IDENTIFIER, InputDeviceIdentifier.class); 190 int intentFromWhere = 191 activity.getIntent().getIntExtra(android.provider.Settings.EXTRA_ENTRYPOINT, -1); 192 if (intentFromWhere != -1) { 193 mMetricsFeatureProvider.action( 194 getContext(), SettingsEnums.ACTION_OPEN_PK_SETTINGS_FROM, intentFromWhere); 195 } 196 if (inputDeviceIdentifier != null) { 197 mAutoInputDeviceIdentifier = inputDeviceIdentifier; 198 } 199 // Don't repeat the autoselection. 200 if (isAutoSelection(bundle, inputDeviceIdentifier)) { 201 showEnabledLocalesKeyboardLayoutList(inputDeviceIdentifier); 202 } 203 } 204 isAutoSelection(Bundle bundle, InputDeviceIdentifier identifier)205 private static boolean isAutoSelection(Bundle bundle, InputDeviceIdentifier identifier) { 206 if (bundle != null && bundle.getParcelable(EXTRA_AUTO_SELECTION, 207 InputDeviceIdentifier.class) != null) { 208 return false; 209 } 210 return identifier != null; 211 } 212 213 @Override onPreferenceTreeClick(Preference preference)214 public boolean onPreferenceTreeClick(Preference preference) { 215 if (KEYBOARD_SHORTCUTS_HELPER.equals(preference.getKey())) { 216 writePreferenceClickMetric(preference); 217 toggleKeyboardShortcutsMenu(); 218 return true; 219 } 220 return super.onPreferenceTreeClick(preference); 221 } 222 223 @Override onResume()224 public void onResume() { 225 super.onResume(); 226 mLastHardKeyboards.clear(); 227 scheduleUpdateHardKeyboards(); 228 mIm.registerInputDeviceListener(this, null); 229 Objects.requireNonNull(mAccessibilityBounceKeys).setOnPreferenceChangeListener( 230 mAccessibilityBounceKeysSwitchPreferenceChangeListener); 231 Objects.requireNonNull(mAccessibilitySlowKeys).setOnPreferenceChangeListener( 232 mAccessibilitySlowKeysSwitchPreferenceChangeListener); 233 Objects.requireNonNull(mAccessibilityStickyKeys).setOnPreferenceChangeListener( 234 mAccessibilityStickyKeysSwitchPreferenceChangeListener); 235 Objects.requireNonNull(mAccessibilityMouseKeys).setOnPreferenceChangeListener( 236 mAccessibilityMouseKeysSwitchPreferenceChangeListener); 237 registerSettingsObserver(); 238 } 239 240 @Override onPause()241 public void onPause() { 242 super.onPause(); 243 mLastHardKeyboards.clear(); 244 mIm.unregisterInputDeviceListener(this); 245 Objects.requireNonNull(mAccessibilityBounceKeys).setOnPreferenceChangeListener(null); 246 Objects.requireNonNull(mAccessibilitySlowKeys).setOnPreferenceChangeListener(null); 247 Objects.requireNonNull(mAccessibilityStickyKeys).setOnPreferenceChangeListener(null); 248 Objects.requireNonNull(mAccessibilityMouseKeys).setOnPreferenceChangeListener(null); 249 unregisterSettingsObserver(); 250 } 251 252 @Override onInputDeviceAdded(int deviceId)253 public void onInputDeviceAdded(int deviceId) { 254 scheduleUpdateHardKeyboards(); 255 } 256 257 @Override onInputDeviceRemoved(int deviceId)258 public void onInputDeviceRemoved(int deviceId) { 259 scheduleUpdateHardKeyboards(); 260 } 261 262 @Override onInputDeviceChanged(int deviceId)263 public void onInputDeviceChanged(int deviceId) { 264 scheduleUpdateHardKeyboards(); 265 } 266 267 @Override getMetricsCategory()268 public int getMetricsCategory() { 269 return SettingsEnums.PHYSICAL_KEYBOARDS; 270 } 271 scheduleUpdateHardKeyboards()272 private void scheduleUpdateHardKeyboards() { 273 final Context context = getContext(); 274 ThreadUtils.postOnBackgroundThread(() -> { 275 final List<HardKeyboardDeviceInfo> newHardKeyboards = getHardKeyboards(context); 276 if (newHardKeyboards.isEmpty()) { 277 getActivity().finish(); 278 return; 279 } 280 ThreadUtils.postOnMainThread(() -> updateHardKeyboards(context, newHardKeyboards)); 281 }); 282 } 283 updateHardKeyboards(@onNull Context context, @NonNull List<HardKeyboardDeviceInfo> newHardKeyboards)284 private void updateHardKeyboards(@NonNull Context context, 285 @NonNull List<HardKeyboardDeviceInfo> newHardKeyboards) { 286 if (Objects.equals(mLastHardKeyboards, newHardKeyboards)) { 287 // Nothing has changed. Ignore. 288 return; 289 } 290 291 // TODO(yukawa): Maybe we should follow the style used in ConnectedDeviceDashboardFragment. 292 293 mLastHardKeyboards.clear(); 294 mLastHardKeyboards.addAll(newHardKeyboards); 295 296 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 297 preferenceScreen.removeAll(); 298 final PreferenceCategory category = new PreferenceCategory(getPrefContext()); 299 category.setTitle(R.string.builtin_keyboard_settings_title); 300 category.setOrder(0); 301 preferenceScreen.addPreference(category); 302 303 for (HardKeyboardDeviceInfo hardKeyboardDeviceInfo : newHardKeyboards) { 304 // TODO(yukawa): Consider using com.android.settings.widget.GearPreference 305 final Preference pref = new Preference(getPrefContext()); 306 pref.setTitle(hardKeyboardDeviceInfo.mDeviceName); 307 String currentLayout = 308 InputPeripheralsSettingsUtils.getSelectedKeyboardLayoutLabelForUser(context, 309 UserHandle.myUserId(), hardKeyboardDeviceInfo.mDeviceIdentifier); 310 if (currentLayout != null) { 311 pref.setSummary(currentLayout); 312 } 313 pref.setOnPreferenceClickListener( 314 preference -> { 315 showEnabledLocalesKeyboardLayoutList( 316 hardKeyboardDeviceInfo.mDeviceIdentifier); 317 return true; 318 }); 319 320 category.addPreference(pref); 321 StringBuilder vendorAndProductId = new StringBuilder(); 322 String vendorId = String.valueOf(hardKeyboardDeviceInfo.mVendorId); 323 String productId = String.valueOf(hardKeyboardDeviceInfo.mProductId); 324 vendorAndProductId.append(vendorId); 325 vendorAndProductId.append("-"); 326 vendorAndProductId.append(productId); 327 mMetricsFeatureProvider.action( 328 context, 329 SettingsEnums.ACTION_USE_SPECIFIC_KEYBOARD, 330 vendorAndProductId.toString()); 331 } 332 mKeyboardAssistanceCategory.setOrder(1); 333 preferenceScreen.addPreference(mKeyboardAssistanceCategory); 334 if (mSupportsFirmwareUpdate) { 335 mFeatureProvider.registerKeyboardInformationCategory(preferenceScreen); 336 } 337 338 Objects.requireNonNull(mKeyboardA11yCategory).setOrder(2); 339 preferenceScreen.addPreference(mKeyboardA11yCategory); 340 updateAccessibilityStickyKeysSwitch(context); 341 updateAccessibilityBounceKeysSwitch(context); 342 updateAccessibilitySlowKeysSwitch(context); 343 if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) { 344 updateAccessibilityMouseKeysSwitch(context); 345 } 346 } 347 showEnabledLocalesKeyboardLayoutList(InputDeviceIdentifier inputDeviceIdentifier)348 private void showEnabledLocalesKeyboardLayoutList(InputDeviceIdentifier inputDeviceIdentifier) { 349 Bundle arguments = new Bundle(); 350 arguments.putParcelable(InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER, 351 inputDeviceIdentifier); 352 new SubSettingLauncher(getContext()) 353 .setSourceMetricsCategory(getMetricsCategory()) 354 .setDestination(NewKeyboardLayoutEnabledLocalesFragment.class.getName()) 355 .setArguments(arguments) 356 .launch(); 357 } 358 registerSettingsObserver()359 private void registerSettingsObserver() { 360 unregisterSettingsObserver(); 361 ContentResolver contentResolver = getActivity().getContentResolver(); 362 contentResolver.registerContentObserver( 363 sVirtualKeyboardSettingsUri, 364 false, 365 mContentObserver, 366 UserHandle.myUserId()); 367 contentResolver.registerContentObserver( 368 sAccessibilityBounceKeysUri, 369 false, 370 mContentObserver, 371 UserHandle.myUserId()); 372 contentResolver.registerContentObserver( 373 sAccessibilitySlowKeysUri, 374 false, 375 mContentObserver, 376 UserHandle.myUserId()); 377 contentResolver.registerContentObserver( 378 sAccessibilityStickyKeysUri, 379 false, 380 mContentObserver, 381 UserHandle.myUserId()); 382 if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) { 383 contentResolver.registerContentObserver( 384 sAccessibilityMouseKeysUri, 385 false, 386 mContentObserver, 387 UserHandle.myUserId()); 388 } 389 final Context context = getContext(); 390 updateAccessibilityBounceKeysSwitch(context); 391 updateAccessibilitySlowKeysSwitch(context); 392 updateAccessibilityStickyKeysSwitch(context); 393 updateAccessibilityMouseKeysSwitch(context); 394 } 395 unregisterSettingsObserver()396 private void unregisterSettingsObserver() { 397 getActivity().getContentResolver().unregisterContentObserver(mContentObserver); 398 if (mSupportsFirmwareUpdate) { 399 mFeatureProvider.unregisterKeyboardInformationCategory(); 400 } 401 } 402 updateAccessibilityBounceKeysSwitch(@onNull Context context)403 private void updateAccessibilityBounceKeysSwitch(@NonNull Context context) { 404 Objects.requireNonNull(mAccessibilityBounceKeys).setChecked( 405 InputSettings.isAccessibilityBounceKeysEnabled(context)); 406 } 407 updateAccessibilitySlowKeysSwitch(@onNull Context context)408 private void updateAccessibilitySlowKeysSwitch(@NonNull Context context) { 409 Objects.requireNonNull(mAccessibilitySlowKeys).setChecked( 410 InputSettings.isAccessibilitySlowKeysEnabled(context)); 411 } 412 updateAccessibilityStickyKeysSwitch(@onNull Context context)413 private void updateAccessibilityStickyKeysSwitch(@NonNull Context context) { 414 Objects.requireNonNull(mAccessibilityStickyKeys).setChecked( 415 InputSettings.isAccessibilityStickyKeysEnabled(context)); 416 } 417 updateAccessibilityMouseKeysSwitch(@onNull Context context)418 private void updateAccessibilityMouseKeysSwitch(@NonNull Context context) { 419 if (!InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) { 420 return; 421 } 422 Objects.requireNonNull(mAccessibilityMouseKeys).setChecked( 423 InputSettings.isAccessibilityMouseKeysEnabled(context)); 424 } 425 toggleKeyboardShortcutsMenu()426 private void toggleKeyboardShortcutsMenu() { 427 getActivity().requestShowKeyboardShortcuts(); 428 } 429 430 private final OnPreferenceChangeListener 431 mAccessibilityBounceKeysSwitchPreferenceChangeListener = (preference, newValue) -> { 432 InputSettings.setAccessibilityBounceKeysThreshold(getContext(), 433 ((Boolean) newValue) ? 500 : 0); 434 return true; 435 }; 436 437 private final OnPreferenceChangeListener 438 mAccessibilitySlowKeysSwitchPreferenceChangeListener = (preference, newValue) -> { 439 InputSettings.setAccessibilitySlowKeysThreshold(getContext(), 440 ((Boolean) newValue) ? 500 : 0); 441 return true; 442 }; 443 444 private final OnPreferenceChangeListener 445 mAccessibilityStickyKeysSwitchPreferenceChangeListener = (preference, newValue) -> { 446 InputSettings.setAccessibilityStickyKeysEnabled(getContext(), (Boolean) newValue); 447 return true; 448 }; 449 450 private final OnPreferenceChangeListener 451 mAccessibilityMouseKeysSwitchPreferenceChangeListener = (preference, newValue) -> { 452 InputSettings.setAccessibilityMouseKeysEnabled(getContext(), (Boolean) newValue); 453 return true; 454 }; 455 456 private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) { 457 @Override 458 public void onChange(boolean selfChange, Uri uri) { 459 if (sAccessibilityBounceKeysUri.equals(uri)) { 460 updateAccessibilityBounceKeysSwitch(getContext()); 461 } else if (sAccessibilitySlowKeysUri.equals(uri)) { 462 updateAccessibilitySlowKeysSwitch(getContext()); 463 } else if (sAccessibilityStickyKeysUri.equals(uri)) { 464 updateAccessibilityStickyKeysSwitch(getContext()); 465 } else if (sAccessibilityMouseKeysUri.equals(uri)) { 466 updateAccessibilityMouseKeysSwitch(getContext()); 467 } 468 } 469 }; 470 471 @NonNull getHardKeyboards(@onNull Context context)472 static List<HardKeyboardDeviceInfo> getHardKeyboards(@NonNull Context context) { 473 final List<HardKeyboardDeviceInfo> keyboards = new ArrayList<>(); 474 final InputManager im = context.getSystemService(InputManager.class); 475 if (im == null) { 476 return new ArrayList<>(); 477 } 478 for (int deviceId : InputDevice.getDeviceIds()) { 479 final InputDevice device = InputDevice.getDevice(deviceId); 480 if (device == null || device.isVirtual() || !device.isFullKeyboard()) { 481 continue; 482 } 483 keyboards.add(new HardKeyboardDeviceInfo( 484 device.getName(), 485 device.getIdentifier(), 486 device.getBluetoothAddress(), 487 device.getVendorId(), 488 device.getProductId())); 489 } 490 491 // We intentionally don't reuse Comparator because Collator may not be thread-safe. 492 final Collator collator = Collator.getInstance(); 493 keyboards.sort((a, b) -> { 494 int result = collator.compare(a.mDeviceName, b.mDeviceName); 495 if (result != 0) { 496 return result; 497 } 498 return a.mDeviceIdentifier.getDescriptor().compareTo( 499 b.mDeviceIdentifier.getDescriptor()); 500 }); 501 return keyboards; 502 } 503 504 public static final class HardKeyboardDeviceInfo { 505 @NonNull 506 public final String mDeviceName; 507 @NonNull 508 public final InputDeviceIdentifier mDeviceIdentifier; 509 @Nullable 510 public final String mBluetoothAddress; 511 @NonNull 512 public final int mVendorId; 513 @NonNull 514 public final int mProductId; 515 HardKeyboardDeviceInfo( @ullable String deviceName, @NonNull InputDeviceIdentifier deviceIdentifier, @Nullable String bluetoothAddress, @NonNull int vendorId, @NonNull int productId)516 public HardKeyboardDeviceInfo( 517 @Nullable String deviceName, 518 @NonNull InputDeviceIdentifier deviceIdentifier, 519 @Nullable String bluetoothAddress, 520 @NonNull int vendorId, 521 @NonNull int productId) { 522 mDeviceName = TextUtils.emptyIfNull(deviceName); 523 mDeviceIdentifier = deviceIdentifier; 524 mBluetoothAddress = bluetoothAddress; 525 mVendorId = vendorId; 526 mProductId = productId; 527 } 528 529 @Override equals(Object o)530 public boolean equals(Object o) { 531 if (o == this) return true; 532 if (o == null) return false; 533 534 if (!(o instanceof HardKeyboardDeviceInfo)) return false; 535 536 final HardKeyboardDeviceInfo that = (HardKeyboardDeviceInfo) o; 537 if (!TextUtils.equals(mDeviceName, that.mDeviceName)) { 538 return false; 539 } 540 if (!Objects.equals(mDeviceIdentifier, that.mDeviceIdentifier)) { 541 return false; 542 } 543 if (!TextUtils.equals(mBluetoothAddress, that.mBluetoothAddress)) { 544 return false; 545 } 546 547 return true; 548 } 549 } 550 551 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 552 new BaseSearchIndexProvider() { 553 @Override 554 public List<SearchIndexableResource> getXmlResourcesToIndex( 555 Context context, boolean enabled) { 556 final SearchIndexableResource sir = new SearchIndexableResource(context); 557 sir.xmlResId = R.xml.physical_keyboard_settings; 558 return Arrays.asList(sir); 559 } 560 561 @Override 562 protected boolean isPageSearchEnabled(Context context) { 563 return !getHardKeyboards(context).isEmpty(); 564 } 565 }; 566 } 567