OSDN Git Service

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