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.SettingsPreferenceFragment;
     20 
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.pm.ApplicationInfo;
     24 import android.content.pm.PackageManager;
     25 import android.preference.CheckBoxPreference;
     26 import android.preference.Preference;
     27 import android.preference.PreferenceScreen;
     28 import android.provider.Settings;
     29 import android.provider.Settings.SettingNotFoundException;
     30 import android.text.TextUtils;
     31 import android.util.Log;
     32 import android.view.inputmethod.InputMethodInfo;
     33 import android.view.inputmethod.InputMethodManager;
     34 import android.view.inputmethod.InputMethodSubtype;
     35 
     36 import java.util.HashMap;
     37 import java.util.HashSet;
     38 import java.util.List;
     39 import java.util.Map;
     40 
     41 public class InputMethodAndSubtypeUtil {
     42 
     43     private static final boolean DEBUG = false;
     44     static final String TAG = "InputMethdAndSubtypeUtil";
     45 
     46     private static final char INPUT_METHOD_SEPARATER = ':';
     47     private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
     48     private static final int NOT_A_SUBTYPE_ID = -1;
     49 
     50     private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
     51             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
     52 
     53     private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
     54             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
     55 
     56     private static void buildEnabledInputMethodsString(
     57             StringBuilder builder, String imi, HashSet<String> subtypes) {
     58         builder.append(imi);
     59         // Inputmethod and subtypes are saved in the settings as follows:
     60         // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
     61         for (String subtypeId: subtypes) {
     62             builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
     63         }
     64     }
     65 
     66     public static void buildInputMethodsAndSubtypesString(
     67             StringBuilder builder, HashMap<String, HashSet<String>> imsList) {
     68         boolean needsAppendSeparator = false;
     69         for (String imi: imsList.keySet()) {
     70             if (needsAppendSeparator) {
     71                 builder.append(INPUT_METHOD_SEPARATER);
     72             } else {
     73                 needsAppendSeparator = true;
     74             }
     75             buildEnabledInputMethodsString(builder, imi, imsList.get(imi));
     76         }
     77     }
     78 
     79     public static void buildDisabledSystemInputMethods(
     80             StringBuilder builder, HashSet<String> imes) {
     81         boolean needsAppendSeparator = false;
     82         for (String ime: imes) {
     83             if (needsAppendSeparator) {
     84                 builder.append(INPUT_METHOD_SEPARATER);
     85             } else {
     86                 needsAppendSeparator = true;
     87             }
     88             builder.append(ime);
     89         }
     90     }
     91 
     92     private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
     93         try {
     94             return Settings.Secure.getInt(resolver,
     95                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
     96         } catch (SettingNotFoundException e) {
     97             return NOT_A_SUBTYPE_ID;
     98         }
     99     }
    100 
    101     private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
    102         return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
    103     }
    104 
    105     private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
    106         Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
    107     }
    108 
    109     // Needs to modify InputMethodManageService if you want to change the format of saved string.
    110     private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
    111             ContentResolver resolver) {
    112         final String enabledInputMethodsStr = Settings.Secure.getString(
    113                 resolver, Settings.Secure.ENABLED_INPUT_METHODS);
    114         HashMap<String, HashSet<String>> imsList
    115                 = new HashMap<String, HashSet<String>>();
    116         if (DEBUG) {
    117             Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
    118         }
    119 
    120         if (TextUtils.isEmpty(enabledInputMethodsStr)) {
    121             return imsList;
    122         }
    123         sStringInputMethodSplitter.setString(enabledInputMethodsStr);
    124         while (sStringInputMethodSplitter.hasNext()) {
    125             String nextImsStr = sStringInputMethodSplitter.next();
    126             sStringInputMethodSubtypeSplitter.setString(nextImsStr);
    127             if (sStringInputMethodSubtypeSplitter.hasNext()) {
    128                 HashSet<String> subtypeHashes = new HashSet<String>();
    129                 // The first element is ime id.
    130                 String imeId = sStringInputMethodSubtypeSplitter.next();
    131                 while (sStringInputMethodSubtypeSplitter.hasNext()) {
    132                     subtypeHashes.add(sStringInputMethodSubtypeSplitter.next());
    133                 }
    134                 imsList.put(imeId, subtypeHashes);
    135             }
    136         }
    137         return imsList;
    138     }
    139 
    140     private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
    141         HashSet<String> set = new HashSet<String>();
    142         String disabledIMEsStr = Settings.Secure.getString(
    143                 resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
    144         if (TextUtils.isEmpty(disabledIMEsStr)) {
    145             return set;
    146         }
    147         sStringInputMethodSplitter.setString(disabledIMEsStr);
    148         while(sStringInputMethodSplitter.hasNext()) {
    149             set.add(sStringInputMethodSplitter.next());
    150         }
    151         return set;
    152     }
    153 
    154     public static CharSequence getCurrentInputMethodName(Context context, ContentResolver resolver,
    155             InputMethodManager imm, List<InputMethodInfo> imis, PackageManager pm) {
    156         if (resolver == null || imis == null) return null;
    157         final String currentInputMethodId = Settings.Secure.getString(resolver,
    158                 Settings.Secure.DEFAULT_INPUT_METHOD);
    159         if (TextUtils.isEmpty(currentInputMethodId)) return null;
    160         for (InputMethodInfo imi : imis) {
    161             if (currentInputMethodId.equals(imi.getId())) {
    162                 final InputMethodSubtype subtype = imm.getCurrentInputMethodSubtype();
    163                 final CharSequence imiLabel = imi.loadLabel(pm);
    164                 final CharSequence summary = subtype != null
    165                         ? TextUtils.concat(subtype.getDisplayName(context,
    166                                     imi.getPackageName(), imi.getServiceInfo().applicationInfo),
    167                                             (TextUtils.isEmpty(imiLabel) ?
    168                                                     "" : " - " + imiLabel))
    169                         : imiLabel;
    170                 return summary;
    171             }
    172         }
    173         return null;
    174     }
    175 
    176     public static void saveInputMethodSubtypeList(SettingsPreferenceFragment context,
    177             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
    178             boolean hasHardKeyboard) {
    179         String currentInputMethodId = Settings.Secure.getString(resolver,
    180                 Settings.Secure.DEFAULT_INPUT_METHOD);
    181         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
    182         HashMap<String, HashSet<String>> enabledIMEAndSubtypesMap =
    183                 getEnabledInputMethodsAndSubtypeList(resolver);
    184         HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
    185 
    186         final boolean onlyOneIME = inputMethodInfos.size() == 1;
    187         boolean needsToResetSelectedSubtype = false;
    188         for (InputMethodInfo imi : inputMethodInfos) {
    189             final String imiId = imi.getId();
    190             Preference pref = context.findPreference(imiId);
    191             if (pref == null) continue;
    192             // In the Configure input method screen or in the subtype enabler screen.
    193             // pref is instance of CheckBoxPreference in the Configure input method screen.
    194             final boolean isImeChecked = (pref instanceof CheckBoxPreference) ?
    195                     ((CheckBoxPreference) pref).isChecked()
    196                             : enabledIMEAndSubtypesMap.containsKey(imiId);
    197             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
    198             final boolean auxIme = isAuxiliaryIme(imi);
    199             final boolean systemIme = isSystemIme(imi);
    200             if (((onlyOneIME || (systemIme && !auxIme)) && !hasHardKeyboard) || isImeChecked) {
    201                 if (!enabledIMEAndSubtypesMap.containsKey(imiId)) {
    202                     // imiId has just been enabled
    203                     enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>());
    204                 }
    205                 HashSet<String> subtypesSet = enabledIMEAndSubtypesMap.get(imiId);
    206 
    207                 boolean subtypePrefFound = false;
    208                 final int subtypeCount = imi.getSubtypeCount();
    209                 for (int i = 0; i < subtypeCount; ++i) {
    210                     InputMethodSubtype subtype = imi.getSubtypeAt(i);
    211                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
    212                     CheckBoxPreference subtypePref = (CheckBoxPreference) context.findPreference(
    213                             imiId + subtypeHashCodeStr);
    214                     // In the Configure input method screen which does not have subtype preferences.
    215                     if (subtypePref == null) continue;
    216                     if (!subtypePrefFound) {
    217                         // Once subtype checkbox is found, subtypeSet needs to be cleared.
    218                         // Because of system change, hashCode value could have been changed.
    219                         subtypesSet.clear();
    220                         // If selected subtype preference is disabled, needs to reset.
    221                         needsToResetSelectedSubtype = true;
    222                         subtypePrefFound = true;
    223                     }
    224                     if (subtypePref.isChecked()) {
    225                         subtypesSet.add(subtypeHashCodeStr);
    226                         if (isCurrentInputMethod) {
    227                             if (selectedInputMethodSubtype == subtype.hashCode()) {
    228                                 // Selected subtype is still enabled, there is no need to reset
    229                                 // selected subtype.
    230                                 needsToResetSelectedSubtype = false;
    231                             }
    232                         }
    233                     } else {
    234                         subtypesSet.remove(subtypeHashCodeStr);
    235                     }
    236                 }
    237             } else {
    238                 enabledIMEAndSubtypesMap.remove(imiId);
    239                 if (isCurrentInputMethod) {
    240                     // We are processing the current input method, but found that it's not enabled.
    241                     // This means that the current input method has been uninstalled.
    242                     // If currentInputMethod is already uninstalled, InputMethodManagerService will
    243                     // find the applicable IME from the history and the system locale.
    244                     if (DEBUG) {
    245                         Log.d(TAG, "Current IME was uninstalled or disabled.");
    246                     }
    247                     currentInputMethodId = null;
    248                 }
    249             }
    250             // If it's a disabled system ime, add it to the disabled list so that it
    251             // doesn't get enabled automatically on any changes to the package list
    252             if (systemIme && hasHardKeyboard) {
    253                 if (disabledSystemIMEs.contains(imiId)) {
    254                     if (isImeChecked) {
    255                         disabledSystemIMEs.remove(imiId);
    256                     }
    257                 } else {
    258                     if (!isImeChecked) {
    259                         disabledSystemIMEs.add(imiId);
    260                     }
    261                 }
    262             }
    263         }
    264 
    265         StringBuilder builder = new StringBuilder();
    266         buildInputMethodsAndSubtypesString(builder, enabledIMEAndSubtypesMap);
    267         StringBuilder disabledSysImesBuilder = new StringBuilder();
    268         buildDisabledSystemInputMethods(disabledSysImesBuilder, disabledSystemIMEs);
    269         if (DEBUG) {
    270             Log.d(TAG, "--- Save enabled inputmethod settings. :" + builder.toString());
    271             Log.d(TAG, "--- Save disable system inputmethod settings. :"
    272                     + disabledSysImesBuilder.toString());
    273             Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
    274             Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
    275             Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
    276         }
    277 
    278         // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
    279         // selected. And if the selected subtype of the current input method was disabled,
    280         // We should reset the selected input method's subtype.
    281         if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
    282             if (DEBUG) {
    283                 Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
    284             }
    285             putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
    286         }
    287 
    288         Settings.Secure.putString(resolver,
    289                 Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
    290         if (disabledSysImesBuilder.length() > 0) {
    291             Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
    292                     disabledSysImesBuilder.toString());
    293         }
    294         // If the current input method is unset, InputMethodManagerService will find the applicable
    295         // IME from the history and the system locale.
    296         Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
    297                 currentInputMethodId != null ? currentInputMethodId : "");
    298     }
    299 
    300     public static void loadInputMethodSubtypeList(
    301             SettingsPreferenceFragment context, ContentResolver resolver,
    302             List<InputMethodInfo> inputMethodInfos,
    303             final Map<String, List<Preference>> inputMethodPrefsMap) {
    304         HashMap<String, HashSet<String>> enabledSubtypes =
    305                 getEnabledInputMethodsAndSubtypeList(resolver);
    306 
    307         for (InputMethodInfo imi : inputMethodInfos) {
    308             final String imiId = imi.getId();
    309             Preference pref = context.findPreference(imiId);
    310             if (pref != null && pref instanceof CheckBoxPreference) {
    311                 CheckBoxPreference checkBoxPreference = (CheckBoxPreference) pref;
    312                 boolean isEnabled = enabledSubtypes.containsKey(imiId);
    313                 checkBoxPreference.setChecked(isEnabled);
    314                 if (inputMethodPrefsMap != null) {
    315                     for (Preference childPref: inputMethodPrefsMap.get(imiId)) {
    316                         childPref.setEnabled(isEnabled);
    317                     }
    318                 }
    319                 setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
    320             }
    321         }
    322         updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
    323     }
    324 
    325     public static void setSubtypesPreferenceEnabled(SettingsPreferenceFragment context,
    326             List<InputMethodInfo> inputMethodProperties, String id, boolean enabled) {
    327         PreferenceScreen preferenceScreen = context.getPreferenceScreen();
    328         for (InputMethodInfo imi : inputMethodProperties) {
    329             if (id.equals(imi.getId())) {
    330                 final int subtypeCount = imi.getSubtypeCount();
    331                 for (int i = 0; i < subtypeCount; ++i) {
    332                     InputMethodSubtype subtype = imi.getSubtypeAt(i);
    333                     CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
    334                             id + subtype.hashCode());
    335                     if (pref != null) {
    336                         pref.setEnabled(enabled);
    337                     }
    338                 }
    339             }
    340         }
    341     }
    342 
    343     public static void updateSubtypesPreferenceChecked(SettingsPreferenceFragment context,
    344             List<InputMethodInfo> inputMethodProperties,
    345             HashMap<String, HashSet<String>> enabledSubtypes) {
    346         PreferenceScreen preferenceScreen = context.getPreferenceScreen();
    347         for (InputMethodInfo imi : inputMethodProperties) {
    348             String id = imi.getId();
    349             if (!enabledSubtypes.containsKey(id)) break;
    350             final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
    351             final int subtypeCount = imi.getSubtypeCount();
    352             for (int i = 0; i < subtypeCount; ++i) {
    353                 InputMethodSubtype subtype = imi.getSubtypeAt(i);
    354                 String hashCode = String.valueOf(subtype.hashCode());
    355                 if (DEBUG) {
    356                     Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
    357                             + enabledSubtypesSet.contains(hashCode));
    358                 }
    359                 CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
    360                         id + hashCode);
    361                 if (pref != null) {
    362                     pref.setChecked(enabledSubtypesSet.contains(hashCode));
    363                 }
    364             }
    365         }
    366     }
    367 
    368     public static boolean isSystemIme(InputMethodInfo property) {
    369         return (property.getServiceInfo().applicationInfo.flags
    370                 & ApplicationInfo.FLAG_SYSTEM) != 0;
    371     }
    372 
    373     public static boolean isAuxiliaryIme(InputMethodInfo imi) {
    374         return imi.isAuxiliaryIme();
    375     }
    376 }
    377