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