InputMethodAndLanguageSettings.java revision 96cdcd80a8ba9d9a808a712037c3e4fa3fdcf084
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.Utils;
24import com.android.settings.VoiceInputOutputSettings;
25
26import android.app.Activity;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.pm.PackageManager;
31import android.content.res.Configuration;
32import android.database.ContentObserver;
33import android.hardware.input.InputManager;
34import android.hardware.input.KeyboardLayout;
35import android.os.Bundle;
36import android.os.Handler;
37import android.preference.CheckBoxPreference;
38import android.preference.ListPreference;
39import android.preference.Preference;
40import android.preference.PreferenceCategory;
41import android.preference.PreferenceScreen;
42import android.provider.Settings;
43import android.provider.Settings.System;
44import android.text.TextUtils;
45import android.view.InputDevice;
46import android.view.inputmethod.InputMethodInfo;
47import android.view.inputmethod.InputMethodManager;
48
49import java.util.ArrayList;
50import java.util.Collections;
51import java.util.List;
52import java.util.Set;
53
54public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
55        implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener {
56
57    private static final String KEY_PHONE_LANGUAGE = "phone_language";
58    private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
59    private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
60    private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
61    // false: on ICS or later
62    private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
63
64    private static final String[] sSystemSettingNames = {
65        System.TEXT_AUTO_REPLACE, System.TEXT_AUTO_CAPS, System.TEXT_AUTO_PUNCTUATE,
66    };
67
68    private static final String[] sHardKeyboardKeys = {
69        "auto_replace", "auto_caps", "auto_punctuate",
70    };
71
72    private int mDefaultInputMethodSelectorVisibility = 0;
73    private ListPreference mShowInputMethodSelectorPref;
74    private PreferenceCategory mKeyboardSettingsCategory;
75    private PreferenceCategory mHardKeyboardCategory;
76    private Preference mLanguagePref;
77    private final ArrayList<InputMethodPreference> mInputMethodPreferenceList =
78            new ArrayList<InputMethodPreference>();
79    private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList =
80            new ArrayList<PreferenceScreen>();
81    private InputManager mIm;
82    private InputMethodManager mImm;
83    private List<InputMethodInfo> mImis;
84    private boolean mIsOnlyImeSettings;
85    private Handler mHandler;
86    @SuppressWarnings("unused")
87    private SettingsObserver mSettingsObserver;
88
89    @Override
90    public void onCreate(Bundle icicle) {
91        super.onCreate(icicle);
92
93        addPreferencesFromResource(R.xml.language_settings);
94
95        try {
96            mDefaultInputMethodSelectorVisibility = Integer.valueOf(
97                    getString(R.string.input_method_selector_visibility_default_value));
98        } catch (NumberFormatException e) {
99        }
100
101        if (getActivity().getAssets().getLocales().length == 1) {
102            // No "Select language" pref if there's only one system locale available.
103            getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
104        } else {
105            mLanguagePref = findPreference(KEY_PHONE_LANGUAGE);
106        }
107        if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
108            mShowInputMethodSelectorPref = (ListPreference)findPreference(
109                    KEY_INPUT_METHOD_SELECTOR);
110            mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
111            // TODO: Update current input method name on summary
112            updateInputMethodSelectorSummary(loadInputMethodSelectorVisibility());
113        }
114
115        new VoiceInputOutputSettings(this).onCreate();
116
117        // Get references to dynamically constructed categories.
118        mHardKeyboardCategory = (PreferenceCategory)findPreference("hard_keyboard");
119        mKeyboardSettingsCategory = (PreferenceCategory)findPreference(
120                "keyboard_settings_category");
121
122        // Filter out irrelevant features if invoked from IME settings button.
123        mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
124                getActivity().getIntent().getAction());
125        getActivity().getIntent().setAction(null);
126        if (mIsOnlyImeSettings) {
127            getPreferenceScreen().removeAll();
128            getPreferenceScreen().addPreference(mHardKeyboardCategory);
129            if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
130                getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
131            }
132            getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
133        }
134
135        // Build IME preference category.
136        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
137        mImis = mImm.getInputMethodList();
138
139        mKeyboardSettingsCategory.removeAll();
140        if (!mIsOnlyImeSettings) {
141            final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null);
142            currentIme.setKey(KEY_CURRENT_INPUT_METHOD);
143            currentIme.setTitle(getResources().getString(R.string.current_input_method));
144            mKeyboardSettingsCategory.addPreference(currentIme);
145        }
146
147        mInputMethodPreferenceList.clear();
148        final int N = (mImis == null ? 0 : mImis.size());
149        for (int i = 0; i < N; ++i) {
150            final InputMethodInfo imi = mImis.get(i);
151            final InputMethodPreference pref = getInputMethodPreference(imi, N);
152            mInputMethodPreferenceList.add(pref);
153        }
154
155        if (!mInputMethodPreferenceList.isEmpty()) {
156            Collections.sort(mInputMethodPreferenceList);
157            for (int i = 0; i < N; ++i) {
158                mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i));
159            }
160        }
161
162        // Build hard keyboard preference category.
163        mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE);
164        updateHardKeyboards();
165
166        // Spell Checker
167        final Intent intent = new Intent(Intent.ACTION_MAIN);
168        intent.setClass(getActivity(), SpellCheckersSettingsActivity.class);
169        final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference(
170                "spellcheckers_settings"));
171        if (scp != null) {
172            scp.setFragmentIntent(this, intent);
173        }
174
175        mHandler = new Handler();
176        mSettingsObserver = new SettingsObserver(mHandler, getActivity());
177    }
178
179    private void updateInputMethodSelectorSummary(int value) {
180        String[] inputMethodSelectorTitles = getResources().getStringArray(
181                R.array.input_method_selector_titles);
182        if (inputMethodSelectorTitles.length > value) {
183            mShowInputMethodSelectorPref.setSummary(inputMethodSelectorTitles[value]);
184            mShowInputMethodSelectorPref.setValue(String.valueOf(value));
185        }
186    }
187
188    private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
189        final Activity activity = getActivity();
190        final Set<String> localeList = UserDictionaryList.getUserDictionaryLocalesList(activity);
191        if (null == localeList) {
192            // The locale list is null if and only if the user dictionary service is
193            // not present or disabled. In this case we need to remove the preference.
194            getPreferenceScreen().removePreference(userDictionaryPreference);
195        } else if (localeList.size() <= 1) {
196            final Intent intent =
197                    new Intent(UserDictionaryList.USER_DICTIONARY_SETTINGS_INTENT_ACTION);
198            userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title);
199            userDictionaryPreference.setIntent(intent);
200            // If the size of localeList is 0, we don't set the locale parameter in the
201            // extras. This will be interpreted by the UserDictionarySettings class as
202            // meaning "the current locale".
203            // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesList()
204            // the locale list always has at least one element, since it always includes the current
205            // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesList().
206            if (localeList.size() == 1) {
207                final String locale = (String)localeList.toArray()[0];
208                userDictionaryPreference.getExtras().putString("locale", locale);
209            }
210        } else {
211            userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title);
212            userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
213        }
214    }
215
216    @Override
217    public void onResume() {
218        super.onResume();
219
220        mIm.registerInputDeviceListener(this, null);
221
222        if (!mIsOnlyImeSettings) {
223            if (mLanguagePref != null) {
224                Configuration conf = getResources().getConfiguration();
225                String locale = conf.locale.getDisplayName(conf.locale);
226                if (locale != null && locale.length() > 1) {
227                    locale = Character.toUpperCase(locale.charAt(0)) + locale.substring(1);
228                    mLanguagePref.setSummary(locale);
229                }
230            }
231
232            updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
233            if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
234                mShowInputMethodSelectorPref.setOnPreferenceChangeListener(this);
235            }
236        }
237
238        // Hard keyboard
239        if (!mHardKeyboardPreferenceList.isEmpty()) {
240            for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
241                CheckBoxPreference chkPref = (CheckBoxPreference)
242                        mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i]);
243                chkPref.setChecked(
244                        System.getInt(getContentResolver(), sSystemSettingNames[i], 1) > 0);
245            }
246        }
247
248        updateHardKeyboards();
249
250        // IME
251        InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
252                this, getContentResolver(), mImis, null);
253        updateActiveInputMethodsSummary();
254    }
255
256    @Override
257    public void onPause() {
258        super.onPause();
259
260        mIm.unregisterInputDeviceListener(this);
261
262        if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
263            mShowInputMethodSelectorPref.setOnPreferenceChangeListener(null);
264        }
265        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
266                this, getContentResolver(), mImis, !mHardKeyboardPreferenceList.isEmpty());
267    }
268
269    @Override
270    public void onInputDeviceAdded(int deviceId) {
271        updateHardKeyboards();
272    }
273
274    @Override
275    public void onInputDeviceChanged(int deviceId) {
276        updateHardKeyboards();
277    }
278
279    @Override
280    public void onInputDeviceRemoved(int deviceId) {
281        updateHardKeyboards();
282    }
283
284    @Override
285    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
286        // Input Method stuff
287        if (Utils.isMonkeyRunning()) {
288            return false;
289        }
290        if (preference instanceof PreferenceScreen) {
291            if (preference.getFragment() != null) {
292                // Fragment will be handled correctly by the super class.
293            } else if (KEY_CURRENT_INPUT_METHOD.equals(preference.getKey())) {
294                final InputMethodManager imm = (InputMethodManager)
295                        getSystemService(Context.INPUT_METHOD_SERVICE);
296                imm.showInputMethodPicker();
297            }
298        } else if (preference instanceof CheckBoxPreference) {
299            final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
300            if (!mHardKeyboardPreferenceList.isEmpty()) {
301                for (int i = 0; i < sHardKeyboardKeys.length; ++i) {
302                    if (chkPref == mHardKeyboardCategory.findPreference(sHardKeyboardKeys[i])) {
303                        System.putInt(getContentResolver(), sSystemSettingNames[i],
304                                chkPref.isChecked() ? 1 : 0);
305                        return true;
306                    }
307                }
308            }
309        }
310        return super.onPreferenceTreeClick(preferenceScreen, preference);
311    }
312
313    private void saveInputMethodSelectorVisibility(String value) {
314        try {
315            int intValue = Integer.valueOf(value);
316            Settings.Secure.putInt(getContentResolver(),
317                    Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY, intValue);
318            updateInputMethodSelectorSummary(intValue);
319        } catch(NumberFormatException e) {
320        }
321    }
322
323    private int loadInputMethodSelectorVisibility() {
324        return Settings.Secure.getInt(getContentResolver(),
325                Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
326                mDefaultInputMethodSelectorVisibility);
327    }
328
329    @Override
330    public boolean onPreferenceChange(Preference preference, Object value) {
331        if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
332            if (preference == mShowInputMethodSelectorPref) {
333                if (value instanceof String) {
334                    saveInputMethodSelectorVisibility((String)value);
335                }
336            }
337        }
338        return false;
339    }
340
341    private void updateActiveInputMethodsSummary() {
342        for (Preference pref : mInputMethodPreferenceList) {
343            if (pref instanceof InputMethodPreference) {
344                ((InputMethodPreference)pref).updateSummary();
345            }
346        }
347        updateCurrentImeName();
348    }
349
350    private void updateCurrentImeName() {
351        final Context context = getActivity();
352        if (context == null || mImm == null) return;
353        final Preference curPref = getPreferenceScreen().findPreference(KEY_CURRENT_INPUT_METHOD);
354        if (curPref != null) {
355            final CharSequence curIme = InputMethodAndSubtypeUtil.getCurrentInputMethodName(
356                    context, getContentResolver(), mImm, mImis, getPackageManager());
357            if (!TextUtils.isEmpty(curIme)) {
358                synchronized(this) {
359                    curPref.setSummary(curIme);
360                }
361            }
362        }
363    }
364
365    private InputMethodPreference getInputMethodPreference(InputMethodInfo imi, int imiSize) {
366        final PackageManager pm = getPackageManager();
367        final CharSequence label = imi.loadLabel(pm);
368        // IME settings
369        final Intent intent;
370        final String settingsActivity = imi.getSettingsActivity();
371        if (!TextUtils.isEmpty(settingsActivity)) {
372            intent = new Intent(Intent.ACTION_MAIN);
373            intent.setClassName(imi.getPackageName(), settingsActivity);
374        } else {
375            intent = null;
376        }
377
378        // Add a check box for enabling/disabling IME
379        InputMethodPreference pref = new InputMethodPreference(this, intent, mImm, imi, imiSize);
380        pref.setKey(imi.getId());
381        pref.setTitle(label);
382        return pref;
383    }
384
385    private void updateHardKeyboards() {
386        mHardKeyboardPreferenceList.clear();
387        if (getResources().getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY) {
388            final int[] devices = InputDevice.getDeviceIds();
389            for (int i = 0; i < devices.length; i++) {
390                InputDevice device = InputDevice.getDevice(devices[i]);
391                if (device != null
392                        && !device.isVirtual()
393                        && (device.getSources() & InputDevice.SOURCE_KEYBOARD) != 0
394                        && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
395                    final String inputDeviceDescriptor = device.getDescriptor();
396                    final String keyboardLayoutDescriptor =
397                            mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
398                    final KeyboardLayout keyboardLayout = keyboardLayoutDescriptor != null ?
399                            mIm.getKeyboardLayout(keyboardLayoutDescriptor) : null;
400
401                    final Intent intent = new Intent(Intent.ACTION_MAIN);
402                    intent.setClass(getActivity(), KeyboardLayoutPickerActivity.class);
403                    intent.putExtra(KeyboardLayoutPicker.EXTRA_INPUT_DEVICE_DESCRIPTOR,
404                            inputDeviceDescriptor);
405
406                    final PreferenceScreen pref = new PreferenceScreen(getActivity(), null);
407                    pref.setTitle(device.getName());
408                    if (keyboardLayout != null) {
409                        pref.setSummary(keyboardLayout.getLabel());
410                    }
411                    pref.setIntent(intent);
412                    mHardKeyboardPreferenceList.add(pref);
413                }
414            }
415        }
416
417        if (!mHardKeyboardPreferenceList.isEmpty()) {
418            for (int i = mHardKeyboardCategory.getPreferenceCount(); i-- > 0; ) {
419                final Preference pref = mHardKeyboardCategory.getPreference(i);
420                if (pref.getOrder() < 1000) {
421                    mHardKeyboardCategory.removePreference(pref);
422                }
423            }
424
425            Collections.sort(mHardKeyboardPreferenceList);
426            final int count = mHardKeyboardPreferenceList.size();
427            for (int i = 0; i < count; i++) {
428                final Preference pref = mHardKeyboardPreferenceList.get(i);
429                pref.setOrder(i);
430                mHardKeyboardCategory.addPreference(pref);
431            }
432
433            getPreferenceScreen().addPreference(mHardKeyboardCategory);
434        } else {
435            getPreferenceScreen().removePreference(mHardKeyboardCategory);
436        }
437    }
438
439    private class SettingsObserver extends ContentObserver {
440        public SettingsObserver(Handler handler, Context context) {
441            super(handler);
442            final ContentResolver cr = context.getContentResolver();
443            cr.registerContentObserver(
444                    Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
445            cr.registerContentObserver(Settings.Secure.getUriFor(
446                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
447        }
448
449        @Override public void onChange(boolean selfChange) {
450            updateCurrentImeName();
451        }
452    }
453}
454