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