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
testVoiceImes()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
testKeyboardImes()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
testParcelable()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
testGetImplicitlyApplicableSubtypesLocked()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
testContainsSubtypeOf()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 
assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes, final Locale systemLocale, String... expectedImeNames)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 
cloneViaParcel(final List<InputMethodInfo> list)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 
createTargetContextWithLocales(final LocaleList locales)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 
getResourcesForLocales(Locale... locales)796     private Resources getResourcesForLocales(Locale... locales) {
797         return createTargetContextWithLocales(new LocaleList(locales)).getResources();
798     }
799 
getPackageNames(final ArrayList<InputMethodInfo> imis)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 
verifyEquality(InputMethodInfo expected, InputMethodInfo actual)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 
verifyEquality(InputMethodSubtype expected, InputMethodSubtype actual)818     private static void verifyEquality(InputMethodSubtype expected, InputMethodSubtype actual) {
819         assertEquals(expected, actual);
820         assertEquals(expected.hashCode(), actual.hashCode());
821     }
822 
createDummyInputMethodInfo(String packageName, String name, CharSequence label, boolean isAuxIme, boolean isDefault, List<InputMethodSubtype> subtypes)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 
createDummyInputMethodSubtype(String locale, String mode, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable)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 
createDummyInputMethodSubtype(String locale, String languageTag, String mode, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable)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 
getImesWithDefaultVoiceIme()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 
getImesWithoutDefaultVoiceIme()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 
contains(final String[] textList, final String textToBeChecked)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 
getSamplePreinstalledImes(final String localeString)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
testGetSuitableLocalesForSpellChecker()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
testParseInputMethodsAndSubtypesString()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
testbuildInputMethodsAndSubtypesString()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
testConstructLocaleFromString()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