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