InputMethodAndSubtypeEnabler.java revision ae70ee49492bd89c949946562b43a75f3c81b0ad
1/*
2 * Copyright (C) 2010 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.SettingsPreferenceFragment;
21
22import android.app.AlertDialog;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.content.res.Configuration;
28import android.os.Bundle;
29import android.preference.CheckBoxPreference;
30import android.preference.Preference;
31import android.preference.PreferenceCategory;
32import android.preference.PreferenceScreen;
33import android.text.TextUtils;
34import android.util.Log;
35import android.view.inputmethod.InputMethodInfo;
36import android.view.inputmethod.InputMethodManager;
37import android.view.inputmethod.InputMethodSubtype;
38
39import java.util.ArrayList;
40import java.util.Collections;
41import java.util.Comparator;
42import java.util.HashMap;
43import java.util.List;
44
45public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment {
46    private static final String TAG =InputMethodAndSubtypeEnabler.class.getSimpleName();
47    private AlertDialog mDialog = null;
48    private boolean mHaveHardKeyboard;
49    final private HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
50            new HashMap<String, List<Preference>>();
51    final private HashMap<String, CheckBoxPreference> mSubtypeAutoSelectionCBMap =
52            new HashMap<String, CheckBoxPreference>();
53    private InputMethodManager mImm;
54    private List<InputMethodInfo> mInputMethodProperties;
55    private String mInputMethodId;
56    private String mTitle;
57    private String mSystemLocale = "";
58
59    @Override
60    public void onCreate(Bundle icicle) {
61        super.onCreate(icicle);
62        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
63        final Configuration config = getResources().getConfiguration();
64        mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
65
66        final Bundle arguments = getArguments();
67        // Input method id should be available from an Intent when this preference is launched as a
68        // single Activity (see InputMethodAndSubtypeEnablerActivity). It should be available
69        // from a preference argument when the preference is launched as a part of the other
70        // Activity (like a right pane of 2-pane Settings app)
71        mInputMethodId = getActivity().getIntent().getStringExtra(
72                android.provider.Settings.EXTRA_INPUT_METHOD_ID);
73        if (mInputMethodId == null && (arguments != null)) {
74            final String inputMethodId =
75                    arguments.getString(android.provider.Settings.EXTRA_INPUT_METHOD_ID);
76            if (inputMethodId != null) {
77                mInputMethodId = inputMethodId;
78            }
79        }
80        mTitle = getActivity().getIntent().getStringExtra(Intent.EXTRA_TITLE);
81        if (mTitle == null && (arguments != null)) {
82            final String title = arguments.getString(Intent.EXTRA_TITLE);
83            if (title != null) {
84                mTitle = title;
85            }
86        }
87
88        mSystemLocale = config.locale.toString();
89        onCreateIMM();
90        setPreferenceScreen(createPreferenceHierarchy());
91    }
92
93    @Override
94    public void onActivityCreated(Bundle icicle) {
95        super.onActivityCreated(icicle);
96        if (!TextUtils.isEmpty(mTitle)) {
97            getActivity().setTitle(mTitle);
98        }
99    }
100
101    @Override
102    public void onResume() {
103        super.onResume();
104        InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
105                this, getContentResolver(), mInputMethodProperties, mInputMethodAndSubtypePrefsMap);
106        updateAutoSelectionCB();
107    }
108
109    @Override
110    public void onPause() {
111        super.onPause();
112        // Clear all subtypes of all IMEs to make sure
113        clearImplicitlyEnabledSubtypes(null);
114        InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
115                mInputMethodProperties, mHaveHardKeyboard);
116    }
117
118    @Override
119    public boolean onPreferenceTreeClick(
120            PreferenceScreen preferenceScreen, Preference preference) {
121
122        if (preference instanceof CheckBoxPreference) {
123            final CheckBoxPreference chkPref = (CheckBoxPreference) preference;
124
125            for (String imiId: mSubtypeAutoSelectionCBMap.keySet()) {
126                if (mSubtypeAutoSelectionCBMap.get(imiId) == chkPref) {
127                    // We look for the first preference item in subtype enabler.
128                    // The first item is used for turning on/off subtype auto selection.
129                    // We are in the subtype enabler and trying selecting subtypes automatically.
130                    setSubtypeAutoSelectionEnabled(imiId, chkPref.isChecked());
131                    return super.onPreferenceTreeClick(preferenceScreen, preference);
132                }
133            }
134
135            final String id = chkPref.getKey();
136            if (chkPref.isChecked()) {
137                InputMethodInfo selImi = null;
138                final int N = mInputMethodProperties.size();
139                for (int i = 0; i < N; i++) {
140                    InputMethodInfo imi = mInputMethodProperties.get(i);
141                    if (id.equals(imi.getId())) {
142                        selImi = imi;
143                        if (InputMethodAndSubtypeUtil.isSystemIme(imi)) {
144                            InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled(
145                                    this, mInputMethodProperties, id, true);
146                            // This is a built-in IME, so no need to warn.
147                            return super.onPreferenceTreeClick(preferenceScreen, preference);
148                        }
149                        break;
150                    }
151                }
152                if (selImi == null) {
153                    return super.onPreferenceTreeClick(preferenceScreen, preference);
154                }
155                chkPref.setChecked(false);
156                if (mDialog == null) {
157                    mDialog = (new AlertDialog.Builder(getActivity()))
158                            .setTitle(android.R.string.dialog_alert_title)
159                            .setIcon(android.R.drawable.ic_dialog_alert)
160                            .setCancelable(true)
161                            .setPositiveButton(android.R.string.ok,
162                                    new DialogInterface.OnClickListener() {
163                                        @Override
164                                        public void onClick(DialogInterface dialog, int which) {
165                                            chkPref.setChecked(true);
166                                            InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled(
167                                                    InputMethodAndSubtypeEnabler.this,
168                                                    mInputMethodProperties, id, true);
169                                        }
170
171                            })
172                            .setNegativeButton(android.R.string.cancel,
173                                    new DialogInterface.OnClickListener() {
174                                        @Override
175                                        public void onClick(DialogInterface dialog, int which) {
176                                        }
177
178                            })
179                            .create();
180                } else {
181                    if (mDialog.isShowing()) {
182                        mDialog.dismiss();
183                    }
184                }
185                mDialog.setMessage(getResources().getString(
186                        R.string.ime_security_warning,
187                        selImi.getServiceInfo().applicationInfo.loadLabel(getPackageManager())));
188                mDialog.show();
189            } else {
190                InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled(
191                        this, mInputMethodProperties, id, false);
192                updateAutoSelectionCB();
193            }
194        }
195        return super.onPreferenceTreeClick(preferenceScreen, preference);
196    }
197
198    @Override
199    public void onDestroy() {
200        super.onDestroy();
201        if (mDialog != null) {
202            mDialog.dismiss();
203            mDialog = null;
204        }
205    }
206
207    private void onCreateIMM() {
208        InputMethodManager imm = (InputMethodManager) getSystemService(
209                Context.INPUT_METHOD_SERVICE);
210
211        // TODO: Change mInputMethodProperties to Map
212        mInputMethodProperties = imm.getInputMethodList();
213    }
214
215    private PreferenceScreen createPreferenceHierarchy() {
216        // Root
217        final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
218        final Context context = getActivity();
219
220        int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size());
221
222        for (int i = 0; i < N; ++i) {
223            final InputMethodInfo imi = mInputMethodProperties.get(i);
224            final int subtypeCount = imi.getSubtypeCount();
225            if (subtypeCount <= 1) continue;
226            final String imiId = imi.getId();
227            // Add this subtype to the list when no IME is specified or when the IME of this
228            // subtype is the specified IME.
229            if (!TextUtils.isEmpty(mInputMethodId) && !mInputMethodId.equals(imiId)) {
230                continue;
231            }
232            final PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context);
233            root.addPreference(keyboardSettingsCategory);
234            final PackageManager pm = getPackageManager();
235            final CharSequence label = imi.loadLabel(pm);
236
237            keyboardSettingsCategory.setTitle(label);
238            keyboardSettingsCategory.setKey(imiId);
239            // TODO: Use toggle Preference if images are ready.
240            final CheckBoxPreference autoCB = new CheckBoxPreference(context);
241            mSubtypeAutoSelectionCBMap.put(imiId, autoCB);
242            keyboardSettingsCategory.addPreference(autoCB);
243
244            final PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context);
245            activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
246            root.addPreference(activeInputMethodsCategory);
247
248            boolean isAutoSubtype = false;
249            CharSequence autoSubtypeLabel = null;
250            final ArrayList<Preference> subtypePreferences = new ArrayList<Preference>();
251            if (subtypeCount > 0) {
252                for (int j = 0; j < subtypeCount; ++j) {
253                    final InputMethodSubtype subtype = imi.getSubtypeAt(j);
254                    final CharSequence subtypeLabel = subtype.getDisplayName(context,
255                            imi.getPackageName(), imi.getServiceInfo().applicationInfo);
256                    if (subtype.overridesImplicitlyEnabledSubtype()) {
257                        if (!isAutoSubtype) {
258                            isAutoSubtype = true;
259                            autoSubtypeLabel = subtypeLabel;
260                        }
261                    } else {
262                        final CheckBoxPreference chkbxPref = new SubtypeCheckBoxPreference(
263                                context, subtype.getLocale(), mSystemLocale);
264                        chkbxPref.setKey(imiId + subtype.hashCode());
265                        chkbxPref.setTitle(subtypeLabel);
266                        subtypePreferences.add(chkbxPref);
267                    }
268                }
269                Collections.sort(subtypePreferences);
270                for (int j = 0; j < subtypePreferences.size(); ++j) {
271                    activeInputMethodsCategory.addPreference(subtypePreferences.get(j));
272                }
273                mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
274            }
275            if (isAutoSubtype) {
276                if (TextUtils.isEmpty(autoSubtypeLabel)) {
277                    Log.w(TAG, "Title for auto subtype is empty.");
278                    autoCB.setTitle("---");
279                } else {
280                    autoCB.setTitle(autoSubtypeLabel);
281                }
282            } else {
283                autoCB.setTitle(R.string.use_system_language_to_select_input_method_subtypes);
284            }
285        }
286        return root;
287    }
288
289    private boolean isNoSubtypesExplicitlySelected(String imiId) {
290        boolean allSubtypesOff = true;
291        final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
292        for (Preference subtypePref: subtypePrefs) {
293            if (subtypePref instanceof CheckBoxPreference
294                    && ((CheckBoxPreference)subtypePref).isChecked()) {
295                allSubtypesOff = false;
296                break;
297            }
298        }
299        return allSubtypesOff;
300    }
301
302    private void setSubtypeAutoSelectionEnabled(String imiId, boolean autoSelectionEnabled) {
303        CheckBoxPreference autoSelectionCB = mSubtypeAutoSelectionCBMap.get(imiId);
304        if (autoSelectionCB == null) return;
305        autoSelectionCB.setChecked(autoSelectionEnabled);
306        final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
307        for (Preference subtypePref: subtypePrefs) {
308            if (subtypePref instanceof CheckBoxPreference) {
309                // When autoSelectionEnabled is true, all subtype prefs need to be disabled with
310                // implicitly checked subtypes. In case of false, all subtype prefs need to be
311                // enabled.
312                subtypePref.setEnabled(!autoSelectionEnabled);
313                if (autoSelectionEnabled) {
314                    ((CheckBoxPreference)subtypePref).setChecked(false);
315                }
316            }
317        }
318        if (autoSelectionEnabled) {
319            InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
320                    mInputMethodProperties, mHaveHardKeyboard);
321            setCheckedImplicitlyEnabledSubtypes(imiId);
322        }
323    }
324
325    private void setCheckedImplicitlyEnabledSubtypes(String targetImiId) {
326        updateImplicitlyEnabledSubtypes(targetImiId, true);
327    }
328
329    private void clearImplicitlyEnabledSubtypes(String targetImiId) {
330        updateImplicitlyEnabledSubtypes(targetImiId, false);
331    }
332
333    private void updateImplicitlyEnabledSubtypes(String targetImiId, boolean check) {
334        // When targetImiId is null, apply to all subtypes of all IMEs
335        for (InputMethodInfo imi: mInputMethodProperties) {
336            String imiId = imi.getId();
337            if (targetImiId != null && !targetImiId.equals(imiId)) continue;
338            final CheckBoxPreference autoCB = mSubtypeAutoSelectionCBMap.get(imiId);
339            // No need to update implicitly enabled subtypes when the user has unchecked the
340            // "subtype auto selection".
341            if (autoCB == null || !autoCB.isChecked()) continue;
342            final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
343            final List<InputMethodSubtype> implicitlyEnabledSubtypes =
344                    mImm.getEnabledInputMethodSubtypeList(imi, true);
345            if (subtypePrefs == null || implicitlyEnabledSubtypes == null) continue;
346            for (Preference subtypePref: subtypePrefs) {
347                if (subtypePref instanceof CheckBoxPreference) {
348                    CheckBoxPreference cb = (CheckBoxPreference)subtypePref;
349                    cb.setChecked(false);
350                    if (check) {
351                        for (InputMethodSubtype subtype: implicitlyEnabledSubtypes) {
352                            String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
353                            if (cb.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
354                                cb.setChecked(true);
355                                break;
356                            }
357                        }
358                    }
359                }
360            }
361        }
362    }
363
364    private void updateAutoSelectionCB() {
365        for (String imiId: mInputMethodAndSubtypePrefsMap.keySet()) {
366            setSubtypeAutoSelectionEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
367        }
368        setCheckedImplicitlyEnabledSubtypes(null);
369    }
370
371    private static class SubtypeCheckBoxPreference extends CheckBoxPreference {
372        private final boolean mIsSystemLocale;
373        private final boolean mIsSystemLanguage;
374
375        public SubtypeCheckBoxPreference(
376                Context context, String subtypeLocale, String systemLocale) {
377            super(context);
378            if (TextUtils.isEmpty(subtypeLocale)) {
379                mIsSystemLocale = false;
380                mIsSystemLanguage = false;
381            } else {
382                mIsSystemLocale = subtypeLocale.equals(systemLocale);
383                mIsSystemLanguage = mIsSystemLocale
384                        || subtypeLocale.startsWith(systemLocale.substring(0, 2));
385            }
386        }
387
388        @Override
389        public int compareTo(Preference p) {
390            if (p instanceof SubtypeCheckBoxPreference) {
391                final SubtypeCheckBoxPreference pref = ((SubtypeCheckBoxPreference)p);
392                final CharSequence t0 = getTitle();
393                final CharSequence t1 = pref.getTitle();
394                if (TextUtils.equals(t0, t1)) {
395                    return 0;
396                }
397                if (mIsSystemLocale) {
398                    return -1;
399                }
400                if (pref.mIsSystemLocale) {
401                    return 1;
402                }
403                if (mIsSystemLanguage) {
404                    return -1;
405                }
406                if (pref.mIsSystemLanguage) {
407                    return 1;
408                }
409                if (TextUtils.isEmpty(t0)) {
410                    return 1;
411                }
412                if (TextUtils.isEmpty(t1)) {
413                    return -1;
414                }
415                return t0.toString().compareTo(t1.toString());
416            } else {
417                Log.w(TAG, "Illegal preference type.");
418                return -1;
419            }
420        }
421    }
422}
423