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