Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2013 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.internal.inputmethod;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.content.pm.ApplicationInfo;
     22 import android.content.pm.PackageManager;
     23 import android.content.pm.PackageManager.NameNotFoundException;
     24 import android.content.res.Resources;
     25 import android.provider.Settings;
     26 import android.provider.Settings.SettingNotFoundException;
     27 import android.text.TextUtils;
     28 import android.util.Pair;
     29 import android.util.Slog;
     30 import android.view.inputmethod.InputMethodInfo;
     31 import android.view.inputmethod.InputMethodSubtype;
     32 import android.view.textservice.SpellCheckerInfo;
     33 import android.view.textservice.TextServicesManager;
     34 
     35 import java.util.ArrayList;
     36 import java.util.HashMap;
     37 import java.util.List;
     38 import java.util.Locale;
     39 
     40 /**
     41  * InputMethodManagerUtils contains some static methods that provides IME informations.
     42  * This methods are supposed to be used in both the framework and the Settings application.
     43  */
     44 public class InputMethodUtils {
     45     public static final boolean DEBUG = false;
     46     public static final int NOT_A_SUBTYPE_ID = -1;
     47     public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
     48     public static final String SUBTYPE_MODE_VOICE = "voice";
     49     private static final String TAG = "InputMethodUtils";
     50     private static final Locale ENGLISH_LOCALE = new Locale("en");
     51     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
     52     private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
     53             "EnabledWhenDefaultIsNotAsciiCapable";
     54     private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
     55 
     56     private InputMethodUtils() {
     57         // This utility class is not publicly instantiable.
     58     }
     59 
     60     public static boolean isSystemIme(InputMethodInfo inputMethod) {
     61         return (inputMethod.getServiceInfo().applicationInfo.flags
     62                 & ApplicationInfo.FLAG_SYSTEM) != 0;
     63     }
     64 
     65     public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
     66         if (!isSystemIme(imi)) {
     67             return false;
     68         }
     69         return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
     70     }
     71 
     72     private static boolean isSystemAuxilialyImeThatHashAutomaticSubtype(InputMethodInfo imi) {
     73         if (!isSystemIme(imi)) {
     74             return false;
     75         }
     76         if (!imi.isAuxiliaryIme()) {
     77             return false;
     78         }
     79         final int subtypeCount = imi.getSubtypeCount();
     80         for (int i = 0; i < subtypeCount; ++i) {
     81             final InputMethodSubtype s = imi.getSubtypeAt(i);
     82             if (s.overridesImplicitlyEnabledSubtype()) {
     83                 return true;
     84             }
     85         }
     86         return false;
     87     }
     88 
     89     public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
     90             Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) {
     91         final ArrayList<InputMethodInfo> retval = new ArrayList<InputMethodInfo>();
     92         boolean auxilialyImeAdded = false;
     93         for (int i = 0; i < imis.size(); ++i) {
     94             final InputMethodInfo imi = imis.get(i);
     95             if (isDefaultEnabledIme(isSystemReady, imi, context)) {
     96                 retval.add(imi);
     97                 if (imi.isAuxiliaryIme()) {
     98                     auxilialyImeAdded = true;
     99                 }
    100             }
    101         }
    102         if (auxilialyImeAdded) {
    103             return retval;
    104         }
    105         for (int i = 0; i < imis.size(); ++i) {
    106             final InputMethodInfo imi = imis.get(i);
    107             if (isSystemAuxilialyImeThatHashAutomaticSubtype(imi)) {
    108                 retval.add(imi);
    109             }
    110         }
    111         return retval;
    112     }
    113 
    114     // TODO: Rename isSystemDefaultImeThatHasCurrentLanguageSubtype
    115     public static boolean isValidSystemDefaultIme(
    116             boolean isSystemReady, InputMethodInfo imi, Context context) {
    117         if (!isSystemReady) {
    118             return false;
    119         }
    120         if (!isSystemIme(imi)) {
    121             return false;
    122         }
    123         if (imi.getIsDefaultResourceId() != 0) {
    124             try {
    125                 if (imi.isDefault(context) && containsSubtypeOf(
    126                         imi, context.getResources().getConfiguration().locale.getLanguage(),
    127                         null /* mode */)) {
    128                     return true;
    129                 }
    130             } catch (Resources.NotFoundException ex) {
    131             }
    132         }
    133         if (imi.getSubtypeCount() == 0) {
    134             Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
    135         }
    136         return false;
    137     }
    138 
    139     public static boolean isDefaultEnabledIme(
    140             boolean isSystemReady, InputMethodInfo imi, Context context) {
    141         return isValidSystemDefaultIme(isSystemReady, imi, context)
    142                 || isSystemImeThatHasEnglishKeyboardSubtype(imi);
    143     }
    144 
    145     private static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
    146         final int N = imi.getSubtypeCount();
    147         for (int i = 0; i < N; ++i) {
    148             if (!imi.getSubtypeAt(i).getLocale().startsWith(language)) {
    149                 continue;
    150             }
    151             if(!TextUtils.isEmpty(mode) && !imi.getSubtypeAt(i).getMode().equalsIgnoreCase(mode)) {
    152                 continue;
    153             }
    154             return true;
    155         }
    156         return false;
    157     }
    158 
    159     public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
    160         ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
    161         final int subtypeCount = imi.getSubtypeCount();
    162         for (int i = 0; i < subtypeCount; ++i) {
    163             subtypes.add(imi.getSubtypeAt(i));
    164         }
    165         return subtypes;
    166     }
    167 
    168     public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
    169             InputMethodInfo imi, String mode) {
    170         ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
    171         final int subtypeCount = imi.getSubtypeCount();
    172         for (int i = 0; i < subtypeCount; ++i) {
    173             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    174             if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
    175                 subtypes.add(subtype);
    176             }
    177         }
    178         return subtypes;
    179     }
    180 
    181     public static InputMethodInfo getMostApplicableDefaultIME(
    182             List<InputMethodInfo> enabledImes) {
    183         if (enabledImes != null && enabledImes.size() > 0) {
    184             // We'd prefer to fall back on a system IME, since that is safer.
    185             int i = enabledImes.size();
    186             int firstFoundSystemIme = -1;
    187             while (i > 0) {
    188                 i--;
    189                 final InputMethodInfo imi = enabledImes.get(i);
    190                 if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
    191                         && !imi.isAuxiliaryIme()) {
    192                     return imi;
    193                 }
    194                 if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)
    195                         && !imi.isAuxiliaryIme()) {
    196                     firstFoundSystemIme = i;
    197                 }
    198             }
    199             return enabledImes.get(Math.max(firstFoundSystemIme, 0));
    200         }
    201         return null;
    202     }
    203 
    204     public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
    205         return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
    206     }
    207 
    208     public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
    209         if (imi != null) {
    210             final int subtypeCount = imi.getSubtypeCount();
    211             for (int i = 0; i < subtypeCount; ++i) {
    212                 InputMethodSubtype ims = imi.getSubtypeAt(i);
    213                 if (subtypeHashCode == ims.hashCode()) {
    214                     return i;
    215                 }
    216             }
    217         }
    218         return NOT_A_SUBTYPE_ID;
    219     }
    220 
    221     private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
    222             Resources res, InputMethodInfo imi) {
    223         final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
    224         final String systemLocale = res.getConfiguration().locale.toString();
    225         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
    226         final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
    227                 new HashMap<String, InputMethodSubtype>();
    228         final int N = subtypes.size();
    229         for (int i = 0; i < N; ++i) {
    230             // scan overriding implicitly enabled subtypes.
    231             InputMethodSubtype subtype = subtypes.get(i);
    232             if (subtype.overridesImplicitlyEnabledSubtype()) {
    233                 final String mode = subtype.getMode();
    234                 if (!applicableModeAndSubtypesMap.containsKey(mode)) {
    235                     applicableModeAndSubtypesMap.put(mode, subtype);
    236                 }
    237             }
    238         }
    239         if (applicableModeAndSubtypesMap.size() > 0) {
    240             return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
    241         }
    242         for (int i = 0; i < N; ++i) {
    243             final InputMethodSubtype subtype = subtypes.get(i);
    244             final String locale = subtype.getLocale();
    245             final String mode = subtype.getMode();
    246             // When system locale starts with subtype's locale, that subtype will be applicable
    247             // for system locale
    248             // For instance, it's clearly applicable for cases like system locale = en_US and
    249             // subtype = en, but it is not necessarily considered applicable for cases like system
    250             // locale = en and subtype = en_US.
    251             // We just call systemLocale.startsWith(locale) in this function because there is no
    252             // need to find applicable subtypes aggressively unlike
    253             // findLastResortApplicableSubtypeLocked.
    254             if (systemLocale.startsWith(locale)) {
    255                 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
    256                 // If more applicable subtypes are contained, skip.
    257                 if (applicableSubtype != null) {
    258                     if (systemLocale.equals(applicableSubtype.getLocale())) continue;
    259                     if (!systemLocale.equals(locale)) continue;
    260                 }
    261                 applicableModeAndSubtypesMap.put(mode, subtype);
    262             }
    263         }
    264         final InputMethodSubtype keyboardSubtype
    265                 = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
    266         final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
    267                 applicableModeAndSubtypesMap.values());
    268         if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
    269             for (int i = 0; i < N; ++i) {
    270                 final InputMethodSubtype subtype = subtypes.get(i);
    271                 final String mode = subtype.getMode();
    272                 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
    273                         TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
    274                     applicableSubtypes.add(subtype);
    275                 }
    276             }
    277         }
    278         if (keyboardSubtype == null) {
    279             InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
    280                     res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
    281             if (lastResortKeyboardSubtype != null) {
    282                 applicableSubtypes.add(lastResortKeyboardSubtype);
    283             }
    284         }
    285         return applicableSubtypes;
    286     }
    287 
    288     private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
    289             Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes,
    290             boolean allowsImplicitlySelectedSubtypes) {
    291         if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
    292             enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    293                     context.getResources(), imi);
    294         }
    295         return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
    296     }
    297 
    298     /**
    299      * If there are no selected subtypes, tries finding the most applicable one according to the
    300      * given locale.
    301      * @param subtypes this function will search the most applicable subtype in subtypes
    302      * @param mode subtypes will be filtered by mode
    303      * @param locale subtypes will be filtered by locale
    304      * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
    305      * it will return the first subtype matched with mode
    306      * @return the most applicable subtypeId
    307      */
    308     public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
    309             Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
    310             boolean canIgnoreLocaleAsLastResort) {
    311         if (subtypes == null || subtypes.size() == 0) {
    312             return null;
    313         }
    314         if (TextUtils.isEmpty(locale)) {
    315             locale = res.getConfiguration().locale.toString();
    316         }
    317         final String language = locale.substring(0, 2);
    318         boolean partialMatchFound = false;
    319         InputMethodSubtype applicableSubtype = null;
    320         InputMethodSubtype firstMatchedModeSubtype = null;
    321         final int N = subtypes.size();
    322         for (int i = 0; i < N; ++i) {
    323             InputMethodSubtype subtype = subtypes.get(i);
    324             final String subtypeLocale = subtype.getLocale();
    325             // An applicable subtype should match "mode". If mode is null, mode will be ignored,
    326             // and all subtypes with all modes can be candidates.
    327             if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
    328                 if (firstMatchedModeSubtype == null) {
    329                     firstMatchedModeSubtype = subtype;
    330                 }
    331                 if (locale.equals(subtypeLocale)) {
    332                     // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
    333                     applicableSubtype = subtype;
    334                     break;
    335                 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
    336                     // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
    337                     applicableSubtype = subtype;
    338                     partialMatchFound = true;
    339                 }
    340             }
    341         }
    342 
    343         if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
    344             return firstMatchedModeSubtype;
    345         }
    346 
    347         // The first subtype applicable to the system locale will be defined as the most applicable
    348         // subtype.
    349         if (DEBUG) {
    350             if (applicableSubtype != null) {
    351                 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
    352                         + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
    353             }
    354         }
    355         return applicableSubtype;
    356     }
    357 
    358     public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
    359         if (subtype == null) return true;
    360         return !subtype.isAuxiliary();
    361     }
    362 
    363     public static void setNonSelectedSystemImesDisabledUntilUsed(
    364             PackageManager packageManager, List<InputMethodInfo> enabledImis) {
    365         if (DEBUG) {
    366             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
    367         }
    368         final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
    369                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
    370         if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
    371             return;
    372         }
    373         // Only the current spell checker should be treated as an enabled one.
    374         final SpellCheckerInfo currentSpellChecker =
    375                 TextServicesManager.getInstance().getCurrentSpellChecker();
    376         for (final String packageName : systemImesDisabledUntilUsed) {
    377             if (DEBUG) {
    378                 Slog.d(TAG, "check " + packageName);
    379             }
    380             boolean enabledIme = false;
    381             for (int j = 0; j < enabledImis.size(); ++j) {
    382                 final InputMethodInfo imi = enabledImis.get(j);
    383                 if (packageName.equals(imi.getPackageName())) {
    384                     enabledIme = true;
    385                     break;
    386                 }
    387             }
    388             if (enabledIme) {
    389                 // enabled ime. skip
    390                 continue;
    391             }
    392             if (currentSpellChecker != null
    393                     && packageName.equals(currentSpellChecker.getPackageName())) {
    394                 // enabled spell checker. skip
    395                 if (DEBUG) {
    396                     Slog.d(TAG, packageName + " is the current spell checker. skip");
    397                 }
    398                 continue;
    399             }
    400             ApplicationInfo ai = null;
    401             try {
    402                 ai = packageManager.getApplicationInfo(packageName,
    403                         PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
    404             } catch (NameNotFoundException e) {
    405                 Slog.w(TAG, "NameNotFoundException: " + packageName, e);
    406             }
    407             if (ai == null) {
    408                 // No app found for packageName
    409                 continue;
    410             }
    411             final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    412             if (!isSystemPackage) {
    413                 continue;
    414             }
    415             setDisabledUntilUsed(packageManager, packageName);
    416         }
    417     }
    418 
    419     private static void setDisabledUntilUsed(PackageManager packageManager, String packageName) {
    420         final int state = packageManager.getApplicationEnabledSetting(packageName);
    421         if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
    422                 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
    423             if (DEBUG) {
    424                 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
    425             }
    426             packageManager.setApplicationEnabledSetting(packageName,
    427                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0);
    428         } else {
    429             if (DEBUG) {
    430                 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
    431             }
    432         }
    433     }
    434 
    435     /**
    436      * Utility class for putting and getting settings for InputMethod
    437      * TODO: Move all putters and getters of settings to this class.
    438      */
    439     public static class InputMethodSettings {
    440         // The string for enabled input method is saved as follows:
    441         // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
    442         private static final char INPUT_METHOD_SEPARATER = ':';
    443         private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
    444         private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
    445                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
    446 
    447         private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
    448                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
    449 
    450         private final Resources mRes;
    451         private final ContentResolver mResolver;
    452         private final HashMap<String, InputMethodInfo> mMethodMap;
    453         private final ArrayList<InputMethodInfo> mMethodList;
    454 
    455         private String mEnabledInputMethodsStrCache;
    456         private int mCurrentUserId;
    457 
    458         private static void buildEnabledInputMethodsSettingString(
    459                 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
    460             String id = pair.first;
    461             ArrayList<String> subtypes = pair.second;
    462             builder.append(id);
    463             // Inputmethod and subtypes are saved in the settings as follows:
    464             // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
    465             for (String subtypeId: subtypes) {
    466                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
    467             }
    468         }
    469 
    470         public InputMethodSettings(
    471                 Resources res, ContentResolver resolver,
    472                 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
    473                 int userId) {
    474             setCurrentUserId(userId);
    475             mRes = res;
    476             mResolver = resolver;
    477             mMethodMap = methodMap;
    478             mMethodList = methodList;
    479         }
    480 
    481         public void setCurrentUserId(int userId) {
    482             if (DEBUG) {
    483                 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId);
    484             }
    485             // IMMS settings are kept per user, so keep track of current user
    486             mCurrentUserId = userId;
    487         }
    488 
    489         public List<InputMethodInfo> getEnabledInputMethodListLocked() {
    490             return createEnabledInputMethodListLocked(
    491                     getEnabledInputMethodsAndSubtypeListLocked());
    492         }
    493 
    494         public List<Pair<InputMethodInfo, ArrayList<String>>>
    495                 getEnabledInputMethodAndSubtypeHashCodeListLocked() {
    496             return createEnabledInputMethodAndSubtypeHashCodeListLocked(
    497                     getEnabledInputMethodsAndSubtypeListLocked());
    498         }
    499 
    500         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
    501                 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
    502             List<InputMethodSubtype> enabledSubtypes =
    503                     getEnabledInputMethodSubtypeListLocked(imi);
    504             if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
    505                 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    506                         context.getResources(), imi);
    507             }
    508             return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
    509         }
    510 
    511         private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
    512                 InputMethodInfo imi) {
    513             List<Pair<String, ArrayList<String>>> imsList =
    514                     getEnabledInputMethodsAndSubtypeListLocked();
    515             ArrayList<InputMethodSubtype> enabledSubtypes =
    516                     new ArrayList<InputMethodSubtype>();
    517             if (imi != null) {
    518                 for (Pair<String, ArrayList<String>> imsPair : imsList) {
    519                     InputMethodInfo info = mMethodMap.get(imsPair.first);
    520                     if (info != null && info.getId().equals(imi.getId())) {
    521                         final int subtypeCount = info.getSubtypeCount();
    522                         for (int i = 0; i < subtypeCount; ++i) {
    523                             InputMethodSubtype ims = info.getSubtypeAt(i);
    524                             for (String s: imsPair.second) {
    525                                 if (String.valueOf(ims.hashCode()).equals(s)) {
    526                                     enabledSubtypes.add(ims);
    527                                 }
    528                             }
    529                         }
    530                         break;
    531                     }
    532                 }
    533             }
    534             return enabledSubtypes;
    535         }
    536 
    537         // At the initial boot, the settings for input methods are not set,
    538         // so we need to enable IME in that case.
    539         public void enableAllIMEsIfThereIsNoEnabledIME() {
    540             if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
    541                 StringBuilder sb = new StringBuilder();
    542                 final int N = mMethodList.size();
    543                 for (int i = 0; i < N; i++) {
    544                     InputMethodInfo imi = mMethodList.get(i);
    545                     Slog.i(TAG, "Adding: " + imi.getId());
    546                     if (i > 0) sb.append(':');
    547                     sb.append(imi.getId());
    548                 }
    549                 putEnabledInputMethodsStr(sb.toString());
    550             }
    551         }
    552 
    553         public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
    554             ArrayList<Pair<String, ArrayList<String>>> imsList
    555                     = new ArrayList<Pair<String, ArrayList<String>>>();
    556             final String enabledInputMethodsStr = getEnabledInputMethodsStr();
    557             if (TextUtils.isEmpty(enabledInputMethodsStr)) {
    558                 return imsList;
    559             }
    560             mInputMethodSplitter.setString(enabledInputMethodsStr);
    561             while (mInputMethodSplitter.hasNext()) {
    562                 String nextImsStr = mInputMethodSplitter.next();
    563                 mSubtypeSplitter.setString(nextImsStr);
    564                 if (mSubtypeSplitter.hasNext()) {
    565                     ArrayList<String> subtypeHashes = new ArrayList<String>();
    566                     // The first element is ime id.
    567                     String imeId = mSubtypeSplitter.next();
    568                     while (mSubtypeSplitter.hasNext()) {
    569                         subtypeHashes.add(mSubtypeSplitter.next());
    570                     }
    571                     imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
    572                 }
    573             }
    574             return imsList;
    575         }
    576 
    577         public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
    578             if (reloadInputMethodStr) {
    579                 getEnabledInputMethodsStr();
    580             }
    581             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
    582                 // Add in the newly enabled input method.
    583                 putEnabledInputMethodsStr(id);
    584             } else {
    585                 putEnabledInputMethodsStr(
    586                         mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
    587             }
    588         }
    589 
    590         /**
    591          * Build and put a string of EnabledInputMethods with removing specified Id.
    592          * @return the specified id was removed or not.
    593          */
    594         public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
    595                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
    596             boolean isRemoved = false;
    597             boolean needsAppendSeparator = false;
    598             for (Pair<String, ArrayList<String>> ims: imsList) {
    599                 String curId = ims.first;
    600                 if (curId.equals(id)) {
    601                     // We are disabling this input method, and it is
    602                     // currently enabled.  Skip it to remove from the
    603                     // new list.
    604                     isRemoved = true;
    605                 } else {
    606                     if (needsAppendSeparator) {
    607                         builder.append(INPUT_METHOD_SEPARATER);
    608                     } else {
    609                         needsAppendSeparator = true;
    610                     }
    611                     buildEnabledInputMethodsSettingString(builder, ims);
    612                 }
    613             }
    614             if (isRemoved) {
    615                 // Update the setting with the new list of input methods.
    616                 putEnabledInputMethodsStr(builder.toString());
    617             }
    618             return isRemoved;
    619         }
    620 
    621         private List<InputMethodInfo> createEnabledInputMethodListLocked(
    622                 List<Pair<String, ArrayList<String>>> imsList) {
    623             final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
    624             for (Pair<String, ArrayList<String>> ims: imsList) {
    625                 InputMethodInfo info = mMethodMap.get(ims.first);
    626                 if (info != null) {
    627                     res.add(info);
    628                 }
    629             }
    630             return res;
    631         }
    632 
    633         private List<Pair<InputMethodInfo, ArrayList<String>>>
    634                 createEnabledInputMethodAndSubtypeHashCodeListLocked(
    635                         List<Pair<String, ArrayList<String>>> imsList) {
    636             final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
    637                     = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
    638             for (Pair<String, ArrayList<String>> ims : imsList) {
    639                 InputMethodInfo info = mMethodMap.get(ims.first);
    640                 if (info != null) {
    641                     res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
    642                 }
    643             }
    644             return res;
    645         }
    646 
    647         private void putEnabledInputMethodsStr(String str) {
    648             Settings.Secure.putStringForUser(
    649                     mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
    650             mEnabledInputMethodsStrCache = str;
    651             if (DEBUG) {
    652                 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
    653             }
    654         }
    655 
    656         public String getEnabledInputMethodsStr() {
    657             mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
    658                     mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
    659             if (DEBUG) {
    660                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
    661                         + ", " + mCurrentUserId);
    662             }
    663             return mEnabledInputMethodsStrCache;
    664         }
    665 
    666         private void saveSubtypeHistory(
    667                 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
    668             StringBuilder builder = new StringBuilder();
    669             boolean isImeAdded = false;
    670             if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
    671                 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
    672                         newSubtypeId);
    673                 isImeAdded = true;
    674             }
    675             for (Pair<String, String> ime: savedImes) {
    676                 String imeId = ime.first;
    677                 String subtypeId = ime.second;
    678                 if (TextUtils.isEmpty(subtypeId)) {
    679                     subtypeId = NOT_A_SUBTYPE_ID_STR;
    680                 }
    681                 if (isImeAdded) {
    682                     builder.append(INPUT_METHOD_SEPARATER);
    683                 } else {
    684                     isImeAdded = true;
    685                 }
    686                 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
    687                         subtypeId);
    688             }
    689             // Remove the last INPUT_METHOD_SEPARATER
    690             putSubtypeHistoryStr(builder.toString());
    691         }
    692 
    693         private void addSubtypeToHistory(String imeId, String subtypeId) {
    694             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
    695             for (Pair<String, String> ime: subtypeHistory) {
    696                 if (ime.first.equals(imeId)) {
    697                     if (DEBUG) {
    698                         Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
    699                                 + ime.second);
    700                     }
    701                     // We should break here
    702                     subtypeHistory.remove(ime);
    703                     break;
    704                 }
    705             }
    706             if (DEBUG) {
    707                 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
    708             }
    709             saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
    710         }
    711 
    712         private void putSubtypeHistoryStr(String str) {
    713             if (DEBUG) {
    714                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
    715             }
    716             Settings.Secure.putStringForUser(
    717                     mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
    718         }
    719 
    720         public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
    721             // Gets the first one from the history
    722             return getLastSubtypeForInputMethodLockedInternal(null);
    723         }
    724 
    725         public String getLastSubtypeForInputMethodLocked(String imeId) {
    726             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
    727             if (ime != null) {
    728                 return ime.second;
    729             } else {
    730                 return null;
    731             }
    732         }
    733 
    734         private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
    735             List<Pair<String, ArrayList<String>>> enabledImes =
    736                     getEnabledInputMethodsAndSubtypeListLocked();
    737             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
    738             for (Pair<String, String> imeAndSubtype : subtypeHistory) {
    739                 final String imeInTheHistory = imeAndSubtype.first;
    740                 // If imeId is empty, returns the first IME and subtype in the history
    741                 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
    742                     final String subtypeInTheHistory = imeAndSubtype.second;
    743                     final String subtypeHashCode =
    744                             getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
    745                                     enabledImes, imeInTheHistory, subtypeInTheHistory);
    746                     if (!TextUtils.isEmpty(subtypeHashCode)) {
    747                         if (DEBUG) {
    748                             Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
    749                         }
    750                         return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
    751                     }
    752                 }
    753             }
    754             if (DEBUG) {
    755                 Slog.d(TAG, "No enabled IME found in the history");
    756             }
    757             return null;
    758         }
    759 
    760         private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
    761                 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
    762             for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
    763                 if (enabledIme.first.equals(imeId)) {
    764                     final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
    765                     final InputMethodInfo imi = mMethodMap.get(imeId);
    766                     if (explicitlyEnabledSubtypes.size() == 0) {
    767                         // If there are no explicitly enabled subtypes, applicable subtypes are
    768                         // enabled implicitly.
    769                         // If IME is enabled and no subtypes are enabled, applicable subtypes
    770                         // are enabled implicitly, so needs to treat them to be enabled.
    771                         if (imi != null && imi.getSubtypeCount() > 0) {
    772                             List<InputMethodSubtype> implicitlySelectedSubtypes =
    773                                     getImplicitlyApplicableSubtypesLocked(mRes, imi);
    774                             if (implicitlySelectedSubtypes != null) {
    775                                 final int N = implicitlySelectedSubtypes.size();
    776                                 for (int i = 0; i < N; ++i) {
    777                                     final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
    778                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
    779                                         return subtypeHashCode;
    780                                     }
    781                                 }
    782                             }
    783                         }
    784                     } else {
    785                         for (String s: explicitlyEnabledSubtypes) {
    786                             if (s.equals(subtypeHashCode)) {
    787                                 // If both imeId and subtypeId are enabled, return subtypeId.
    788                                 try {
    789                                     final int hashCode = Integer.valueOf(subtypeHashCode);
    790                                     // Check whether the subtype id is valid or not
    791                                     if (isValidSubtypeId(imi, hashCode)) {
    792                                         return s;
    793                                     } else {
    794                                         return NOT_A_SUBTYPE_ID_STR;
    795                                     }
    796                                 } catch (NumberFormatException e) {
    797                                     return NOT_A_SUBTYPE_ID_STR;
    798                                 }
    799                             }
    800                         }
    801                     }
    802                     // If imeId was enabled but subtypeId was disabled.
    803                     return NOT_A_SUBTYPE_ID_STR;
    804                 }
    805             }
    806             // If both imeId and subtypeId are disabled, return null
    807             return null;
    808         }
    809 
    810         private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
    811             ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
    812             final String subtypeHistoryStr = getSubtypeHistoryStr();
    813             if (TextUtils.isEmpty(subtypeHistoryStr)) {
    814                 return imsList;
    815             }
    816             mInputMethodSplitter.setString(subtypeHistoryStr);
    817             while (mInputMethodSplitter.hasNext()) {
    818                 String nextImsStr = mInputMethodSplitter.next();
    819                 mSubtypeSplitter.setString(nextImsStr);
    820                 if (mSubtypeSplitter.hasNext()) {
    821                     String subtypeId = NOT_A_SUBTYPE_ID_STR;
    822                     // The first element is ime id.
    823                     String imeId = mSubtypeSplitter.next();
    824                     while (mSubtypeSplitter.hasNext()) {
    825                         subtypeId = mSubtypeSplitter.next();
    826                         break;
    827                     }
    828                     imsList.add(new Pair<String, String>(imeId, subtypeId));
    829                 }
    830             }
    831             return imsList;
    832         }
    833 
    834         private String getSubtypeHistoryStr() {
    835             if (DEBUG) {
    836                 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
    837                         mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
    838             }
    839             return Settings.Secure.getStringForUser(
    840                     mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
    841         }
    842 
    843         public void putSelectedInputMethod(String imeId) {
    844             if (DEBUG) {
    845                 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
    846                         + mCurrentUserId);
    847             }
    848             Settings.Secure.putStringForUser(
    849                     mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
    850         }
    851 
    852         public void putSelectedSubtype(int subtypeId) {
    853             if (DEBUG) {
    854                 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
    855                         + mCurrentUserId);
    856             }
    857             Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
    858                     subtypeId, mCurrentUserId);
    859         }
    860 
    861         public String getDisabledSystemInputMethods() {
    862             return Settings.Secure.getStringForUser(
    863                     mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
    864         }
    865 
    866         public String getSelectedInputMethod() {
    867             if (DEBUG) {
    868                 Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
    869                         mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
    870                         + ", " + mCurrentUserId);
    871             }
    872             return Settings.Secure.getStringForUser(
    873                     mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
    874         }
    875 
    876         public boolean isSubtypeSelected() {
    877             return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
    878         }
    879 
    880         private int getSelectedInputMethodSubtypeHashCode() {
    881             try {
    882                 return Settings.Secure.getIntForUser(
    883                         mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
    884             } catch (SettingNotFoundException e) {
    885                 return NOT_A_SUBTYPE_ID;
    886             }
    887         }
    888 
    889         public int getCurrentUserId() {
    890             return mCurrentUserId;
    891         }
    892 
    893         public int getSelectedInputMethodSubtypeId(String selectedImiId) {
    894             final InputMethodInfo imi = mMethodMap.get(selectedImiId);
    895             if (imi == null) {
    896                 return NOT_A_SUBTYPE_ID;
    897             }
    898             final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
    899             return getSubtypeIdFromHashCode(imi, subtypeHashCode);
    900         }
    901 
    902         public void saveCurrentInputMethodAndSubtypeToHistory(
    903                 String curMethodId, InputMethodSubtype currentSubtype) {
    904             String subtypeId = NOT_A_SUBTYPE_ID_STR;
    905             if (currentSubtype != null) {
    906                 subtypeId = String.valueOf(currentSubtype.hashCode());
    907             }
    908             if (canAddToLastInputMethod(currentSubtype)) {
    909                 addSubtypeToHistory(curMethodId, subtypeId);
    910             }
    911         }
    912     }
    913 }
    914