Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.inputmethod;
     18 
     19 import android.content.Context;
     20 import android.content.pm.ApplicationInfo;
     21 import android.content.pm.ResolveInfo;
     22 import android.content.pm.ServiceInfo;
     23 import android.content.res.Configuration;
     24 import android.content.res.Resources;
     25 import android.os.LocaleList;
     26 import android.os.Parcel;
     27 import android.test.InstrumentationTestCase;
     28 import android.test.suitebuilder.annotation.SmallTest;
     29 import android.util.ArrayMap;
     30 import android.util.ArraySet;
     31 import android.view.inputmethod.InputMethodInfo;
     32 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
     33 import android.view.inputmethod.InputMethodSubtype;
     34 
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 import java.util.Locale;
     38 import java.util.Objects;
     39 
     40 import static org.hamcrest.MatcherAssert.assertThat;
     41 import static org.hamcrest.Matchers.isIn;
     42 import static org.hamcrest.Matchers.not;
     43 
     44 public class InputMethodUtilsTest extends InstrumentationTestCase {
     45     private static final boolean IS_AUX = true;
     46     private static final boolean IS_DEFAULT = true;
     47     private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true;
     48     private static final boolean IS_ASCII_CAPABLE = true;
     49     private static final boolean IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = true;
     50     private static final boolean IS_SYSTEM_READY = true;
     51     private static final Locale LOCALE_EN = new Locale("en");
     52     private static final Locale LOCALE_EN_US = new Locale("en", "US");
     53     private static final Locale LOCALE_EN_GB = new Locale("en", "GB");
     54     private static final Locale LOCALE_EN_IN = new Locale("en", "IN");
     55     private static final Locale LOCALE_FI = new Locale("fi");
     56     private static final Locale LOCALE_FI_FI = new Locale("fi", "FI");
     57     private static final Locale LOCALE_FIL = new Locale("fil");
     58     private static final Locale LOCALE_FIL_PH = new Locale("fil", "PH");
     59     private static final Locale LOCALE_FR = new Locale("fr");
     60     private static final Locale LOCALE_FR_CA = new Locale("fr", "CA");
     61     private static final Locale LOCALE_HI = new Locale("hi");
     62     private static final Locale LOCALE_JA = new Locale("ja");
     63     private static final Locale LOCALE_JA_JP = new Locale("ja", "JP");
     64     private static final Locale LOCALE_ZH_CN = new Locale("zh", "CN");
     65     private static final Locale LOCALE_ZH_TW = new Locale("zh", "TW");
     66     private static final Locale LOCALE_IN = new Locale("in");
     67     private static final Locale LOCALE_ID = new Locale("id");
     68     private static final Locale LOCALE_TH = new Locale("ht");
     69     private static final Locale LOCALE_TH_TH = new Locale("ht", "TH");
     70     private static final Locale LOCALE_TH_TH_TH = new Locale("ht", "TH", "TH");
     71     private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
     72     private static final String SUBTYPE_MODE_VOICE = "voice";
     73     private static final String SUBTYPE_MODE_HANDWRITING = "handwriting";
     74     private static final String SUBTYPE_MODE_ANY = null;
     75     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
     76     private static final String EXTRA_VALUE_ASCII_CAPABLE = "AsciiCapable";
     77     private static final String EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
     78             "EnabledWhenDefaultIsNotAsciiCapable";
     79 
     80     @SmallTest
     81     public void testVoiceImes() throws Exception {
     82         // locale: en_US
     83         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
     84                 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
     85         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
     86                 "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
     87                 "DummyNonDefaultAutoVoiceIme1");
     88 
     89         // locale: en_GB
     90         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
     91                 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
     92         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
     93                 "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
     94                 "DummyNonDefaultAutoVoiceIme1");
     95 
     96         // locale: ja_JP
     97         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
     98                 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
     99         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
    100                 "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
    101                 "DummyNonDefaultAutoVoiceIme1");
    102     }
    103 
    104     @SmallTest
    105     public void testKeyboardImes() throws Exception {
    106         // locale: en_US
    107         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
    108                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
    109 
    110         // locale: en_GB
    111         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
    112                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
    113 
    114         // locale: en_IN
    115         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
    116                 "com.android.apps.inputmethod.hindi",
    117                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
    118 
    119         // locale: hi
    120         assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
    121                 "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin",
    122                 "com.android.apps.inputmethod.voice");
    123 
    124         // locale: ja_JP
    125         assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
    126                 "com.android.apps.inputmethod.japanese", "com.android.apps.inputmethod.voice");
    127 
    128         // locale: zh_CN
    129         assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
    130                 "com.android.apps.inputmethod.pinyin", "com.android.apps.inputmethod.voice");
    131 
    132         // locale: zh_TW
    133         // Note: In this case, no IME is suitable for the system locale. Hence we will pick up a
    134         // fallback IME regardless of the "default" attribute.
    135         assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
    136                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
    137     }
    138 
    139     @SmallTest
    140     public void testParcelable() throws Exception {
    141         final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes("en-rUS");
    142         final List<InputMethodInfo> clonedList = cloneViaParcel(originalList);
    143         assertNotNull(clonedList);
    144         final List<InputMethodInfo> clonedClonedList = cloneViaParcel(clonedList);
    145         assertNotNull(clonedClonedList);
    146         assertEquals(originalList, clonedList);
    147         assertEquals(clonedList, clonedClonedList);
    148         assertEquals(originalList.size(), clonedList.size());
    149         assertEquals(clonedList.size(), clonedClonedList.size());
    150         for (int imeIndex = 0; imeIndex < originalList.size(); ++imeIndex) {
    151             verifyEquality(originalList.get(imeIndex), clonedList.get(imeIndex));
    152             verifyEquality(clonedList.get(imeIndex), clonedClonedList.get(imeIndex));
    153         }
    154     }
    155 
    156     @SmallTest
    157     public void testGetImplicitlyApplicableSubtypesLocked() throws Exception {
    158         final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
    159                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    160                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    161         final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
    162                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    163                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    164         final InputMethodSubtype nonAutoEnIN = createDummyInputMethodSubtype("en_IN",
    165                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    166                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    167         final InputMethodSubtype nonAutoFrCA = createDummyInputMethodSubtype("fr_CA",
    168                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    169                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    170         final InputMethodSubtype nonAutoFr = createDummyInputMethodSubtype("fr_CA",
    171                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    172                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    173         final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
    174                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    175                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    176         final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
    177                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    178                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    179         final InputMethodSubtype nonAutoId = createDummyInputMethodSubtype("id",
    180                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    181                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    182         final InputMethodSubtype autoSubtype = createDummyInputMethodSubtype("auto",
    183                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    184                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    185         final InputMethodSubtype nonAutoJa = createDummyInputMethodSubtype("ja",
    186                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    187                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    188         final InputMethodSubtype nonAutoHi = createDummyInputMethodSubtype("hi",
    189                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    190                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    191         final InputMethodSubtype nonAutoSrCyrl = createDummyInputMethodSubtype("sr",
    192                 "sr-Cyrl", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    193                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    194                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    195         final InputMethodSubtype nonAutoSrLatn = createDummyInputMethodSubtype("sr_ZZ",
    196                 "sr-Latn", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    197                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
    198                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    199         final InputMethodSubtype nonAutoHandwritingEn = createDummyInputMethodSubtype("en",
    200                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    201                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    202         final InputMethodSubtype nonAutoHandwritingFr = createDummyInputMethodSubtype("fr",
    203                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    204                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    205         final InputMethodSubtype nonAutoHandwritingSrCyrl = createDummyInputMethodSubtype("sr",
    206                 "sr-Cyrl", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
    207                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    208                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    209         final InputMethodSubtype nonAutoHandwritingSrLatn = createDummyInputMethodSubtype("sr_ZZ",
    210                 "sr-Latn", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
    211                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    212                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    213         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype =
    214                 createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    215                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    216                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    217         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2 =
    218                 createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    219                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    220                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    221 
    222         // Make sure that an automatic subtype (overridesImplicitlyEnabledSubtype:true) is
    223         // selected no matter what locale is specified.
    224         {
    225             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    226             subtypes.add(nonAutoEnUS);
    227             subtypes.add(nonAutoEnGB);
    228             subtypes.add(nonAutoJa);
    229             subtypes.add(nonAutoFil);
    230             subtypes.add(autoSubtype);  // overridesImplicitlyEnabledSubtype == true
    231             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    232             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
    233             subtypes.add(nonAutoHandwritingEn);
    234             subtypes.add(nonAutoHandwritingFr);
    235             final InputMethodInfo imi = createDummyInputMethodInfo(
    236                     "com.android.apps.inputmethod.latin",
    237                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    238                     subtypes);
    239             final ArrayList<InputMethodSubtype> result =
    240                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    241                             getResourcesForLocales(LOCALE_EN_US), imi);
    242             assertEquals(1, result.size());
    243             verifyEquality(autoSubtype, result.get(0));
    244         }
    245 
    246         // Make sure that a subtype whose locale is exactly equal to the specified locale is
    247         // selected as long as there is no no automatic subtype
    248         // (overridesImplicitlyEnabledSubtype:true) in the given list.
    249         {
    250             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    251             subtypes.add(nonAutoEnUS);  // locale == "en_US"
    252             subtypes.add(nonAutoEnGB);
    253             subtypes.add(nonAutoJa);
    254             subtypes.add(nonAutoFil);
    255             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    256             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
    257             subtypes.add(nonAutoHandwritingEn);
    258             subtypes.add(nonAutoHandwritingFr);
    259             final InputMethodInfo imi = createDummyInputMethodInfo(
    260                     "com.android.apps.inputmethod.latin",
    261                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    262                     subtypes);
    263             final ArrayList<InputMethodSubtype> result =
    264                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    265                             getResourcesForLocales(LOCALE_EN_US), imi);
    266             assertEquals(2, result.size());
    267             verifyEquality(nonAutoEnUS, result.get(0));
    268             verifyEquality(nonAutoHandwritingEn, result.get(1));
    269         }
    270 
    271         // Make sure that a subtype whose locale is exactly equal to the specified locale is
    272         // selected as long as there is no automatic subtype
    273         // (overridesImplicitlyEnabledSubtype:true) in the given list.
    274         {
    275             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    276             subtypes.add(nonAutoEnUS);
    277             subtypes.add(nonAutoEnGB); // locale == "en_GB"
    278             subtypes.add(nonAutoJa);
    279             subtypes.add(nonAutoFil);
    280             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    281             subtypes.add(nonAutoHandwritingEn);
    282             subtypes.add(nonAutoHandwritingFr);
    283             final InputMethodInfo imi = createDummyInputMethodInfo(
    284                     "com.android.apps.inputmethod.latin",
    285                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    286                     subtypes);
    287             final ArrayList<InputMethodSubtype> result =
    288                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    289                             getResourcesForLocales(LOCALE_EN_GB), imi);
    290             assertEquals(2, result.size());
    291             verifyEquality(nonAutoEnGB, result.get(0));
    292             verifyEquality(nonAutoHandwritingEn, result.get(1));
    293         }
    294 
    295         // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and
    296         // any subtype whose locale is exactly equal to the specified locale in the given list,
    297         // try to find a subtype whose language is equal to the language part of the given locale.
    298         // Here make sure that a subtype (locale: "fr_CA") can be found with locale: "fr".
    299         {
    300             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    301             subtypes.add(nonAutoFrCA);  // locale == "fr_CA"
    302             subtypes.add(nonAutoJa);
    303             subtypes.add(nonAutoFil);
    304             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    305             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
    306             subtypes.add(nonAutoHandwritingEn);
    307             subtypes.add(nonAutoHandwritingFr);
    308             final InputMethodInfo imi = createDummyInputMethodInfo(
    309                     "com.android.apps.inputmethod.latin",
    310                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    311                     subtypes);
    312             final ArrayList<InputMethodSubtype> result =
    313                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    314                             getResourcesForLocales(LOCALE_FR), imi);
    315             assertEquals(2, result.size());
    316             verifyEquality(nonAutoFrCA, result.get(0));
    317             verifyEquality(nonAutoHandwritingFr, result.get(1));
    318         }
    319         // Then make sure that a subtype (locale: "fr") can be found with locale: "fr_CA".
    320         {
    321             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    322             subtypes.add(nonAutoFr);  // locale == "fr"
    323             subtypes.add(nonAutoJa);
    324             subtypes.add(nonAutoFil);
    325             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    326             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
    327             subtypes.add(nonAutoHandwritingEn);
    328             subtypes.add(nonAutoHandwritingFr);
    329             final InputMethodInfo imi = createDummyInputMethodInfo(
    330                     "com.android.apps.inputmethod.latin",
    331                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    332                     subtypes);
    333             final ArrayList<InputMethodSubtype> result =
    334                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    335                             getResourcesForLocales(LOCALE_FR_CA), imi);
    336             assertEquals(2, result.size());
    337             verifyEquality(nonAutoFrCA, result.get(0));
    338             verifyEquality(nonAutoHandwritingFr, result.get(1));
    339         }
    340 
    341         // Make sure that subtypes which have "EnabledWhenDefaultIsNotAsciiCapable" in its
    342         // extra value is selected if and only if all other selected IMEs are not AsciiCapable.
    343         {
    344             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    345             subtypes.add(nonAutoEnUS);
    346             subtypes.add(nonAutoJa);    // not ASCII capable
    347             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    348             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
    349             subtypes.add(nonAutoHandwritingEn);
    350             subtypes.add(nonAutoHandwritingFr);
    351             final InputMethodInfo imi = createDummyInputMethodInfo(
    352                     "com.android.apps.inputmethod.latin",
    353                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    354                     subtypes);
    355             final ArrayList<InputMethodSubtype> result =
    356                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    357                             getResourcesForLocales(LOCALE_JA_JP), imi);
    358             assertEquals(3, result.size());
    359             verifyEquality(nonAutoJa, result.get(0));
    360             verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1));
    361             verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2, result.get(2));
    362         }
    363 
    364         // Make sure that if there is no subtype that matches the language requested, then we just
    365         // use the first keyboard subtype.
    366         {
    367             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    368             subtypes.add(nonAutoHi);
    369             subtypes.add(nonAutoEnUS);
    370             subtypes.add(nonAutoHandwritingEn);
    371             subtypes.add(nonAutoHandwritingFr);
    372             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    373             final InputMethodInfo imi = createDummyInputMethodInfo(
    374                     "com.android.apps.inputmethod.latin",
    375                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    376                     subtypes);
    377             final ArrayList<InputMethodSubtype> result =
    378                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    379                             getResourcesForLocales(LOCALE_JA_JP), imi);
    380             assertEquals(1, result.size());
    381             verifyEquality(nonAutoHi, result.get(0));
    382         }
    383         {
    384             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    385             subtypes.add(nonAutoEnUS);
    386             subtypes.add(nonAutoHi);
    387             subtypes.add(nonAutoHandwritingEn);
    388             subtypes.add(nonAutoHandwritingFr);
    389             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    390             final InputMethodInfo imi = createDummyInputMethodInfo(
    391                     "com.android.apps.inputmethod.latin",
    392                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    393                     subtypes);
    394             final ArrayList<InputMethodSubtype> result =
    395                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    396                             getResourcesForLocales(LOCALE_JA_JP), imi);
    397             assertEquals(1, result.size());
    398             verifyEquality(nonAutoEnUS, result.get(0));
    399         }
    400         {
    401             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    402             subtypes.add(nonAutoHandwritingEn);
    403             subtypes.add(nonAutoHandwritingFr);
    404             subtypes.add(nonAutoEnUS);
    405             subtypes.add(nonAutoHi);
    406             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    407             final InputMethodInfo imi = createDummyInputMethodInfo(
    408                     "com.android.apps.inputmethod.latin",
    409                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    410                     subtypes);
    411             final ArrayList<InputMethodSubtype> result =
    412                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    413                             getResourcesForLocales(LOCALE_JA_JP), imi);
    414             assertEquals(1, result.size());
    415             verifyEquality(nonAutoEnUS, result.get(0));
    416         }
    417 
    418         // Make sure that both language and script are taken into account to find the best matching
    419         // subtype.
    420         {
    421             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    422             subtypes.add(nonAutoEnUS);
    423             subtypes.add(nonAutoSrCyrl);
    424             subtypes.add(nonAutoSrLatn);
    425             subtypes.add(nonAutoHandwritingEn);
    426             subtypes.add(nonAutoHandwritingFr);
    427             subtypes.add(nonAutoHandwritingSrCyrl);
    428             subtypes.add(nonAutoHandwritingSrLatn);
    429             final InputMethodInfo imi = createDummyInputMethodInfo(
    430                     "com.android.apps.inputmethod.latin",
    431                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    432                     subtypes);
    433             final ArrayList<InputMethodSubtype> result =
    434                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    435                             getResourcesForLocales(Locale.forLanguageTag("sr-Latn-RS")), imi);
    436             assertEquals(2, result.size());
    437             assertThat(nonAutoSrLatn, isIn(result));
    438             assertThat(nonAutoHandwritingSrLatn, isIn(result));
    439         }
    440         {
    441             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    442             subtypes.add(nonAutoEnUS);
    443             subtypes.add(nonAutoSrCyrl);
    444             subtypes.add(nonAutoSrLatn);
    445             subtypes.add(nonAutoHandwritingEn);
    446             subtypes.add(nonAutoHandwritingFr);
    447             subtypes.add(nonAutoHandwritingSrCyrl);
    448             subtypes.add(nonAutoHandwritingSrLatn);
    449             final InputMethodInfo imi = createDummyInputMethodInfo(
    450                     "com.android.apps.inputmethod.latin",
    451                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    452                     subtypes);
    453             final ArrayList<InputMethodSubtype> result =
    454                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    455                             getResourcesForLocales(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
    456             assertEquals(2, result.size());
    457             assertThat(nonAutoSrCyrl, isIn(result));
    458             assertThat(nonAutoHandwritingSrCyrl, isIn(result));
    459         }
    460 
    461         // Make sure that secondary locales are taken into account to find the best matching
    462         // subtype.
    463         {
    464             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    465             subtypes.add(nonAutoEnUS);
    466             subtypes.add(nonAutoEnGB);
    467             subtypes.add(nonAutoSrCyrl);
    468             subtypes.add(nonAutoSrLatn);
    469             subtypes.add(nonAutoFr);
    470             subtypes.add(nonAutoFrCA);
    471             subtypes.add(nonAutoHandwritingEn);
    472             subtypes.add(nonAutoHandwritingFr);
    473             subtypes.add(nonAutoHandwritingSrCyrl);
    474             subtypes.add(nonAutoHandwritingSrLatn);
    475             final InputMethodInfo imi = createDummyInputMethodInfo(
    476                     "com.android.apps.inputmethod.latin",
    477                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    478                     subtypes);
    479             final ArrayList<InputMethodSubtype> result =
    480                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    481                             getResourcesForLocales(
    482                                     Locale.forLanguageTag("sr-Latn-RS-x-android"),
    483                                     Locale.forLanguageTag("ja-JP"),
    484                                     Locale.forLanguageTag("fr-FR"),
    485                                     Locale.forLanguageTag("en-GB"),
    486                                     Locale.forLanguageTag("en-US")),
    487                             imi);
    488             assertEquals(6, result.size());
    489             assertThat(nonAutoEnGB, isIn(result));
    490             assertThat(nonAutoFr, isIn(result));
    491             assertThat(nonAutoSrLatn, isIn(result));
    492             assertThat(nonAutoHandwritingEn, isIn(result));
    493             assertThat(nonAutoHandwritingFr, isIn(result));
    494             assertThat(nonAutoHandwritingSrLatn, isIn(result));
    495         }
    496 
    497         // Make sure that 3-letter language code can be handled.
    498         {
    499             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    500             subtypes.add(nonAutoEnUS);
    501             subtypes.add(nonAutoFil);
    502             final InputMethodInfo imi = createDummyInputMethodInfo(
    503                     "com.android.apps.inputmethod.latin",
    504                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    505                     subtypes);
    506             final ArrayList<InputMethodSubtype> result =
    507                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    508                             getResourcesForLocales(LOCALE_FIL_PH), imi);
    509             assertEquals(1, result.size());
    510             verifyEquality(nonAutoFil, result.get(0));
    511         }
    512 
    513         // Make sure that we never end up matching "fi" (finnish) with "fil" (filipino).
    514         // Also make sure that the first subtype will be used as the last-resort candidate.
    515         {
    516             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    517             subtypes.add(nonAutoJa);
    518             subtypes.add(nonAutoEnUS);
    519             subtypes.add(nonAutoFil);
    520             final InputMethodInfo imi = createDummyInputMethodInfo(
    521                     "com.android.apps.inputmethod.latin",
    522                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    523                     subtypes);
    524             final ArrayList<InputMethodSubtype> result =
    525                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    526                             getResourcesForLocales(LOCALE_FI), imi);
    527             assertEquals(1, result.size());
    528             verifyEquality(nonAutoJa, result.get(0));
    529         }
    530 
    531         // Make sure that "in" and "id" conversion is taken into account.
    532         {
    533             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    534             subtypes.add(nonAutoIn);
    535             subtypes.add(nonAutoEnUS);
    536             final InputMethodInfo imi = createDummyInputMethodInfo(
    537                     "com.android.apps.inputmethod.latin",
    538                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    539                     subtypes);
    540             final ArrayList<InputMethodSubtype> result =
    541                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    542                             getResourcesForLocales(LOCALE_IN), imi);
    543             assertEquals(1, result.size());
    544             verifyEquality(nonAutoIn, result.get(0));
    545         }
    546         {
    547             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    548             subtypes.add(nonAutoIn);
    549             subtypes.add(nonAutoEnUS);
    550             final InputMethodInfo imi = createDummyInputMethodInfo(
    551                     "com.android.apps.inputmethod.latin",
    552                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    553                     subtypes);
    554             final ArrayList<InputMethodSubtype> result =
    555                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    556                             getResourcesForLocales(LOCALE_ID), imi);
    557             assertEquals(1, result.size());
    558             verifyEquality(nonAutoIn, result.get(0));
    559         }
    560         {
    561             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    562             subtypes.add(nonAutoId);
    563             subtypes.add(nonAutoEnUS);
    564             final InputMethodInfo imi = createDummyInputMethodInfo(
    565                     "com.android.apps.inputmethod.latin",
    566                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    567                     subtypes);
    568             final ArrayList<InputMethodSubtype> result =
    569                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    570                             getResourcesForLocales(LOCALE_IN), imi);
    571             assertEquals(1, result.size());
    572             verifyEquality(nonAutoId, result.get(0));
    573         }
    574         {
    575             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    576             subtypes.add(nonAutoId);
    577             subtypes.add(nonAutoEnUS);
    578             final InputMethodInfo imi = createDummyInputMethodInfo(
    579                     "com.android.apps.inputmethod.latin",
    580                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    581                     subtypes);
    582             final ArrayList<InputMethodSubtype> result =
    583                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    584                             getResourcesForLocales(LOCALE_ID), imi);
    585             assertEquals(1, result.size());
    586             verifyEquality(nonAutoId, result.get(0));
    587         }
    588 
    589         // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and the system
    590         // provides multiple locales, we try to enable multiple subtypes.
    591         {
    592             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    593             subtypes.add(nonAutoEnUS);
    594             subtypes.add(nonAutoFrCA);
    595             subtypes.add(nonAutoIn);
    596             subtypes.add(nonAutoJa);
    597             subtypes.add(nonAutoFil);
    598             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
    599             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
    600             final InputMethodInfo imi = createDummyInputMethodInfo(
    601                     "com.android.apps.inputmethod.latin",
    602                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    603                     subtypes);
    604             final ArrayList<InputMethodSubtype> result =
    605                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
    606                             getResourcesForLocales(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
    607             assertThat(nonAutoFrCA, isIn(result));
    608             assertThat(nonAutoEnUS, isIn(result));
    609             assertThat(nonAutoJa, isIn(result));
    610             assertThat(nonAutoIn, not(isIn(result)));
    611             assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(isIn(result)));
    612             assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(isIn(result)));
    613         }
    614     }
    615 
    616     @SmallTest
    617     public void testContainsSubtypeOf() throws Exception {
    618         final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
    619                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    620                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    621         final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
    622                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    623                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    624         final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
    625                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    626                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    627         final InputMethodSubtype nonAutoFilPH = createDummyInputMethodSubtype("fil_PH",
    628                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    629                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    630         final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
    631                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    632                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    633         final InputMethodSubtype nonAutoId = createDummyInputMethodSubtype("id",
    634                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
    635                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    636 
    637         final boolean CHECK_COUNTRY = true;
    638 
    639         {
    640             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    641             subtypes.add(nonAutoEnUS);
    642             final InputMethodInfo imi = createDummyInputMethodInfo(
    643                     "com.android.apps.inputmethod.latin",
    644                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    645                     subtypes);
    646 
    647             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
    648                     SUBTYPE_MODE_KEYBOARD));
    649             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY,
    650                     SUBTYPE_MODE_KEYBOARD));
    651             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
    652                     SUBTYPE_MODE_KEYBOARD));
    653             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
    654                     SUBTYPE_MODE_KEYBOARD));
    655             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
    656                     SUBTYPE_MODE_VOICE));
    657             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
    658                     SUBTYPE_MODE_VOICE));
    659             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
    660                     SUBTYPE_MODE_ANY));
    661             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
    662                     SUBTYPE_MODE_ANY));
    663 
    664             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY,
    665                     SUBTYPE_MODE_KEYBOARD));
    666             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY,
    667                     SUBTYPE_MODE_KEYBOARD));
    668         }
    669 
    670         // Make sure that 3-letter language code ("fil") can be handled.
    671         {
    672             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    673             subtypes.add(nonAutoFil);
    674             final InputMethodInfo imi = createDummyInputMethodInfo(
    675                     "com.android.apps.inputmethod.latin",
    676                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    677                     subtypes);
    678             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
    679                     SUBTYPE_MODE_KEYBOARD));
    680             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
    681                     SUBTYPE_MODE_KEYBOARD));
    682             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
    683                     SUBTYPE_MODE_KEYBOARD));
    684             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
    685                     SUBTYPE_MODE_KEYBOARD));
    686 
    687             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
    688                     SUBTYPE_MODE_KEYBOARD));
    689             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
    690                     SUBTYPE_MODE_KEYBOARD));
    691             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
    692                     SUBTYPE_MODE_KEYBOARD));
    693             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
    694                     SUBTYPE_MODE_KEYBOARD));
    695         }
    696 
    697         // Make sure that 3-letter language code ("fil_PH") can be handled.
    698         {
    699             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    700             subtypes.add(nonAutoFilPH);
    701             final InputMethodInfo imi = createDummyInputMethodInfo(
    702                     "com.android.apps.inputmethod.latin",
    703                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    704                     subtypes);
    705             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
    706                     SUBTYPE_MODE_KEYBOARD));
    707             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
    708                     SUBTYPE_MODE_KEYBOARD));
    709             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
    710                     SUBTYPE_MODE_KEYBOARD));
    711             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
    712                     SUBTYPE_MODE_KEYBOARD));
    713 
    714             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
    715                     SUBTYPE_MODE_KEYBOARD));
    716             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
    717                     SUBTYPE_MODE_KEYBOARD));
    718             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
    719                     SUBTYPE_MODE_KEYBOARD));
    720             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
    721                     SUBTYPE_MODE_KEYBOARD));
    722         }
    723 
    724         // Make sure that a subtype whose locale is "in" can be queried with "id".
    725         {
    726             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    727             subtypes.add(nonAutoIn);
    728             subtypes.add(nonAutoEnUS);
    729             final InputMethodInfo imi = createDummyInputMethodInfo(
    730                     "com.android.apps.inputmethod.latin",
    731                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    732                     subtypes);
    733             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
    734                     SUBTYPE_MODE_KEYBOARD));
    735             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
    736                     SUBTYPE_MODE_KEYBOARD));
    737             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
    738                     SUBTYPE_MODE_KEYBOARD));
    739             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
    740                     SUBTYPE_MODE_KEYBOARD));
    741         }
    742 
    743         // Make sure that a subtype whose locale is "id" can be queried with "in".
    744         {
    745             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    746             subtypes.add(nonAutoId);
    747             subtypes.add(nonAutoEnUS);
    748             final InputMethodInfo imi = createDummyInputMethodInfo(
    749                     "com.android.apps.inputmethod.latin",
    750                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
    751                     subtypes);
    752             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
    753                     SUBTYPE_MODE_KEYBOARD));
    754             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
    755                     SUBTYPE_MODE_KEYBOARD));
    756             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
    757                     SUBTYPE_MODE_KEYBOARD));
    758             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
    759                     SUBTYPE_MODE_KEYBOARD));
    760         }
    761     }
    762 
    763     private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
    764             final Locale systemLocale, String... expectedImeNames) {
    765         final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
    766         final String[] actualImeNames = getPackageNames(
    767                 InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes));
    768         assertEquals(expectedImeNames.length, actualImeNames.length);
    769         for (int i = 0; i < expectedImeNames.length; ++i) {
    770             assertEquals(expectedImeNames[i], actualImeNames[i]);
    771         }
    772     }
    773 
    774     private static List<InputMethodInfo> cloneViaParcel(final List<InputMethodInfo> list) {
    775         Parcel p = null;
    776         try {
    777             p = Parcel.obtain();
    778             p.writeTypedList(list);
    779             p.setDataPosition(0);
    780             return p.createTypedArrayList(InputMethodInfo.CREATOR);
    781         } finally {
    782             if (p != null) {
    783                 p.recycle();
    784             }
    785         }
    786     }
    787 
    788     private Context createTargetContextWithLocales(final LocaleList locales) {
    789         final Configuration resourceConfiguration = new Configuration();
    790         resourceConfiguration.setLocales(locales);
    791         return getInstrumentation()
    792                 .getTargetContext()
    793                 .createConfigurationContext(resourceConfiguration);
    794     }
    795 
    796     private Resources getResourcesForLocales(Locale... locales) {
    797         return createTargetContextWithLocales(new LocaleList(locales)).getResources();
    798     }
    799 
    800     private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
    801         final String[] packageNames = new String[imis.size()];
    802         for (int i = 0; i < imis.size(); ++i) {
    803             packageNames[i] = imis.get(i).getPackageName();
    804         }
    805         return packageNames;
    806     }
    807 
    808     private static void verifyEquality(InputMethodInfo expected, InputMethodInfo actual) {
    809         assertEquals(expected, actual);
    810         assertEquals(expected.getSubtypeCount(), actual.getSubtypeCount());
    811         for (int subtypeIndex = 0; subtypeIndex < expected.getSubtypeCount(); ++subtypeIndex) {
    812             final InputMethodSubtype expectedSubtype = expected.getSubtypeAt(subtypeIndex);
    813             final InputMethodSubtype actualSubtype = actual.getSubtypeAt(subtypeIndex);
    814             verifyEquality(expectedSubtype, actualSubtype);
    815         }
    816     }
    817 
    818     private static void verifyEquality(InputMethodSubtype expected, InputMethodSubtype actual) {
    819         assertEquals(expected, actual);
    820         assertEquals(expected.hashCode(), actual.hashCode());
    821     }
    822 
    823     private static InputMethodInfo createDummyInputMethodInfo(String packageName, String name,
    824             CharSequence label, boolean isAuxIme, boolean isDefault,
    825             List<InputMethodSubtype> subtypes) {
    826         final ResolveInfo ri = new ResolveInfo();
    827         final ServiceInfo si = new ServiceInfo();
    828         final ApplicationInfo ai = new ApplicationInfo();
    829         ai.packageName = packageName;
    830         ai.enabled = true;
    831         ai.flags |= ApplicationInfo.FLAG_SYSTEM;
    832         si.applicationInfo = ai;
    833         si.enabled = true;
    834         si.packageName = packageName;
    835         si.name = name;
    836         si.exported = true;
    837         si.nonLocalizedLabel = label;
    838         ri.serviceInfo = si;
    839         return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
    840     }
    841 
    842     private static InputMethodSubtype createDummyInputMethodSubtype(String locale, String mode,
    843             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
    844             boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable) {
    845         return createDummyInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
    846                 overridesImplicitlyEnabledSubtype, isAsciiCapable,
    847                 isEnabledWhenDefaultIsNotAsciiCapable);
    848     }
    849 
    850     private static InputMethodSubtype createDummyInputMethodSubtype(String locale,
    851             String languageTag, String mode, boolean isAuxiliary,
    852             boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable,
    853             boolean isEnabledWhenDefaultIsNotAsciiCapable) {
    854         final StringBuilder subtypeExtraValue = new StringBuilder();
    855         if (isEnabledWhenDefaultIsNotAsciiCapable) {
    856             subtypeExtraValue.append(EXTRA_VALUE_PAIR_SEPARATOR);
    857             subtypeExtraValue.append(EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
    858         }
    859 
    860         // TODO: Remove following code. InputMethodSubtype#isAsciiCapable() has been publicly
    861         // available since API level 19 (KitKat). We no longer need to rely on extra value.
    862         if (isAsciiCapable) {
    863             subtypeExtraValue.append(EXTRA_VALUE_PAIR_SEPARATOR);
    864             subtypeExtraValue.append(EXTRA_VALUE_ASCII_CAPABLE);
    865         }
    866 
    867         return new InputMethodSubtypeBuilder()
    868                 .setSubtypeNameResId(0)
    869                 .setSubtypeIconResId(0)
    870                 .setSubtypeLocale(locale)
    871                 .setLanguageTag(languageTag)
    872                 .setSubtypeMode(mode)
    873                 .setSubtypeExtraValue(subtypeExtraValue.toString())
    874                 .setIsAuxiliary(isAuxiliary)
    875                 .setOverridesImplicitlyEnabledSubtype(overridesImplicitlyEnabledSubtype)
    876                 .setIsAsciiCapable(isAsciiCapable)
    877                 .build();
    878     }
    879 
    880     private static ArrayList<InputMethodInfo> getImesWithDefaultVoiceIme() {
    881         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
    882         {
    883             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    884             subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
    885                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    886                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    887             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
    888                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    889                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    890             preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultAutoVoiceIme",
    891                     "dummy.voice0", "DummyVoice0", IS_AUX, IS_DEFAULT, subtypes));
    892         }
    893         preinstalledImes.addAll(getImesWithoutDefaultVoiceIme());
    894         return preinstalledImes;
    895     }
    896 
    897     private static ArrayList<InputMethodInfo> getImesWithoutDefaultVoiceIme() {
    898         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
    899         {
    900             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    901             subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
    902                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    903                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    904             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
    905                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    906                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    907             preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme0",
    908                     "dummy.voice1", "DummyVoice1", IS_AUX, !IS_DEFAULT, subtypes));
    909         }
    910         {
    911             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    912             subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
    913                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    914                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    915             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
    916                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    917                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    918             preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme1",
    919                     "dummy.voice2", "DummyVoice2", IS_AUX, !IS_DEFAULT, subtypes));
    920         }
    921         {
    922             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    923             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
    924                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    925                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    926             preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultVoiceIme2",
    927                     "dummy.voice3", "DummyVoice3", IS_AUX, !IS_DEFAULT, subtypes));
    928         }
    929         {
    930             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    931             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    932                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
    933                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    934             preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultEnKeyboardIme",
    935                     "dummy.keyboard0", "DummyKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
    936         }
    937         return preinstalledImes;
    938     }
    939 
    940     private static boolean contains(final String[] textList, final String textToBeChecked) {
    941         if (textList == null) {
    942             return false;
    943         }
    944         for (final String text : textList) {
    945             if (Objects.equals(textToBeChecked, text)) {
    946                 return true;
    947             }
    948         }
    949         return false;
    950     }
    951 
    952     private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) {
    953         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
    954 
    955         // a dummy Voice IME
    956         {
    957             final boolean isDefaultIme = false;
    958             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    959             subtypes.add(createDummyInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
    960                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    961                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    962             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.voice",
    963                     "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, isDefaultIme,
    964                     subtypes));
    965         }
    966         // a dummy Hindi IME
    967         {
    968             final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString);
    969             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    970             // TODO: This subtype should be marked as IS_ASCII_CAPABLE
    971             subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    972                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    973                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    974             subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    975                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    976                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    977             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.hindi",
    978                     "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, isDefaultIme,
    979                     subtypes));
    980         }
    981 
    982         // a dummy Pinyin IME
    983         {
    984             final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString);
    985             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    986             subtypes.add(createDummyInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    987                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
    988                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
    989             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.pinyin",
    990                     "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, isDefaultIme,
    991                     subtypes));
    992         }
    993 
    994         // a dummy Korean IME
    995         {
    996             final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString);
    997             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
    998             subtypes.add(createDummyInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
    999                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
   1000                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
   1001             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.korean",
   1002                     "com.android.apps.inputmethod.korean", "DummyKoreanIme", !IS_AUX, isDefaultIme,
   1003                     subtypes));
   1004         }
   1005 
   1006         // a dummy Latin IME
   1007         {
   1008             final boolean isDefaultIme = contains(
   1009                     new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString);
   1010             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
   1011             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
   1012                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
   1013                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
   1014             subtypes.add(createDummyInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
   1015                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
   1016                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
   1017             subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
   1018                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
   1019                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
   1020             subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
   1021                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
   1022                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
   1023             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.latin",
   1024                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, isDefaultIme,
   1025                     subtypes));
   1026         }
   1027 
   1028         // a dummy Japanese IME
   1029         {
   1030             final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString);
   1031             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
   1032             subtypes.add(createDummyInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
   1033                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
   1034                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
   1035             subtypes.add(createDummyInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
   1036                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
   1037                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
   1038             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.japanese",
   1039                     "com.android.apps.inputmethod.japanese", "DummyJapaneseIme", !IS_AUX,
   1040                     isDefaultIme, subtypes));
   1041         }
   1042 
   1043         return preinstalledImes;
   1044     }
   1045 
   1046     @SmallTest
   1047     public void testGetSuitableLocalesForSpellChecker() throws Exception {
   1048         {
   1049             final ArrayList<Locale> locales =
   1050                     InputMethodUtils.getSuitableLocalesForSpellChecker(LOCALE_EN_US);
   1051             assertEquals(3, locales.size());
   1052             assertEquals(LOCALE_EN_US, locales.get(0));
   1053             assertEquals(LOCALE_EN_GB, locales.get(1));
   1054             assertEquals(LOCALE_EN, locales.get(2));
   1055         }
   1056 
   1057         {
   1058             final ArrayList<Locale> locales =
   1059                     InputMethodUtils.getSuitableLocalesForSpellChecker(LOCALE_EN_GB);
   1060             assertEquals(3, locales.size());
   1061             assertEquals(LOCALE_EN_GB, locales.get(0));
   1062             assertEquals(LOCALE_EN_US, locales.get(1));
   1063             assertEquals(LOCALE_EN, locales.get(2));
   1064         }
   1065 
   1066         {
   1067             final ArrayList<Locale> locales =
   1068                     InputMethodUtils.getSuitableLocalesForSpellChecker(LOCALE_EN);
   1069             assertEquals(3, locales.size());
   1070             assertEquals(LOCALE_EN, locales.get(0));
   1071             assertEquals(LOCALE_EN_US, locales.get(1));
   1072             assertEquals(LOCALE_EN_GB, locales.get(2));
   1073         }
   1074 
   1075         {
   1076             final ArrayList<Locale> locales =
   1077                     InputMethodUtils.getSuitableLocalesForSpellChecker(LOCALE_EN_IN);
   1078             assertEquals(4, locales.size());
   1079             assertEquals(LOCALE_EN_IN, locales.get(0));
   1080             assertEquals(LOCALE_EN_US, locales.get(1));
   1081             assertEquals(LOCALE_EN_GB, locales.get(2));
   1082             assertEquals(LOCALE_EN, locales.get(3));
   1083         }
   1084 
   1085         {
   1086             final ArrayList<Locale> locales =
   1087                     InputMethodUtils.getSuitableLocalesForSpellChecker(LOCALE_JA_JP);
   1088             assertEquals(5, locales.size());
   1089             assertEquals(LOCALE_JA_JP, locales.get(0));
   1090             assertEquals(LOCALE_JA, locales.get(1));
   1091             assertEquals(LOCALE_EN_US, locales.get(2));
   1092             assertEquals(LOCALE_EN_GB, locales.get(3));
   1093             assertEquals(Locale.ENGLISH, locales.get(4));
   1094         }
   1095 
   1096         // Test 3-letter language code.
   1097         {
   1098             final ArrayList<Locale> locales =
   1099                     InputMethodUtils.getSuitableLocalesForSpellChecker(LOCALE_FIL_PH);
   1100             assertEquals(5, locales.size());
   1101             assertEquals(LOCALE_FIL_PH, locales.get(0));
   1102             assertEquals(LOCALE_FIL, locales.get(1));
   1103             assertEquals(LOCALE_EN_US, locales.get(2));
   1104             assertEquals(LOCALE_EN_GB, locales.get(3));
   1105             assertEquals(Locale.ENGLISH, locales.get(4));
   1106         }
   1107 
   1108         // Test variant.
   1109         {
   1110             final ArrayList<Locale> locales =
   1111                     InputMethodUtils.getSuitableLocalesForSpellChecker(LOCALE_TH_TH_TH);
   1112             assertEquals(6, locales.size());
   1113             assertEquals(LOCALE_TH_TH_TH, locales.get(0));
   1114             assertEquals(LOCALE_TH_TH, locales.get(1));
   1115             assertEquals(LOCALE_TH, locales.get(2));
   1116             assertEquals(LOCALE_EN_US, locales.get(3));
   1117             assertEquals(LOCALE_EN_GB, locales.get(4));
   1118             assertEquals(Locale.ENGLISH, locales.get(5));
   1119         }
   1120 
   1121         // Test Locale extension.
   1122         {
   1123             final Locale localeWithoutVariant = LOCALE_JA_JP;
   1124             final Locale localeWithVariant = new Locale.Builder()
   1125                     .setLocale(LOCALE_JA_JP)
   1126                     .setExtension('x', "android")
   1127                     .build();
   1128             assertFalse(localeWithoutVariant.equals(localeWithVariant));
   1129 
   1130             final ArrayList<Locale> locales =
   1131                     InputMethodUtils.getSuitableLocalesForSpellChecker(localeWithVariant);
   1132             assertEquals(5, locales.size());
   1133             assertEquals(LOCALE_JA_JP, locales.get(0));
   1134             assertEquals(LOCALE_JA, locales.get(1));
   1135             assertEquals(LOCALE_EN_US, locales.get(2));
   1136             assertEquals(LOCALE_EN_GB, locales.get(3));
   1137             assertEquals(Locale.ENGLISH, locales.get(4));
   1138         }
   1139     }
   1140 
   1141     @SmallTest
   1142     public void testParseInputMethodsAndSubtypesString() {
   1143         // Trivial cases.
   1144         {
   1145             assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString("").isEmpty());
   1146             assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString(null).isEmpty());
   1147         }
   1148 
   1149         // No subtype cases.
   1150         {
   1151             ArrayMap<String, ArraySet<String>> r =
   1152                     InputMethodUtils.parseInputMethodsAndSubtypesString("ime0");
   1153             assertEquals(1, r.size());
   1154             assertTrue(r.containsKey("ime0"));
   1155             assertTrue(r.get("ime0").isEmpty());
   1156         }
   1157         {
   1158             ArrayMap<String, ArraySet<String>> r =
   1159                     InputMethodUtils.parseInputMethodsAndSubtypesString("ime0:ime1");
   1160             assertEquals(2, r.size());
   1161             assertTrue(r.containsKey("ime0"));
   1162             assertTrue(r.get("ime0").isEmpty());
   1163             assertTrue(r.containsKey("ime1"));
   1164             assertTrue(r.get("ime1").isEmpty());
   1165         }
   1166 
   1167         // Input metho IDs and their subtypes.
   1168         {
   1169             ArrayMap<String, ArraySet<String>> r =
   1170                     InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0");
   1171             assertEquals(1, r.size());
   1172             assertTrue(r.containsKey("ime0"));
   1173             ArraySet<String> subtypes = r.get("ime0");
   1174             assertEquals(1, subtypes.size());
   1175             assertTrue(subtypes.contains("subtype0"));
   1176         }
   1177         {
   1178             ArrayMap<String, ArraySet<String>> r =
   1179                     InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype0");
   1180             assertEquals(1, r.size());
   1181             assertTrue(r.containsKey("ime0"));
   1182             ArraySet<String> subtypes = r.get("ime0");
   1183             assertEquals(1, subtypes.size());
   1184             assertTrue(subtypes.contains("subtype0"));
   1185         }
   1186         {
   1187             ArrayMap<String, ArraySet<String>> r =
   1188                     InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype1");
   1189             assertEquals(1, r.size());
   1190             assertTrue(r.containsKey("ime0"));
   1191             ArraySet<String> subtypes = r.get("ime0");
   1192             assertEquals(2, subtypes.size());
   1193             assertTrue(subtypes.contains("subtype0"));
   1194             assertTrue(subtypes.contains("subtype1"));
   1195         }
   1196         {
   1197             ArrayMap<String, ArraySet<String>> r =
   1198                     InputMethodUtils.parseInputMethodsAndSubtypesString(
   1199                             "ime0;subtype0:ime1;subtype1");
   1200             assertEquals(2, r.size());
   1201             assertTrue(r.containsKey("ime0"));
   1202             assertTrue(r.containsKey("ime1"));
   1203             ArraySet<String> subtypes0 = r.get("ime0");
   1204             assertEquals(1, subtypes0.size());
   1205             assertTrue(subtypes0.contains("subtype0"));
   1206 
   1207             ArraySet<String> subtypes1 = r.get("ime1");
   1208             assertEquals(1, subtypes1.size());
   1209             assertTrue(subtypes1.contains("subtype1"));
   1210         }
   1211         {
   1212             ArrayMap<String, ArraySet<String>> r =
   1213                     InputMethodUtils.parseInputMethodsAndSubtypesString(
   1214                             "ime0;subtype0;subtype1:ime1;subtype2");
   1215             assertEquals(2, r.size());
   1216             assertTrue(r.containsKey("ime0"));
   1217             assertTrue(r.containsKey("ime1"));
   1218             ArraySet<String> subtypes0 = r.get("ime0");
   1219             assertEquals(2, subtypes0.size());
   1220             assertTrue(subtypes0.contains("subtype0"));
   1221             assertTrue(subtypes0.contains("subtype1"));
   1222 
   1223             ArraySet<String> subtypes1 = r.get("ime1");
   1224             assertEquals(1, subtypes1.size());
   1225             assertTrue(subtypes1.contains("subtype2"));
   1226         }
   1227         {
   1228             ArrayMap<String, ArraySet<String>> r =
   1229                     InputMethodUtils.parseInputMethodsAndSubtypesString(
   1230                             "ime0;subtype0;subtype1:ime1;subtype1;subtype2");
   1231             assertEquals(2, r.size());
   1232             assertTrue(r.containsKey("ime0"));
   1233             assertTrue(r.containsKey("ime1"));
   1234             ArraySet<String> subtypes0 = r.get("ime0");
   1235             assertEquals(2, subtypes0.size());
   1236             assertTrue(subtypes0.contains("subtype0"));
   1237             assertTrue(subtypes0.contains("subtype1"));
   1238 
   1239             ArraySet<String> subtypes1 = r.get("ime1");
   1240             assertEquals(2, subtypes1.size());
   1241             assertTrue(subtypes0.contains("subtype1"));
   1242             assertTrue(subtypes1.contains("subtype2"));
   1243         }
   1244         {
   1245             ArrayMap<String, ArraySet<String>> r =
   1246                     InputMethodUtils.parseInputMethodsAndSubtypesString(
   1247                             "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
   1248             assertEquals(3, r.size());
   1249             assertTrue(r.containsKey("ime0"));
   1250             assertTrue(r.containsKey("ime1"));
   1251             assertTrue(r.containsKey("ime2"));
   1252             ArraySet<String> subtypes0 = r.get("ime0");
   1253             assertEquals(2, subtypes0.size());
   1254             assertTrue(subtypes0.contains("subtype0"));
   1255             assertTrue(subtypes0.contains("subtype1"));
   1256 
   1257             ArraySet<String> subtypes1 = r.get("ime1");
   1258             assertEquals(2, subtypes1.size());
   1259             assertTrue(subtypes0.contains("subtype1"));
   1260             assertTrue(subtypes1.contains("subtype2"));
   1261 
   1262             ArraySet<String> subtypes2 = r.get("ime2");
   1263             assertTrue(subtypes2.isEmpty());
   1264         }
   1265     }
   1266 
   1267     @SmallTest
   1268     public void testbuildInputMethodsAndSubtypesString() {
   1269         {
   1270             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1271             assertEquals("", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
   1272         }
   1273         {
   1274             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1275             map.put("ime0", new ArraySet<String>());
   1276             assertEquals("ime0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
   1277         }
   1278         {
   1279             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1280             ArraySet<String> subtypes1 = new ArraySet<>();
   1281             subtypes1.add("subtype0");
   1282             map.put("ime0", subtypes1);
   1283             assertEquals("ime0;subtype0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
   1284         }
   1285         {
   1286             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1287             ArraySet<String> subtypes1 = new ArraySet<>();
   1288             subtypes1.add("subtype0");
   1289             subtypes1.add("subtype1");
   1290             map.put("ime0", subtypes1);
   1291 
   1292             // We do not expect what order will be used to concatenate items in
   1293             // InputMethodUtils.buildInputMethodsAndSubtypesString() hence enumerate all possible
   1294             // permutations here.
   1295             ArraySet<String> validSequences = new ArraySet<>();
   1296             validSequences.add("ime0;subtype0;subtype1");
   1297             validSequences.add("ime0;subtype1;subtype0");
   1298             assertTrue(validSequences.contains(
   1299                     InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
   1300         }
   1301         {
   1302             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1303             map.put("ime0", new ArraySet<String>());
   1304             map.put("ime1", new ArraySet<String>());
   1305 
   1306             ArraySet<String> validSequences = new ArraySet<>();
   1307             validSequences.add("ime0:ime1");
   1308             validSequences.add("ime1:ime0");
   1309             assertTrue(validSequences.contains(
   1310                     InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
   1311         }
   1312         {
   1313             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1314             ArraySet<String> subtypes1 = new ArraySet<>();
   1315             subtypes1.add("subtype0");
   1316             map.put("ime0", subtypes1);
   1317             map.put("ime1", new ArraySet<String>());
   1318 
   1319             ArraySet<String> validSequences = new ArraySet<>();
   1320             validSequences.add("ime0;subtype0:ime1");
   1321             validSequences.add("ime1;ime0;subtype0");
   1322             assertTrue(validSequences.contains(
   1323                     InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
   1324         }
   1325         {
   1326             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1327             ArraySet<String> subtypes1 = new ArraySet<>();
   1328             subtypes1.add("subtype0");
   1329             subtypes1.add("subtype1");
   1330             map.put("ime0", subtypes1);
   1331             map.put("ime1", new ArraySet<String>());
   1332 
   1333             ArraySet<String> validSequences = new ArraySet<>();
   1334             validSequences.add("ime0;subtype0;subtype1:ime1");
   1335             validSequences.add("ime0;subtype1;subtype0:ime1");
   1336             validSequences.add("ime1:ime0;subtype0;subtype1");
   1337             validSequences.add("ime1:ime0;subtype1;subtype0");
   1338             assertTrue(validSequences.contains(
   1339                     InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
   1340         }
   1341         {
   1342             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1343             ArraySet<String> subtypes1 = new ArraySet<>();
   1344             subtypes1.add("subtype0");
   1345             map.put("ime0", subtypes1);
   1346 
   1347             ArraySet<String> subtypes2 = new ArraySet<>();
   1348             subtypes2.add("subtype1");
   1349             map.put("ime1", subtypes2);
   1350 
   1351             ArraySet<String> validSequences = new ArraySet<>();
   1352             validSequences.add("ime0;subtype0:ime1;subtype1");
   1353             validSequences.add("ime1;subtype1:ime0;subtype0");
   1354             assertTrue(validSequences.contains(
   1355                     InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
   1356         }
   1357         {
   1358             ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
   1359             ArraySet<String> subtypes1 = new ArraySet<>();
   1360             subtypes1.add("subtype0");
   1361             subtypes1.add("subtype1");
   1362             map.put("ime0", subtypes1);
   1363 
   1364             ArraySet<String> subtypes2 = new ArraySet<>();
   1365             subtypes2.add("subtype2");
   1366             subtypes2.add("subtype3");
   1367             map.put("ime1", subtypes2);
   1368 
   1369             ArraySet<String> validSequences = new ArraySet<>();
   1370             validSequences.add("ime0;subtype0;subtype1:ime1;subtype2;subtype3");
   1371             validSequences.add("ime0;subtype1;subtype0:ime1;subtype2;subtype3");
   1372             validSequences.add("ime0;subtype0;subtype1:ime1;subtype3;subtype2");
   1373             validSequences.add("ime0;subtype1;subtype0:ime1;subtype3;subtype2");
   1374             validSequences.add("ime1;subtype2;subtype3:ime0;subtype0;subtype1");
   1375             validSequences.add("ime2;subtype3;subtype2:ime0;subtype0;subtype1");
   1376             validSequences.add("ime3;subtype2;subtype3:ime0;subtype1;subtype0");
   1377             validSequences.add("ime4;subtype3;subtype2:ime0;subtype1;subtype0");
   1378             assertTrue(validSequences.contains(
   1379                     InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
   1380         }
   1381     }
   1382 
   1383     @SmallTest
   1384     public void testConstructLocaleFromString() throws Exception {
   1385         assertEquals(new Locale("en"), InputMethodUtils.constructLocaleFromString("en"));
   1386         assertEquals(new Locale("en", "US"), InputMethodUtils.constructLocaleFromString("en_US"));
   1387         assertEquals(new Locale("en", "US", "POSIX"),
   1388                 InputMethodUtils.constructLocaleFromString("en_US_POSIX"));
   1389 
   1390         // Special rewrite rule for "tl" for versions of Android earlier than Lollipop that did not
   1391         // support three letter language codes, and used "tl" (Tagalog) as the language string for
   1392         // "fil" (Filipino).
   1393         assertEquals(new Locale("fil"), InputMethodUtils.constructLocaleFromString("tl"));
   1394         assertEquals(new Locale("fil", "PH"), InputMethodUtils.constructLocaleFromString("tl_PH"));
   1395         assertEquals(new Locale("fil", "PH", "POSIX"),
   1396                 InputMethodUtils.constructLocaleFromString("tl_PH_POSIX"));
   1397 
   1398         // So far rejecting an invalid/unexpected locale string is out of the scope of this method.
   1399         assertEquals(new Locale("a"), InputMethodUtils.constructLocaleFromString("a"));
   1400         assertEquals(new Locale("a b c"), InputMethodUtils.constructLocaleFromString("a b c"));
   1401         assertEquals(new Locale("en-US"), InputMethodUtils.constructLocaleFromString("en-US"));
   1402     }
   1403 }
   1404