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 static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR;
     20 import static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_VIEW_HAS_FOCUS;
     21 
     22 import android.annotation.NonNull;
     23 import android.annotation.Nullable;
     24 import android.annotation.UserIdInt;
     25 import android.app.AppOpsManager;
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.content.pm.ApplicationInfo;
     29 import android.content.pm.IPackageManager;
     30 import android.content.pm.PackageManager;
     31 import android.content.res.Resources;
     32 import android.os.Build;
     33 import android.os.LocaleList;
     34 import android.os.RemoteException;
     35 import android.provider.Settings;
     36 import android.text.TextUtils;
     37 import android.text.TextUtils.SimpleStringSplitter;
     38 import android.util.ArrayMap;
     39 import android.util.ArraySet;
     40 import android.util.Log;
     41 import android.util.Pair;
     42 import android.util.Printer;
     43 import android.util.Slog;
     44 import android.view.inputmethod.InputMethodInfo;
     45 import android.view.inputmethod.InputMethodSubtype;
     46 import android.view.textservice.SpellCheckerInfo;
     47 import android.view.textservice.TextServicesManager;
     48 
     49 import com.android.internal.annotations.GuardedBy;
     50 import com.android.internal.annotations.VisibleForTesting;
     51 
     52 import java.util.ArrayList;
     53 import java.util.Arrays;
     54 import java.util.HashMap;
     55 import java.util.LinkedHashSet;
     56 import java.util.List;
     57 import java.util.Locale;
     58 
     59 /**
     60  * InputMethodManagerUtils contains some static methods that provides IME informations.
     61  * This methods are supposed to be used in both the framework and the Settings application.
     62  */
     63 public class InputMethodUtils {
     64     public static final boolean DEBUG = false;
     65     public static final int NOT_A_SUBTYPE_ID = -1;
     66     public static final String SUBTYPE_MODE_ANY = null;
     67     public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
     68     public static final String SUBTYPE_MODE_VOICE = "voice";
     69     private static final String TAG = "InputMethodUtils";
     70     private static final Locale ENGLISH_LOCALE = new Locale("en");
     71     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
     72     private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
     73             "EnabledWhenDefaultIsNotAsciiCapable";
     74     private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
     75 
     76     // The string for enabled input method is saved as follows:
     77     // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
     78     private static final char INPUT_METHOD_SEPARATOR = ':';
     79     private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
     80     /**
     81      * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
     82      * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
     83      * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
     84      * doesn't automatically match {@code Locale("en", "IN")}.
     85      */
     86     private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
     87         Locale.ENGLISH, // "en"
     88         Locale.US, // "en_US"
     89         Locale.UK, // "en_GB"
     90     };
     91 
     92     // A temporary workaround for the performance concerns in
     93     // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
     94     // TODO: Optimize all the critical paths including this one.
     95     private static final Object sCacheLock = new Object();
     96     @GuardedBy("sCacheLock")
     97     private static LocaleList sCachedSystemLocales;
     98     @GuardedBy("sCacheLock")
     99     private static InputMethodInfo sCachedInputMethodInfo;
    100     @GuardedBy("sCacheLock")
    101     private static ArrayList<InputMethodSubtype> sCachedResult;
    102 
    103     private InputMethodUtils() {
    104         // This utility class is not publicly instantiable.
    105     }
    106 
    107     // ----------------------------------------------------------------------
    108     // Utilities for debug
    109     public static String getApiCallStack() {
    110         String apiCallStack = "";
    111         try {
    112             throw new RuntimeException();
    113         } catch (RuntimeException e) {
    114             final StackTraceElement[] frames = e.getStackTrace();
    115             for (int j = 1; j < frames.length; ++j) {
    116                 final String tempCallStack = frames[j].toString();
    117                 if (TextUtils.isEmpty(apiCallStack)) {
    118                     // Overwrite apiCallStack if it's empty
    119                     apiCallStack = tempCallStack;
    120                 } else if (tempCallStack.indexOf("Transact(") < 0) {
    121                     // Overwrite apiCallStack if it's not a binder call
    122                     apiCallStack = tempCallStack;
    123                 } else {
    124                     break;
    125                 }
    126             }
    127         }
    128         return apiCallStack;
    129     }
    130     // ----------------------------------------------------------------------
    131 
    132     public static boolean isSystemIme(InputMethodInfo inputMethod) {
    133         return (inputMethod.getServiceInfo().applicationInfo.flags
    134                 & ApplicationInfo.FLAG_SYSTEM) != 0;
    135     }
    136 
    137     public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
    138             final Context context, final boolean checkDefaultAttribute,
    139             @Nullable final Locale requiredLocale, final boolean checkCountry,
    140             final String requiredSubtypeMode) {
    141         if (!isSystemIme(imi)) {
    142             return false;
    143         }
    144         if (checkDefaultAttribute && !imi.isDefault(context)) {
    145             return false;
    146         }
    147         if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) {
    148             return false;
    149         }
    150         return true;
    151     }
    152 
    153     @Nullable
    154     public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
    155             final Context context) {
    156         // At first, find the fallback locale from the IMEs that are declared as "default" in the
    157         // current locale.  Note that IME developers can declare an IME as "default" only for
    158         // some particular locales but "not default" for other locales.
    159         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
    160             for (int i = 0; i < imis.size(); ++i) {
    161                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
    162                         true /* checkDefaultAttribute */, fallbackLocale,
    163                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
    164                     return fallbackLocale;
    165                 }
    166             }
    167         }
    168         // If no fallback locale is found in the above condition, find fallback locales regardless
    169         // of the "default" attribute as a last resort.
    170         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
    171             for (int i = 0; i < imis.size(); ++i) {
    172                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
    173                         false /* checkDefaultAttribute */, fallbackLocale,
    174                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
    175                     return fallbackLocale;
    176                 }
    177             }
    178         }
    179         Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray()));
    180         return null;
    181     }
    182 
    183     private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi,
    184             final Context context, final boolean checkDefaultAttribute) {
    185         if (!isSystemIme(imi)) {
    186             return false;
    187         }
    188         if (checkDefaultAttribute && !imi.isDefault(context)) {
    189             return false;
    190         }
    191         if (!imi.isAuxiliaryIme()) {
    192             return false;
    193         }
    194         final int subtypeCount = imi.getSubtypeCount();
    195         for (int i = 0; i < subtypeCount; ++i) {
    196             final InputMethodSubtype s = imi.getSubtypeAt(i);
    197             if (s.overridesImplicitlyEnabledSubtype()) {
    198                 return true;
    199             }
    200         }
    201         return false;
    202     }
    203 
    204     public static Locale getSystemLocaleFromContext(final Context context) {
    205         try {
    206             return context.getResources().getConfiguration().locale;
    207         } catch (Resources.NotFoundException ex) {
    208             return null;
    209         }
    210     }
    211 
    212     private static final class InputMethodListBuilder {
    213         // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration
    214         // order can have non-trivial effect in the call sites.
    215         @NonNull
    216         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
    217 
    218         public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis,
    219                 final Context context, final boolean checkDefaultAttribute,
    220                 @Nullable final Locale locale, final boolean checkCountry,
    221                 final String requiredSubtypeMode) {
    222             for (int i = 0; i < imis.size(); ++i) {
    223                 final InputMethodInfo imi = imis.get(i);
    224                 if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
    225                         checkCountry, requiredSubtypeMode)) {
    226                     mInputMethodSet.add(imi);
    227                 }
    228             }
    229             return this;
    230         }
    231 
    232         // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
    233         // documented more clearly.
    234         public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis,
    235                 final Context context) {
    236             // If one or more auxiliary input methods are available, OK to stop populating the list.
    237             for (final InputMethodInfo imi : mInputMethodSet) {
    238                 if (imi.isAuxiliaryIme()) {
    239                     return this;
    240                 }
    241             }
    242             boolean added = false;
    243             for (int i = 0; i < imis.size(); ++i) {
    244                 final InputMethodInfo imi = imis.get(i);
    245                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
    246                         true /* checkDefaultAttribute */)) {
    247                     mInputMethodSet.add(imi);
    248                     added = true;
    249                 }
    250             }
    251             if (added) {
    252                 return this;
    253             }
    254             for (int i = 0; i < imis.size(); ++i) {
    255                 final InputMethodInfo imi = imis.get(i);
    256                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
    257                         false /* checkDefaultAttribute */)) {
    258                     mInputMethodSet.add(imi);
    259                 }
    260             }
    261             return this;
    262         }
    263 
    264         public boolean isEmpty() {
    265             return mInputMethodSet.isEmpty();
    266         }
    267 
    268         @NonNull
    269         public ArrayList<InputMethodInfo> build() {
    270             return new ArrayList<>(mInputMethodSet);
    271         }
    272     }
    273 
    274     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
    275             final ArrayList<InputMethodInfo> imis, final Context context,
    276             @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) {
    277         // Once the system becomes ready, we pick up at least one keyboard in the following order.
    278         // Secondary users fall into this category in general.
    279         // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
    280         // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false
    281         // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
    282         // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
    283         // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
    284         // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
    285         // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
    286 
    287         final InputMethodListBuilder builder = new InputMethodListBuilder();
    288         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
    289                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    290         if (!builder.isEmpty()) {
    291             return builder;
    292         }
    293         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
    294                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    295         if (!builder.isEmpty()) {
    296             return builder;
    297         }
    298         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
    299                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    300         if (!builder.isEmpty()) {
    301             return builder;
    302         }
    303         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
    304                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    305         if (!builder.isEmpty()) {
    306             return builder;
    307         }
    308         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
    309                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    310         if (!builder.isEmpty()) {
    311             return builder;
    312         }
    313         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
    314                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
    315         if (!builder.isEmpty()) {
    316             return builder;
    317         }
    318         Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
    319                 + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale);
    320         return builder;
    321     }
    322 
    323     public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
    324             Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
    325         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
    326         // We will primarily rely on the system locale, but also keep relying on the fallback locale
    327         // as a last resort.
    328         // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
    329         // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
    330         // subtype)
    331         final Locale systemLocale = getSystemLocaleFromContext(context);
    332         final InputMethodListBuilder builder =
    333                 getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale);
    334         if (!onlyMinimum) {
    335             builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
    336                     true /* checkCountry */, SUBTYPE_MODE_ANY)
    337                     .fillAuxiliaryImes(imis, context);
    338         }
    339         return builder.build();
    340     }
    341 
    342     public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
    343             Context context, ArrayList<InputMethodInfo> imis) {
    344         return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
    345     }
    346 
    347     public static Locale constructLocaleFromString(String localeStr) {
    348         if (TextUtils.isEmpty(localeStr)) {
    349             return null;
    350         }
    351         // TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(languageTag)}.
    352         String[] localeParams = localeStr.split("_", 3);
    353         if (localeParams.length >= 1 && "tl".equals(localeParams[0])) {
    354              // Convert a locale whose language is "tl" to one whose language is "fil".
    355              // For example, "tl_PH" will get converted to "fil_PH".
    356              // Versions of Android earlier than Lollipop did not support three letter language
    357              // codes, and used "tl" (Tagalog) as the language string for "fil" (Filipino).
    358              // On Lollipop and above, the current three letter version must be used.
    359              localeParams[0] = "fil";
    360         }
    361         // The length of localeStr is guaranteed to always return a 1 <= value <= 3
    362         // because localeStr is not empty.
    363         if (localeParams.length == 1) {
    364             return new Locale(localeParams[0]);
    365         } else if (localeParams.length == 2) {
    366             return new Locale(localeParams[0], localeParams[1]);
    367         } else if (localeParams.length == 3) {
    368             return new Locale(localeParams[0], localeParams[1], localeParams[2]);
    369         }
    370         return null;
    371     }
    372 
    373     public static boolean containsSubtypeOf(final InputMethodInfo imi,
    374             @Nullable final Locale locale, final boolean checkCountry, final String mode) {
    375         if (locale == null) {
    376             return false;
    377         }
    378         final int N = imi.getSubtypeCount();
    379         for (int i = 0; i < N; ++i) {
    380             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    381             if (checkCountry) {
    382                 final Locale subtypeLocale = subtype.getLocaleObject();
    383                 if (subtypeLocale == null ||
    384                         !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
    385                         !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
    386                     continue;
    387                 }
    388             } else {
    389                 final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
    390                         subtype.getLocale()));
    391                 if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) {
    392                     continue;
    393                 }
    394             }
    395             if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
    396                     mode.equalsIgnoreCase(subtype.getMode())) {
    397                 return true;
    398             }
    399         }
    400         return false;
    401     }
    402 
    403     public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
    404         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    405         final int subtypeCount = imi.getSubtypeCount();
    406         for (int i = 0; i < subtypeCount; ++i) {
    407             subtypes.add(imi.getSubtypeAt(i));
    408         }
    409         return subtypes;
    410     }
    411 
    412     public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
    413             InputMethodInfo imi, String mode) {
    414         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    415         final int subtypeCount = imi.getSubtypeCount();
    416         for (int i = 0; i < subtypeCount; ++i) {
    417             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    418             if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
    419                 subtypes.add(subtype);
    420             }
    421         }
    422         return subtypes;
    423     }
    424 
    425     public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
    426         if (enabledImes == null || enabledImes.isEmpty()) {
    427             return null;
    428         }
    429         // We'd prefer to fall back on a system IME, since that is safer.
    430         int i = enabledImes.size();
    431         int firstFoundSystemIme = -1;
    432         while (i > 0) {
    433             i--;
    434             final InputMethodInfo imi = enabledImes.get(i);
    435             if (imi.isAuxiliaryIme()) {
    436                 continue;
    437             }
    438             if (InputMethodUtils.isSystemIme(imi)
    439                     && containsSubtypeOf(imi, ENGLISH_LOCALE, false /* checkCountry */,
    440                             SUBTYPE_MODE_KEYBOARD)) {
    441                 return imi;
    442             }
    443             if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)) {
    444                 firstFoundSystemIme = i;
    445             }
    446         }
    447         return enabledImes.get(Math.max(firstFoundSystemIme, 0));
    448     }
    449 
    450     public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
    451         return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
    452     }
    453 
    454     public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
    455         if (imi != null) {
    456             final int subtypeCount = imi.getSubtypeCount();
    457             for (int i = 0; i < subtypeCount; ++i) {
    458                 InputMethodSubtype ims = imi.getSubtypeAt(i);
    459                 if (subtypeHashCode == ims.hashCode()) {
    460                     return i;
    461                 }
    462             }
    463         }
    464         return NOT_A_SUBTYPE_ID;
    465     }
    466 
    467     private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
    468             new LocaleUtils.LocaleExtractor<InputMethodSubtype>() {
    469                 @Override
    470                 public Locale get(InputMethodSubtype source) {
    471                     return source != null ? source.getLocaleObject() : null;
    472                 }
    473             };
    474 
    475     @VisibleForTesting
    476     public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
    477             Resources res, InputMethodInfo imi) {
    478         final LocaleList systemLocales = res.getConfiguration().getLocales();
    479 
    480         synchronized (sCacheLock) {
    481             // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
    482             // it does not check if subtypes are also identical.
    483             if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) {
    484                 return new ArrayList<>(sCachedResult);
    485             }
    486         }
    487 
    488         // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl().
    489         // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
    490         // LocaleList rather than Resource.
    491         final ArrayList<InputMethodSubtype> result =
    492                 getImplicitlyApplicableSubtypesLockedImpl(res, imi);
    493         synchronized (sCacheLock) {
    494             // Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
    495             sCachedSystemLocales = systemLocales;
    496             sCachedInputMethodInfo = imi;
    497             sCachedResult = new ArrayList<>(result);
    498         }
    499         return result;
    500     }
    501 
    502     private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
    503             Resources res, InputMethodInfo imi) {
    504         final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
    505         final LocaleList systemLocales = res.getConfiguration().getLocales();
    506         final String systemLocale = systemLocales.get(0).toString();
    507         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>();
    508         final int numSubtypes = subtypes.size();
    509 
    510         // Handle overridesImplicitlyEnabledSubtype mechanism.
    511         final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new HashMap<>();
    512         for (int i = 0; i < numSubtypes; ++i) {
    513             // scan overriding implicitly enabled subtypes.
    514             final InputMethodSubtype subtype = subtypes.get(i);
    515             if (subtype.overridesImplicitlyEnabledSubtype()) {
    516                 final String mode = subtype.getMode();
    517                 if (!applicableModeAndSubtypesMap.containsKey(mode)) {
    518                     applicableModeAndSubtypesMap.put(mode, subtype);
    519                 }
    520             }
    521         }
    522         if (applicableModeAndSubtypesMap.size() > 0) {
    523             return new ArrayList<>(applicableModeAndSubtypesMap.values());
    524         }
    525 
    526         final HashMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap =
    527                 new HashMap<>();
    528         final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>();
    529 
    530         for (int i = 0; i < numSubtypes; ++i) {
    531             final InputMethodSubtype subtype = subtypes.get(i);
    532             final String mode = subtype.getMode();
    533             if (SUBTYPE_MODE_KEYBOARD.equals(mode)) {
    534                 keyboardSubtypes.add(subtype);
    535             } else {
    536                 if (!nonKeyboardSubtypesMap.containsKey(mode)) {
    537                     nonKeyboardSubtypesMap.put(mode, new ArrayList<>());
    538                 }
    539                 nonKeyboardSubtypesMap.get(mode).add(subtype);
    540             }
    541         }
    542 
    543         final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>();
    544         LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales,
    545                 applicableSubtypes);
    546 
    547         if (!applicableSubtypes.isEmpty()) {
    548             boolean hasAsciiCapableKeyboard = false;
    549             final int numApplicationSubtypes = applicableSubtypes.size();
    550             for (int i = 0; i < numApplicationSubtypes; ++i) {
    551                 final InputMethodSubtype subtype = applicableSubtypes.get(i);
    552                 if (subtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
    553                     hasAsciiCapableKeyboard = true;
    554                     break;
    555                 }
    556             }
    557             if (!hasAsciiCapableKeyboard) {
    558                 final int numKeyboardSubtypes = keyboardSubtypes.size();
    559                 for (int i = 0; i < numKeyboardSubtypes; ++i) {
    560                     final InputMethodSubtype subtype = keyboardSubtypes.get(i);
    561                     final String mode = subtype.getMode();
    562                     if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
    563                             TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
    564                         applicableSubtypes.add(subtype);
    565                     }
    566                 }
    567             }
    568         }
    569 
    570         if (applicableSubtypes.isEmpty()) {
    571             InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
    572                     res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
    573             if (lastResortKeyboardSubtype != null) {
    574                 applicableSubtypes.add(lastResortKeyboardSubtype);
    575             }
    576         }
    577 
    578         // For each non-keyboard mode, extract subtypes with system locales.
    579         for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) {
    580             LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales,
    581                     applicableSubtypes);
    582         }
    583 
    584         return applicableSubtypes;
    585     }
    586 
    587     /**
    588      * Returns the language component of a given locale string.
    589      * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
    590      */
    591     public static String getLanguageFromLocaleString(String locale) {
    592         final int idx = locale.indexOf('_');
    593         if (idx < 0) {
    594             return locale;
    595         } else {
    596             return locale.substring(0, idx);
    597         }
    598     }
    599 
    600     /**
    601      * If there are no selected subtypes, tries finding the most applicable one according to the
    602      * given locale.
    603      * @param subtypes this function will search the most applicable subtype in subtypes
    604      * @param mode subtypes will be filtered by mode
    605      * @param locale subtypes will be filtered by locale
    606      * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
    607      * it will return the first subtype matched with mode
    608      * @return the most applicable subtypeId
    609      */
    610     public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
    611             Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
    612             boolean canIgnoreLocaleAsLastResort) {
    613         if (subtypes == null || subtypes.size() == 0) {
    614             return null;
    615         }
    616         if (TextUtils.isEmpty(locale)) {
    617             locale = res.getConfiguration().locale.toString();
    618         }
    619         final String language = getLanguageFromLocaleString(locale);
    620         boolean partialMatchFound = false;
    621         InputMethodSubtype applicableSubtype = null;
    622         InputMethodSubtype firstMatchedModeSubtype = null;
    623         final int N = subtypes.size();
    624         for (int i = 0; i < N; ++i) {
    625             InputMethodSubtype subtype = subtypes.get(i);
    626             final String subtypeLocale = subtype.getLocale();
    627             final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
    628             // An applicable subtype should match "mode". If mode is null, mode will be ignored,
    629             // and all subtypes with all modes can be candidates.
    630             if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
    631                 if (firstMatchedModeSubtype == null) {
    632                     firstMatchedModeSubtype = subtype;
    633                 }
    634                 if (locale.equals(subtypeLocale)) {
    635                     // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
    636                     applicableSubtype = subtype;
    637                     break;
    638                 } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
    639                     // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
    640                     applicableSubtype = subtype;
    641                     partialMatchFound = true;
    642                 }
    643             }
    644         }
    645 
    646         if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
    647             return firstMatchedModeSubtype;
    648         }
    649 
    650         // The first subtype applicable to the system locale will be defined as the most applicable
    651         // subtype.
    652         if (DEBUG) {
    653             if (applicableSubtype != null) {
    654                 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
    655                         + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
    656             }
    657         }
    658         return applicableSubtype;
    659     }
    660 
    661     public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
    662         if (subtype == null) return true;
    663         return !subtype.isAuxiliary();
    664     }
    665 
    666     public static void setNonSelectedSystemImesDisabledUntilUsed(
    667             IPackageManager packageManager, List<InputMethodInfo> enabledImis,
    668             int userId, String callingPackage) {
    669         if (DEBUG) {
    670             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
    671         }
    672         final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
    673                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
    674         if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
    675             return;
    676         }
    677         // Only the current spell checker should be treated as an enabled one.
    678         final SpellCheckerInfo currentSpellChecker =
    679                 TextServicesManager.getInstance().getCurrentSpellChecker();
    680         for (final String packageName : systemImesDisabledUntilUsed) {
    681             if (DEBUG) {
    682                 Slog.d(TAG, "check " + packageName);
    683             }
    684             boolean enabledIme = false;
    685             for (int j = 0; j < enabledImis.size(); ++j) {
    686                 final InputMethodInfo imi = enabledImis.get(j);
    687                 if (packageName.equals(imi.getPackageName())) {
    688                     enabledIme = true;
    689                     break;
    690                 }
    691             }
    692             if (enabledIme) {
    693                 // enabled ime. skip
    694                 continue;
    695             }
    696             if (currentSpellChecker != null
    697                     && packageName.equals(currentSpellChecker.getPackageName())) {
    698                 // enabled spell checker. skip
    699                 if (DEBUG) {
    700                     Slog.d(TAG, packageName + " is the current spell checker. skip");
    701                 }
    702                 continue;
    703             }
    704             ApplicationInfo ai = null;
    705             try {
    706                 ai = packageManager.getApplicationInfo(packageName,
    707                         PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
    708             } catch (RemoteException e) {
    709                 Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName
    710                         + " userId=" + userId, e);
    711                 continue;
    712             }
    713             if (ai == null) {
    714                 // No app found for packageName
    715                 continue;
    716             }
    717             final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    718             if (!isSystemPackage) {
    719                 continue;
    720             }
    721             setDisabledUntilUsed(packageManager, packageName, userId, callingPackage);
    722         }
    723     }
    724 
    725     private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName,
    726             int userId, String callingPackage) {
    727         final int state;
    728         try {
    729             state = packageManager.getApplicationEnabledSetting(packageName, userId);
    730         } catch (RemoteException e) {
    731             Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName
    732                     + " userId=" + userId, e);
    733             return;
    734         }
    735         if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
    736                 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
    737             if (DEBUG) {
    738                 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
    739             }
    740             try {
    741                 packageManager.setApplicationEnabledSetting(packageName,
    742                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
    743                         0 /* newState */, userId, callingPackage);
    744             } catch (RemoteException e) {
    745                 Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName
    746                         + " userId=" + userId + " callingPackage=" + callingPackage, e);
    747                 return;
    748             }
    749         } else {
    750             if (DEBUG) {
    751                 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
    752             }
    753         }
    754     }
    755 
    756     public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
    757             InputMethodSubtype subtype) {
    758         final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
    759         return subtype != null
    760                 ? TextUtils.concat(subtype.getDisplayName(context,
    761                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
    762                                 (TextUtils.isEmpty(imiLabel) ?
    763                                         "" : " - " + imiLabel))
    764                 : imiLabel;
    765     }
    766 
    767     /**
    768      * Returns true if a package name belongs to a UID.
    769      *
    770      * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p>
    771      * @param appOpsManager the {@link AppOpsManager} object to be used for the validation.
    772      * @param uid the UID to be validated.
    773      * @param packageName the package name.
    774      * @return {@code true} if the package name belongs to the UID.
    775      */
    776     public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager,
    777             final int uid, final String packageName) {
    778         try {
    779             appOpsManager.checkPackage(uid, packageName);
    780             return true;
    781         } catch (SecurityException e) {
    782             return false;
    783         }
    784     }
    785 
    786     /**
    787      * Parses the setting stored input methods and subtypes string value.
    788      *
    789      * @param inputMethodsAndSubtypesString The input method subtypes value stored in settings.
    790      * @return Map from input method ID to set of input method subtypes IDs.
    791      */
    792     @VisibleForTesting
    793     public static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString(
    794             @Nullable final String inputMethodsAndSubtypesString) {
    795 
    796         final ArrayMap<String, ArraySet<String>> imeMap = new ArrayMap<>();
    797         if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
    798             return imeMap;
    799         }
    800 
    801         final SimpleStringSplitter typeSplitter =
    802                 new SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
    803         final SimpleStringSplitter subtypeSplitter =
    804                 new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
    805 
    806         List<Pair<String, ArrayList<String>>> allImeSettings =
    807                 InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString,
    808                         typeSplitter,
    809                         subtypeSplitter);
    810         for (Pair<String, ArrayList<String>> ime : allImeSettings) {
    811             ArraySet<String> subtypes = new ArraySet<>();
    812             if (ime.second != null) {
    813                 subtypes.addAll(ime.second);
    814             }
    815             imeMap.put(ime.first, subtypes);
    816         }
    817         return imeMap;
    818     }
    819 
    820     @NonNull
    821     public static String buildInputMethodsAndSubtypesString(
    822             @NonNull final ArrayMap<String, ArraySet<String>> map) {
    823         // we want to use the canonical InputMethodSettings implementation,
    824         // so we convert data structures first.
    825         List<Pair<String, ArrayList<String>>> imeMap = new ArrayList<>(4);
    826         for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) {
    827             final String imeName = entry.getKey();
    828             final ArraySet<String> subtypeSet = entry.getValue();
    829             final ArrayList<String> subtypes = new ArrayList<>(2);
    830             if (subtypeSet != null) {
    831                 subtypes.addAll(subtypeSet);
    832             }
    833             imeMap.add(new Pair<>(imeName, subtypes));
    834         }
    835         return InputMethodSettings.buildInputMethodsSettingString(imeMap);
    836     }
    837 
    838     /**
    839      * Utility class for putting and getting settings for InputMethod
    840      * TODO: Move all putters and getters of settings to this class.
    841      */
    842     public static class InputMethodSettings {
    843         private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
    844                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
    845 
    846         private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
    847                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
    848 
    849         private final Resources mRes;
    850         private final ContentResolver mResolver;
    851         private final HashMap<String, InputMethodInfo> mMethodMap;
    852 
    853         /**
    854          * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
    855          */
    856         private final HashMap<String, String> mCopyOnWriteDataStore = new HashMap<>();
    857 
    858         private boolean mCopyOnWrite = false;
    859         @NonNull
    860         private String mEnabledInputMethodsStrCache = "";
    861         @UserIdInt
    862         private int mCurrentUserId;
    863         private int[] mCurrentProfileIds = new int[0];
    864 
    865         private static void buildEnabledInputMethodsSettingString(
    866                 StringBuilder builder, Pair<String, ArrayList<String>> ime) {
    867             builder.append(ime.first);
    868             // Inputmethod and subtypes are saved in the settings as follows:
    869             // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
    870             for (String subtypeId: ime.second) {
    871                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
    872             }
    873         }
    874 
    875         public static String buildInputMethodsSettingString(
    876                 List<Pair<String, ArrayList<String>>> allImeSettingsMap) {
    877             final StringBuilder b = new StringBuilder();
    878             boolean needsSeparator = false;
    879             for (Pair<String, ArrayList<String>> ime : allImeSettingsMap) {
    880                 if (needsSeparator) {
    881                     b.append(INPUT_METHOD_SEPARATOR);
    882                 }
    883                 buildEnabledInputMethodsSettingString(b, ime);
    884                 needsSeparator = true;
    885             }
    886             return b.toString();
    887         }
    888 
    889         public static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
    890                 String enabledInputMethodsStr,
    891                 TextUtils.SimpleStringSplitter inputMethodSplitter,
    892                 TextUtils.SimpleStringSplitter subtypeSplitter) {
    893             ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
    894             if (TextUtils.isEmpty(enabledInputMethodsStr)) {
    895                 return imsList;
    896             }
    897             inputMethodSplitter.setString(enabledInputMethodsStr);
    898             while (inputMethodSplitter.hasNext()) {
    899                 String nextImsStr = inputMethodSplitter.next();
    900                 subtypeSplitter.setString(nextImsStr);
    901                 if (subtypeSplitter.hasNext()) {
    902                     ArrayList<String> subtypeHashes = new ArrayList<>();
    903                     // The first element is ime id.
    904                     String imeId = subtypeSplitter.next();
    905                     while (subtypeSplitter.hasNext()) {
    906                         subtypeHashes.add(subtypeSplitter.next());
    907                     }
    908                     imsList.add(new Pair<>(imeId, subtypeHashes));
    909                 }
    910             }
    911             return imsList;
    912         }
    913 
    914         public InputMethodSettings(
    915                 Resources res, ContentResolver resolver,
    916                 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
    917                 @UserIdInt int userId, boolean copyOnWrite) {
    918             mRes = res;
    919             mResolver = resolver;
    920             mMethodMap = methodMap;
    921             switchCurrentUser(userId, copyOnWrite);
    922         }
    923 
    924         /**
    925          * Must be called when the current user is changed.
    926          *
    927          * @param userId The user ID.
    928          * @param copyOnWrite If {@code true}, for each settings key
    929          * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
    930          * settings on the {@link Settings.Secure} until we do the first write operation.
    931          */
    932         public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
    933             if (DEBUG) {
    934                 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
    935             }
    936             if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
    937                 mCopyOnWriteDataStore.clear();
    938                 mEnabledInputMethodsStrCache = "";
    939                 // TODO: mCurrentProfileIds should be cleared here.
    940             }
    941             mCurrentUserId = userId;
    942             mCopyOnWrite = copyOnWrite;
    943             // TODO: mCurrentProfileIds should be updated here.
    944         }
    945 
    946         private void putString(@NonNull final String key, @Nullable final String str) {
    947             if (mCopyOnWrite) {
    948                 mCopyOnWriteDataStore.put(key, str);
    949             } else {
    950                 Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
    951             }
    952         }
    953 
    954         @Nullable
    955         private String getString(@NonNull final String key, @Nullable final String defaultValue) {
    956             final String result;
    957             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
    958                 result = mCopyOnWriteDataStore.get(key);
    959             } else {
    960                 result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
    961             }
    962             return result != null ? result : defaultValue;
    963         }
    964 
    965         private void putInt(final String key, final int value) {
    966             if (mCopyOnWrite) {
    967                 mCopyOnWriteDataStore.put(key, String.valueOf(value));
    968             } else {
    969                 Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
    970             }
    971         }
    972 
    973         private int getInt(final String key, final int defaultValue) {
    974             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
    975                 final String result = mCopyOnWriteDataStore.get(key);
    976                 return result != null ? Integer.parseInt(result) : 0;
    977             }
    978             return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
    979         }
    980 
    981         private void putBoolean(final String key, final boolean value) {
    982             putInt(key, value ? 1 : 0);
    983         }
    984 
    985         private boolean getBoolean(final String key, final boolean defaultValue) {
    986             return getInt(key, defaultValue ? 1 : 0) == 1;
    987         }
    988 
    989         public void setCurrentProfileIds(int[] currentProfileIds) {
    990             synchronized (this) {
    991                 mCurrentProfileIds = currentProfileIds;
    992             }
    993         }
    994 
    995         public boolean isCurrentProfile(int userId) {
    996             synchronized (this) {
    997                 if (userId == mCurrentUserId) return true;
    998                 for (int i = 0; i < mCurrentProfileIds.length; i++) {
    999                     if (userId == mCurrentProfileIds[i]) return true;
   1000                 }
   1001                 return false;
   1002             }
   1003         }
   1004 
   1005         public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
   1006             return createEnabledInputMethodListLocked(
   1007                     getEnabledInputMethodsAndSubtypeListLocked());
   1008         }
   1009 
   1010         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
   1011                 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
   1012             List<InputMethodSubtype> enabledSubtypes =
   1013                     getEnabledInputMethodSubtypeListLocked(imi);
   1014             if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
   1015                 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
   1016                         context.getResources(), imi);
   1017             }
   1018             return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
   1019         }
   1020 
   1021         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
   1022                 InputMethodInfo imi) {
   1023             List<Pair<String, ArrayList<String>>> imsList =
   1024                     getEnabledInputMethodsAndSubtypeListLocked();
   1025             ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
   1026             if (imi != null) {
   1027                 for (Pair<String, ArrayList<String>> imsPair : imsList) {
   1028                     InputMethodInfo info = mMethodMap.get(imsPair.first);
   1029                     if (info != null && info.getId().equals(imi.getId())) {
   1030                         final int subtypeCount = info.getSubtypeCount();
   1031                         for (int i = 0; i < subtypeCount; ++i) {
   1032                             InputMethodSubtype ims = info.getSubtypeAt(i);
   1033                             for (String s: imsPair.second) {
   1034                                 if (String.valueOf(ims.hashCode()).equals(s)) {
   1035                                     enabledSubtypes.add(ims);
   1036                                 }
   1037                             }
   1038                         }
   1039                         break;
   1040                     }
   1041                 }
   1042             }
   1043             return enabledSubtypes;
   1044         }
   1045 
   1046         public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
   1047             return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
   1048                     mInputMethodSplitter,
   1049                     mSubtypeSplitter);
   1050         }
   1051 
   1052         public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
   1053             if (reloadInputMethodStr) {
   1054                 getEnabledInputMethodsStr();
   1055             }
   1056             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
   1057                 // Add in the newly enabled input method.
   1058                 putEnabledInputMethodsStr(id);
   1059             } else {
   1060                 putEnabledInputMethodsStr(
   1061                         mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATOR + id);
   1062             }
   1063         }
   1064 
   1065         /**
   1066          * Build and put a string of EnabledInputMethods with removing specified Id.
   1067          * @return the specified id was removed or not.
   1068          */
   1069         public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
   1070                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
   1071             boolean isRemoved = false;
   1072             boolean needsAppendSeparator = false;
   1073             for (Pair<String, ArrayList<String>> ims: imsList) {
   1074                 String curId = ims.first;
   1075                 if (curId.equals(id)) {
   1076                     // We are disabling this input method, and it is
   1077                     // currently enabled.  Skip it to remove from the
   1078                     // new list.
   1079                     isRemoved = true;
   1080                 } else {
   1081                     if (needsAppendSeparator) {
   1082                         builder.append(INPUT_METHOD_SEPARATOR);
   1083                     } else {
   1084                         needsAppendSeparator = true;
   1085                     }
   1086                     buildEnabledInputMethodsSettingString(builder, ims);
   1087                 }
   1088             }
   1089             if (isRemoved) {
   1090                 // Update the setting with the new list of input methods.
   1091                 putEnabledInputMethodsStr(builder.toString());
   1092             }
   1093             return isRemoved;
   1094         }
   1095 
   1096         private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
   1097                 List<Pair<String, ArrayList<String>>> imsList) {
   1098             final ArrayList<InputMethodInfo> res = new ArrayList<>();
   1099             for (Pair<String, ArrayList<String>> ims: imsList) {
   1100                 InputMethodInfo info = mMethodMap.get(ims.first);
   1101                 if (info != null && !info.isVrOnly()) {
   1102                     res.add(info);
   1103                 }
   1104             }
   1105             return res;
   1106         }
   1107 
   1108         private void putEnabledInputMethodsStr(@Nullable String str) {
   1109             if (DEBUG) {
   1110                 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
   1111             }
   1112             if (TextUtils.isEmpty(str)) {
   1113                 // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
   1114                 // empty data scenario.
   1115                 putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
   1116             } else {
   1117                 putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
   1118             }
   1119             // TODO: Update callers of putEnabledInputMethodsStr to make str @NonNull.
   1120             mEnabledInputMethodsStrCache = (str != null ? str : "");
   1121         }
   1122 
   1123         @NonNull
   1124         public String getEnabledInputMethodsStr() {
   1125             mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
   1126             if (DEBUG) {
   1127                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
   1128                         + ", " + mCurrentUserId);
   1129             }
   1130             return mEnabledInputMethodsStrCache;
   1131         }
   1132 
   1133         private void saveSubtypeHistory(
   1134                 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
   1135             StringBuilder builder = new StringBuilder();
   1136             boolean isImeAdded = false;
   1137             if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
   1138                 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
   1139                         newSubtypeId);
   1140                 isImeAdded = true;
   1141             }
   1142             for (Pair<String, String> ime: savedImes) {
   1143                 String imeId = ime.first;
   1144                 String subtypeId = ime.second;
   1145                 if (TextUtils.isEmpty(subtypeId)) {
   1146                     subtypeId = NOT_A_SUBTYPE_ID_STR;
   1147                 }
   1148                 if (isImeAdded) {
   1149                     builder.append(INPUT_METHOD_SEPARATOR);
   1150                 } else {
   1151                     isImeAdded = true;
   1152                 }
   1153                 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
   1154                         subtypeId);
   1155             }
   1156             // Remove the last INPUT_METHOD_SEPARATOR
   1157             putSubtypeHistoryStr(builder.toString());
   1158         }
   1159 
   1160         private void addSubtypeToHistory(String imeId, String subtypeId) {
   1161             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
   1162             for (Pair<String, String> ime: subtypeHistory) {
   1163                 if (ime.first.equals(imeId)) {
   1164                     if (DEBUG) {
   1165                         Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
   1166                                 + ime.second);
   1167                     }
   1168                     // We should break here
   1169                     subtypeHistory.remove(ime);
   1170                     break;
   1171                 }
   1172             }
   1173             if (DEBUG) {
   1174                 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
   1175             }
   1176             saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
   1177         }
   1178 
   1179         private void putSubtypeHistoryStr(@NonNull String str) {
   1180             if (DEBUG) {
   1181                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
   1182             }
   1183             if (TextUtils.isEmpty(str)) {
   1184                 // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
   1185                 // data scenario.
   1186                 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
   1187             } else {
   1188                 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
   1189             }
   1190         }
   1191 
   1192         public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
   1193             // Gets the first one from the history
   1194             return getLastSubtypeForInputMethodLockedInternal(null);
   1195         }
   1196 
   1197         public String getLastSubtypeForInputMethodLocked(String imeId) {
   1198             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
   1199             if (ime != null) {
   1200                 return ime.second;
   1201             } else {
   1202                 return null;
   1203             }
   1204         }
   1205 
   1206         private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
   1207             List<Pair<String, ArrayList<String>>> enabledImes =
   1208                     getEnabledInputMethodsAndSubtypeListLocked();
   1209             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
   1210             for (Pair<String, String> imeAndSubtype : subtypeHistory) {
   1211                 final String imeInTheHistory = imeAndSubtype.first;
   1212                 // If imeId is empty, returns the first IME and subtype in the history
   1213                 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
   1214                     final String subtypeInTheHistory = imeAndSubtype.second;
   1215                     final String subtypeHashCode =
   1216                             getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
   1217                                     enabledImes, imeInTheHistory, subtypeInTheHistory);
   1218                     if (!TextUtils.isEmpty(subtypeHashCode)) {
   1219                         if (DEBUG) {
   1220                             Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
   1221                         }
   1222                         return new Pair<>(imeInTheHistory, subtypeHashCode);
   1223                     }
   1224                 }
   1225             }
   1226             if (DEBUG) {
   1227                 Slog.d(TAG, "No enabled IME found in the history");
   1228             }
   1229             return null;
   1230         }
   1231 
   1232         private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
   1233                 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
   1234             for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
   1235                 if (enabledIme.first.equals(imeId)) {
   1236                     final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
   1237                     final InputMethodInfo imi = mMethodMap.get(imeId);
   1238                     if (explicitlyEnabledSubtypes.size() == 0) {
   1239                         // If there are no explicitly enabled subtypes, applicable subtypes are
   1240                         // enabled implicitly.
   1241                         // If IME is enabled and no subtypes are enabled, applicable subtypes
   1242                         // are enabled implicitly, so needs to treat them to be enabled.
   1243                         if (imi != null && imi.getSubtypeCount() > 0) {
   1244                             List<InputMethodSubtype> implicitlySelectedSubtypes =
   1245                                     getImplicitlyApplicableSubtypesLocked(mRes, imi);
   1246                             if (implicitlySelectedSubtypes != null) {
   1247                                 final int N = implicitlySelectedSubtypes.size();
   1248                                 for (int i = 0; i < N; ++i) {
   1249                                     final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
   1250                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
   1251                                         return subtypeHashCode;
   1252                                     }
   1253                                 }
   1254                             }
   1255                         }
   1256                     } else {
   1257                         for (String s: explicitlyEnabledSubtypes) {
   1258                             if (s.equals(subtypeHashCode)) {
   1259                                 // If both imeId and subtypeId are enabled, return subtypeId.
   1260                                 try {
   1261                                     final int hashCode = Integer.parseInt(subtypeHashCode);
   1262                                     // Check whether the subtype id is valid or not
   1263                                     if (isValidSubtypeId(imi, hashCode)) {
   1264                                         return s;
   1265                                     } else {
   1266                                         return NOT_A_SUBTYPE_ID_STR;
   1267                                     }
   1268                                 } catch (NumberFormatException e) {
   1269                                     return NOT_A_SUBTYPE_ID_STR;
   1270                                 }
   1271                             }
   1272                         }
   1273                     }
   1274                     // If imeId was enabled but subtypeId was disabled.
   1275                     return NOT_A_SUBTYPE_ID_STR;
   1276                 }
   1277             }
   1278             // If both imeId and subtypeId are disabled, return null
   1279             return null;
   1280         }
   1281 
   1282         private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
   1283             ArrayList<Pair<String, String>> imsList = new ArrayList<>();
   1284             final String subtypeHistoryStr = getSubtypeHistoryStr();
   1285             if (TextUtils.isEmpty(subtypeHistoryStr)) {
   1286                 return imsList;
   1287             }
   1288             mInputMethodSplitter.setString(subtypeHistoryStr);
   1289             while (mInputMethodSplitter.hasNext()) {
   1290                 String nextImsStr = mInputMethodSplitter.next();
   1291                 mSubtypeSplitter.setString(nextImsStr);
   1292                 if (mSubtypeSplitter.hasNext()) {
   1293                     String subtypeId = NOT_A_SUBTYPE_ID_STR;
   1294                     // The first element is ime id.
   1295                     String imeId = mSubtypeSplitter.next();
   1296                     while (mSubtypeSplitter.hasNext()) {
   1297                         subtypeId = mSubtypeSplitter.next();
   1298                         break;
   1299                     }
   1300                     imsList.add(new Pair<>(imeId, subtypeId));
   1301                 }
   1302             }
   1303             return imsList;
   1304         }
   1305 
   1306         @NonNull
   1307         private String getSubtypeHistoryStr() {
   1308             final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
   1309             if (DEBUG) {
   1310                 Slog.d(TAG, "getSubtypeHistoryStr: " + history);
   1311             }
   1312             return history;
   1313         }
   1314 
   1315         public void putSelectedInputMethod(String imeId) {
   1316             if (DEBUG) {
   1317                 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
   1318                         + mCurrentUserId);
   1319             }
   1320             putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
   1321         }
   1322 
   1323         public void putSelectedSubtype(int subtypeId) {
   1324             if (DEBUG) {
   1325                 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
   1326                         + mCurrentUserId);
   1327             }
   1328             putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
   1329         }
   1330 
   1331         @Nullable
   1332         public String getSelectedInputMethod() {
   1333             final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
   1334             if (DEBUG) {
   1335                 Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
   1336             }
   1337             return imi;
   1338         }
   1339 
   1340         public boolean isSubtypeSelected() {
   1341             return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
   1342         }
   1343 
   1344         private int getSelectedInputMethodSubtypeHashCode() {
   1345             return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
   1346         }
   1347 
   1348         public boolean isShowImeWithHardKeyboardEnabled() {
   1349             return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
   1350         }
   1351 
   1352         public void setShowImeWithHardKeyboard(boolean show) {
   1353             putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
   1354         }
   1355 
   1356         @UserIdInt
   1357         public int getCurrentUserId() {
   1358             return mCurrentUserId;
   1359         }
   1360 
   1361         public int getSelectedInputMethodSubtypeId(String selectedImiId) {
   1362             final InputMethodInfo imi = mMethodMap.get(selectedImiId);
   1363             if (imi == null) {
   1364                 return NOT_A_SUBTYPE_ID;
   1365             }
   1366             final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
   1367             return getSubtypeIdFromHashCode(imi, subtypeHashCode);
   1368         }
   1369 
   1370         public void saveCurrentInputMethodAndSubtypeToHistory(
   1371                 String curMethodId, InputMethodSubtype currentSubtype) {
   1372             String subtypeId = NOT_A_SUBTYPE_ID_STR;
   1373             if (currentSubtype != null) {
   1374                 subtypeId = String.valueOf(currentSubtype.hashCode());
   1375             }
   1376             if (canAddToLastInputMethod(currentSubtype)) {
   1377                 addSubtypeToHistory(curMethodId, subtypeId);
   1378             }
   1379         }
   1380 
   1381         public HashMap<InputMethodInfo, List<InputMethodSubtype>>
   1382                 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) {
   1383             HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
   1384                     new HashMap<>();
   1385             for (InputMethodInfo imi: getEnabledInputMethodListLocked()) {
   1386                 enabledInputMethodAndSubtypes.put(
   1387                         imi, getEnabledInputMethodSubtypeListLocked(context, imi, true));
   1388             }
   1389             return enabledInputMethodAndSubtypes;
   1390         }
   1391 
   1392         public void dumpLocked(final Printer pw, final String prefix) {
   1393             pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
   1394             pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
   1395             pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
   1396             pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache);
   1397         }
   1398     }
   1399 
   1400     // For spell checker service manager.
   1401     // TODO: Should we have TextServicesUtils.java?
   1402     private static final Locale LOCALE_EN_US = new Locale("en", "US");
   1403     private static final Locale LOCALE_EN_GB = new Locale("en", "GB");
   1404 
   1405     /**
   1406      * Returns a list of {@link Locale} in the order of appropriateness for the default spell
   1407      * checker service.
   1408      *
   1409      * <p>If the system language is English, and the region is also explicitly specified in the
   1410      * system locale, the following fallback order will be applied.</p>
   1411      * <ul>
   1412      * <li>(system-locale-language, system-locale-region, system-locale-variant) (if exists)</li>
   1413      * <li>(system-locale-language, system-locale-region)</li>
   1414      * <li>("en", "US")</li>
   1415      * <li>("en", "GB")</li>
   1416      * <li>("en")</li>
   1417      * </ul>
   1418      *
   1419      * <p>If the system language is English, but no region is specified in the system locale,
   1420      * the following fallback order will be applied.</p>
   1421      * <ul>
   1422      * <li>("en")</li>
   1423      * <li>("en", "US")</li>
   1424      * <li>("en", "GB")</li>
   1425      * </ul>
   1426      *
   1427      * <p>If the system language is not English, the following fallback order will be applied.</p>
   1428      * <ul>
   1429      * <li>(system-locale-language, system-locale-region, system-locale-variant) (if exists)</li>
   1430      * <li>(system-locale-language, system-locale-region) (if exists)</li>
   1431      * <li>(system-locale-language) (if exists)</li>
   1432      * <li>("en", "US")</li>
   1433      * <li>("en", "GB")</li>
   1434      * <li>("en")</li>
   1435      * </ul>
   1436      *
   1437      * @param systemLocale the current system locale to be taken into consideration.
   1438      * @return a list of {@link Locale}. The first one is considered to be most appropriate.
   1439      */
   1440     @VisibleForTesting
   1441     public static ArrayList<Locale> getSuitableLocalesForSpellChecker(
   1442             @Nullable final Locale systemLocale) {
   1443         final Locale systemLocaleLanguageCountryVariant;
   1444         final Locale systemLocaleLanguageCountry;
   1445         final Locale systemLocaleLanguage;
   1446         if (systemLocale != null) {
   1447             final String language = systemLocale.getLanguage();
   1448             final boolean hasLanguage = !TextUtils.isEmpty(language);
   1449             final String country = systemLocale.getCountry();
   1450             final boolean hasCountry = !TextUtils.isEmpty(country);
   1451             final String variant = systemLocale.getVariant();
   1452             final boolean hasVariant = !TextUtils.isEmpty(variant);
   1453             if (hasLanguage && hasCountry && hasVariant) {
   1454                 systemLocaleLanguageCountryVariant = new Locale(language, country, variant);
   1455             } else {
   1456                 systemLocaleLanguageCountryVariant = null;
   1457             }
   1458             if (hasLanguage && hasCountry) {
   1459                 systemLocaleLanguageCountry = new Locale(language, country);
   1460             } else {
   1461                 systemLocaleLanguageCountry = null;
   1462             }
   1463             if (hasLanguage) {
   1464                 systemLocaleLanguage = new Locale(language);
   1465             } else {
   1466                 systemLocaleLanguage = null;
   1467             }
   1468         } else {
   1469             systemLocaleLanguageCountryVariant = null;
   1470             systemLocaleLanguageCountry = null;
   1471             systemLocaleLanguage = null;
   1472         }
   1473 
   1474         final ArrayList<Locale> locales = new ArrayList<>();
   1475         if (systemLocaleLanguageCountryVariant != null) {
   1476             locales.add(systemLocaleLanguageCountryVariant);
   1477         }
   1478 
   1479         if (Locale.ENGLISH.equals(systemLocaleLanguage)) {
   1480             if (systemLocaleLanguageCountry != null) {
   1481                 // If the system language is English, and the region is also explicitly specified,
   1482                 // following fallback order will be applied.
   1483                 // - systemLocaleLanguageCountry [if systemLocaleLanguageCountry is non-null]
   1484                 // - en_US [if systemLocaleLanguageCountry is non-null and not en_US]
   1485                 // - en_GB [if systemLocaleLanguageCountry is non-null and not en_GB]
   1486                 // - en
   1487                 if (systemLocaleLanguageCountry != null) {
   1488                     locales.add(systemLocaleLanguageCountry);
   1489                 }
   1490                 if (!LOCALE_EN_US.equals(systemLocaleLanguageCountry)) {
   1491                     locales.add(LOCALE_EN_US);
   1492                 }
   1493                 if (!LOCALE_EN_GB.equals(systemLocaleLanguageCountry)) {
   1494                     locales.add(LOCALE_EN_GB);
   1495                 }
   1496                 locales.add(Locale.ENGLISH);
   1497             } else {
   1498                 // If the system language is English, but no region is specified, following
   1499                 // fallback order will be applied.
   1500                 // - en
   1501                 // - en_US
   1502                 // - en_GB
   1503                 locales.add(Locale.ENGLISH);
   1504                 locales.add(LOCALE_EN_US);
   1505                 locales.add(LOCALE_EN_GB);
   1506             }
   1507         } else {
   1508             // If the system language is not English, the fallback order will be
   1509             // - systemLocaleLanguageCountry  [if non-null]
   1510             // - systemLocaleLanguage  [if non-null]
   1511             // - en_US
   1512             // - en_GB
   1513             // - en
   1514             if (systemLocaleLanguageCountry != null) {
   1515                 locales.add(systemLocaleLanguageCountry);
   1516             }
   1517             if (systemLocaleLanguage != null) {
   1518                 locales.add(systemLocaleLanguage);
   1519             }
   1520             locales.add(LOCALE_EN_US);
   1521             locales.add(LOCALE_EN_GB);
   1522             locales.add(Locale.ENGLISH);
   1523         }
   1524         return locales;
   1525     }
   1526 
   1527     public static boolean isSoftInputModeStateVisibleAllowed(
   1528             int targetSdkVersion, int controlFlags) {
   1529         if (targetSdkVersion < Build.VERSION_CODES.P) {
   1530             // for compatibility.
   1531             return true;
   1532         }
   1533         if ((controlFlags & CONTROL_WINDOW_VIEW_HAS_FOCUS) == 0) {
   1534             return false;
   1535         }
   1536         if ((controlFlags & CONTROL_WINDOW_IS_TEXT_EDITOR) == 0) {
   1537             return false;
   1538         }
   1539         return true;
   1540     }
   1541 
   1542 }
   1543