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.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.app.AppOpsManager;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.PackageManager;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.res.Resources;
     28 import android.provider.Settings;
     29 import android.provider.Settings.SettingNotFoundException;
     30 import android.text.TextUtils;
     31 import android.util.Pair;
     32 import android.util.Slog;
     33 import android.view.inputmethod.InputMethodInfo;
     34 import android.view.inputmethod.InputMethodSubtype;
     35 import android.view.textservice.SpellCheckerInfo;
     36 import android.view.textservice.TextServicesManager;
     37 
     38 import java.util.ArrayList;
     39 import java.util.Arrays;
     40 import java.util.HashMap;
     41 import java.util.LinkedHashSet;
     42 import java.util.List;
     43 import java.util.Locale;
     44 
     45 /**
     46  * InputMethodManagerUtils contains some static methods that provides IME informations.
     47  * This methods are supposed to be used in both the framework and the Settings application.
     48  */
     49 public class InputMethodUtils {
     50     public static final boolean DEBUG = false;
     51     public static final int NOT_A_SUBTYPE_ID = -1;
     52     public static final String SUBTYPE_MODE_ANY = null;
     53     public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
     54     public static final String SUBTYPE_MODE_VOICE = "voice";
     55     private static final String TAG = "InputMethodUtils";
     56     private static final Locale ENGLISH_LOCALE = new Locale("en");
     57     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
     58     private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
     59             "EnabledWhenDefaultIsNotAsciiCapable";
     60     private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
     61     /**
     62      * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
     63      * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
     64      * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
     65      * doesn't automatically match {@code Locale("en", "IN")}.
     66      */
     67     private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
     68         Locale.ENGLISH, // "en"
     69         Locale.US, // "en_US"
     70         Locale.UK, // "en_GB"
     71     };
     72 
     73     private InputMethodUtils() {
     74         // This utility class is not publicly instantiable.
     75     }
     76 
     77     // ----------------------------------------------------------------------
     78     // Utilities for debug
     79     public static String getStackTrace() {
     80         final StringBuilder sb = new StringBuilder();
     81         try {
     82             throw new RuntimeException();
     83         } catch (RuntimeException e) {
     84             final StackTraceElement[] frames = e.getStackTrace();
     85             // Start at 1 because the first frame is here and we don't care about it
     86             for (int j = 1; j < frames.length; ++j) {
     87                 sb.append(frames[j].toString() + "\n");
     88             }
     89         }
     90         return sb.toString();
     91     }
     92 
     93     public static String getApiCallStack() {
     94         String apiCallStack = "";
     95         try {
     96             throw new RuntimeException();
     97         } catch (RuntimeException e) {
     98             final StackTraceElement[] frames = e.getStackTrace();
     99             for (int j = 1; j < frames.length; ++j) {
    100                 final String tempCallStack = frames[j].toString();
    101                 if (TextUtils.isEmpty(apiCallStack)) {
    102                     // Overwrite apiCallStack if it's empty
    103                     apiCallStack = tempCallStack;
    104                 } else if (tempCallStack.indexOf("Transact(") < 0) {
    105                     // Overwrite apiCallStack if it's not a binder call
    106                     apiCallStack = tempCallStack;
    107                 } else {
    108                     break;
    109                 }
    110             }
    111         }
    112         return apiCallStack;
    113     }
    114     // ----------------------------------------------------------------------
    115 
    116     public static boolean isSystemIme(InputMethodInfo inputMethod) {
    117         return (inputMethod.getServiceInfo().applicationInfo.flags
    118                 & ApplicationInfo.FLAG_SYSTEM) != 0;
    119     }
    120 
    121     /**
    122      * @deprecated Use {@link #isSystemImeThatHasSubtypeOf(InputMethodInfo, Context, boolean,
    123      * Locale, boolean, String)} instead.
    124      */
    125     @Deprecated
    126     public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
    127         if (!isSystemIme(imi)) {
    128             return false;
    129         }
    130         return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
    131     }
    132 
    133     private static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
    134             final Context context, final boolean checkDefaultAttribute,
    135             @Nullable final Locale requiredLocale, final boolean checkCountry,
    136             final String requiredSubtypeMode) {
    137         if (!isSystemIme(imi)) {
    138             return false;
    139         }
    140         if (checkDefaultAttribute && !imi.isDefault(context)) {
    141             return false;
    142         }
    143         if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) {
    144             return false;
    145         }
    146         return true;
    147     }
    148 
    149     @Nullable
    150     public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
    151             final Context context) {
    152         // At first, find the fallback locale from the IMEs that are declared as "default" in the
    153         // current locale.  Note that IME developers can declare an IME as "default" only for
    154         // some particular locales but "not default" for other locales.
    155         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
    156             for (int i = 0; i < imis.size(); ++i) {
    157                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
    158                         true /* checkDefaultAttribute */, fallbackLocale,
    159                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
    160                     return fallbackLocale;
    161                 }
    162             }
    163         }
    164         // If no fallback locale is found in the above condition, find fallback locales regardless
    165         // of the "default" attribute as a last resort.
    166         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
    167             for (int i = 0; i < imis.size(); ++i) {
    168                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
    169                         false /* checkDefaultAttribute */, fallbackLocale,
    170                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
    171                     return fallbackLocale;
    172                 }
    173             }
    174         }
    175         Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray()));
    176         return null;
    177     }
    178 
    179     private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi,
    180             final Context context, final boolean checkDefaultAttribute) {
    181         if (!isSystemIme(imi)) {
    182             return false;
    183         }
    184         if (checkDefaultAttribute && !imi.isDefault(context)) {
    185             return false;
    186         }
    187         if (!imi.isAuxiliaryIme()) {
    188             return false;
    189         }
    190         final int subtypeCount = imi.getSubtypeCount();
    191         for (int i = 0; i < subtypeCount; ++i) {
    192             final InputMethodSubtype s = imi.getSubtypeAt(i);
    193             if (s.overridesImplicitlyEnabledSubtype()) {
    194                 return true;
    195             }
    196         }
    197         return false;
    198     }
    199 
    200     public static Locale getSystemLocaleFromContext(final Context context) {
    201         try {
    202             return context.getResources().getConfiguration().locale;
    203         } catch (Resources.NotFoundException ex) {
    204             return null;
    205         }
    206     }
    207 
    208     private static final class InputMethodListBuilder {
    209         // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration
    210         // order can have non-trivial effect in the call sites.
    211         @NonNull
    212         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
    213 
    214         public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis,
    215                 final Context context, final boolean checkDefaultAttribute,
    216                 @Nullable final Locale locale, final boolean checkCountry,
    217                 final String requiredSubtypeMode) {
    218             for (int i = 0; i < imis.size(); ++i) {
    219                 final InputMethodInfo imi = imis.get(i);
    220                 if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
    221                         checkCountry, requiredSubtypeMode)) {
    222                     mInputMethodSet.add(imi);
    223                 }
    224             }
    225             return this;
    226         }
    227 
    228         // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
    229         // documented more clearly.
    230         public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis,
    231                 final Context context) {
    232             // If one or more auxiliary input methods are available, OK to stop populating the list.
    233             for (final InputMethodInfo imi : mInputMethodSet) {
    234                 if (imi.isAuxiliaryIme()) {
    235                     return this;
    236                 }
    237             }
    238             boolean added = false;
    239             for (int i = 0; i < imis.size(); ++i) {
    240                 final InputMethodInfo imi = imis.get(i);
    241                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
    242                         true /* checkDefaultAttribute */)) {
    243                     mInputMethodSet.add(imi);
    244                     added = true;
    245                 }
    246             }
    247             if (added) {
    248                 return this;
    249             }
    250             for (int i = 0; i < imis.size(); ++i) {
    251                 final InputMethodInfo imi = imis.get(i);
    252                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
    253                         false /* checkDefaultAttribute */)) {
    254                     mInputMethodSet.add(imi);
    255                 }
    256             }
    257             return this;
    258         }
    259 
    260         public boolean isEmpty() {
    261             return mInputMethodSet.isEmpty();
    262         }
    263 
    264         @NonNull
    265         public ArrayList<InputMethodInfo> build() {
    266             return new ArrayList<>(mInputMethodSet);
    267         }
    268     }
    269 
    270     private static InputMethodListBuilder getMinimumKeyboardSetWithoutSystemLocale(
    271             final ArrayList<InputMethodInfo> imis, final Context context,
    272             @Nullable final Locale fallbackLocale) {
    273         // Before the system becomes ready, we pick up at least one keyboard in the following order.
    274         // The first user (device owner) falls into this category.
    275         // 1. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
    276         // 2. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
    277         // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
    278         // 4. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
    279         // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
    280 
    281         final InputMethodListBuilder builder = new InputMethodListBuilder();
    282         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
    283                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    284         if (!builder.isEmpty()) {
    285             return builder;
    286         }
    287         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
    288                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    289         if (!builder.isEmpty()) {
    290             return builder;
    291         }
    292         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
    293                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    294         if (!builder.isEmpty()) {
    295             return builder;
    296         }
    297         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
    298                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    299         if (!builder.isEmpty()) {
    300             return builder;
    301         }
    302         Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
    303                 + " fallbackLocale=" + fallbackLocale);
    304         return builder;
    305     }
    306 
    307     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
    308             final ArrayList<InputMethodInfo> imis, final Context context,
    309             @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) {
    310         // Once the system becomes ready, we pick up at least one keyboard in the following order.
    311         // Secondary users fall into this category in general.
    312         // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
    313         // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false
    314         // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
    315         // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
    316         // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
    317         // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
    318         // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
    319 
    320         final InputMethodListBuilder builder = new InputMethodListBuilder();
    321         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
    322                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    323         if (!builder.isEmpty()) {
    324             return builder;
    325         }
    326         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
    327                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    328         if (!builder.isEmpty()) {
    329             return builder;
    330         }
    331         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
    332                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    333         if (!builder.isEmpty()) {
    334             return builder;
    335         }
    336         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
    337                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    338         if (!builder.isEmpty()) {
    339             return builder;
    340         }
    341         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
    342                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    343         if (!builder.isEmpty()) {
    344             return builder;
    345         }
    346         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
    347                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    348         if (!builder.isEmpty()) {
    349             return builder;
    350         }
    351         Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
    352                 + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale);
    353         return builder;
    354     }
    355 
    356     public static ArrayList<InputMethodInfo> getDefaultEnabledImes(final Context context,
    357             final boolean isSystemReady, final ArrayList<InputMethodInfo> imis) {
    358         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
    359         if (!isSystemReady) {
    360             // When the system is not ready, the system locale is not stable and reliable. Hence
    361             // we will pick up IMEs that support software keyboard based on the fallback locale.
    362             // Also pick up suitable IMEs regardless of the software keyboard support.
    363             // (e.g. Voice IMEs)
    364             return getMinimumKeyboardSetWithoutSystemLocale(imis, context, fallbackLocale)
    365                     .fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
    366                             true /* checkCountry */, SUBTYPE_MODE_ANY)
    367                     .build();
    368         }
    369 
    370         // When the system is ready, we will primarily rely on the system locale, but also keep
    371         // relying on the fallback locale as a last resort.
    372         // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
    373         // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
    374         // subtype)
    375         final Locale systemLocale = getSystemLocaleFromContext(context);
    376         return getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale)
    377                 .fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
    378                         true /* checkCountry */, SUBTYPE_MODE_ANY)
    379                 .fillAuxiliaryImes(imis, context)
    380                 .build();
    381     }
    382 
    383     /**
    384      * @deprecated Use {@link #isSystemImeThatHasSubtypeOf(InputMethodInfo, Context, boolean,
    385      * Locale, boolean, String)} instead.
    386      */
    387     @Deprecated
    388     public static boolean isValidSystemDefaultIme(
    389             boolean isSystemReady, InputMethodInfo imi, Context context) {
    390         if (!isSystemReady) {
    391             return false;
    392         }
    393         if (!isSystemIme(imi)) {
    394             return false;
    395         }
    396         if (imi.getIsDefaultResourceId() != 0) {
    397             try {
    398                 if (imi.isDefault(context) && containsSubtypeOf(
    399                         imi, context.getResources().getConfiguration().locale.getLanguage(),
    400                         SUBTYPE_MODE_ANY)) {
    401                     return true;
    402                 }
    403             } catch (Resources.NotFoundException ex) {
    404             }
    405         }
    406         if (imi.getSubtypeCount() == 0) {
    407             Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
    408         }
    409         return false;
    410     }
    411 
    412     public static boolean containsSubtypeOf(final InputMethodInfo imi,
    413             @Nullable final Locale locale, final boolean checkCountry, final String mode) {
    414         if (locale == null) {
    415             return false;
    416         }
    417         final int N = imi.getSubtypeCount();
    418         for (int i = 0; i < N; ++i) {
    419             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    420             if (checkCountry) {
    421                 // TODO: Use {@link Locale#toLanguageTag()} and
    422                 // {@link Locale#forLanguageTag(languageTag)} instead.
    423                 if (!TextUtils.equals(subtype.getLocale(), locale.toString())) {
    424                     continue;
    425                 }
    426             } else {
    427                 final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
    428                         subtype.getLocale()));
    429                 if (!subtypeLocale.getLanguage().equals(locale.getLanguage())) {
    430                     continue;
    431                 }
    432             }
    433             if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
    434                     mode.equalsIgnoreCase(subtype.getMode())) {
    435                 return true;
    436             }
    437         }
    438         return false;
    439     }
    440 
    441     /**
    442      * @deprecated Use {@link #containsSubtypeOf(InputMethodInfo, Locale, boolean, String)} instead.
    443      */
    444     @Deprecated
    445     public static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
    446         final int N = imi.getSubtypeCount();
    447         for (int i = 0; i < N; ++i) {
    448             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    449             if (!subtype.getLocale().startsWith(language)) {
    450                 continue;
    451             }
    452             if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
    453                     mode.equalsIgnoreCase(subtype.getMode())) {
    454                 return true;
    455             }
    456         }
    457         return false;
    458     }
    459 
    460     public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
    461         ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
    462         final int subtypeCount = imi.getSubtypeCount();
    463         for (int i = 0; i < subtypeCount; ++i) {
    464             subtypes.add(imi.getSubtypeAt(i));
    465         }
    466         return subtypes;
    467     }
    468 
    469     public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
    470             InputMethodInfo imi, String mode) {
    471         ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
    472         final int subtypeCount = imi.getSubtypeCount();
    473         for (int i = 0; i < subtypeCount; ++i) {
    474             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    475             if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
    476                 subtypes.add(subtype);
    477             }
    478         }
    479         return subtypes;
    480     }
    481 
    482     public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
    483         if (enabledImes == null || enabledImes.isEmpty()) {
    484             return null;
    485         }
    486         // We'd prefer to fall back on a system IME, since that is safer.
    487         int i = enabledImes.size();
    488         int firstFoundSystemIme = -1;
    489         while (i > 0) {
    490             i--;
    491             final InputMethodInfo imi = enabledImes.get(i);
    492             if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
    493                     && !imi.isAuxiliaryIme()) {
    494                 return imi;
    495             }
    496             if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)
    497                     && !imi.isAuxiliaryIme()) {
    498                 firstFoundSystemIme = i;
    499             }
    500         }
    501         return enabledImes.get(Math.max(firstFoundSystemIme, 0));
    502     }
    503 
    504     public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
    505         return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
    506     }
    507 
    508     public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
    509         if (imi != null) {
    510             final int subtypeCount = imi.getSubtypeCount();
    511             for (int i = 0; i < subtypeCount; ++i) {
    512                 InputMethodSubtype ims = imi.getSubtypeAt(i);
    513                 if (subtypeHashCode == ims.hashCode()) {
    514                     return i;
    515                 }
    516             }
    517         }
    518         return NOT_A_SUBTYPE_ID;
    519     }
    520 
    521     private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
    522             Resources res, InputMethodInfo imi) {
    523         final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
    524         final String systemLocale = res.getConfiguration().locale.toString();
    525         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
    526         final String systemLanguage = res.getConfiguration().locale.getLanguage();
    527         final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
    528                 new HashMap<String, InputMethodSubtype>();
    529         final int N = subtypes.size();
    530         for (int i = 0; i < N; ++i) {
    531             // scan overriding implicitly enabled subtypes.
    532             InputMethodSubtype subtype = subtypes.get(i);
    533             if (subtype.overridesImplicitlyEnabledSubtype()) {
    534                 final String mode = subtype.getMode();
    535                 if (!applicableModeAndSubtypesMap.containsKey(mode)) {
    536                     applicableModeAndSubtypesMap.put(mode, subtype);
    537                 }
    538             }
    539         }
    540         if (applicableModeAndSubtypesMap.size() > 0) {
    541             return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
    542         }
    543         for (int i = 0; i < N; ++i) {
    544             final InputMethodSubtype subtype = subtypes.get(i);
    545             final String locale = subtype.getLocale();
    546             final String mode = subtype.getMode();
    547             final String language = getLanguageFromLocaleString(locale);
    548             // When system locale starts with subtype's locale, that subtype will be applicable
    549             // for system locale. We need to make sure the languages are the same, to prevent
    550             // locales like "fil" (Filipino) being matched by "fi" (Finnish).
    551             //
    552             // For instance, it's clearly applicable for cases like system locale = en_US and
    553             // subtype = en, but it is not necessarily considered applicable for cases like system
    554             // locale = en and subtype = en_US.
    555             //
    556             // We just call systemLocale.startsWith(locale) in this function because there is no
    557             // need to find applicable subtypes aggressively unlike
    558             // findLastResortApplicableSubtypeLocked.
    559             //
    560             // TODO: This check is broken. It won't take scripts into account and doesn't
    561             // account for the mandatory conversions performed by Locale#toString.
    562             if (language.equals(systemLanguage) && systemLocale.startsWith(locale)) {
    563                 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
    564                 // If more applicable subtypes are contained, skip.
    565                 if (applicableSubtype != null) {
    566                     if (systemLocale.equals(applicableSubtype.getLocale())) continue;
    567                     if (!systemLocale.equals(locale)) continue;
    568                 }
    569                 applicableModeAndSubtypesMap.put(mode, subtype);
    570             }
    571         }
    572         final InputMethodSubtype keyboardSubtype
    573                 = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
    574         final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
    575                 applicableModeAndSubtypesMap.values());
    576         if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
    577             for (int i = 0; i < N; ++i) {
    578                 final InputMethodSubtype subtype = subtypes.get(i);
    579                 final String mode = subtype.getMode();
    580                 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
    581                         TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
    582                     applicableSubtypes.add(subtype);
    583                 }
    584             }
    585         }
    586         if (keyboardSubtype == null) {
    587             InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
    588                     res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
    589             if (lastResortKeyboardSubtype != null) {
    590                 applicableSubtypes.add(lastResortKeyboardSubtype);
    591             }
    592         }
    593         return applicableSubtypes;
    594     }
    595 
    596     /**
    597      * Returns the language component of a given locale string.
    598      * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
    599      */
    600     public static String getLanguageFromLocaleString(String locale) {
    601         final int idx = locale.indexOf('_');
    602         if (idx < 0) {
    603             return locale;
    604         } else {
    605             return locale.substring(0, idx);
    606         }
    607     }
    608 
    609     /**
    610      * If there are no selected subtypes, tries finding the most applicable one according to the
    611      * given locale.
    612      * @param subtypes this function will search the most applicable subtype in subtypes
    613      * @param mode subtypes will be filtered by mode
    614      * @param locale subtypes will be filtered by locale
    615      * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
    616      * it will return the first subtype matched with mode
    617      * @return the most applicable subtypeId
    618      */
    619     public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
    620             Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
    621             boolean canIgnoreLocaleAsLastResort) {
    622         if (subtypes == null || subtypes.size() == 0) {
    623             return null;
    624         }
    625         if (TextUtils.isEmpty(locale)) {
    626             locale = res.getConfiguration().locale.toString();
    627         }
    628         final String language = getLanguageFromLocaleString(locale);
    629         boolean partialMatchFound = false;
    630         InputMethodSubtype applicableSubtype = null;
    631         InputMethodSubtype firstMatchedModeSubtype = null;
    632         final int N = subtypes.size();
    633         for (int i = 0; i < N; ++i) {
    634             InputMethodSubtype subtype = subtypes.get(i);
    635             final String subtypeLocale = subtype.getLocale();
    636             final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
    637             // An applicable subtype should match "mode". If mode is null, mode will be ignored,
    638             // and all subtypes with all modes can be candidates.
    639             if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
    640                 if (firstMatchedModeSubtype == null) {
    641                     firstMatchedModeSubtype = subtype;
    642                 }
    643                 if (locale.equals(subtypeLocale)) {
    644                     // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
    645                     applicableSubtype = subtype;
    646                     break;
    647                 } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
    648                     // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
    649                     applicableSubtype = subtype;
    650                     partialMatchFound = true;
    651                 }
    652             }
    653         }
    654 
    655         if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
    656             return firstMatchedModeSubtype;
    657         }
    658 
    659         // The first subtype applicable to the system locale will be defined as the most applicable
    660         // subtype.
    661         if (DEBUG) {
    662             if (applicableSubtype != null) {
    663                 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
    664                         + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
    665             }
    666         }
    667         return applicableSubtype;
    668     }
    669 
    670     public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
    671         if (subtype == null) return true;
    672         return !subtype.isAuxiliary();
    673     }
    674 
    675     public static void setNonSelectedSystemImesDisabledUntilUsed(
    676             PackageManager packageManager, List<InputMethodInfo> enabledImis) {
    677         if (DEBUG) {
    678             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
    679         }
    680         final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
    681                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
    682         if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
    683             return;
    684         }
    685         // Only the current spell checker should be treated as an enabled one.
    686         final SpellCheckerInfo currentSpellChecker =
    687                 TextServicesManager.getInstance().getCurrentSpellChecker();
    688         for (final String packageName : systemImesDisabledUntilUsed) {
    689             if (DEBUG) {
    690                 Slog.d(TAG, "check " + packageName);
    691             }
    692             boolean enabledIme = false;
    693             for (int j = 0; j < enabledImis.size(); ++j) {
    694                 final InputMethodInfo imi = enabledImis.get(j);
    695                 if (packageName.equals(imi.getPackageName())) {
    696                     enabledIme = true;
    697                     break;
    698                 }
    699             }
    700             if (enabledIme) {
    701                 // enabled ime. skip
    702                 continue;
    703             }
    704             if (currentSpellChecker != null
    705                     && packageName.equals(currentSpellChecker.getPackageName())) {
    706                 // enabled spell checker. skip
    707                 if (DEBUG) {
    708                     Slog.d(TAG, packageName + " is the current spell checker. skip");
    709                 }
    710                 continue;
    711             }
    712             ApplicationInfo ai = null;
    713             try {
    714                 ai = packageManager.getApplicationInfo(packageName,
    715                         PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
    716             } catch (NameNotFoundException e) {
    717                 Slog.w(TAG, "NameNotFoundException: " + packageName, e);
    718             }
    719             if (ai == null) {
    720                 // No app found for packageName
    721                 continue;
    722             }
    723             final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    724             if (!isSystemPackage) {
    725                 continue;
    726             }
    727             setDisabledUntilUsed(packageManager, packageName);
    728         }
    729     }
    730 
    731     private static void setDisabledUntilUsed(PackageManager packageManager, String packageName) {
    732         final int state = packageManager.getApplicationEnabledSetting(packageName);
    733         if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
    734                 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
    735             if (DEBUG) {
    736                 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
    737             }
    738             packageManager.setApplicationEnabledSetting(packageName,
    739                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0);
    740         } else {
    741             if (DEBUG) {
    742                 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
    743             }
    744         }
    745     }
    746 
    747     public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
    748             InputMethodSubtype subtype) {
    749         final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
    750         return subtype != null
    751                 ? TextUtils.concat(subtype.getDisplayName(context,
    752                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
    753                                 (TextUtils.isEmpty(imiLabel) ?
    754                                         "" : " - " + imiLabel))
    755                 : imiLabel;
    756     }
    757 
    758     /**
    759      * Returns true if a package name belongs to a UID.
    760      *
    761      * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p>
    762      * @param appOpsManager the {@link AppOpsManager} object to be used for the validation.
    763      * @param uid the UID to be validated.
    764      * @param packageName the package name.
    765      * @return {@code true} if the package name belongs to the UID.
    766      */
    767     public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager,
    768             final int uid, final String packageName) {
    769         try {
    770             appOpsManager.checkPackage(uid, packageName);
    771             return true;
    772         } catch (SecurityException e) {
    773             return false;
    774         }
    775     }
    776 
    777     /**
    778      * Utility class for putting and getting settings for InputMethod
    779      * TODO: Move all putters and getters of settings to this class.
    780      */
    781     public static class InputMethodSettings {
    782         // The string for enabled input method is saved as follows:
    783         // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
    784         private static final char INPUT_METHOD_SEPARATER = ':';
    785         private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
    786         private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
    787                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
    788 
    789         private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
    790                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
    791 
    792         private final Resources mRes;
    793         private final ContentResolver mResolver;
    794         private final HashMap<String, InputMethodInfo> mMethodMap;
    795         private final ArrayList<InputMethodInfo> mMethodList;
    796 
    797         private String mEnabledInputMethodsStrCache;
    798         private int mCurrentUserId;
    799         private int[] mCurrentProfileIds = new int[0];
    800 
    801         private static void buildEnabledInputMethodsSettingString(
    802                 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
    803             String id = pair.first;
    804             ArrayList<String> subtypes = pair.second;
    805             builder.append(id);
    806             // Inputmethod and subtypes are saved in the settings as follows:
    807             // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
    808             for (String subtypeId: subtypes) {
    809                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
    810             }
    811         }
    812 
    813         public InputMethodSettings(
    814                 Resources res, ContentResolver resolver,
    815                 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
    816                 int userId) {
    817             setCurrentUserId(userId);
    818             mRes = res;
    819             mResolver = resolver;
    820             mMethodMap = methodMap;
    821             mMethodList = methodList;
    822         }
    823 
    824         public void setCurrentUserId(int userId) {
    825             if (DEBUG) {
    826                 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId);
    827             }
    828             // IMMS settings are kept per user, so keep track of current user
    829             mCurrentUserId = userId;
    830         }
    831 
    832         public void setCurrentProfileIds(int[] currentProfileIds) {
    833             synchronized (this) {
    834                 mCurrentProfileIds = currentProfileIds;
    835             }
    836         }
    837 
    838         public boolean isCurrentProfile(int userId) {
    839             synchronized (this) {
    840                 if (userId == mCurrentUserId) return true;
    841                 for (int i = 0; i < mCurrentProfileIds.length; i++) {
    842                     if (userId == mCurrentProfileIds[i]) return true;
    843                 }
    844                 return false;
    845             }
    846         }
    847 
    848         public List<InputMethodInfo> getEnabledInputMethodListLocked() {
    849             return createEnabledInputMethodListLocked(
    850                     getEnabledInputMethodsAndSubtypeListLocked());
    851         }
    852 
    853         public List<Pair<InputMethodInfo, ArrayList<String>>>
    854                 getEnabledInputMethodAndSubtypeHashCodeListLocked() {
    855             return createEnabledInputMethodAndSubtypeHashCodeListLocked(
    856                     getEnabledInputMethodsAndSubtypeListLocked());
    857         }
    858 
    859         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
    860                 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
    861             List<InputMethodSubtype> enabledSubtypes =
    862                     getEnabledInputMethodSubtypeListLocked(imi);
    863             if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
    864                 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    865                         context.getResources(), imi);
    866             }
    867             return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
    868         }
    869 
    870         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
    871                 InputMethodInfo imi) {
    872             List<Pair<String, ArrayList<String>>> imsList =
    873                     getEnabledInputMethodsAndSubtypeListLocked();
    874             ArrayList<InputMethodSubtype> enabledSubtypes =
    875                     new ArrayList<InputMethodSubtype>();
    876             if (imi != null) {
    877                 for (Pair<String, ArrayList<String>> imsPair : imsList) {
    878                     InputMethodInfo info = mMethodMap.get(imsPair.first);
    879                     if (info != null && info.getId().equals(imi.getId())) {
    880                         final int subtypeCount = info.getSubtypeCount();
    881                         for (int i = 0; i < subtypeCount; ++i) {
    882                             InputMethodSubtype ims = info.getSubtypeAt(i);
    883                             for (String s: imsPair.second) {
    884                                 if (String.valueOf(ims.hashCode()).equals(s)) {
    885                                     enabledSubtypes.add(ims);
    886                                 }
    887                             }
    888                         }
    889                         break;
    890                     }
    891                 }
    892             }
    893             return enabledSubtypes;
    894         }
    895 
    896         // At the initial boot, the settings for input methods are not set,
    897         // so we need to enable IME in that case.
    898         public void enableAllIMEsIfThereIsNoEnabledIME() {
    899             if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
    900                 StringBuilder sb = new StringBuilder();
    901                 final int N = mMethodList.size();
    902                 for (int i = 0; i < N; i++) {
    903                     InputMethodInfo imi = mMethodList.get(i);
    904                     Slog.i(TAG, "Adding: " + imi.getId());
    905                     if (i > 0) sb.append(':');
    906                     sb.append(imi.getId());
    907                 }
    908                 putEnabledInputMethodsStr(sb.toString());
    909             }
    910         }
    911 
    912         public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
    913             ArrayList<Pair<String, ArrayList<String>>> imsList
    914                     = new ArrayList<Pair<String, ArrayList<String>>>();
    915             final String enabledInputMethodsStr = getEnabledInputMethodsStr();
    916             if (TextUtils.isEmpty(enabledInputMethodsStr)) {
    917                 return imsList;
    918             }
    919             mInputMethodSplitter.setString(enabledInputMethodsStr);
    920             while (mInputMethodSplitter.hasNext()) {
    921                 String nextImsStr = mInputMethodSplitter.next();
    922                 mSubtypeSplitter.setString(nextImsStr);
    923                 if (mSubtypeSplitter.hasNext()) {
    924                     ArrayList<String> subtypeHashes = new ArrayList<String>();
    925                     // The first element is ime id.
    926                     String imeId = mSubtypeSplitter.next();
    927                     while (mSubtypeSplitter.hasNext()) {
    928                         subtypeHashes.add(mSubtypeSplitter.next());
    929                     }
    930                     imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
    931                 }
    932             }
    933             return imsList;
    934         }
    935 
    936         public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
    937             if (reloadInputMethodStr) {
    938                 getEnabledInputMethodsStr();
    939             }
    940             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
    941                 // Add in the newly enabled input method.
    942                 putEnabledInputMethodsStr(id);
    943             } else {
    944                 putEnabledInputMethodsStr(
    945                         mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
    946             }
    947         }
    948 
    949         /**
    950          * Build and put a string of EnabledInputMethods with removing specified Id.
    951          * @return the specified id was removed or not.
    952          */
    953         public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
    954                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
    955             boolean isRemoved = false;
    956             boolean needsAppendSeparator = false;
    957             for (Pair<String, ArrayList<String>> ims: imsList) {
    958                 String curId = ims.first;
    959                 if (curId.equals(id)) {
    960                     // We are disabling this input method, and it is
    961                     // currently enabled.  Skip it to remove from the
    962                     // new list.
    963                     isRemoved = true;
    964                 } else {
    965                     if (needsAppendSeparator) {
    966                         builder.append(INPUT_METHOD_SEPARATER);
    967                     } else {
    968                         needsAppendSeparator = true;
    969                     }
    970                     buildEnabledInputMethodsSettingString(builder, ims);
    971                 }
    972             }
    973             if (isRemoved) {
    974                 // Update the setting with the new list of input methods.
    975                 putEnabledInputMethodsStr(builder.toString());
    976             }
    977             return isRemoved;
    978         }
    979 
    980         private List<InputMethodInfo> createEnabledInputMethodListLocked(
    981                 List<Pair<String, ArrayList<String>>> imsList) {
    982             final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
    983             for (Pair<String, ArrayList<String>> ims: imsList) {
    984                 InputMethodInfo info = mMethodMap.get(ims.first);
    985                 if (info != null) {
    986                     res.add(info);
    987                 }
    988             }
    989             return res;
    990         }
    991 
    992         private List<Pair<InputMethodInfo, ArrayList<String>>>
    993                 createEnabledInputMethodAndSubtypeHashCodeListLocked(
    994                         List<Pair<String, ArrayList<String>>> imsList) {
    995             final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
    996                     = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
    997             for (Pair<String, ArrayList<String>> ims : imsList) {
    998                 InputMethodInfo info = mMethodMap.get(ims.first);
    999                 if (info != null) {
   1000                     res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
   1001                 }
   1002             }
   1003             return res;
   1004         }
   1005 
   1006         private void putEnabledInputMethodsStr(String str) {
   1007             Settings.Secure.putStringForUser(
   1008                     mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
   1009             mEnabledInputMethodsStrCache = str;
   1010             if (DEBUG) {
   1011                 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
   1012             }
   1013         }
   1014 
   1015         public String getEnabledInputMethodsStr() {
   1016             mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
   1017                     mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
   1018             if (DEBUG) {
   1019                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
   1020                         + ", " + mCurrentUserId);
   1021             }
   1022             return mEnabledInputMethodsStrCache;
   1023         }
   1024 
   1025         private void saveSubtypeHistory(
   1026                 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
   1027             StringBuilder builder = new StringBuilder();
   1028             boolean isImeAdded = false;
   1029             if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
   1030                 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
   1031                         newSubtypeId);
   1032                 isImeAdded = true;
   1033             }
   1034             for (Pair<String, String> ime: savedImes) {
   1035                 String imeId = ime.first;
   1036                 String subtypeId = ime.second;
   1037                 if (TextUtils.isEmpty(subtypeId)) {
   1038                     subtypeId = NOT_A_SUBTYPE_ID_STR;
   1039                 }
   1040                 if (isImeAdded) {
   1041                     builder.append(INPUT_METHOD_SEPARATER);
   1042                 } else {
   1043                     isImeAdded = true;
   1044                 }
   1045                 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
   1046                         subtypeId);
   1047             }
   1048             // Remove the last INPUT_METHOD_SEPARATER
   1049             putSubtypeHistoryStr(builder.toString());
   1050         }
   1051 
   1052         private void addSubtypeToHistory(String imeId, String subtypeId) {
   1053             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
   1054             for (Pair<String, String> ime: subtypeHistory) {
   1055                 if (ime.first.equals(imeId)) {
   1056                     if (DEBUG) {
   1057                         Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
   1058                                 + ime.second);
   1059                     }
   1060                     // We should break here
   1061                     subtypeHistory.remove(ime);
   1062                     break;
   1063                 }
   1064             }
   1065             if (DEBUG) {
   1066                 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
   1067             }
   1068             saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
   1069         }
   1070 
   1071         private void putSubtypeHistoryStr(String str) {
   1072             if (DEBUG) {
   1073                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
   1074             }
   1075             Settings.Secure.putStringForUser(
   1076                     mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
   1077         }
   1078 
   1079         public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
   1080             // Gets the first one from the history
   1081             return getLastSubtypeForInputMethodLockedInternal(null);
   1082         }
   1083 
   1084         public String getLastSubtypeForInputMethodLocked(String imeId) {
   1085             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
   1086             if (ime != null) {
   1087                 return ime.second;
   1088             } else {
   1089                 return null;
   1090             }
   1091         }
   1092 
   1093         private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
   1094             List<Pair<String, ArrayList<String>>> enabledImes =
   1095                     getEnabledInputMethodsAndSubtypeListLocked();
   1096             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
   1097             for (Pair<String, String> imeAndSubtype : subtypeHistory) {
   1098                 final String imeInTheHistory = imeAndSubtype.first;
   1099                 // If imeId is empty, returns the first IME and subtype in the history
   1100                 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
   1101                     final String subtypeInTheHistory = imeAndSubtype.second;
   1102                     final String subtypeHashCode =
   1103                             getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
   1104                                     enabledImes, imeInTheHistory, subtypeInTheHistory);
   1105                     if (!TextUtils.isEmpty(subtypeHashCode)) {
   1106                         if (DEBUG) {
   1107                             Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
   1108                         }
   1109                         return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
   1110                     }
   1111                 }
   1112             }
   1113             if (DEBUG) {
   1114                 Slog.d(TAG, "No enabled IME found in the history");
   1115             }
   1116             return null;
   1117         }
   1118 
   1119         private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
   1120                 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
   1121             for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
   1122                 if (enabledIme.first.equals(imeId)) {
   1123                     final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
   1124                     final InputMethodInfo imi = mMethodMap.get(imeId);
   1125                     if (explicitlyEnabledSubtypes.size() == 0) {
   1126                         // If there are no explicitly enabled subtypes, applicable subtypes are
   1127                         // enabled implicitly.
   1128                         // If IME is enabled and no subtypes are enabled, applicable subtypes
   1129                         // are enabled implicitly, so needs to treat them to be enabled.
   1130                         if (imi != null && imi.getSubtypeCount() > 0) {
   1131                             List<InputMethodSubtype> implicitlySelectedSubtypes =
   1132                                     getImplicitlyApplicableSubtypesLocked(mRes, imi);
   1133                             if (implicitlySelectedSubtypes != null) {
   1134                                 final int N = implicitlySelectedSubtypes.size();
   1135                                 for (int i = 0; i < N; ++i) {
   1136                                     final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
   1137                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
   1138                                         return subtypeHashCode;
   1139                                     }
   1140                                 }
   1141                             }
   1142                         }
   1143                     } else {
   1144                         for (String s: explicitlyEnabledSubtypes) {
   1145                             if (s.equals(subtypeHashCode)) {
   1146                                 // If both imeId and subtypeId are enabled, return subtypeId.
   1147                                 try {
   1148                                     final int hashCode = Integer.valueOf(subtypeHashCode);
   1149                                     // Check whether the subtype id is valid or not
   1150                                     if (isValidSubtypeId(imi, hashCode)) {
   1151                                         return s;
   1152                                     } else {
   1153                                         return NOT_A_SUBTYPE_ID_STR;
   1154                                     }
   1155                                 } catch (NumberFormatException e) {
   1156                                     return NOT_A_SUBTYPE_ID_STR;
   1157                                 }
   1158                             }
   1159                         }
   1160                     }
   1161                     // If imeId was enabled but subtypeId was disabled.
   1162                     return NOT_A_SUBTYPE_ID_STR;
   1163                 }
   1164             }
   1165             // If both imeId and subtypeId are disabled, return null
   1166             return null;
   1167         }
   1168 
   1169         private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
   1170             ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
   1171             final String subtypeHistoryStr = getSubtypeHistoryStr();
   1172             if (TextUtils.isEmpty(subtypeHistoryStr)) {
   1173                 return imsList;
   1174             }
   1175             mInputMethodSplitter.setString(subtypeHistoryStr);
   1176             while (mInputMethodSplitter.hasNext()) {
   1177                 String nextImsStr = mInputMethodSplitter.next();
   1178                 mSubtypeSplitter.setString(nextImsStr);
   1179                 if (mSubtypeSplitter.hasNext()) {
   1180                     String subtypeId = NOT_A_SUBTYPE_ID_STR;
   1181                     // The first element is ime id.
   1182                     String imeId = mSubtypeSplitter.next();
   1183                     while (mSubtypeSplitter.hasNext()) {
   1184                         subtypeId = mSubtypeSplitter.next();
   1185                         break;
   1186                     }
   1187                     imsList.add(new Pair<String, String>(imeId, subtypeId));
   1188                 }
   1189             }
   1190             return imsList;
   1191         }
   1192 
   1193         private String getSubtypeHistoryStr() {
   1194             if (DEBUG) {
   1195                 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
   1196                         mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
   1197             }
   1198             return Settings.Secure.getStringForUser(
   1199                     mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
   1200         }
   1201 
   1202         public void putSelectedInputMethod(String imeId) {
   1203             if (DEBUG) {
   1204                 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
   1205                         + mCurrentUserId);
   1206             }
   1207             Settings.Secure.putStringForUser(
   1208                     mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
   1209         }
   1210 
   1211         public void putSelectedSubtype(int subtypeId) {
   1212             if (DEBUG) {
   1213                 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
   1214                         + mCurrentUserId);
   1215             }
   1216             Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
   1217                     subtypeId, mCurrentUserId);
   1218         }
   1219 
   1220         public String getDisabledSystemInputMethods() {
   1221             return Settings.Secure.getStringForUser(
   1222                     mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
   1223         }
   1224 
   1225         public String getSelectedInputMethod() {
   1226             if (DEBUG) {
   1227                 Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
   1228                         mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
   1229                         + ", " + mCurrentUserId);
   1230             }
   1231             return Settings.Secure.getStringForUser(
   1232                     mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
   1233         }
   1234 
   1235         public boolean isSubtypeSelected() {
   1236             return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
   1237         }
   1238 
   1239         private int getSelectedInputMethodSubtypeHashCode() {
   1240             try {
   1241                 return Settings.Secure.getIntForUser(
   1242                         mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
   1243             } catch (SettingNotFoundException e) {
   1244                 return NOT_A_SUBTYPE_ID;
   1245             }
   1246         }
   1247 
   1248         public boolean isShowImeWithHardKeyboardEnabled() {
   1249                 return Settings.Secure.getIntForUser(mResolver,
   1250                         Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mCurrentUserId) == 1;
   1251         }
   1252 
   1253         public void setShowImeWithHardKeyboard(boolean show) {
   1254             Settings.Secure.putIntForUser(mResolver, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
   1255                     show ? 1 : 0, mCurrentUserId);
   1256         }
   1257 
   1258         public int getCurrentUserId() {
   1259             return mCurrentUserId;
   1260         }
   1261 
   1262         public int getSelectedInputMethodSubtypeId(String selectedImiId) {
   1263             final InputMethodInfo imi = mMethodMap.get(selectedImiId);
   1264             if (imi == null) {
   1265                 return NOT_A_SUBTYPE_ID;
   1266             }
   1267             final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
   1268             return getSubtypeIdFromHashCode(imi, subtypeHashCode);
   1269         }
   1270 
   1271         public void saveCurrentInputMethodAndSubtypeToHistory(
   1272                 String curMethodId, InputMethodSubtype currentSubtype) {
   1273             String subtypeId = NOT_A_SUBTYPE_ID_STR;
   1274             if (currentSubtype != null) {
   1275                 subtypeId = String.valueOf(currentSubtype.hashCode());
   1276             }
   1277             if (canAddToLastInputMethod(currentSubtype)) {
   1278                 addSubtypeToHistory(curMethodId, subtypeId);
   1279             }
   1280         }
   1281 
   1282         public HashMap<InputMethodInfo, List<InputMethodSubtype>>
   1283                 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) {
   1284             HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
   1285                     new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
   1286             for (InputMethodInfo imi: getEnabledInputMethodListLocked()) {
   1287                 enabledInputMethodAndSubtypes.put(
   1288                         imi, getEnabledInputMethodSubtypeListLocked(context, imi, true));
   1289             }
   1290             return enabledInputMethodAndSubtypes;
   1291         }
   1292     }
   1293 }
   1294