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