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