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.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
    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             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
    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
    313     public void onInputDeviceAdded(int deviceId) {
    314         updateInputDevices();
    315     }
    316 
    317     @Override
    318     public void onInputDeviceChanged(int deviceId) {
    319         updateInputDevices();
    320     }
    321 
    322     @Override
    323     public void onInputDeviceRemoved(int deviceId) {
    324         updateInputDevices();
    325     }
    326 
    327     @Override
    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 
    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 
    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 
    371     private int loadInputMethodSelectorVisibility() {
    372         return Settings.Secure.getInt(getContentResolver(),
    373                 Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
    374                 mDefaultInputMethodSelectorVisibility);
    375     }
    376 
    377     @Override
    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 
    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
    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 
    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 
    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 
    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 
    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 
    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 
    522     private void updateInputDevices() {
    523         updateHardKeyboards();
    524         updateGameControllers();
    525     }
    526 
    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 
    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
    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
    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 
    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 
    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 
    644         public SettingsObserver(Handler handler, Context context) {
    645             super(handler);
    646             mContext = context;
    647         }
    648 
    649         @Override public void onChange(boolean selfChange) {
    650             updateCurrentImeName();
    651         }
    652 
    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 
    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 
    671         public SummaryProvider(Context context, SummaryLoader summaryLoader) {
    672             mContext = context;
    673             mSummaryLoader = summaryLoader;
    674         }
    675 
    676         @Override
    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