InputMethodAndLanguageSettings.java revision 813a54d216010a16d714355c61d606dd3eb589aa
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 com.android.settings.R;
20import com.android.settings.Settings.KeyboardLayoutPickerActivity;
21import com.android.settings.Settings.SpellCheckersSettingsActivity;
22import com.android.settings.SettingsPreferenceFragment;
23import com.android.settings.UserDictionarySettings;
24import com.android.settings.Utils;
25import com.android.settings.VoiceInputOutputSettings;
26
27import android.app.Activity;
28import android.app.Fragment;
29import android.content.ContentResolver;
30import android.content.Context;
31import android.content.Intent;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.content.res.Resources;
35import android.database.ContentObserver;
36import android.hardware.input.InputDeviceIdentifier;
37import android.hardware.input.InputManager;
38import android.hardware.input.KeyboardLayout;
39import android.os.Bundle;
40import android.os.Handler;
41import android.preference.CheckBoxPreference;
42import android.preference.ListPreference;
43import android.preference.Preference;
44import android.preference.Preference.OnPreferenceChangeListener;
45import android.preference.Preference.OnPreferenceClickListener;
46import android.preference.PreferenceCategory;
47import android.preference.PreferenceScreen;
48import android.provider.Settings;
49import android.provider.Settings.System;
50import android.text.TextUtils;
51import android.view.InputDevice;
52import android.view.inputmethod.InputMethodInfo;
53import android.view.inputmethod.InputMethodManager;
54import android.widget.BaseAdapter;
55
56import java.util.ArrayList;
57import java.util.Collections;
58import java.util.List;
59import java.util.TreeSet;
60
61public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
62        implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener,
63        KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener {
64
65    private static final String KEY_PHONE_LANGUAGE = "phone_language";
66    private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
67    private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
68    private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
69    // false: on ICS or later
70    private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
71
72    private static final String[] sSystemSettingNames = {
73        System.TEXT_AUTO_REPLACE, System.TEXT_AUTO_CAPS, System.TEXT_AUTO_PUNCTUATE,
74    };
75
76    private static final String[] sHardKeyboardKeys = {
77        "auto_replace", "auto_caps", "auto_punctuate",
78    };
79
80    private int mDefaultInputMethodSelectorVisibility = 0;
81    private ListPreference mShowInputMethodSelectorPref;
82    private PreferenceCategory mKeyboardSettingsCategory;
83    private PreferenceCategory mHardKeyboardCategory;
84    private PreferenceCategory mGameControllerCategory;
85    private Preference mLanguagePref;
86    private final ArrayList<InputMethodPreference> mInputMethodPreferenceList =
87            new ArrayList<InputMethodPreference>();
88    private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList =
89            new ArrayList<PreferenceScreen>();
90    private InputManager mIm;
91    private InputMethodManager mImm;
92    private boolean mIsOnlyImeSettings;
93    private Handler mHandler;
94    private SettingsObserver mSettingsObserver;
95    private Intent mIntentWaitingForResult;
96    private InputMethodSettingValuesWrapper mInputMethodSettingValues;
97
98    private final OnPreferenceChangeListener mOnImePreferenceChangedListener =
99            new OnPreferenceChangeListener() {
100                @Override
101                public boolean onPreferenceChange(Preference arg0, Object arg1) {
102                    InputMethodSettingValuesWrapper.getInstance(
103                            arg0.getContext()).refreshAllInputMethodAndSubtypes();
104                    ((BaseAdapter)getPreferenceScreen().getRootAdapter()).notifyDataSetChanged();
105                    updateInputMethodPreferenceViews();
106                    return true;
107                }
108            };
109
110    @Override
111    public void onCreate(Bundle icicle) {
112        super.onCreate(icicle);
113
114        addPreferencesFromResource(R.xml.language_settings);
115
116        try {
117            mDefaultInputMethodSelectorVisibility = Integer.valueOf(
118                    getString(R.string.input_method_selector_visibility_default_value));
119        } catch (NumberFormatException e) {
120        }
121
122        if (getActivity().getAssets().getLocales().length == 1) {
123            // No "Select language" pref if there's only one system locale available.
124            getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
125        } else {
126            mLanguagePref = findPreference(KEY_PHONE_LANGUAGE);
127        }
128        if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
129            mShowInputMethodSelectorPref = (ListPreference)findPreference(
130                    KEY_INPUT_METHOD_SELECTOR);
131            mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
132            // TODO: Update current input method name on summary
133            updateInputMethodSelectorSummary(loadInputMethodSelectorVisibility());
134        }
135
136        new VoiceInputOutputSettings(this).onCreate();
137
138        // Get references to dynamically constructed categories.
139        mHardKeyboardCategory = (PreferenceCategory)findPreference("hard_keyboard");
140        mKeyboardSettingsCategory = (PreferenceCategory)findPreference(
141                "keyboard_settings_category");
142        mGameControllerCategory = (PreferenceCategory)findPreference(
143                "game_controller_settings_category");
144
145        // Filter out irrelevant features if invoked from IME settings button.
146        mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
147                getActivity().getIntent().getAction());
148        getActivity().getIntent().setAction(null);
149        if (mIsOnlyImeSettings) {
150            getPreferenceScreen().removeAll();
151            getPreferenceScreen().addPreference(mHardKeyboardCategory);
152            if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
153                getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
154            }
155            getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
156        }
157
158        // Build IME preference category.
159        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
160        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(getActivity());
161
162        mKeyboardSettingsCategory.removeAll();
163        if (!mIsOnlyImeSettings) {
164            final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null);
165            currentIme.setKey(KEY_CURRENT_INPUT_METHOD);
166            currentIme.setTitle(getResources().getString(R.string.current_input_method));
167            mKeyboardSettingsCategory.addPreference(currentIme);
168        }
169
170        // Build hard keyboard and game controller preference categories.
171        mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE);
172        updateInputDevices();
173
174        // Spell Checker
175        final Intent intent = new Intent(Intent.ACTION_MAIN);
176        intent.setClass(getActivity(), SpellCheckersSettingsActivity.class);
177        final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference(
178                "spellcheckers_settings"));
179        if (scp != null) {
180            scp.setFragmentIntent(this, intent);
181        }
182
183        mHandler = new Handler();
184        mSettingsObserver = new SettingsObserver(mHandler, getActivity());
185    }
186
187    private void updateInputMethodSelectorSummary(int value) {
188        String[] inputMethodSelectorTitles = getResources().getStringArray(
189                R.array.input_method_selector_titles);
190        if (inputMethodSelectorTitles.length > value) {
191            mShowInputMethodSelectorPref.setSummary(inputMethodSelectorTitles[value]);
192            mShowInputMethodSelectorPref.setValue(String.valueOf(value));
193        }
194    }
195
196    private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
197        final Activity activity = getActivity();
198        final TreeSet<String> localeSet = UserDictionaryList.getUserDictionaryLocalesSet(activity);
199        if (null == localeSet) {
200            // The locale list is null if and only if the user dictionary service is
201            // not present or disabled. In this case we need to remove the preference.
202            getPreferenceScreen().removePreference(userDictionaryPreference);
203        } else {
204            userDictionaryPreference.setOnPreferenceClickListener(
205                    new OnPreferenceClickListener() {
206                        @Override
207                        public boolean onPreferenceClick(Preference arg0) {
208                            // Redirect to UserDictionarySettings if the user needs only one
209                            // language.
210                            final Bundle extras = new Bundle();
211                            final Class<? extends Fragment> targetFragment;
212                            if (localeSet.size() <= 1) {
213                                if (!localeSet.isEmpty()) {
214                                    // If the size of localeList is 0, we don't set the locale
215                                    // parameter in the extras. This will be interpreted by the
216                                    // UserDictionarySettings class as meaning
217                                    // "the current locale". Note that with the current code for
218                                    // UserDictionaryList#getUserDictionaryLocalesSet()
219                                    // the locale list always has at least one element, since it
220                                    // always includes the current locale explicitly.
221                                    // @see UserDictionaryList.getUserDictionaryLocalesSet().
222                                    extras.putString("locale", localeSet.first());
223                                }
224                                targetFragment = UserDictionarySettings.class;
225                            } else {
226                                targetFragment = UserDictionaryList.class;
227                            }
228                            startFragment(InputMethodAndLanguageSettings.this,
229                                    targetFragment.getCanonicalName(), -1, extras);
230                            return true;
231                        }
232                    });
233        }
234    }
235
236    @Override
237    public void onResume() {
238        super.onResume();
239
240        mSettingsObserver.resume();
241        mIm.registerInputDeviceListener(this, null);
242
243        if (!mIsOnlyImeSettings) {
244            if (mLanguagePref != null) {
245                Configuration conf = getResources().getConfiguration();
246                String language = conf.locale.getLanguage();
247                String localeString;
248                // TODO: This is not an accurate way to display the locale, as it is
249                // just working around the fact that we support limited dialects
250                // and want to pretend that the language is valid for all locales.
251                // We need a way to support languages that aren't tied to a particular
252                // locale instead of hiding the locale qualifier.
253                if (language.equals("zz")) {
254                    String country = conf.locale.getCountry();
255                    if (country.equals("ZZ")) {
256                        localeString = "[Developer] Accented English (zz_ZZ)";
257                    } else if (country.equals("ZY")) {
258                        localeString = "[Developer] Fake Bi-Directional (zz_ZY)";
259                    } else {
260                        localeString = "";
261                    }
262                } else if (hasOnlyOneLanguageInstance(language,
263                        Resources.getSystem().getAssets().getLocales())) {
264                    localeString = conf.locale.getDisplayLanguage(conf.locale);
265                } else {
266                    localeString = conf.locale.getDisplayName(conf.locale);
267                }
268                if (localeString.length() > 1) {
269                    localeString = Character.toUpperCase(localeString.charAt(0))
270                            + localeString.substring(1);
271                    mLanguagePref.setSummary(localeString);
272                }
273            }
274
275            updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
276            if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
277                mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
278            }
279        }
280
281        // Hard keyboard
282        if (!mHardKeyboardPreferenceList.isEmpty()) {
283            for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
284                CheckBoxPreference chkPref = (CheckBoxPreference)
285                        mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]);
286                chkPref.setChecked(
287                        System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0);
288            }
289        }
290
291        updateInputDevices();
292
293        // Refresh internal states in mInputMethodSettingValues to keep the latest
294        // "InputMethodInfo"s and "InputMethodSubtype"s
295        mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
296        updateInputMethodPreferenceViews();
297    }
298
299    @Override
300    public void onPause() {
301        super.onPause();
302
303        mIm.unregisterInputDeviceListener(this);
304        mSettingsObserver.pause();
305
306        if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
307            mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null);
308        }
309        // TODO: Consolidate the logic to InputMethodSettingsWrapper
310        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
311                this, getContentResolver(), mInputMethodSettingValues.getInputMethodList(),
312                !mHardKeyboardPreferenceList.isEmpty());
313    }
314
315    @Override
316    public void onInputDeviceAdded(int deviceId) {
317        updateInputDevices();
318    }
319
320    @Override
321    public void onInputDeviceChanged(int deviceId) {
322        updateInputDevices();
323    }
324
325    @Override
326    public void onInputDeviceRemoved(int deviceId) {
327        updateInputDevices();
328    }
329
330    @Override
331    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
332        // Input Method stuff
333        if (Utils.isMonkeyRunning()) {
334            return false;
335        }
336        if (preference instanceof PreferenceScreen) {
337            if (preference.getFragment() != null) {
338                // Fragment will be handled correctly by the super class.
339            } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) {
340                final InputMethodManager imm = (InputMethodManager)
341                        getSystemService(Context.INPUT_METHOD_SERVICE);
342                imm.showInputMethodPicker();
343            }
344        } else if (preference instanceof CheckBoxPreference) {
345            final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
346            if (!mHardKeyboardPreferenceList.isEmpty()) {
347                for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
348                    if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
349                        System.putInt(getContentResolver(), sSystemSettingNames[i],
350                                chkPref.isChecked() ? 1 : 0);
351                        return true;
352                    }
353                }
354            }
355            if (chkPref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
356                System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
357                        chkPref.isChecked() ? 1 : 0);
358                return true;
359            }
360        }
361        return super.onPreferenceTreeClick(preferenceScreen, preference);
362    }
363
364    private boolean hasOnlyOneLanguageInstance(String languageCode, String[] locales) {
365        int count = 0;
366        for (String localeCode : locales) {
367            if (localeCode.length() > 2
368                    && localeCode.startsWith(languageCode)) {
369                count++;
370                if (count > 1) {
371                    return false;
372                }
373            }
374        }
375        return count == 1;
376    }
377
378    private void saveInputMethodSelectorVisibility(String value) {
379        try {
380            int intValue = Integer.valueOf(value);
381            Settings.Secure.putInt(getContentResolver(),
382                    Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue);
383            updateInputMethodSelectorSummary(intValue);
384        } catch(NumberFormatException e) {
385        }
386    }
387
388    private int loadInputMethodSelectorVisibility() {
389        return Settings.Secure.getInt(getContentResolver(),
390                Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
391                mDefaultInputMethodSelectorVisibility);
392    }
393
394    @Override
395    public boolean onPreferenceChange(Preference preference, Object value) {
396        if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
397            if (preference == mShowInputMethodSelectorPref) {
398                if (value instanceof String) {
399                    saveInputMethodSelectorVisibility((String)value);
400                }
401            }
402        }
403        return false;
404    }
405
406    private void updateInputMethodPreferenceViews() {
407        synchronized (mInputMethodPreferenceList) {
408            // Clear existing "InputMethodPreference"s
409            for (final InputMethodPreference imp : mInputMethodPreferenceList) {
410                mKeyboardSettingsCategory.removePreference(imp);
411            }
412            mInputMethodPreferenceList.clear();
413            final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList();
414            final int N = (imis == null ? 0 : imis.size());
415            for (int i = 0; i < N; ++i) {
416                final InputMethodInfo imi = imis.get(i);
417                final InputMethodPreference pref = getInputMethodPreference(imi);
418                pref.setOnImePreferenceChangeListener(mOnImePreferenceChangedListener);
419                mInputMethodPreferenceList.add(pref);
420            }
421
422            if (!mInputMethodPreferenceList.isEmpty()) {
423                Collections.sort(mInputMethodPreferenceList);
424                for (int i = 0; i < N; ++i) {
425                    mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i));
426                }
427            }
428
429            // update views status
430            for (Preference pref : mInputMethodPreferenceList) {
431                if (pref instanceof InputMethodPreference) {
432                    ((InputMethodPreference) pref).updatePreferenceViews();
433                }
434            }
435        }
436        updateCurrentImeName();
437        // TODO: Consolidate the logic with InputMethodSettingsWrapper
438        // CAVEAT: The preference class here does not know about the default value - that is
439        // managed by the Input Method Manager Service, so in this case it could save the wrong
440        // value. Hence we must update the checkboxes here.
441        InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
442                this, getContentResolver(),
443                mInputMethodSettingValues.getInputMethodList(), null);
444    }
445
446    private void updateCurrentImeName() {
447        final Context context = getActivity();
448        if (context == null || mImm == null) return;
449        final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD);
450        if (curPref != null) {
451            final CharSequence curIme =
452                    mInputMethodSettingValues.getCurrentInputMethodName(context);
453            if (!TextUtils.isEmpty(curIme)) {
454                synchronized(this) {
455                    curPref.setSummary(curIme);
456                }
457            }
458        }
459    }
460
461    private InputMethodPreference getInputMethodPreference(InputMethodInfo imi) {
462        final PackageManager pm = getPackageManager();
463        final CharSequence label = imi.loadLabel(pm);
464        // IME settings
465        final Intent intent;
466        final String settingsActivity = imi.getSettingsActivity();
467        if (!TextUtils.isEmpty(settingsActivity)) {
468            intent = new Intent(Intent.ACTION_MAIN);
469            intent.setClassName(imi.getPackageName(), settingsActivity);
470        } else {
471            intent = null;
472        }
473
474        // Add a check box for enabling/disabling IME
475        final InputMethodPreference pref =
476                new InputMethodPreference(this, intent, mImm, imi);
477        pref.setKey(imi.getId());
478        pref.setTitle(label);
479        return pref;
480    }
481
482    private void updateInputDevices() {
483        updateHardKeyboards();
484        updateGameControllers();
485    }
486
487    private void updateHardKeyboards() {
488        mHardKeyboardPreferenceList.clear();
489        if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) {
490            final int[] devices = InputDevice.getDeviceIds();
491            for (int i = 0; i < devices.length; i++) {
492                InputDevice device = InputDevice.getDevice(devices[i]);
493                if (device != null
494                        && !device.isVirtual()
495                        && device.isFullKeyboard()) {
496                    final InputDeviceIdentifier identifier = device.getIdentifier();
497                    final String keyboardLayoutDescriptor =
498                            mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
499                    final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
500                            mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
501
502                    final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
503                    pref.setTitle(device.getName());
504                    if (keyboardLayout != null) {
505                        pref.setSummary(keyboardLayout.toString());
506                    } else {
507                        pref.setSummary(R.string.keyboard_layout_default_label);
508                    }
509                    pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
510                        @Override
511                        public boolean onPreferenceClick(Preference preference) {
512                            showKeyboardLayoutDialog(identifier);
513                            return true;
514                        }
515                    });
516                    mHardKeyboardPreferenceList.add(pref);
517                }
518            }
519        }
520
521        if (!mHardKeyboardPreferenceList.isEmpty()) {
522            for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) {
523                final Preference pref = mHardKeyboardCategory.getPreference(i);
524                if (pref.getOrder() < 1000) {
525                    mHardKeyboardCategory.removePreference(pref);
526                }
527            }
528
529            Collections.sort(mHardKeyboardPreferenceList);
530            final int count = mHardKeyboardPreferenceList.size();
531            for (int i = 0; i < count; i++) {
532                final Preference pref = mHardKeyboardPreferenceList.get(i);
533                pref.setOrder(i);
534                mHardKeyboardCategory.addPreference(pref);
535            }
536
537            getPreferenceScreen().addPreference(mHardKeyboardCategory);
538        } else {
539            getPreferenceScreen().removePreference(mHardKeyboardCategory);
540        }
541    }
542
543    private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
544        KeyboardLayoutDialogFragment fragment =
545                new KeyboardLayoutDialogFragment(inputDeviceIdentifier);
546        fragment.setTargetFragment(this, 0);
547        fragment.show(getActivity().getFragmentManager(), "keyboardLayout");
548    }
549
550    @Override
551    public void onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier) {
552        final Intent intent = new Intent(Intent.ACTION_MAIN);
553        intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class);
554        intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER,
555                inputDeviceIdentifier);
556        mIntentWaitingForResult = intent;
557        startActivityForResult(intent, 0);
558    }
559
560    @Override
561    public void onActivityResult(int requestCode, int resultCode, Intent data) {
562        super.onActivityResult(requestCode, resultCode, data);
563
564        if (mIntentWaitingForResult != null) {
565            InputDeviceIdentifier inputDeviceIdentifier = mIntentWaitingForResult
566                    .getParcelableExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER);
567            mIntentWaitingForResult = null;
568            showKeyboardLayoutDialog(inputDeviceIdentifier);
569        }
570    }
571
572    private void updateGameControllers() {
573        if (haveInputDeviceWithVibrator()) {
574            getPreferenceScreen().addPreference(mGameControllerCategory);
575
576            CheckBoxPreference chkPref = (CheckBoxPreference)
577                    mGameControllerCategory.findPreference("vibrate_input_devices");
578            chkPref.setChecked(System.getInt(getContentResolver(),
579                    Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0);
580        } else {
581            getPreferenceScreen().removePreference(mGameControllerCategory);
582        }
583    }
584
585    private boolean haveInputDeviceWithVibrator() {
586        final int[] devices = InputDevice.getDeviceIds();
587        for (int i = 0; i < devices.length; i++) {
588            InputDevice device = InputDevice.getDevice(devices[i]);
589            if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) {
590                return true;
591            }
592        }
593        return false;
594    }
595
596    private class SettingsObserver extends ContentObserver {
597        private Context mContext;
598
599        public SettingsObserver(Handler handler, Context context) {
600            super(handler);
601            mContext = context;
602        }
603
604        @Override public void onChange(boolean selfChange) {
605            updateCurrentImeName();
606        }
607
608        public void resume() {
609            final ContentResolver cr = mContext.getContentResolver();
610            cr.registerContentObserver(
611                    Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
612            cr.registerContentObserver(Settings.Secure.getUriFor(
613                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
614        }
615
616        public void pause() {
617            mContext.getContentResolver().unregisterContentObserver(this);
618        }
619    }
620}
621