OSDN Git Service

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