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