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