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