1 /* 2 * Copyright (C) 2008 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.Fragment; 21 import android.app.admin.DevicePolicyManager; 22 import android.content.ComponentName; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.SharedPreferences; 27 import android.content.pm.ServiceInfo; 28 import android.content.res.Configuration; 29 import android.database.ContentObserver; 30 import android.hardware.input.InputDeviceIdentifier; 31 import android.hardware.input.InputManager; 32 import android.hardware.input.KeyboardLayout; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.preference.ListPreference; 36 import android.preference.Preference; 37 import android.preference.Preference.OnPreferenceClickListener; 38 import android.preference.PreferenceCategory; 39 import android.preference.PreferenceManager; 40 import android.preference.PreferenceScreen; 41 import android.preference.SwitchPreference; 42 import android.provider.Settings; 43 import android.provider.Settings.System; 44 import android.speech.tts.TtsEngines; 45 import android.text.TextUtils; 46 import android.view.InputDevice; 47 import android.view.inputmethod.InputMethodInfo; 48 import android.view.inputmethod.InputMethodManager; 49 import android.view.inputmethod.InputMethodSubtype; 50 import android.view.textservice.SpellCheckerInfo; 51 import android.view.textservice.TextServicesManager; 52 53 import com.android.internal.app.LocalePicker; 54 import com.android.internal.logging.MetricsLogger; 55 import com.android.settings.R; 56 import com.android.settings.Settings.KeyboardLayoutPickerActivity; 57 import com.android.settings.SettingsActivity; 58 import com.android.settings.SettingsPreferenceFragment; 59 import com.android.settings.SubSettings; 60 import com.android.settings.UserDictionarySettings; 61 import com.android.settings.Utils; 62 import com.android.settings.VoiceInputOutputSettings; 63 import com.android.settings.search.BaseSearchIndexProvider; 64 import com.android.settings.search.Indexable; 65 import com.android.settings.search.SearchIndexableRaw; 66 67 import java.text.Collator; 68 import java.util.ArrayList; 69 import java.util.Collections; 70 import java.util.Comparator; 71 import java.util.HashMap; 72 import java.util.HashSet; 73 import java.util.List; 74 import java.util.Locale; 75 import java.util.TreeSet; 76 77 public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment 78 implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener, 79 KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable, 80 InputMethodPreference.OnSavePreferenceListener { 81 private static final String KEY_SPELL_CHECKERS = "spellcheckers_settings"; 82 private static final String KEY_PHONE_LANGUAGE = "phone_language"; 83 private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method"; 84 private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector"; 85 private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings"; 86 private static final String KEY_PREVIOUSLY_ENABLED_SUBTYPES = "previously_enabled_subtypes"; 87 // false: on ICS or later 88 private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false; 89 90 private int mDefaultInputMethodSelectorVisibility = 0; 91 private ListPreference mShowInputMethodSelectorPref; 92 private PreferenceCategory mKeyboardSettingsCategory; 93 private PreferenceCategory mHardKeyboardCategory; 94 private PreferenceCategory mGameControllerCategory; 95 private Preference mLanguagePref; 96 private final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>(); 97 private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = new ArrayList<>(); 98 private InputManager mIm; 99 private InputMethodManager mImm; 100 private boolean mShowsOnlyFullImeAndKeyboardList; 101 private Handler mHandler; 102 private SettingsObserver mSettingsObserver; 103 private Intent mIntentWaitingForResult; 104 private InputMethodSettingValuesWrapper mInputMethodSettingValues; 105 private DevicePolicyManager mDpm; 106 107 @Override 108 protected int getMetricsCategory() { 109 return MetricsLogger.INPUTMETHOD_LANGUAGE; 110 } 111 112 @Override 113 public void onCreate(Bundle icicle) { 114 super.onCreate(icicle); 115 116 addPreferencesFromResource(R.xml.language_settings); 117 118 final Activity activity = getActivity(); 119 mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 120 mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity); 121 122 try { 123 mDefaultInputMethodSelectorVisibility = Integer.valueOf( 124 getString(R.string.input_method_selector_visibility_default_value)); 125 } catch (NumberFormatException e) { 126 } 127 128 if (activity.getAssets().getLocales().length == 1) { 129 // No "Select language" pref if there's only one system locale available. 130 getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE)); 131 } else { 132 mLanguagePref = findPreference(KEY_PHONE_LANGUAGE); 133 } 134 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 135 mShowInputMethodSelectorPref = (ListPreference)findPreference( 136 KEY_INPUT_METHOD_SELECTOR); 137 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this); 138 // TODO: Update current input method name on summary 139 updateInputMethodSelectorSummary(loadInputMethodSelectorVisibility()); 140 } 141 142 new VoiceInputOutputSettings(this).onCreate(); 143 144 // Get references to dynamically constructed categories. 145 mHardKeyboardCategory = (PreferenceCategory)findPreference("hard_keyboard"); 146 mKeyboardSettingsCategory = (PreferenceCategory)findPreference( 147 "keyboard_settings_category"); 148 mGameControllerCategory = (PreferenceCategory)findPreference( 149 "game_controller_settings_category"); 150 151 final Intent startingIntent = activity.getIntent(); 152 // Filter out irrelevant features if invoked from IME settings button. 153 mShowsOnlyFullImeAndKeyboardList = Settings.ACTION_INPUT_METHOD_SETTINGS.equals( 154 startingIntent.getAction()); 155 if (mShowsOnlyFullImeAndKeyboardList) { 156 getPreferenceScreen().removeAll(); 157 getPreferenceScreen().addPreference(mHardKeyboardCategory); 158 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 159 getPreferenceScreen().addPreference(mShowInputMethodSelectorPref); 160 } 161 mKeyboardSettingsCategory.removeAll(); 162 getPreferenceScreen().addPreference(mKeyboardSettingsCategory); 163 } 164 165 // Build hard keyboard and game controller preference categories. 166 mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE); 167 updateInputDevices(); 168 169 // Spell Checker 170 final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS); 171 if (spellChecker != null) { 172 // Note: KEY_SPELL_CHECKERS preference is marked as persistent="false" in XML. 173 InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(spellChecker); 174 final Intent intent = new Intent(Intent.ACTION_MAIN); 175 intent.setClass(activity, SubSettings.class); 176 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, 177 SpellCheckersSettings.class.getName()); 178 intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 179 R.string.spellcheckers_settings_title); 180 spellChecker.setIntent(intent); 181 } 182 183 mHandler = new Handler(); 184 mSettingsObserver = new SettingsObserver(mHandler, activity); 185 mDpm = (DevicePolicyManager) (getActivity(). 186 getSystemService(Context.DEVICE_POLICY_SERVICE)); 187 188 // If we've launched from the keyboard layout notification, go ahead and just show the 189 // keyboard layout dialog. 190 final InputDeviceIdentifier identifier = 191 startingIntent.getParcelableExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER); 192 if (mShowsOnlyFullImeAndKeyboardList && identifier != null) { 193 showKeyboardLayoutDialog(identifier); 194 } 195 } 196 197 private void updateInputMethodSelectorSummary(int value) { 198 String[] inputMethodSelectorTitles = getResources().getStringArray( 199 R.array.input_method_selector_titles); 200 if (inputMethodSelectorTitles.length > value) { 201 mShowInputMethodSelectorPref.setSummary(inputMethodSelectorTitles[value]); 202 mShowInputMethodSelectorPref.setValue(String.valueOf(value)); 203 } 204 } 205 206 private void updateUserDictionaryPreference(Preference userDictionaryPreference) { 207 final Activity activity = getActivity(); 208 final TreeSet<String> localeSet = UserDictionaryList.getUserDictionaryLocalesSet(activity); 209 if (null == localeSet) { 210 // The locale list is null if and only if the user dictionary service is 211 // not present or disabled. In this case we need to remove the preference. 212 getPreferenceScreen().removePreference(userDictionaryPreference); 213 } else { 214 userDictionaryPreference.setOnPreferenceClickListener( 215 new OnPreferenceClickListener() { 216 @Override 217 public boolean onPreferenceClick(Preference arg0) { 218 // Redirect to UserDictionarySettings if the user needs only one 219 // language. 220 final Bundle extras = new Bundle(); 221 final Class<? extends Fragment> targetFragment; 222 if (localeSet.size() <= 1) { 223 if (!localeSet.isEmpty()) { 224 // If the size of localeList is 0, we don't set the locale 225 // parameter in the extras. This will be interpreted by the 226 // UserDictionarySettings class as meaning 227 // "the current locale". Note that with the current code for 228 // UserDictionaryList#getUserDictionaryLocalesSet() 229 // the locale list always has at least one element, since it 230 // always includes the current locale explicitly. 231 // @see UserDictionaryList.getUserDictionaryLocalesSet(). 232 extras.putString("locale", localeSet.first()); 233 } 234 targetFragment = UserDictionarySettings.class; 235 } else { 236 targetFragment = UserDictionaryList.class; 237 } 238 startFragment(InputMethodAndLanguageSettings.this, 239 targetFragment.getCanonicalName(), -1, -1, extras); 240 return true; 241 } 242 }); 243 } 244 } 245 246 @Override 247 public void onResume() { 248 super.onResume(); 249 250 mSettingsObserver.resume(); 251 mIm.registerInputDeviceListener(this, null); 252 253 final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS); 254 if (spellChecker != null) { 255 final TextServicesManager tsm = (TextServicesManager) getSystemService( 256 Context.TEXT_SERVICES_MANAGER_SERVICE); 257 final SpellCheckerInfo sci = tsm.getCurrentSpellChecker(); 258 spellChecker.setEnabled(sci != null); 259 if (tsm.isSpellCheckerEnabled() && sci != null) { 260 spellChecker.setSummary(sci.loadLabel(getPackageManager())); 261 } else { 262 spellChecker.setSummary(R.string.switch_off_text); 263 } 264 } 265 266 if (!mShowsOnlyFullImeAndKeyboardList) { 267 if (mLanguagePref != null) { 268 String localeName = getLocaleName(getActivity()); 269 mLanguagePref.setSummary(localeName); 270 } 271 272 updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS)); 273 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 274 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this); 275 } 276 } 277 278 updateInputDevices(); 279 280 // Refresh internal states in mInputMethodSettingValues to keep the latest 281 // "InputMethodInfo"s and "InputMethodSubtype"s 282 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 283 updateInputMethodPreferenceViews(); 284 } 285 286 @Override 287 public void onPause() { 288 super.onPause(); 289 290 mIm.unregisterInputDeviceListener(this); 291 mSettingsObserver.pause(); 292 293 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 294 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null); 295 } 296 // TODO: Consolidate the logic to InputMethodSettingsWrapper 297 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList( 298 this, getContentResolver(), mInputMethodSettingValues.getInputMethodList(), 299 !mHardKeyboardPreferenceList.isEmpty()); 300 } 301 302 @Override 303 public void onInputDeviceAdded(int deviceId) { 304 updateInputDevices(); 305 } 306 307 @Override 308 public void onInputDeviceChanged(int deviceId) { 309 updateInputDevices(); 310 } 311 312 @Override 313 public void onInputDeviceRemoved(int deviceId) { 314 updateInputDevices(); 315 } 316 317 @Override 318 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 319 // Input Method stuff 320 if (Utils.isMonkeyRunning()) { 321 return false; 322 } 323 if (preference instanceof PreferenceScreen) { 324 if (preference.getFragment() != null) { 325 // Fragment will be handled correctly by the super class. 326 } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) { 327 final InputMethodManager imm = (InputMethodManager) 328 getSystemService(Context.INPUT_METHOD_SERVICE); 329 imm.showInputMethodPicker(false /* showAuxiliarySubtypes */); 330 } 331 } else if (preference instanceof SwitchPreference) { 332 final SwitchPreference pref = (SwitchPreference) preference; 333 if (pref == mGameControllerCategory.findPreference("vibrate_input_devices")) { 334 System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES, 335 pref.isChecked() ? 1 : 0); 336 return true; 337 } 338 } 339 return super.onPreferenceTreeClick(preferenceScreen, preference); 340 } 341 342 private static String getLocaleName(Context context) { 343 // We want to show the same string that the LocalePicker used. 344 // TODO: should this method be in LocalePicker instead? 345 Locale currentLocale = context.getResources().getConfiguration().locale; 346 List<LocalePicker.LocaleInfo> locales = LocalePicker.getAllAssetLocales(context, true); 347 for (LocalePicker.LocaleInfo locale : locales) { 348 if (locale.getLocale().equals(currentLocale)) { 349 return locale.getLabel(); 350 } 351 } 352 // This can't happen as long as the locale was one set by Settings. 353 // Fall back in case a developer is testing an unsupported locale. 354 return currentLocale.getDisplayName(currentLocale); 355 } 356 357 private void saveInputMethodSelectorVisibility(String value) { 358 try { 359 int intValue = Integer.valueOf(value); 360 Settings.Secure.putInt(getContentResolver(), 361 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue); 362 updateInputMethodSelectorSummary(intValue); 363 } catch(NumberFormatException e) { 364 } 365 } 366 367 private int loadInputMethodSelectorVisibility() { 368 return Settings.Secure.getInt(getContentResolver(), 369 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, 370 mDefaultInputMethodSelectorVisibility); 371 } 372 373 @Override 374 public boolean onPreferenceChange(Preference preference, Object value) { 375 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 376 if (preference == mShowInputMethodSelectorPref) { 377 if (value instanceof String) { 378 saveInputMethodSelectorVisibility((String)value); 379 } 380 } 381 } 382 return false; 383 } 384 385 private void updateInputMethodPreferenceViews() { 386 synchronized (mInputMethodPreferenceList) { 387 // Clear existing "InputMethodPreference"s 388 for (final InputMethodPreference pref : mInputMethodPreferenceList) { 389 mKeyboardSettingsCategory.removePreference(pref); 390 } 391 mInputMethodPreferenceList.clear(); 392 List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser(); 393 final Context context = getActivity(); 394 final List<InputMethodInfo> imis = mShowsOnlyFullImeAndKeyboardList 395 ? mInputMethodSettingValues.getInputMethodList() 396 : mImm.getEnabledInputMethodList(); 397 final int N = (imis == null ? 0 : imis.size()); 398 for (int i = 0; i < N; ++i) { 399 final InputMethodInfo imi = imis.get(i); 400 final boolean isAllowedByOrganization = permittedList == null 401 || permittedList.contains(imi.getPackageName()); 402 final InputMethodPreference pref = new InputMethodPreference( 403 context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */, 404 isAllowedByOrganization, this); 405 mInputMethodPreferenceList.add(pref); 406 } 407 final Collator collator = Collator.getInstance(); 408 Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() { 409 @Override 410 public int compare(InputMethodPreference lhs, InputMethodPreference rhs) { 411 return lhs.compareTo(rhs, collator); 412 } 413 }); 414 for (int i = 0; i < N; ++i) { 415 final InputMethodPreference pref = mInputMethodPreferenceList.get(i); 416 mKeyboardSettingsCategory.addPreference(pref); 417 InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref); 418 pref.updatePreferenceViews(); 419 } 420 } 421 updateCurrentImeName(); 422 // TODO: Consolidate the logic with InputMethodSettingsWrapper 423 // CAVEAT: The preference class here does not know about the default value - that is 424 // managed by the Input Method Manager Service, so in this case it could save the wrong 425 // value. Hence we must update the checkboxes here. 426 InputMethodAndSubtypeUtil.loadInputMethodSubtypeList( 427 this, getContentResolver(), 428 mInputMethodSettingValues.getInputMethodList(), null); 429 } 430 431 @Override 432 public void onSaveInputMethodPreference(final InputMethodPreference pref) { 433 final InputMethodInfo imi = pref.getInputMethodInfo(); 434 if (!pref.isChecked()) { 435 // An IME is being disabled. Save enabled subtypes of the IME to shared preference to be 436 // able to re-enable these subtypes when the IME gets re-enabled. 437 saveEnabledSubtypesOf(imi); 438 } 439 final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard 440 == Configuration.KEYBOARD_QWERTY; 441 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), 442 mImm.getInputMethodList(), hasHardwareKeyboard); 443 // Update input method settings and preference list. 444 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 445 if (pref.isChecked()) { 446 // An IME is being enabled. Load the previously enabled subtypes from shared preference 447 // and enable these subtypes. 448 restorePreviouslyEnabledSubtypesOf(imi); 449 } 450 for (final InputMethodPreference p : mInputMethodPreferenceList) { 451 p.updatePreferenceViews(); 452 } 453 } 454 455 private void saveEnabledSubtypesOf(final InputMethodInfo imi) { 456 final HashSet<String> enabledSubtypeIdSet = new HashSet<>(); 457 final List<InputMethodSubtype> enabledSubtypes = mImm.getEnabledInputMethodSubtypeList( 458 imi, true /* allowsImplicitlySelectedSubtypes */); 459 for (final InputMethodSubtype subtype : enabledSubtypes) { 460 final String subtypeId = Integer.toString(subtype.hashCode()); 461 enabledSubtypeIdSet.add(subtypeId); 462 } 463 final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap = 464 loadPreviouslyEnabledSubtypeIdsMap(); 465 final String imiId = imi.getId(); 466 imeToEnabledSubtypeIdsMap.put(imiId, enabledSubtypeIdSet); 467 savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap); 468 } 469 470 private void restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi) { 471 final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap = 472 loadPreviouslyEnabledSubtypeIdsMap(); 473 final String imiId = imi.getId(); 474 final HashSet<String> enabledSubtypeIdSet = imeToEnabledSubtypeIdsMap.remove(imiId); 475 if (enabledSubtypeIdSet == null) { 476 return; 477 } 478 savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap); 479 InputMethodAndSubtypeUtil.enableInputMethodSubtypesOf( 480 getContentResolver(), imiId, enabledSubtypeIdSet); 481 } 482 483 private HashMap<String, HashSet<String>> loadPreviouslyEnabledSubtypeIdsMap() { 484 final Context context = getActivity(); 485 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 486 final String imesAndSubtypesString = prefs.getString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, null); 487 return InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(imesAndSubtypesString); 488 } 489 490 private void savePreviouslyEnabledSubtypeIdsMap( 491 final HashMap<String, HashSet<String>> subtypesMap) { 492 final Context context = getActivity(); 493 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 494 final String imesAndSubtypesString = InputMethodAndSubtypeUtil 495 .buildInputMethodsAndSubtypesString(subtypesMap); 496 prefs.edit().putString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, imesAndSubtypesString).apply(); 497 } 498 499 private void updateCurrentImeName() { 500 final Context context = getActivity(); 501 if (context == null || mImm == null) return; 502 final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD); 503 if (curPref != null) { 504 final CharSequence curIme = 505 mInputMethodSettingValues.getCurrentInputMethodName(context); 506 if (!TextUtils.isEmpty(curIme)) { 507 synchronized (this) { 508 curPref.setSummary(curIme); 509 } 510 } 511 } 512 } 513 514 private void updateInputDevices() { 515 updateHardKeyboards(); 516 updateGameControllers(); 517 } 518 519 private void updateHardKeyboards() { 520 mHardKeyboardPreferenceList.clear(); 521 final int[] devices = InputDevice.getDeviceIds(); 522 for (int i = 0; i < devices.length; i++) { 523 InputDevice device = InputDevice.getDevice(devices[i]); 524 if (device != null 525 && !device.isVirtual() 526 && device.isFullKeyboard()) { 527 final InputDeviceIdentifier identifier = device.getIdentifier(); 528 final String keyboardLayoutDescriptor = 529 mIm.getCurrentKeyboardLayoutForInputDevice(identifier); 530 final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ? 531 mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null; 532 533 final PreferenceScreen pref = new PreferenceScreen(getActivity(), null); 534 pref.setTitle(device.getName()); 535 if (keyboardLayout != null) { 536 pref.setSummary(keyboardLayout.toString()); 537 } else { 538 pref.setSummary(R.string.keyboard_layout_default_label); 539 } 540 pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { 541 @Override 542 public boolean onPreferenceClick(Preference preference) { 543 showKeyboardLayoutDialog(identifier); 544 return true; 545 } 546 }); 547 mHardKeyboardPreferenceList.add(pref); 548 } 549 } 550 551 if (!mHardKeyboardPreferenceList.isEmpty()) { 552 for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) { 553 final Preference pref = mHardKeyboardCategory.getPreference(i); 554 if (pref.getOrder() < 1000) { 555 mHardKeyboardCategory.removePreference(pref); 556 } 557 } 558 559 Collections.sort(mHardKeyboardPreferenceList); 560 final int count = mHardKeyboardPreferenceList.size(); 561 for (int i = 0; i < count; i++) { 562 final Preference pref = mHardKeyboardPreferenceList.get(i); 563 pref.setOrder(i); 564 mHardKeyboardCategory.addPreference(pref); 565 } 566 567 getPreferenceScreen().addPreference(mHardKeyboardCategory); 568 } else { 569 getPreferenceScreen().removePreference(mHardKeyboardCategory); 570 } 571 } 572 573 private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) { 574 KeyboardLayoutDialogFragment fragment = new KeyboardLayoutDialogFragment( 575 inputDeviceIdentifier); 576 fragment.setTargetFragment(this, 0); 577 fragment.show(getActivity().getFragmentManager(), "keyboardLayout"); 578 } 579 580 @Override 581 public void onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier) { 582 final Intent intent = new Intent(Intent.ACTION_MAIN); 583 intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class); 584 intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER, 585 inputDeviceIdentifier); 586 mIntentWaitingForResult = intent; 587 startActivityForResult(intent, 0); 588 } 589 590 @Override 591 public void onActivityResult(int requestCode, int resultCode, Intent data) { 592 super.onActivityResult(requestCode, resultCode, data); 593 594 if (mIntentWaitingForResult != null) { 595 InputDeviceIdentifier inputDeviceIdentifier = mIntentWaitingForResult 596 .getParcelableExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER); 597 mIntentWaitingForResult = null; 598 showKeyboardLayoutDialog(inputDeviceIdentifier); 599 } 600 } 601 602 private void updateGameControllers() { 603 if (haveInputDeviceWithVibrator()) { 604 getPreferenceScreen().addPreference(mGameControllerCategory); 605 606 SwitchPreference pref = (SwitchPreference) 607 mGameControllerCategory.findPreference("vibrate_input_devices"); 608 pref.setChecked(System.getInt(getContentResolver(), 609 Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0); 610 } else { 611 getPreferenceScreen().removePreference(mGameControllerCategory); 612 } 613 } 614 615 private static boolean haveInputDeviceWithVibrator() { 616 final int[] devices = InputDevice.getDeviceIds(); 617 for (int i = 0; i < devices.length; i++) { 618 InputDevice device = InputDevice.getDevice(devices[i]); 619 if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) { 620 return true; 621 } 622 } 623 return false; 624 } 625 626 private class SettingsObserver extends ContentObserver { 627 private Context mContext; 628 629 public SettingsObserver(Handler handler, Context context) { 630 super(handler); 631 mContext = context; 632 } 633 634 @Override public void onChange(boolean selfChange) { 635 updateCurrentImeName(); 636 } 637 638 public void resume() { 639 final ContentResolver cr = mContext.getContentResolver(); 640 cr.registerContentObserver( 641 Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this); 642 cr.registerContentObserver(Settings.Secure.getUriFor( 643 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); 644 } 645 646 public void pause() { 647 mContext.getContentResolver().unregisterContentObserver(this); 648 } 649 } 650 651 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 652 new BaseSearchIndexProvider() { 653 @Override 654 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 655 List<SearchIndexableRaw> indexables = new ArrayList<>(); 656 657 final String screenTitle = context.getString(R.string.language_keyboard_settings_title); 658 659 // Locale picker. 660 if (context.getAssets().getLocales().length > 1) { 661 String localeName = getLocaleName(context); 662 SearchIndexableRaw indexable = new SearchIndexableRaw(context); 663 indexable.key = KEY_PHONE_LANGUAGE; 664 indexable.title = context.getString(R.string.phone_language); 665 indexable.summaryOn = localeName; 666 indexable.summaryOff = localeName; 667 indexable.screenTitle = screenTitle; 668 indexables.add(indexable); 669 } 670 671 // Spell checker. 672 SearchIndexableRaw indexable = new SearchIndexableRaw(context); 673 indexable.key = KEY_SPELL_CHECKERS; 674 indexable.title = context.getString(R.string.spellcheckers_settings_title); 675 indexable.screenTitle = screenTitle; 676 indexable.keywords = context.getString(R.string.keywords_spell_checker); 677 indexables.add(indexable); 678 679 // User dictionary. 680 if (UserDictionaryList.getUserDictionaryLocalesSet(context) != null) { 681 indexable = new SearchIndexableRaw(context); 682 indexable.key = "user_dict_settings"; 683 indexable.title = context.getString(R.string.user_dict_settings_title); 684 indexable.screenTitle = screenTitle; 685 indexables.add(indexable); 686 } 687 688 // Keyboard settings. 689 indexable = new SearchIndexableRaw(context); 690 indexable.key = "keyboard_settings"; 691 indexable.title = context.getString(R.string.keyboard_settings_category); 692 indexable.screenTitle = screenTitle; 693 indexable.keywords = context.getString(R.string.keywords_keyboard_and_ime); 694 indexables.add(indexable); 695 696 InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper 697 .getInstance(context); 698 immValues.refreshAllInputMethodAndSubtypes(); 699 700 // Current IME. 701 String currImeName = immValues.getCurrentInputMethodName(context).toString(); 702 indexable = new SearchIndexableRaw(context); 703 indexable.key = KEY_CURRENT_INPUT_METHOD; 704 indexable.title = context.getString(R.string.current_input_method); 705 indexable.summaryOn = currImeName; 706 indexable.summaryOff = currImeName; 707 indexable.screenTitle = screenTitle; 708 indexables.add(indexable); 709 710 InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService( 711 Context.INPUT_METHOD_SERVICE); 712 713 // All other IMEs. 714 List<InputMethodInfo> inputMethods = immValues.getInputMethodList(); 715 final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size()); 716 for (int i = 0; i < inputMethodCount; ++i) { 717 InputMethodInfo inputMethod = inputMethods.get(i); 718 719 StringBuilder builder = new StringBuilder(); 720 List<InputMethodSubtype> subtypes = inputMethodManager 721 .getEnabledInputMethodSubtypeList(inputMethod, true); 722 final int subtypeCount = subtypes.size(); 723 for (int j = 0; j < subtypeCount; j++) { 724 InputMethodSubtype subtype = subtypes.get(j); 725 if (builder.length() > 0) { 726 builder.append(','); 727 } 728 CharSequence subtypeLabel = subtype.getDisplayName(context, 729 inputMethod.getPackageName(), inputMethod.getServiceInfo() 730 .applicationInfo); 731 builder.append(subtypeLabel); 732 } 733 String summary = builder.toString(); 734 735 ServiceInfo serviceInfo = inputMethod.getServiceInfo(); 736 ComponentName componentName = new ComponentName(serviceInfo.packageName, 737 serviceInfo.name); 738 739 indexable = new SearchIndexableRaw(context); 740 indexable.key = componentName.flattenToString(); 741 indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString(); 742 indexable.summaryOn = summary; 743 indexable.summaryOff = summary; 744 indexable.screenTitle = screenTitle; 745 indexables.add(indexable); 746 } 747 748 // Hard keyboards 749 InputManager inputManager = (InputManager) context.getSystemService( 750 Context.INPUT_SERVICE); 751 boolean hasHardKeyboards = false; 752 753 final int[] devices = InputDevice.getDeviceIds(); 754 for (int i = 0; i < devices.length; i++) { 755 InputDevice device = InputDevice.getDevice(devices[i]); 756 if (device == null || device.isVirtual() || !device.isFullKeyboard()) { 757 continue; 758 } 759 760 hasHardKeyboards = true; 761 762 InputDeviceIdentifier identifier = device.getIdentifier(); 763 String keyboardLayoutDescriptor = 764 inputManager.getCurrentKeyboardLayoutForInputDevice(identifier); 765 KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ? 766 inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null; 767 768 String summary; 769 if (keyboardLayout != null) { 770 summary = keyboardLayout.toString(); 771 } else { 772 summary = context.getString(R.string.keyboard_layout_default_label); 773 } 774 775 indexable = new SearchIndexableRaw(context); 776 indexable.key = device.getName(); 777 indexable.title = device.getName(); 778 indexable.summaryOn = summary; 779 indexable.summaryOff = summary; 780 indexable.screenTitle = screenTitle; 781 indexables.add(indexable); 782 } 783 784 if (hasHardKeyboards) { 785 // Hard keyboard category. 786 indexable = new SearchIndexableRaw(context); 787 indexable.key = "builtin_keyboard_settings"; 788 indexable.title = context.getString( 789 R.string.builtin_keyboard_settings_title); 790 indexable.screenTitle = screenTitle; 791 indexables.add(indexable); 792 } 793 794 // Text-to-speech. 795 TtsEngines ttsEngines = new TtsEngines(context); 796 if (!ttsEngines.getEngines().isEmpty()) { 797 indexable = new SearchIndexableRaw(context); 798 indexable.key = "tts_settings"; 799 indexable.title = context.getString(R.string.tts_settings_title); 800 indexable.screenTitle = screenTitle; 801 indexable.keywords = context.getString(R.string.keywords_text_to_speech_output); 802 indexables.add(indexable); 803 } 804 805 // Pointer settings. 806 indexable = new SearchIndexableRaw(context); 807 indexable.key = "pointer_settings_category"; 808 indexable.title = context.getString(R.string.pointer_settings_category); 809 indexable.screenTitle = screenTitle; 810 indexables.add(indexable); 811 812 indexable = new SearchIndexableRaw(context); 813 indexable.key = "pointer_speed"; 814 indexable.title = context.getString(R.string.pointer_speed); 815 indexable.screenTitle = screenTitle; 816 indexables.add(indexable); 817 818 // Game controllers. 819 if (haveInputDeviceWithVibrator()) { 820 indexable = new SearchIndexableRaw(context); 821 indexable.key = "vibrate_input_devices"; 822 indexable.title = context.getString(R.string.vibrate_input_devices); 823 indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary); 824 indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary); 825 indexable.screenTitle = screenTitle; 826 indexables.add(indexable); 827 } 828 829 return indexables; 830 } 831 }; 832 } 833