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.os.LocaleList; 36 import android.provider.Settings; 37 import android.provider.Settings.System; 38 import android.speech.tts.TtsEngines; 39 import android.support.v14.preference.SwitchPreference; 40 import android.support.v7.preference.ListPreference; 41 import android.support.v7.preference.Preference; 42 import android.support.v7.preference.Preference.OnPreferenceClickListener; 43 import android.support.v7.preference.PreferenceCategory; 44 import android.support.v7.preference.PreferenceManager; 45 import android.support.v7.preference.PreferenceScreen; 46 import android.text.TextUtils; 47 import android.view.InputDevice; 48 import android.view.inputmethod.InputMethodInfo; 49 import android.view.inputmethod.InputMethodManager; 50 import android.view.inputmethod.InputMethodSubtype; 51 import android.view.textservice.SpellCheckerInfo; 52 import android.view.textservice.TextServicesManager; 53 54 import com.android.internal.app.LocaleHelper; 55 import com.android.internal.app.LocalePicker; 56 import com.android.internal.logging.MetricsProto.MetricsEvent; 57 import com.android.settings.R; 58 import com.android.settings.Settings.KeyboardLayoutPickerActivity; 59 import com.android.settings.SettingsActivity; 60 import com.android.settings.SettingsPreferenceFragment; 61 import com.android.settings.SubSettings; 62 import com.android.settings.UserDictionarySettings; 63 import com.android.settings.Utils; 64 import com.android.settings.VoiceInputOutputSettings; 65 import com.android.settings.dashboard.SummaryLoader; 66 import com.android.settings.search.BaseSearchIndexProvider; 67 import com.android.settings.search.Indexable; 68 import com.android.settings.search.SearchIndexableRaw; 69 70 import java.text.Collator; 71 import java.util.ArrayList; 72 import java.util.Collections; 73 import java.util.Comparator; 74 import java.util.HashMap; 75 import java.util.HashSet; 76 import java.util.List; 77 import java.util.Locale; 78 import java.util.TreeSet; 79 80 public 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 getMetricsCategory()111 protected int getMetricsCategory() { 112 return MetricsEvent.INPUTMETHOD_LANGUAGE; 113 } 114 115 @Override onCreate(Bundle icicle)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 updateInputMethodSelectorSummary(int value)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 updateUserDictionaryPreference(Preference userDictionaryPreference)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 onResume()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 if (!tsm.isSpellCheckerEnabled()) { 265 spellChecker.setSummary(R.string.switch_off_text); 266 } else { 267 final SpellCheckerInfo sci = tsm.getCurrentSpellChecker(); 268 if (sci != null) { 269 spellChecker.setSummary(sci.loadLabel(getPackageManager())); 270 } else { 271 spellChecker.setSummary(R.string.spell_checker_not_selected); 272 } 273 } 274 } 275 276 if (!mShowsOnlyFullImeAndKeyboardList) { 277 if (mLanguagePref != null) { 278 String localeNames = getLocaleNames(getActivity()); 279 mLanguagePref.setSummary(localeNames); 280 } 281 282 updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS)); 283 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 284 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this); 285 } 286 } 287 288 updateInputDevices(); 289 290 // Refresh internal states in mInputMethodSettingValues to keep the latest 291 // "InputMethodInfo"s and "InputMethodSubtype"s 292 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 293 updateInputMethodPreferenceViews(); 294 } 295 296 @Override onPause()297 public void onPause() { 298 super.onPause(); 299 300 mIm.unregisterInputDeviceListener(this); 301 mSettingsObserver.pause(); 302 303 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 304 mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null); 305 } 306 // TODO: Consolidate the logic to InputMethodSettingsWrapper 307 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList( 308 this, getContentResolver(), mInputMethodSettingValues.getInputMethodList(), 309 !mHardKeyboardPreferenceList.isEmpty()); 310 } 311 312 @Override onInputDeviceAdded(int deviceId)313 public void onInputDeviceAdded(int deviceId) { 314 updateInputDevices(); 315 } 316 317 @Override onInputDeviceChanged(int deviceId)318 public void onInputDeviceChanged(int deviceId) { 319 updateInputDevices(); 320 } 321 322 @Override onInputDeviceRemoved(int deviceId)323 public void onInputDeviceRemoved(int deviceId) { 324 updateInputDevices(); 325 } 326 327 @Override onPreferenceTreeClick(Preference preference)328 public boolean onPreferenceTreeClick(Preference preference) { 329 // Input Method stuff 330 if (Utils.isMonkeyRunning()) { 331 return false; 332 } 333 if (preference instanceof PreferenceScreen) { 334 if (preference.getFragment() != null) { 335 // Fragment will be handled correctly by the super class. 336 } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) { 337 final InputMethodManager imm = (InputMethodManager) 338 getSystemService(Context.INPUT_METHOD_SERVICE); 339 imm.showInputMethodPicker(false /* showAuxiliarySubtypes */); 340 } 341 } else if (preference instanceof SwitchPreference) { 342 final SwitchPreference pref = (SwitchPreference) preference; 343 if (pref == mGameControllerCategory.findPreference("vibrate_input_devices")) { 344 System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES, 345 pref.isChecked() ? 1 : 0); 346 return true; 347 } 348 } 349 return super.onPreferenceTreeClick(preference); 350 } 351 getLocaleNames(Context context)352 private static String getLocaleNames(Context context) { 353 final LocaleList locales = LocalePicker.getLocales(); 354 final Locale displayLocale = Locale.getDefault(); 355 return LocaleHelper.toSentenceCase( 356 LocaleHelper.getDisplayLocaleList( 357 locales, displayLocale, 2 /* Show up to two locales from the list */), 358 displayLocale); 359 } 360 saveInputMethodSelectorVisibility(String value)361 private void saveInputMethodSelectorVisibility(String value) { 362 try { 363 int intValue = Integer.valueOf(value); 364 Settings.Secure.putInt(getContentResolver(), 365 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue); 366 updateInputMethodSelectorSummary(intValue); 367 } catch(NumberFormatException e) { 368 } 369 } 370 loadInputMethodSelectorVisibility()371 private int loadInputMethodSelectorVisibility() { 372 return Settings.Secure.getInt(getContentResolver(), 373 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, 374 mDefaultInputMethodSelectorVisibility); 375 } 376 377 @Override onPreferenceChange(Preference preference, Object value)378 public boolean onPreferenceChange(Preference preference, Object value) { 379 if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { 380 if (preference == mShowInputMethodSelectorPref) { 381 if (value instanceof String) { 382 saveInputMethodSelectorVisibility((String)value); 383 } 384 } 385 } 386 return false; 387 } 388 updateInputMethodPreferenceViews()389 private void updateInputMethodPreferenceViews() { 390 if (mKeyboardSettingsCategory == null) { 391 return; 392 } 393 394 synchronized (mInputMethodPreferenceList) { 395 // Clear existing "InputMethodPreference"s 396 for (final InputMethodPreference pref : mInputMethodPreferenceList) { 397 mKeyboardSettingsCategory.removePreference(pref); 398 } 399 mInputMethodPreferenceList.clear(); 400 List<String> permittedList = mDpm.getPermittedInputMethodsForCurrentUser(); 401 final Context context = getPrefContext(); 402 final List<InputMethodInfo> imis = mShowsOnlyFullImeAndKeyboardList 403 ? mInputMethodSettingValues.getInputMethodList() 404 : mImm.getEnabledInputMethodList(); 405 final int N = (imis == null ? 0 : imis.size()); 406 for (int i = 0; i < N; ++i) { 407 final InputMethodInfo imi = imis.get(i); 408 final boolean isAllowedByOrganization = permittedList == null 409 || permittedList.contains(imi.getPackageName()); 410 final InputMethodPreference pref = new InputMethodPreference( 411 context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */, 412 isAllowedByOrganization, this); 413 mInputMethodPreferenceList.add(pref); 414 } 415 final Collator collator = Collator.getInstance(); 416 Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() { 417 @Override 418 public int compare(InputMethodPreference lhs, InputMethodPreference rhs) { 419 return lhs.compareTo(rhs, collator); 420 } 421 }); 422 for (int i = 0; i < N; ++i) { 423 final InputMethodPreference pref = mInputMethodPreferenceList.get(i); 424 mKeyboardSettingsCategory.addPreference(pref); 425 InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref); 426 pref.updatePreferenceViews(); 427 } 428 } 429 updateCurrentImeName(); 430 // TODO: Consolidate the logic with InputMethodSettingsWrapper 431 // CAVEAT: The preference class here does not know about the default value - that is 432 // managed by the Input Method Manager Service, so in this case it could save the wrong 433 // value. Hence we must update the checkboxes here. 434 InputMethodAndSubtypeUtil.loadInputMethodSubtypeList( 435 this, getContentResolver(), 436 mInputMethodSettingValues.getInputMethodList(), null); 437 } 438 439 @Override onSaveInputMethodPreference(final InputMethodPreference pref)440 public void onSaveInputMethodPreference(final InputMethodPreference pref) { 441 final InputMethodInfo imi = pref.getInputMethodInfo(); 442 if (!pref.isChecked()) { 443 // An IME is being disabled. Save enabled subtypes of the IME to shared preference to be 444 // able to re-enable these subtypes when the IME gets re-enabled. 445 saveEnabledSubtypesOf(imi); 446 } 447 final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard 448 == Configuration.KEYBOARD_QWERTY; 449 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), 450 mImm.getInputMethodList(), hasHardwareKeyboard); 451 // Update input method settings and preference list. 452 mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); 453 if (pref.isChecked()) { 454 // An IME is being enabled. Load the previously enabled subtypes from shared preference 455 // and enable these subtypes. 456 restorePreviouslyEnabledSubtypesOf(imi); 457 } 458 for (final InputMethodPreference p : mInputMethodPreferenceList) { 459 p.updatePreferenceViews(); 460 } 461 } 462 saveEnabledSubtypesOf(final InputMethodInfo imi)463 private void saveEnabledSubtypesOf(final InputMethodInfo imi) { 464 final HashSet<String> enabledSubtypeIdSet = new HashSet<>(); 465 final List<InputMethodSubtype> enabledSubtypes = mImm.getEnabledInputMethodSubtypeList( 466 imi, true /* allowsImplicitlySelectedSubtypes */); 467 for (final InputMethodSubtype subtype : enabledSubtypes) { 468 final String subtypeId = Integer.toString(subtype.hashCode()); 469 enabledSubtypeIdSet.add(subtypeId); 470 } 471 final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap = 472 loadPreviouslyEnabledSubtypeIdsMap(); 473 final String imiId = imi.getId(); 474 imeToEnabledSubtypeIdsMap.put(imiId, enabledSubtypeIdSet); 475 savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap); 476 } 477 restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi)478 private void restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi) { 479 final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap = 480 loadPreviouslyEnabledSubtypeIdsMap(); 481 final String imiId = imi.getId(); 482 final HashSet<String> enabledSubtypeIdSet = imeToEnabledSubtypeIdsMap.remove(imiId); 483 if (enabledSubtypeIdSet == null) { 484 return; 485 } 486 savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap); 487 InputMethodAndSubtypeUtil.enableInputMethodSubtypesOf( 488 getContentResolver(), imiId, enabledSubtypeIdSet); 489 } 490 loadPreviouslyEnabledSubtypeIdsMap()491 private HashMap<String, HashSet<String>> loadPreviouslyEnabledSubtypeIdsMap() { 492 final Context context = getActivity(); 493 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 494 final String imesAndSubtypesString = prefs.getString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, null); 495 return InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(imesAndSubtypesString); 496 } 497 savePreviouslyEnabledSubtypeIdsMap( final HashMap<String, HashSet<String>> subtypesMap)498 private void savePreviouslyEnabledSubtypeIdsMap( 499 final HashMap<String, HashSet<String>> subtypesMap) { 500 final Context context = getActivity(); 501 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 502 final String imesAndSubtypesString = InputMethodAndSubtypeUtil 503 .buildInputMethodsAndSubtypesString(subtypesMap); 504 prefs.edit().putString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, imesAndSubtypesString).apply(); 505 } 506 updateCurrentImeName()507 private void updateCurrentImeName() { 508 final Context context = getActivity(); 509 if (context == null || mImm == null) return; 510 final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD); 511 if (curPref != null) { 512 final CharSequence curIme = 513 mInputMethodSettingValues.getCurrentInputMethodName(context); 514 if (!TextUtils.isEmpty(curIme)) { 515 synchronized (this) { 516 curPref.setSummary(curIme); 517 } 518 } 519 } 520 } 521 updateInputDevices()522 private void updateInputDevices() { 523 updateHardKeyboards(); 524 updateGameControllers(); 525 } 526 updateHardKeyboards()527 private void updateHardKeyboards() { 528 if (mHardKeyboardCategory == null) { 529 return; 530 } 531 532 mHardKeyboardPreferenceList.clear(); 533 final int[] devices = InputDevice.getDeviceIds(); 534 for (int i = 0; i < devices.length; i++) { 535 InputDevice device = InputDevice.getDevice(devices[i]); 536 if (device != null 537 && !device.isVirtual() 538 && device.isFullKeyboard()) { 539 final InputDeviceIdentifier identifier = device.getIdentifier(); 540 final String keyboardLayoutDescriptor = 541 mIm.getCurrentKeyboardLayoutForInputDevice(identifier); 542 final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ? 543 mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null; 544 545 final PreferenceScreen pref = new PreferenceScreen(getPrefContext(), null); 546 pref.setTitle(device.getName()); 547 if (keyboardLayout != null) { 548 pref.setSummary(keyboardLayout.toString()); 549 } else { 550 pref.setSummary(R.string.keyboard_layout_default_label); 551 } 552 pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { 553 @Override 554 public boolean onPreferenceClick(Preference preference) { 555 showKeyboardLayoutDialog(identifier); 556 return true; 557 } 558 }); 559 mHardKeyboardPreferenceList.add(pref); 560 } 561 } 562 563 if (!mHardKeyboardPreferenceList.isEmpty()) { 564 for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) { 565 final Preference pref = mHardKeyboardCategory.getPreference(i); 566 if (pref.getOrder() < 1000) { 567 mHardKeyboardCategory.removePreference(pref); 568 } 569 } 570 571 Collections.sort(mHardKeyboardPreferenceList); 572 final int count = mHardKeyboardPreferenceList.size(); 573 for (int i = 0; i < count; i++) { 574 final Preference pref = mHardKeyboardPreferenceList.get(i); 575 pref.setOrder(i); 576 mHardKeyboardCategory.addPreference(pref); 577 } 578 579 getPreferenceScreen().addPreference(mHardKeyboardCategory); 580 } else { 581 getPreferenceScreen().removePreference(mHardKeyboardCategory); 582 } 583 } 584 showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier)585 private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) { 586 KeyboardLayoutDialogFragment fragment = (KeyboardLayoutDialogFragment) 587 getFragmentManager().findFragmentByTag("keyboardLayout"); 588 if (fragment == null) { 589 fragment = new KeyboardLayoutDialogFragment(inputDeviceIdentifier); 590 fragment.setTargetFragment(this, 0); 591 fragment.show(getActivity().getFragmentManager(), "keyboardLayout"); 592 } 593 } 594 595 @Override onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier)596 public void onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier) { 597 final Intent intent = new Intent(Intent.ACTION_MAIN); 598 intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class); 599 intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER, 600 inputDeviceIdentifier); 601 mIntentWaitingForResult = intent; 602 startActivityForResult(intent, 0); 603 } 604 605 @Override onActivityResult(int requestCode, int resultCode, Intent data)606 public void onActivityResult(int requestCode, int resultCode, Intent data) { 607 super.onActivityResult(requestCode, resultCode, data); 608 609 if (mIntentWaitingForResult != null) { 610 InputDeviceIdentifier inputDeviceIdentifier = mIntentWaitingForResult 611 .getParcelableExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER); 612 mIntentWaitingForResult = null; 613 showKeyboardLayoutDialog(inputDeviceIdentifier); 614 } 615 } 616 updateGameControllers()617 private void updateGameControllers() { 618 if (haveInputDeviceWithVibrator()) { 619 getPreferenceScreen().addPreference(mGameControllerCategory); 620 621 SwitchPreference pref = (SwitchPreference) 622 mGameControllerCategory.findPreference("vibrate_input_devices"); 623 pref.setChecked(System.getInt(getContentResolver(), 624 Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0); 625 } else { 626 getPreferenceScreen().removePreference(mGameControllerCategory); 627 } 628 } 629 haveInputDeviceWithVibrator()630 private static boolean haveInputDeviceWithVibrator() { 631 final int[] devices = InputDevice.getDeviceIds(); 632 for (int i = 0; i < devices.length; i++) { 633 InputDevice device = InputDevice.getDevice(devices[i]); 634 if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) { 635 return true; 636 } 637 } 638 return false; 639 } 640 641 private class SettingsObserver extends ContentObserver { 642 private Context mContext; 643 SettingsObserver(Handler handler, Context context)644 public SettingsObserver(Handler handler, Context context) { 645 super(handler); 646 mContext = context; 647 } 648 onChange(boolean selfChange)649 @Override public void onChange(boolean selfChange) { 650 updateCurrentImeName(); 651 } 652 resume()653 public void resume() { 654 final ContentResolver cr = mContext.getContentResolver(); 655 cr.registerContentObserver( 656 Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this); 657 cr.registerContentObserver(Settings.Secure.getUriFor( 658 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); 659 } 660 pause()661 public void pause() { 662 mContext.getContentResolver().unregisterContentObserver(this); 663 } 664 } 665 666 private static class SummaryProvider implements SummaryLoader.SummaryProvider { 667 668 private final Context mContext; 669 private final SummaryLoader mSummaryLoader; 670 SummaryProvider(Context context, SummaryLoader summaryLoader)671 public SummaryProvider(Context context, SummaryLoader summaryLoader) { 672 mContext = context; 673 mSummaryLoader = summaryLoader; 674 } 675 676 @Override setListening(boolean listening)677 public void setListening(boolean listening) { 678 if (listening) { 679 String localeNames = getLocaleNames(mContext); 680 mSummaryLoader.setSummary(this, localeNames); 681 } 682 } 683 } 684 685 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY 686 = new SummaryLoader.SummaryProviderFactory() { 687 @Override 688 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, 689 SummaryLoader summaryLoader) { 690 return new SummaryProvider(activity, summaryLoader); 691 } 692 }; 693 694 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 695 new BaseSearchIndexProvider() { 696 @Override 697 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 698 List<SearchIndexableRaw> indexables = new ArrayList<>(); 699 700 final String screenTitle = context.getString(R.string.language_keyboard_settings_title); 701 702 // Locale picker. 703 if (context.getAssets().getLocales().length > 1) { 704 String localeNames = getLocaleNames(context); 705 SearchIndexableRaw indexable = new SearchIndexableRaw(context); 706 indexable.key = KEY_PHONE_LANGUAGE; 707 indexable.title = context.getString(R.string.phone_language); 708 indexable.summaryOn = localeNames; 709 indexable.summaryOff = localeNames; 710 indexable.screenTitle = screenTitle; 711 indexables.add(indexable); 712 } 713 714 // Spell checker. 715 SearchIndexableRaw indexable = new SearchIndexableRaw(context); 716 indexable.key = KEY_SPELL_CHECKERS; 717 indexable.title = context.getString(R.string.spellcheckers_settings_title); 718 indexable.screenTitle = screenTitle; 719 indexable.keywords = context.getString(R.string.keywords_spell_checker); 720 indexables.add(indexable); 721 722 // User dictionary. 723 if (UserDictionaryList.getUserDictionaryLocalesSet(context) != null) { 724 indexable = new SearchIndexableRaw(context); 725 indexable.key = "user_dict_settings"; 726 indexable.title = context.getString(R.string.user_dict_settings_title); 727 indexable.screenTitle = screenTitle; 728 indexables.add(indexable); 729 } 730 731 // Keyboard settings. 732 indexable = new SearchIndexableRaw(context); 733 indexable.key = "keyboard_settings"; 734 indexable.title = context.getString(R.string.keyboard_settings_category); 735 indexable.screenTitle = screenTitle; 736 indexable.keywords = context.getString(R.string.keywords_keyboard_and_ime); 737 indexables.add(indexable); 738 739 InputMethodSettingValuesWrapper immValues = InputMethodSettingValuesWrapper 740 .getInstance(context); 741 immValues.refreshAllInputMethodAndSubtypes(); 742 743 // Current IME. 744 String currImeName = immValues.getCurrentInputMethodName(context).toString(); 745 indexable = new SearchIndexableRaw(context); 746 indexable.key = KEY_CURRENT_INPUT_METHOD; 747 indexable.title = context.getString(R.string.current_input_method); 748 indexable.summaryOn = currImeName; 749 indexable.summaryOff = currImeName; 750 indexable.screenTitle = screenTitle; 751 indexables.add(indexable); 752 753 InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService( 754 Context.INPUT_METHOD_SERVICE); 755 756 // All other IMEs. 757 List<InputMethodInfo> inputMethods = immValues.getInputMethodList(); 758 final int inputMethodCount = (inputMethods == null ? 0 : inputMethods.size()); 759 for (int i = 0; i < inputMethodCount; ++i) { 760 InputMethodInfo inputMethod = inputMethods.get(i); 761 List<InputMethodSubtype> subtypes = inputMethodManager 762 .getEnabledInputMethodSubtypeList(inputMethod, true); 763 String summary = InputMethodAndSubtypeUtil.getSubtypeLocaleNameListAsSentence( 764 subtypes, context, inputMethod); 765 766 ServiceInfo serviceInfo = inputMethod.getServiceInfo(); 767 ComponentName componentName = new ComponentName(serviceInfo.packageName, 768 serviceInfo.name); 769 770 indexable = new SearchIndexableRaw(context); 771 indexable.key = componentName.flattenToString(); 772 indexable.title = inputMethod.loadLabel(context.getPackageManager()).toString(); 773 indexable.summaryOn = summary; 774 indexable.summaryOff = summary; 775 indexable.screenTitle = screenTitle; 776 indexables.add(indexable); 777 } 778 779 // Hard keyboards 780 InputManager inputManager = (InputManager) context.getSystemService( 781 Context.INPUT_SERVICE); 782 boolean hasHardKeyboards = false; 783 784 final int[] devices = InputDevice.getDeviceIds(); 785 for (int i = 0; i < devices.length; i++) { 786 InputDevice device = InputDevice.getDevice(devices[i]); 787 if (device == null || device.isVirtual() || !device.isFullKeyboard()) { 788 continue; 789 } 790 791 hasHardKeyboards = true; 792 793 InputDeviceIdentifier identifier = device.getIdentifier(); 794 String keyboardLayoutDescriptor = 795 inputManager.getCurrentKeyboardLayoutForInputDevice(identifier); 796 KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ? 797 inputManager.getKeyboardLayout(keyboardLayoutDescriptor) : null; 798 799 String summary; 800 if (keyboardLayout != null) { 801 summary = keyboardLayout.toString(); 802 } else { 803 summary = context.getString(R.string.keyboard_layout_default_label); 804 } 805 806 indexable = new SearchIndexableRaw(context); 807 indexable.key = device.getName(); 808 indexable.title = device.getName(); 809 indexable.summaryOn = summary; 810 indexable.summaryOff = summary; 811 indexable.screenTitle = screenTitle; 812 indexables.add(indexable); 813 } 814 815 if (hasHardKeyboards) { 816 // Hard keyboard category. 817 indexable = new SearchIndexableRaw(context); 818 indexable.key = "builtin_keyboard_settings"; 819 indexable.title = context.getString( 820 R.string.builtin_keyboard_settings_title); 821 indexable.screenTitle = screenTitle; 822 indexables.add(indexable); 823 } 824 825 // Text-to-speech. 826 TtsEngines ttsEngines = new TtsEngines(context); 827 if (!ttsEngines.getEngines().isEmpty()) { 828 indexable = new SearchIndexableRaw(context); 829 indexable.key = "tts_settings"; 830 indexable.title = context.getString(R.string.tts_settings_title); 831 indexable.screenTitle = screenTitle; 832 indexable.keywords = context.getString(R.string.keywords_text_to_speech_output); 833 indexables.add(indexable); 834 } 835 836 // Pointer settings. 837 indexable = new SearchIndexableRaw(context); 838 indexable.key = "pointer_settings_category"; 839 indexable.title = context.getString(R.string.pointer_settings_category); 840 indexable.screenTitle = screenTitle; 841 indexables.add(indexable); 842 843 indexable = new SearchIndexableRaw(context); 844 indexable.key = "pointer_speed"; 845 indexable.title = context.getString(R.string.pointer_speed); 846 indexable.screenTitle = screenTitle; 847 indexables.add(indexable); 848 849 // Game controllers. 850 if (haveInputDeviceWithVibrator()) { 851 indexable = new SearchIndexableRaw(context); 852 indexable.key = "vibrate_input_devices"; 853 indexable.title = context.getString(R.string.vibrate_input_devices); 854 indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary); 855 indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary); 856 indexable.screenTitle = screenTitle; 857 indexables.add(indexable); 858 } 859 860 return indexables; 861 } 862 }; 863 } 864