OSDN Git Service

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