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