OSDN Git Service

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