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.assertTrue;
27 
28 import android.content.Context;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.ServiceInfo;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.os.Build;
35 import android.os.LocaleList;
36 import android.os.Parcel;
37 import android.view.inputmethod.InputMethodInfo;
38 import android.view.inputmethod.InputMethodSubtype;
39 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
40 
41 import androidx.test.InstrumentationRegistry;
42 import androidx.test.filters.SmallTest;
43 import androidx.test.runner.AndroidJUnit4;
44 
45 import com.android.internal.inputmethod.StartInputFlags;
46 
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.Locale;
53 import java.util.Objects;
54 
55 @SmallTest
56 @RunWith(AndroidJUnit4.class)
57 public class InputMethodUtilsTest {
58     private static final boolean IS_AUX = true;
59     private static final boolean IS_DEFAULT = true;
60     private static final boolean IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE = true;
61     private static final boolean IS_ASCII_CAPABLE = true;
62     private static final boolean IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = true;
63     private static final Locale LOCALE_EN = new Locale("en");
64     private static final Locale LOCALE_EN_US = new Locale("en", "US");
65     private static final Locale LOCALE_EN_GB = new Locale("en", "GB");
66     private static final Locale LOCALE_EN_IN = new Locale("en", "IN");
67     private static final Locale LOCALE_FI = new Locale("fi");
68     private static final Locale LOCALE_FI_FI = new Locale("fi", "FI");
69     private static final Locale LOCALE_FIL = new Locale("fil");
70     private static final Locale LOCALE_FIL_PH = new Locale("fil", "PH");
71     private static final Locale LOCALE_FR = new Locale("fr");
72     private static final Locale LOCALE_FR_CA = new Locale("fr", "CA");
73     private static final Locale LOCALE_HI = new Locale("hi");
74     private static final Locale LOCALE_JA_JP = new Locale("ja", "JP");
75     private static final Locale LOCALE_ZH_CN = new Locale("zh", "CN");
76     private static final Locale LOCALE_ZH_TW = new Locale("zh", "TW");
77     private static final Locale LOCALE_IN = new Locale("in");
78     private static final Locale LOCALE_ID = new Locale("id");
79     private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
80     private static final String SUBTYPE_MODE_VOICE = "voice";
81     private static final String SUBTYPE_MODE_HANDWRITING = "handwriting";
82     private static final String SUBTYPE_MODE_ANY = null;
83     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
84     private static final String EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
85             "EnabledWhenDefaultIsNotAsciiCapable";
86 
87     @Test
testVoiceImes()88     public void testVoiceImes() throws Exception {
89         // locale: en_US
90         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
91                 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
92         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
93                 "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
94                 "DummyNonDefaultAutoVoiceIme1");
95         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
96                 "DummyDefaultEnKeyboardIme");
97         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
98                 "DummyDefaultEnKeyboardIme");
99 
100         // locale: en_GB
101         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
102                 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
103         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
104                 "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
105                 "DummyNonDefaultAutoVoiceIme1");
106         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
107                 "DummyDefaultEnKeyboardIme");
108         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
109                 "DummyDefaultEnKeyboardIme");
110 
111         // locale: ja_JP
112         assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
113                 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
114         assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
115                 "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
116                 "DummyNonDefaultAutoVoiceIme1");
117         assertDefaultEnabledMinimumImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
118                 "DummyDefaultEnKeyboardIme");
119         assertDefaultEnabledMinimumImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
120                 "DummyDefaultEnKeyboardIme");
121     }
122 
123     @Test
testKeyboardImes()124     public void testKeyboardImes() throws Exception {
125         // locale: en_US
126         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
127                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
128         assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
129                 "com.android.apps.inputmethod.latin");
130 
131         // locale: en_GB
132         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
133                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
134         assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
135                 "com.android.apps.inputmethod.latin");
136 
137         // locale: en_IN
138         assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
139                 "com.android.apps.inputmethod.hindi",
140                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
141         assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
142                 "com.android.apps.inputmethod.hindi",
143                 "com.android.apps.inputmethod.latin");
144 
145         // locale: hi
146         assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
147                 "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin",
148                 "com.android.apps.inputmethod.voice");
149         assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
150                 "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.latin");
151 
152         // locale: ja_JP
153         assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
154                 "com.android.apps.inputmethod.japanese", "com.android.apps.inputmethod.voice");
155         assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
156                 "com.android.apps.inputmethod.japanese");
157 
158         // locale: zh_CN
159         assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
160                 "com.android.apps.inputmethod.pinyin", "com.android.apps.inputmethod.voice");
161         assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
162                 "com.android.apps.inputmethod.pinyin");
163 
164         // locale: zh_TW
165         // Note: In this case, no IME is suitable for the system locale. Hence we will pick up a
166         // fallback IME regardless of the "default" attribute.
167         assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
168                 "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
169         assertDefaultEnabledMinimumImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
170                 "com.android.apps.inputmethod.latin");
171     }
172 
173     @Test
testParcelable()174     public void testParcelable() throws Exception {
175         final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes("en-rUS");
176         final List<InputMethodInfo> clonedList = cloneViaParcel(originalList);
177         assertNotNull(clonedList);
178         final List<InputMethodInfo> clonedClonedList = cloneViaParcel(clonedList);
179         assertNotNull(clonedClonedList);
180         assertEquals(originalList, clonedList);
181         assertEquals(clonedList, clonedClonedList);
182         assertEquals(originalList.size(), clonedList.size());
183         assertEquals(clonedList.size(), clonedClonedList.size());
184         for (int imeIndex = 0; imeIndex < originalList.size(); ++imeIndex) {
185             verifyEquality(originalList.get(imeIndex), clonedList.get(imeIndex));
186             verifyEquality(clonedList.get(imeIndex), clonedClonedList.get(imeIndex));
187         }
188     }
189 
190     @Test
testGetImplicitlyApplicableSubtypesLocked()191     public void testGetImplicitlyApplicableSubtypesLocked() throws Exception {
192         final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
193                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
194                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
195         final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
196                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
197                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
198         final InputMethodSubtype nonAutoEnIN = createDummyInputMethodSubtype("en_IN",
199                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
200                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
201         final InputMethodSubtype nonAutoFrCA = createDummyInputMethodSubtype("fr_CA",
202                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
203                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
204         final InputMethodSubtype nonAutoFr = createDummyInputMethodSubtype("fr_CA",
205                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
206                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
207         final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
208                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
209                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
210         final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
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 nonAutoId = createDummyInputMethodSubtype("id",
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 autoSubtype = createDummyInputMethodSubtype("auto",
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 nonAutoJa = createDummyInputMethodSubtype("ja",
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 nonAutoHi = createDummyInputMethodSubtype("hi",
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 nonAutoSrCyrl = createDummyInputMethodSubtype("sr",
226                 "sr-Cyrl", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
227                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
228                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
229         final InputMethodSubtype nonAutoSrLatn = createDummyInputMethodSubtype("sr_ZZ",
230                 "sr-Latn", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
231                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
232                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
233         final InputMethodSubtype nonAutoHandwritingEn = createDummyInputMethodSubtype("en",
234                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
235                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
236         final InputMethodSubtype nonAutoHandwritingFr = createDummyInputMethodSubtype("fr",
237                 SUBTYPE_MODE_HANDWRITING, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
238                 !IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
239         final InputMethodSubtype nonAutoHandwritingSrCyrl = createDummyInputMethodSubtype("sr",
240                 "sr-Cyrl", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
241                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
242                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
243         final InputMethodSubtype nonAutoHandwritingSrLatn = createDummyInputMethodSubtype("sr_ZZ",
244                 "sr-Latn", SUBTYPE_MODE_HANDWRITING, !IS_AUX,
245                 !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
246                 !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
247         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype =
248                 createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
249                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
250                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
251         final InputMethodSubtype nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2 =
252                 createDummyInputMethodSubtype("zz", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
253                         !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
254                         IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
255 
256         // Make sure that an automatic subtype (overridesImplicitlyEnabledSubtype:true) is
257         // selected no matter what locale is specified.
258         {
259             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
260             subtypes.add(nonAutoEnUS);
261             subtypes.add(nonAutoEnGB);
262             subtypes.add(nonAutoJa);
263             subtypes.add(nonAutoFil);
264             subtypes.add(autoSubtype);  // overridesImplicitlyEnabledSubtype == true
265             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
266             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
267             subtypes.add(nonAutoHandwritingEn);
268             subtypes.add(nonAutoHandwritingFr);
269             final InputMethodInfo imi = createDummyInputMethodInfo(
270                     "com.android.apps.inputmethod.latin",
271                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
272                     subtypes);
273             final ArrayList<InputMethodSubtype> result =
274                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
275                             getResourcesForLocales(LOCALE_EN_US), imi);
276             assertEquals(1, result.size());
277             verifyEquality(autoSubtype, result.get(0));
278         }
279 
280         // Make sure that a subtype whose locale is exactly equal to the specified locale is
281         // selected as long as there is no no automatic subtype
282         // (overridesImplicitlyEnabledSubtype:true) in the given list.
283         {
284             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
285             subtypes.add(nonAutoEnUS);  // locale == "en_US"
286             subtypes.add(nonAutoEnGB);
287             subtypes.add(nonAutoJa);
288             subtypes.add(nonAutoFil);
289             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
290             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
291             subtypes.add(nonAutoHandwritingEn);
292             subtypes.add(nonAutoHandwritingFr);
293             final InputMethodInfo imi = createDummyInputMethodInfo(
294                     "com.android.apps.inputmethod.latin",
295                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
296                     subtypes);
297             final ArrayList<InputMethodSubtype> result =
298                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
299                             getResourcesForLocales(LOCALE_EN_US), imi);
300             assertEquals(2, result.size());
301             verifyEquality(nonAutoEnUS, result.get(0));
302             verifyEquality(nonAutoHandwritingEn, result.get(1));
303         }
304 
305         // Make sure that a subtype whose locale is exactly equal to the specified locale is
306         // selected as long as there is no automatic subtype
307         // (overridesImplicitlyEnabledSubtype:true) in the given list.
308         {
309             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
310             subtypes.add(nonAutoEnUS);
311             subtypes.add(nonAutoEnGB); // locale == "en_GB"
312             subtypes.add(nonAutoJa);
313             subtypes.add(nonAutoFil);
314             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
315             subtypes.add(nonAutoHandwritingEn);
316             subtypes.add(nonAutoHandwritingFr);
317             final InputMethodInfo imi = createDummyInputMethodInfo(
318                     "com.android.apps.inputmethod.latin",
319                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
320                     subtypes);
321             final ArrayList<InputMethodSubtype> result =
322                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
323                             getResourcesForLocales(LOCALE_EN_GB), imi);
324             assertEquals(2, result.size());
325             verifyEquality(nonAutoEnGB, result.get(0));
326             verifyEquality(nonAutoHandwritingEn, result.get(1));
327         }
328 
329         // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and
330         // any subtype whose locale is exactly equal to the specified locale in the given list,
331         // try to find a subtype whose language is equal to the language part of the given locale.
332         // Here make sure that a subtype (locale: "fr_CA") can be found with locale: "fr".
333         {
334             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
335             subtypes.add(nonAutoFrCA);  // locale == "fr_CA"
336             subtypes.add(nonAutoJa);
337             subtypes.add(nonAutoFil);
338             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
339             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
340             subtypes.add(nonAutoHandwritingEn);
341             subtypes.add(nonAutoHandwritingFr);
342             final InputMethodInfo imi = createDummyInputMethodInfo(
343                     "com.android.apps.inputmethod.latin",
344                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
345                     subtypes);
346             final ArrayList<InputMethodSubtype> result =
347                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
348                             getResourcesForLocales(LOCALE_FR), imi);
349             assertEquals(2, result.size());
350             verifyEquality(nonAutoFrCA, result.get(0));
351             verifyEquality(nonAutoHandwritingFr, result.get(1));
352         }
353         // Then make sure that a subtype (locale: "fr") can be found with locale: "fr_CA".
354         {
355             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
356             subtypes.add(nonAutoFr);  // locale == "fr"
357             subtypes.add(nonAutoJa);
358             subtypes.add(nonAutoFil);
359             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
360             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
361             subtypes.add(nonAutoHandwritingEn);
362             subtypes.add(nonAutoHandwritingFr);
363             final InputMethodInfo imi = createDummyInputMethodInfo(
364                     "com.android.apps.inputmethod.latin",
365                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
366                     subtypes);
367             final ArrayList<InputMethodSubtype> result =
368                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
369                             getResourcesForLocales(LOCALE_FR_CA), imi);
370             assertEquals(2, result.size());
371             verifyEquality(nonAutoFrCA, result.get(0));
372             verifyEquality(nonAutoHandwritingFr, result.get(1));
373         }
374 
375         // Make sure that subtypes which have "EnabledWhenDefaultIsNotAsciiCapable" in its
376         // extra value is selected if and only if all other selected IMEs are not AsciiCapable.
377         {
378             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
379             subtypes.add(nonAutoEnUS);
380             subtypes.add(nonAutoJa);    // not ASCII capable
381             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
382             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
383             subtypes.add(nonAutoHandwritingEn);
384             subtypes.add(nonAutoHandwritingFr);
385             final InputMethodInfo imi = createDummyInputMethodInfo(
386                     "com.android.apps.inputmethod.latin",
387                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
388                     subtypes);
389             final ArrayList<InputMethodSubtype> result =
390                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
391                             getResourcesForLocales(LOCALE_JA_JP), imi);
392             assertEquals(3, result.size());
393             verifyEquality(nonAutoJa, result.get(0));
394             verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1));
395             verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2, result.get(2));
396         }
397 
398         // Make sure that if there is no subtype that matches the language requested, then we just
399         // use the first keyboard subtype.
400         {
401             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
402             subtypes.add(nonAutoHi);
403             subtypes.add(nonAutoEnUS);
404             subtypes.add(nonAutoHandwritingEn);
405             subtypes.add(nonAutoHandwritingFr);
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(nonAutoHi, result.get(0));
416         }
417         {
418             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
419             subtypes.add(nonAutoEnUS);
420             subtypes.add(nonAutoHi);
421             subtypes.add(nonAutoHandwritingEn);
422             subtypes.add(nonAutoHandwritingFr);
423             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
424             final InputMethodInfo imi = createDummyInputMethodInfo(
425                     "com.android.apps.inputmethod.latin",
426                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
427                     subtypes);
428             final ArrayList<InputMethodSubtype> result =
429                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
430                             getResourcesForLocales(LOCALE_JA_JP), imi);
431             assertEquals(1, result.size());
432             verifyEquality(nonAutoEnUS, result.get(0));
433         }
434         {
435             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
436             subtypes.add(nonAutoHandwritingEn);
437             subtypes.add(nonAutoHandwritingFr);
438             subtypes.add(nonAutoEnUS);
439             subtypes.add(nonAutoHi);
440             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
441             final InputMethodInfo imi = createDummyInputMethodInfo(
442                     "com.android.apps.inputmethod.latin",
443                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
444                     subtypes);
445             final ArrayList<InputMethodSubtype> result =
446                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
447                             getResourcesForLocales(LOCALE_JA_JP), imi);
448             assertEquals(1, result.size());
449             verifyEquality(nonAutoEnUS, result.get(0));
450         }
451 
452         // Make sure that both language and script are taken into account to find the best matching
453         // subtype.
454         {
455             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
456             subtypes.add(nonAutoEnUS);
457             subtypes.add(nonAutoSrCyrl);
458             subtypes.add(nonAutoSrLatn);
459             subtypes.add(nonAutoHandwritingEn);
460             subtypes.add(nonAutoHandwritingFr);
461             subtypes.add(nonAutoHandwritingSrCyrl);
462             subtypes.add(nonAutoHandwritingSrLatn);
463             final InputMethodInfo imi = createDummyInputMethodInfo(
464                     "com.android.apps.inputmethod.latin",
465                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
466                     subtypes);
467             final ArrayList<InputMethodSubtype> result =
468                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
469                             getResourcesForLocales(Locale.forLanguageTag("sr-Latn-RS")), imi);
470             assertEquals(2, result.size());
471             assertThat(nonAutoSrLatn, is(in(result)));
472             assertThat(nonAutoHandwritingSrLatn, is(in(result)));
473         }
474         {
475             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
476             subtypes.add(nonAutoEnUS);
477             subtypes.add(nonAutoSrCyrl);
478             subtypes.add(nonAutoSrLatn);
479             subtypes.add(nonAutoHandwritingEn);
480             subtypes.add(nonAutoHandwritingFr);
481             subtypes.add(nonAutoHandwritingSrCyrl);
482             subtypes.add(nonAutoHandwritingSrLatn);
483             final InputMethodInfo imi = createDummyInputMethodInfo(
484                     "com.android.apps.inputmethod.latin",
485                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
486                     subtypes);
487             final ArrayList<InputMethodSubtype> result =
488                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
489                             getResourcesForLocales(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
490             assertEquals(2, result.size());
491             assertThat(nonAutoSrCyrl, is(in(result)));
492             assertThat(nonAutoHandwritingSrCyrl, is(in(result)));
493         }
494 
495         // Make sure that secondary locales are taken into account to find the best matching
496         // subtype.
497         {
498             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
499             subtypes.add(nonAutoEnUS);
500             subtypes.add(nonAutoEnGB);
501             subtypes.add(nonAutoSrCyrl);
502             subtypes.add(nonAutoSrLatn);
503             subtypes.add(nonAutoFr);
504             subtypes.add(nonAutoFrCA);
505             subtypes.add(nonAutoHandwritingEn);
506             subtypes.add(nonAutoHandwritingFr);
507             subtypes.add(nonAutoHandwritingSrCyrl);
508             subtypes.add(nonAutoHandwritingSrLatn);
509             final InputMethodInfo imi = createDummyInputMethodInfo(
510                     "com.android.apps.inputmethod.latin",
511                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
512                     subtypes);
513             final ArrayList<InputMethodSubtype> result =
514                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
515                             getResourcesForLocales(
516                                     Locale.forLanguageTag("sr-Latn-RS-x-android"),
517                                     Locale.forLanguageTag("ja-JP"),
518                                     Locale.forLanguageTag("fr-FR"),
519                                     Locale.forLanguageTag("en-GB"),
520                                     Locale.forLanguageTag("en-US")),
521                             imi);
522             assertEquals(6, result.size());
523             assertThat(nonAutoEnGB, is(in(result)));
524             assertThat(nonAutoFr, is(in(result)));
525             assertThat(nonAutoSrLatn, is(in(result)));
526             assertThat(nonAutoHandwritingEn, is(in(result)));
527             assertThat(nonAutoHandwritingFr, is(in(result)));
528             assertThat(nonAutoHandwritingSrLatn, is(in(result)));
529         }
530 
531         // Make sure that 3-letter language code can be handled.
532         {
533             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
534             subtypes.add(nonAutoEnUS);
535             subtypes.add(nonAutoFil);
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_FIL_PH), imi);
543             assertEquals(1, result.size());
544             verifyEquality(nonAutoFil, result.get(0));
545         }
546 
547         // Make sure that we never end up matching "fi" (finnish) with "fil" (filipino).
548         // Also make sure that the first subtype will be used as the last-resort candidate.
549         {
550             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
551             subtypes.add(nonAutoJa);
552             subtypes.add(nonAutoEnUS);
553             subtypes.add(nonAutoFil);
554             final InputMethodInfo imi = createDummyInputMethodInfo(
555                     "com.android.apps.inputmethod.latin",
556                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
557                     subtypes);
558             final ArrayList<InputMethodSubtype> result =
559                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
560                             getResourcesForLocales(LOCALE_FI), imi);
561             assertEquals(1, result.size());
562             verifyEquality(nonAutoJa, result.get(0));
563         }
564 
565         // Make sure that "in" and "id" conversion is taken into account.
566         {
567             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
568             subtypes.add(nonAutoIn);
569             subtypes.add(nonAutoEnUS);
570             final InputMethodInfo imi = createDummyInputMethodInfo(
571                     "com.android.apps.inputmethod.latin",
572                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
573                     subtypes);
574             final ArrayList<InputMethodSubtype> result =
575                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
576                             getResourcesForLocales(LOCALE_IN), imi);
577             assertEquals(1, result.size());
578             verifyEquality(nonAutoIn, result.get(0));
579         }
580         {
581             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
582             subtypes.add(nonAutoIn);
583             subtypes.add(nonAutoEnUS);
584             final InputMethodInfo imi = createDummyInputMethodInfo(
585                     "com.android.apps.inputmethod.latin",
586                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
587                     subtypes);
588             final ArrayList<InputMethodSubtype> result =
589                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
590                             getResourcesForLocales(LOCALE_ID), imi);
591             assertEquals(1, result.size());
592             verifyEquality(nonAutoIn, result.get(0));
593         }
594         {
595             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
596             subtypes.add(nonAutoId);
597             subtypes.add(nonAutoEnUS);
598             final InputMethodInfo imi = createDummyInputMethodInfo(
599                     "com.android.apps.inputmethod.latin",
600                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
601                     subtypes);
602             final ArrayList<InputMethodSubtype> result =
603                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
604                             getResourcesForLocales(LOCALE_IN), imi);
605             assertEquals(1, result.size());
606             verifyEquality(nonAutoId, result.get(0));
607         }
608         {
609             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
610             subtypes.add(nonAutoId);
611             subtypes.add(nonAutoEnUS);
612             final InputMethodInfo imi = createDummyInputMethodInfo(
613                     "com.android.apps.inputmethod.latin",
614                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
615                     subtypes);
616             final ArrayList<InputMethodSubtype> result =
617                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
618                             getResourcesForLocales(LOCALE_ID), imi);
619             assertEquals(1, result.size());
620             verifyEquality(nonAutoId, result.get(0));
621         }
622 
623         // If there is no automatic subtype (overridesImplicitlyEnabledSubtype:true) and the system
624         // provides multiple locales, we try to enable multiple subtypes.
625         {
626             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
627             subtypes.add(nonAutoEnUS);
628             subtypes.add(nonAutoFrCA);
629             subtypes.add(nonAutoIn);
630             subtypes.add(nonAutoJa);
631             subtypes.add(nonAutoFil);
632             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype);
633             subtypes.add(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype2);
634             final InputMethodInfo imi = createDummyInputMethodInfo(
635                     "com.android.apps.inputmethod.latin",
636                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
637                     subtypes);
638             final ArrayList<InputMethodSubtype> result =
639                     InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
640                             getResourcesForLocales(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
641             assertThat(nonAutoFrCA, is(in(result)));
642             assertThat(nonAutoEnUS, is(in(result)));
643             assertThat(nonAutoJa, is(in(result)));
644             assertThat(nonAutoIn, not(is(in(result))));
645             assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(is(in(result))));
646             assertThat(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, not(is(in(result))));
647         }
648     }
649 
650     @Test
testContainsSubtypeOf()651     public void testContainsSubtypeOf() throws Exception {
652         final InputMethodSubtype nonAutoEnUS = createDummyInputMethodSubtype("en_US",
653                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
654                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
655         final InputMethodSubtype nonAutoEnGB = createDummyInputMethodSubtype("en_GB",
656                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
657                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
658         final InputMethodSubtype nonAutoFil = createDummyInputMethodSubtype("fil",
659                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
660                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
661         final InputMethodSubtype nonAutoFilPH = createDummyInputMethodSubtype("fil_PH",
662                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
663                 IS_ASCII_CAPABLE, !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
664         final InputMethodSubtype nonAutoIn = createDummyInputMethodSubtype("in",
665                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
666                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
667         final InputMethodSubtype nonAutoId = createDummyInputMethodSubtype("id",
668                 SUBTYPE_MODE_KEYBOARD, !IS_AUX, !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE,
669                 IS_ASCII_CAPABLE, IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
670 
671         final boolean CHECK_COUNTRY = true;
672 
673         {
674             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
675             subtypes.add(nonAutoEnUS);
676             final InputMethodInfo imi = createDummyInputMethodInfo(
677                     "com.android.apps.inputmethod.latin",
678                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
679                     subtypes);
680 
681             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
682                     SUBTYPE_MODE_KEYBOARD));
683             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY,
684                     SUBTYPE_MODE_KEYBOARD));
685             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
686                     SUBTYPE_MODE_KEYBOARD));
687             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
688                     SUBTYPE_MODE_KEYBOARD));
689             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
690                     SUBTYPE_MODE_VOICE));
691             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
692                     SUBTYPE_MODE_VOICE));
693             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
694                     SUBTYPE_MODE_ANY));
695             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
696                     SUBTYPE_MODE_ANY));
697 
698             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY,
699                     SUBTYPE_MODE_KEYBOARD));
700             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY,
701                     SUBTYPE_MODE_KEYBOARD));
702         }
703 
704         // Make sure that 3-letter language code ("fil") can be handled.
705         {
706             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
707             subtypes.add(nonAutoFil);
708             final InputMethodInfo imi = createDummyInputMethodInfo(
709                     "com.android.apps.inputmethod.latin",
710                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
711                     subtypes);
712             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
713                     SUBTYPE_MODE_KEYBOARD));
714             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
715                     SUBTYPE_MODE_KEYBOARD));
716             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
717                     SUBTYPE_MODE_KEYBOARD));
718             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
719                     SUBTYPE_MODE_KEYBOARD));
720 
721             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
722                     SUBTYPE_MODE_KEYBOARD));
723             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
724                     SUBTYPE_MODE_KEYBOARD));
725             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
726                     SUBTYPE_MODE_KEYBOARD));
727             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
728                     SUBTYPE_MODE_KEYBOARD));
729         }
730 
731         // Make sure that 3-letter language code ("fil_PH") can be handled.
732         {
733             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
734             subtypes.add(nonAutoFilPH);
735             final InputMethodInfo imi = createDummyInputMethodInfo(
736                     "com.android.apps.inputmethod.latin",
737                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
738                     subtypes);
739             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
740                     SUBTYPE_MODE_KEYBOARD));
741             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
742                     SUBTYPE_MODE_KEYBOARD));
743             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
744                     SUBTYPE_MODE_KEYBOARD));
745             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
746                     SUBTYPE_MODE_KEYBOARD));
747 
748             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
749                     SUBTYPE_MODE_KEYBOARD));
750             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
751                     SUBTYPE_MODE_KEYBOARD));
752             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
753                     SUBTYPE_MODE_KEYBOARD));
754             assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
755                     SUBTYPE_MODE_KEYBOARD));
756         }
757 
758         // Make sure that a subtype whose locale is "in" can be queried with "id".
759         {
760             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
761             subtypes.add(nonAutoIn);
762             subtypes.add(nonAutoEnUS);
763             final InputMethodInfo imi = createDummyInputMethodInfo(
764                     "com.android.apps.inputmethod.latin",
765                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
766                     subtypes);
767             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
768                     SUBTYPE_MODE_KEYBOARD));
769             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
770                     SUBTYPE_MODE_KEYBOARD));
771             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
772                     SUBTYPE_MODE_KEYBOARD));
773             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
774                     SUBTYPE_MODE_KEYBOARD));
775         }
776 
777         // Make sure that a subtype whose locale is "id" can be queried with "in".
778         {
779             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
780             subtypes.add(nonAutoId);
781             subtypes.add(nonAutoEnUS);
782             final InputMethodInfo imi = createDummyInputMethodInfo(
783                     "com.android.apps.inputmethod.latin",
784                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
785                     subtypes);
786             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
787                     SUBTYPE_MODE_KEYBOARD));
788             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
789                     SUBTYPE_MODE_KEYBOARD));
790             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
791                     SUBTYPE_MODE_KEYBOARD));
792             assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
793                     SUBTYPE_MODE_KEYBOARD));
794         }
795     }
796 
assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes, final Locale systemLocale, String... expectedImeNames)797     private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
798             final Locale systemLocale, String... expectedImeNames) {
799         final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
800         final String[] actualImeNames = getPackageNames(
801                 InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes));
802         assertEquals(expectedImeNames.length, actualImeNames.length);
803         for (int i = 0; i < expectedImeNames.length; ++i) {
804             assertEquals(expectedImeNames[i], actualImeNames[i]);
805         }
806     }
807 
assertDefaultEnabledMinimumImes(final ArrayList<InputMethodInfo> preinstalledImes, final Locale systemLocale, String... expectedImeNames)808     private void assertDefaultEnabledMinimumImes(final ArrayList<InputMethodInfo> preinstalledImes,
809             final Locale systemLocale, String... expectedImeNames) {
810         final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
811         final String[] actualImeNames = getPackageNames(
812                 InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes,
813                         true /* onlyMinimum */));
814         assertEquals(expectedImeNames.length, actualImeNames.length);
815         for (int i = 0; i < expectedImeNames.length; ++i) {
816             assertEquals(expectedImeNames[i], actualImeNames[i]);
817         }
818     }
819 
cloneViaParcel(final List<InputMethodInfo> list)820     private static List<InputMethodInfo> cloneViaParcel(final List<InputMethodInfo> list) {
821         Parcel p = null;
822         try {
823             p = Parcel.obtain();
824             p.writeTypedList(list);
825             p.setDataPosition(0);
826             return p.createTypedArrayList(InputMethodInfo.CREATOR);
827         } finally {
828             if (p != null) {
829                 p.recycle();
830             }
831         }
832     }
833 
createTargetContextWithLocales(final LocaleList locales)834     private Context createTargetContextWithLocales(final LocaleList locales) {
835         final Configuration resourceConfiguration = new Configuration();
836         resourceConfiguration.setLocales(locales);
837         return InstrumentationRegistry.getInstrumentation()
838                 .getTargetContext()
839                 .createConfigurationContext(resourceConfiguration);
840     }
841 
getResourcesForLocales(Locale... locales)842     private Resources getResourcesForLocales(Locale... locales) {
843         return createTargetContextWithLocales(new LocaleList(locales)).getResources();
844     }
845 
getPackageNames(final ArrayList<InputMethodInfo> imis)846     private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
847         final String[] packageNames = new String[imis.size()];
848         for (int i = 0; i < imis.size(); ++i) {
849             packageNames[i] = imis.get(i).getPackageName();
850         }
851         return packageNames;
852     }
853 
verifyEquality(InputMethodInfo expected, InputMethodInfo actual)854     private static void verifyEquality(InputMethodInfo expected, InputMethodInfo actual) {
855         assertEquals(expected, actual);
856         assertEquals(expected.getSubtypeCount(), actual.getSubtypeCount());
857         for (int subtypeIndex = 0; subtypeIndex < expected.getSubtypeCount(); ++subtypeIndex) {
858             final InputMethodSubtype expectedSubtype = expected.getSubtypeAt(subtypeIndex);
859             final InputMethodSubtype actualSubtype = actual.getSubtypeAt(subtypeIndex);
860             verifyEquality(expectedSubtype, actualSubtype);
861         }
862     }
863 
verifyEquality(InputMethodSubtype expected, InputMethodSubtype actual)864     private static void verifyEquality(InputMethodSubtype expected, InputMethodSubtype actual) {
865         assertEquals(expected, actual);
866         assertEquals(expected.hashCode(), actual.hashCode());
867     }
868 
createDummyInputMethodInfo(String packageName, String name, CharSequence label, boolean isAuxIme, boolean isDefault, List<InputMethodSubtype> subtypes)869     private static InputMethodInfo createDummyInputMethodInfo(String packageName, String name,
870             CharSequence label, boolean isAuxIme, boolean isDefault,
871             List<InputMethodSubtype> subtypes) {
872         final ResolveInfo ri = new ResolveInfo();
873         final ServiceInfo si = new ServiceInfo();
874         final ApplicationInfo ai = new ApplicationInfo();
875         ai.packageName = packageName;
876         ai.enabled = true;
877         ai.flags |= ApplicationInfo.FLAG_SYSTEM;
878         si.applicationInfo = ai;
879         si.enabled = true;
880         si.packageName = packageName;
881         si.name = name;
882         si.exported = true;
883         si.nonLocalizedLabel = label;
884         ri.serviceInfo = si;
885         return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
886     }
887 
createDummyInputMethodSubtype(String locale, String mode, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable)888     private static InputMethodSubtype createDummyInputMethodSubtype(String locale, String mode,
889             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
890             boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable) {
891         return createDummyInputMethodSubtype(locale, null /* languageTag */, mode, isAuxiliary,
892                 overridesImplicitlyEnabledSubtype, isAsciiCapable,
893                 isEnabledWhenDefaultIsNotAsciiCapable);
894     }
895 
createDummyInputMethodSubtype(String locale, String languageTag, String mode, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable, boolean isEnabledWhenDefaultIsNotAsciiCapable)896     private static InputMethodSubtype createDummyInputMethodSubtype(String locale,
897             String languageTag, String mode, boolean isAuxiliary,
898             boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable,
899             boolean isEnabledWhenDefaultIsNotAsciiCapable) {
900         final StringBuilder subtypeExtraValue = new StringBuilder();
901         if (isEnabledWhenDefaultIsNotAsciiCapable) {
902             subtypeExtraValue.append(EXTRA_VALUE_PAIR_SEPARATOR);
903             subtypeExtraValue.append(EXTRA_VALUE_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE);
904         }
905 
906         return new InputMethodSubtypeBuilder()
907                 .setSubtypeNameResId(0)
908                 .setSubtypeIconResId(0)
909                 .setSubtypeLocale(locale)
910                 .setLanguageTag(languageTag)
911                 .setSubtypeMode(mode)
912                 .setSubtypeExtraValue(subtypeExtraValue.toString())
913                 .setIsAuxiliary(isAuxiliary)
914                 .setOverridesImplicitlyEnabledSubtype(overridesImplicitlyEnabledSubtype)
915                 .setIsAsciiCapable(isAsciiCapable)
916                 .build();
917     }
918 
getImesWithDefaultVoiceIme()919     private static ArrayList<InputMethodInfo> getImesWithDefaultVoiceIme() {
920         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
921         {
922             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
923             subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
924                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
925                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
926             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
927                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
928                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
929             preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultAutoVoiceIme",
930                     "dummy.voice0", "DummyVoice0", IS_AUX, IS_DEFAULT, subtypes));
931         }
932         preinstalledImes.addAll(getImesWithoutDefaultVoiceIme());
933         return preinstalledImes;
934     }
935 
getImesWithoutDefaultVoiceIme()936     private static ArrayList<InputMethodInfo> getImesWithoutDefaultVoiceIme() {
937         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
938         {
939             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
940             subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
941                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
942                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
943             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
944                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
945                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
946             preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme0",
947                     "dummy.voice1", "DummyVoice1", IS_AUX, !IS_DEFAULT, subtypes));
948         }
949         {
950             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
951             subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX,
952                     IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
953                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
954             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
955                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
956                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
957             preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme1",
958                     "dummy.voice2", "DummyVoice2", IS_AUX, !IS_DEFAULT, subtypes));
959         }
960         {
961             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
962             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
963                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
964                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
965             preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultVoiceIme2",
966                     "dummy.voice3", "DummyVoice3", IS_AUX, !IS_DEFAULT, subtypes));
967         }
968         {
969             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
970             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
971                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
972                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
973             preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultEnKeyboardIme",
974                     "dummy.keyboard0", "DummyKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
975         }
976         return preinstalledImes;
977     }
978 
contains(final String[] textList, final String textToBeChecked)979     private static boolean contains(final String[] textList, final String textToBeChecked) {
980         if (textList == null) {
981             return false;
982         }
983         for (final String text : textList) {
984             if (Objects.equals(textToBeChecked, text)) {
985                 return true;
986             }
987         }
988         return false;
989     }
990 
getSamplePreinstalledImes(final String localeString)991     private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) {
992         ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
993 
994         // a dummy Voice IME
995         {
996             final boolean isDefaultIme = false;
997             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
998             subtypes.add(createDummyInputMethodSubtype("", SUBTYPE_MODE_VOICE, 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.voice",
1002                     "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, isDefaultIme,
1003                     subtypes));
1004         }
1005         // a dummy Hindi IME
1006         {
1007             final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString);
1008             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
1009             // TODO: This subtype should be marked as IS_ASCII_CAPABLE
1010             subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1011                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
1012                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1013             subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1014                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
1015                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1016             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.hindi",
1017                     "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, isDefaultIme,
1018                     subtypes));
1019         }
1020 
1021         // a dummy Pinyin IME
1022         {
1023             final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString);
1024             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
1025             subtypes.add(createDummyInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1026                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
1027                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1028             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.pinyin",
1029                     "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, isDefaultIme,
1030                     subtypes));
1031         }
1032 
1033         // a dummy Korean IME
1034         {
1035             final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString);
1036             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
1037             subtypes.add(createDummyInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1038                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
1039                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1040             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.korean",
1041                     "com.android.apps.inputmethod.korean", "DummyKoreanIme", !IS_AUX, isDefaultIme,
1042                     subtypes));
1043         }
1044 
1045         // a dummy Latin IME
1046         {
1047             final boolean isDefaultIme = contains(
1048                     new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString);
1049             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
1050             subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1051                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
1052                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1053             subtypes.add(createDummyInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1054                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
1055                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1056             subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1057                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
1058                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1059             subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1060                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, IS_ASCII_CAPABLE,
1061                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1062             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.latin",
1063                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, isDefaultIme,
1064                     subtypes));
1065         }
1066 
1067         // a dummy Japanese IME
1068         {
1069             final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString);
1070             final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
1071             subtypes.add(createDummyInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1072                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
1073                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1074             subtypes.add(createDummyInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
1075                     !IS_OVERRIDES_IMPLICITLY_ENABLED_SUBTYPE, !IS_ASCII_CAPABLE,
1076                     !IS_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE));
1077             preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.japanese",
1078                     "com.android.apps.inputmethod.japanese", "DummyJapaneseIme", !IS_AUX,
1079                     isDefaultIme, subtypes));
1080         }
1081 
1082         return preinstalledImes;
1083     }
1084 
1085     @Test
testIsSoftInputModeStateVisibleAllowed()1086     public void testIsSoftInputModeStateVisibleAllowed() {
1087         // On pre-P devices, SOFT_INPUT_STATE_VISIBLE/SOFT_INPUT_STATE_ALWAYS_VISIBLE are always
1088         // allowed, regardless of the focused view state.
1089         assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
1090                 Build.VERSION_CODES.O_MR1, 0));
1091         assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
1092                 Build.VERSION_CODES.O_MR1, StartInputFlags.VIEW_HAS_FOCUS));
1093         assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
1094                 Build.VERSION_CODES.O_MR1,
1095                 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
1096 
1097         // On P+ devices, SOFT_INPUT_STATE_VISIBLE/SOFT_INPUT_STATE_ALWAYS_VISIBLE are allowed only
1098         // when there is a focused View and its View#onCheckIsTextEditor() returns true.
1099         assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
1100                 Build.VERSION_CODES.P, 0));
1101         assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
1102                 Build.VERSION_CODES.P, StartInputFlags.VIEW_HAS_FOCUS));
1103         assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed(
1104                 Build.VERSION_CODES.P,
1105                 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
1106     }
1107 }
1108