1 /*
2  * Copyright (C) 2014 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.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertTrue;
22 
23 import android.content.ComponentName;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.ResolveInfo;
26 import android.content.pm.ServiceInfo;
27 import android.view.inputmethod.InputMethodInfo;
28 import android.view.inputmethod.InputMethodSubtype;
29 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
30 
31 import androidx.test.filters.SmallTest;
32 import androidx.test.runner.AndroidJUnit4;
33 
34 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
35 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
36 
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.List;
43 
44 @SmallTest
45 @RunWith(AndroidJUnit4.class)
46 public class InputMethodSubtypeSwitchingControllerTest {
47     private static final String DUMMY_PACKAGE_NAME = "dummy package name";
48     private static final String DUMMY_IME_LABEL = "dummy ime label";
49     private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
50     private static final boolean DUMMY_IS_AUX_IME = false;
51     private static final boolean DUMMY_FORCE_DEFAULT = false;
52     private static final boolean DUMMY_IS_VR_IME = false;
53     private static final int DUMMY_IS_DEFAULT_RES_ID = 0;
54     private static final String SYSTEM_LOCALE = "en_US";
55     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
56 
createDummySubtype(final String locale)57     private static InputMethodSubtype createDummySubtype(final String locale) {
58         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
59         return builder.setSubtypeNameResId(0)
60                 .setSubtypeIconResId(0)
61                 .setSubtypeLocale(locale)
62                 .setIsAsciiCapable(true)
63                 .build();
64     }
65 
addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items, String imeName, String imeLabel, List<String> subtypeLocales, boolean supportsSwitchingToNextInputMethod)66     private static void addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items,
67             String imeName, String imeLabel, List<String> subtypeLocales,
68             boolean supportsSwitchingToNextInputMethod) {
69         final ResolveInfo ri = new ResolveInfo();
70         final ServiceInfo si = new ServiceInfo();
71         final ApplicationInfo ai = new ApplicationInfo();
72         ai.packageName = DUMMY_PACKAGE_NAME;
73         ai.enabled = true;
74         si.applicationInfo = ai;
75         si.enabled = true;
76         si.packageName = DUMMY_PACKAGE_NAME;
77         si.name = imeName;
78         si.exported = true;
79         si.nonLocalizedLabel = imeLabel;
80         ri.serviceInfo = si;
81         List<InputMethodSubtype> subtypes = null;
82         if (subtypeLocales != null) {
83             subtypes = new ArrayList<InputMethodSubtype>();
84             for (String subtypeLocale : subtypeLocales) {
85                 subtypes.add(createDummySubtype(subtypeLocale));
86             }
87         }
88         final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
89                 DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
90                 DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, DUMMY_IS_VR_IME);
91         if (subtypes == null) {
92             items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
93                     NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
94         } else {
95             for (int i = 0; i < subtypes.size(); ++i) {
96                 final String subtypeLocale = subtypeLocales.get(i);
97                 items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
98                         SYSTEM_LOCALE));
99             }
100         }
101     }
102 
createDummyItem(ComponentName imeComponentName, String imeName, String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale)103     private static ImeSubtypeListItem createDummyItem(ComponentName imeComponentName,
104             String imeName, String subtypeName, String subtypeLocale, int subtypeIndex,
105             String systemLocale) {
106         final ResolveInfo ri = new ResolveInfo();
107         final ServiceInfo si = new ServiceInfo();
108         final ApplicationInfo ai = new ApplicationInfo();
109         ai.packageName = imeComponentName.getPackageName();
110         ai.enabled = true;
111         si.applicationInfo = ai;
112         si.enabled = true;
113         si.packageName = imeComponentName.getPackageName();
114         si.name = imeComponentName.getClassName();
115         si.exported = true;
116         si.nonLocalizedLabel = DUMMY_IME_LABEL;
117         ri.serviceInfo = si;
118         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
119         subtypes.add(new InputMethodSubtypeBuilder()
120                 .setSubtypeNameResId(0)
121                 .setSubtypeIconResId(0)
122                 .setSubtypeLocale(subtypeLocale)
123                 .setIsAsciiCapable(true)
124                 .build());
125         final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
126                 DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
127                 DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
128                 DUMMY_IS_VR_IME);
129         return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
130                 systemLocale);
131     }
132 
createEnabledImeSubtypes()133     private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
134         final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
135         addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
136                 true /* supportsSwitchingToNextInputMethod*/);
137         addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
138                 Arrays.asList("en_UK", "hi"),
139                 false /* supportsSwitchingToNextInputMethod*/);
140         addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null,
141                 false /* supportsSwitchingToNextInputMethod*/);
142         addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"),
143                 true /* supportsSwitchingToNextInputMethod*/);
144         addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme",
145                 Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
146         return items;
147     }
148 
createDisabledImeSubtypes()149     private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
150         final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
151         addDummyImeSubtypeListItems(items,
152                 "UnknownIme", "UnknownIme",
153                 Arrays.asList("en_US", "hi"),
154                 true /* supportsSwitchingToNextInputMethod*/);
155         addDummyImeSubtypeListItems(items,
156                 "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme",
157                 Arrays.asList("en_US"),
158                 false /* supportsSwitchingToNextInputMethod*/);
159         addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme",
160                 "UnknownSubtypeUnawareIme", null,
161                 false /* supportsSwitchingToNextInputMethod*/);
162         return items;
163     }
164 
assertNextInputMethod(final ControllerImpl controller, final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem)165     private void assertNextInputMethod(final ControllerImpl controller,
166             final boolean onlyCurrentIme,
167             final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) {
168         InputMethodSubtype subtype = null;
169         if (currentItem.mSubtypeName != null) {
170             subtype = createDummySubtype(currentItem.mSubtypeName.toString());
171         }
172         final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
173                 currentItem.mImi, subtype);
174         assertEquals(nextItem, nextIme);
175     }
176 
assertRotationOrder(final ControllerImpl controller, final boolean onlyCurrentIme, final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList)177     private void assertRotationOrder(final ControllerImpl controller,
178             final boolean onlyCurrentIme,
179             final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
180         final int N = expectedRotationOrderOfImeSubtypeList.length;
181         for (int i = 0; i < N; i++) {
182             final int currentIndex = i;
183             final int nextIndex = (currentIndex + 1) % N;
184             final ImeSubtypeListItem currentItem =
185                     expectedRotationOrderOfImeSubtypeList[currentIndex];
186             final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
187             assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem);
188         }
189     }
190 
onUserAction(final ControllerImpl controller, final ImeSubtypeListItem subtypeListItem)191     private void onUserAction(final ControllerImpl controller,
192             final ImeSubtypeListItem subtypeListItem) {
193         InputMethodSubtype subtype = null;
194         if (subtypeListItem.mSubtypeName != null) {
195             subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString());
196         }
197         controller.onUserActionLocked(subtypeListItem.mImi, subtype);
198     }
199 
200     @Test
testControllerImpl()201     public void testControllerImpl() throws Exception {
202         final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
203         final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0);
204         final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1);
205         final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2);
206         final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3);
207 
208         final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
209         final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
210         final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
211         final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
212         final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
213         final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
214         final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
215         final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
216 
217         final ControllerImpl controller = ControllerImpl.createFrom(
218                 null /* currentInstance */, enabledItems);
219 
220         // switching-aware loop
221         assertRotationOrder(controller, false /* onlyCurrentIme */,
222                 latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
223 
224         // switching-unaware loop
225         assertRotationOrder(controller, false /* onlyCurrentIme */,
226                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
227                 switchUnawareJapaneseIme_ja_JP);
228 
229         // test onlyCurrentIme == true
230         assertRotationOrder(controller, true /* onlyCurrentIme */,
231                 latinIme_en_US, latinIme_fr);
232         assertRotationOrder(controller, true /* onlyCurrentIme */,
233                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
234         assertNextInputMethod(controller, true /* onlyCurrentIme */,
235                 subtypeUnawareIme, null);
236         assertNextInputMethod(controller, true /* onlyCurrentIme */,
237                 japaneseIme_ja_JP, null);
238         assertNextInputMethod(controller, true /* onlyCurrentIme */,
239                 switchUnawareJapaneseIme_ja_JP, null);
240 
241         // Make sure that disabled IMEs are not accepted.
242         assertNextInputMethod(controller, false /* onlyCurrentIme */,
243                 disabledIme_en_US, null);
244         assertNextInputMethod(controller, false /* onlyCurrentIme */,
245                 disabledIme_hi, null);
246         assertNextInputMethod(controller, false /* onlyCurrentIme */,
247                 disabledSwitchingUnawareIme, null);
248         assertNextInputMethod(controller, false /* onlyCurrentIme */,
249                 disabledSubtypeUnawareIme, null);
250         assertNextInputMethod(controller, true /* onlyCurrentIme */,
251                 disabledIme_en_US, null);
252         assertNextInputMethod(controller, true /* onlyCurrentIme */,
253                 disabledIme_hi, null);
254         assertNextInputMethod(controller, true /* onlyCurrentIme */,
255                 disabledSwitchingUnawareIme, null);
256         assertNextInputMethod(controller, true /* onlyCurrentIme */,
257                 disabledSubtypeUnawareIme, null);
258     }
259 
260     @Test
testControllerImplWithUserAction()261     public void testControllerImplWithUserAction() throws Exception {
262         final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
263         final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
264         final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
265         final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
266         final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
267         final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
268         final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
269         final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
270 
271         final ControllerImpl controller = ControllerImpl.createFrom(
272                 null /* currentInstance */, enabledItems);
273 
274         // === switching-aware loop ===
275         assertRotationOrder(controller, false /* onlyCurrentIme */,
276                 latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
277         // Then notify that a user did something for latinIme_fr.
278         onUserAction(controller, latinIme_fr);
279         assertRotationOrder(controller, false /* onlyCurrentIme */,
280                 latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
281         // Then notify that a user did something for latinIme_fr again.
282         onUserAction(controller, latinIme_fr);
283         assertRotationOrder(controller, false /* onlyCurrentIme */,
284                 latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
285         // Then notify that a user did something for japaneseIme_ja_JP.
286         onUserAction(controller, latinIme_fr);
287         assertRotationOrder(controller, false /* onlyCurrentIme */,
288                 japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
289         // Check onlyCurrentIme == true.
290         assertNextInputMethod(controller, true /* onlyCurrentIme */,
291                 japaneseIme_ja_JP, null);
292         assertRotationOrder(controller, true /* onlyCurrentIme */,
293                 latinIme_fr, latinIme_en_US);
294         assertRotationOrder(controller, true /* onlyCurrentIme */,
295                 latinIme_en_US, latinIme_fr);
296 
297         // === switching-unaware loop ===
298         assertRotationOrder(controller, false /* onlyCurrentIme */,
299                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
300                 switchUnawareJapaneseIme_ja_JP);
301         // User action should be ignored for switching unaware IMEs.
302         onUserAction(controller, switchingUnawarelatinIme_hi);
303         assertRotationOrder(controller, false /* onlyCurrentIme */,
304                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
305                 switchUnawareJapaneseIme_ja_JP);
306         // User action should be ignored for switching unaware IMEs.
307         onUserAction(controller, switchUnawareJapaneseIme_ja_JP);
308         assertRotationOrder(controller, false /* onlyCurrentIme */,
309                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
310                 switchUnawareJapaneseIme_ja_JP);
311         // Check onlyCurrentIme == true.
312         assertRotationOrder(controller, true /* onlyCurrentIme */,
313                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
314         assertNextInputMethod(controller, true /* onlyCurrentIme */,
315                 subtypeUnawareIme, null);
316         assertNextInputMethod(controller, true /* onlyCurrentIme */,
317                 switchUnawareJapaneseIme_ja_JP, null);
318 
319         // Rotation order should be preserved when created with the same subtype list.
320         final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
321         final ControllerImpl newController = ControllerImpl.createFrom(controller,
322                 sameEnabledItems);
323         assertRotationOrder(newController, false /* onlyCurrentIme */,
324                 japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
325         assertRotationOrder(newController, false /* onlyCurrentIme */,
326                 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
327                 switchUnawareJapaneseIme_ja_JP);
328 
329         // Rotation order should be initialized when created with a different subtype list.
330         final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList(
331                 latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK,
332                 switchUnawareJapaneseIme_ja_JP);
333         final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
334                 differentEnabledItems);
335         assertRotationOrder(anotherController, false /* onlyCurrentIme */,
336                 latinIme_en_US, latinIme_fr);
337         assertRotationOrder(anotherController, false /* onlyCurrentIme */,
338                 switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP);
339     }
340 
341     @Test
testImeSubtypeListItem()342     public void testImeSubtypeListItem() throws Exception {
343         final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>();
344         addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
345                 Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
346                 true /* supportsSwitchingToNextInputMethod*/);
347         final ImeSubtypeListItem item_en_US = items.get(0);
348         final ImeSubtypeListItem item_fr = items.get(1);
349         final ImeSubtypeListItem item_en = items.get(2);
350         final ImeSubtypeListItem item_enn = items.get(3);
351         final ImeSubtypeListItem item_e = items.get(4);
352         final ImeSubtypeListItem item_EN_US = items.get(5);
353 
354         assertTrue(item_en_US.mIsSystemLocale);
355         assertFalse(item_fr.mIsSystemLocale);
356         assertFalse(item_en.mIsSystemLocale);
357         assertFalse(item_en.mIsSystemLocale);
358         assertFalse(item_enn.mIsSystemLocale);
359         assertFalse(item_e.mIsSystemLocale);
360         assertFalse(item_EN_US.mIsSystemLocale);
361 
362         assertTrue(item_en_US.mIsSystemLanguage);
363         assertFalse(item_fr.mIsSystemLanguage);
364         assertTrue(item_en.mIsSystemLanguage);
365         assertFalse(item_enn.mIsSystemLocale);
366         assertFalse(item_e.mIsSystemLocale);
367         assertFalse(item_EN_US.mIsSystemLocale);
368     }
369 
370     @Test
testImeSubtypeListComparator()371     public void testImeSubtypeListComparator() throws Exception {
372         final ComponentName imeX1 = new ComponentName("com.example.imeX", "Ime1");
373         final ComponentName imeX2 = new ComponentName("com.example.imeX", "Ime2");
374         final ComponentName imeY1 = new ComponentName("com.example.imeY", "Ime1");
375         final ComponentName imeZ1 = new ComponentName("com.example.imeZ", "Ime1");
376         {
377             final List<ImeSubtypeListItem> items = Arrays.asList(
378                     // Subtypes of two IMEs that have the same display name "X".
379                     // Subtypes that has the same locale of the system's.
380                     createDummyItem(imeX1, "X", "E", "en_US", 0, "en_US"),
381                     createDummyItem(imeX2, "X", "E", "en_US", 0, "en_US"),
382                     createDummyItem(imeX1, "X", "Z", "en_US", 3, "en_US"),
383                     createDummyItem(imeX2, "X", "Z", "en_US", 3, "en_US"),
384                     createDummyItem(imeX1, "X", "", "en_US", 6, "en_US"),
385                     createDummyItem(imeX2, "X", "", "en_US", 6, "en_US"),
386                     // Subtypes that has the same language of the system's.
387                     createDummyItem(imeX1, "X", "E", "en", 1, "en_US"),
388                     createDummyItem(imeX2, "X", "E", "en", 1, "en_US"),
389                     createDummyItem(imeX1, "X", "Z", "en", 4, "en_US"),
390                     createDummyItem(imeX2, "X", "Z", "en", 4, "en_US"),
391                     createDummyItem(imeX1, "X", "", "en", 7, "en_US"),
392                     createDummyItem(imeX2, "X", "", "en", 7, "en_US"),
393                     // Subtypes that has different language than the system's.
394                     createDummyItem(imeX1, "X", "A", "hi_IN", 27, "en_US"),
395                     createDummyItem(imeX2, "X", "A", "hi_IN", 27, "en_US"),
396                     createDummyItem(imeX1, "X", "E", "ja", 2, "en_US"),
397                     createDummyItem(imeX2, "X", "E", "ja", 2, "en_US"),
398                     createDummyItem(imeX1, "X", "Z", "ja", 5, "en_US"),
399                     createDummyItem(imeX2, "X", "Z", "ja", 5, "en_US"),
400                     createDummyItem(imeX1, "X", "", "ja", 8, "en_US"),
401                     createDummyItem(imeX2, "X", "", "ja", 8, "en_US"),
402 
403                     // Subtypes of IME "Y".
404                     // Subtypes that has the same locale of the system's.
405                     createDummyItem(imeY1, "Y", "E", "en_US", 9, "en_US"),
406                     createDummyItem(imeY1, "Y", "Z", "en_US", 12, "en_US"),
407                     createDummyItem(imeY1, "Y", "", "en_US", 15, "en_US"),
408                     // Subtypes that has the same language of the system's.
409                     createDummyItem(imeY1, "Y", "E", "en", 10, "en_US"),
410                     createDummyItem(imeY1, "Y", "Z", "en", 13, "en_US"),
411                     createDummyItem(imeY1, "Y", "", "en", 16, "en_US"),
412                     // Subtypes that has different language than the system's.
413                     createDummyItem(imeY1, "Y", "A", "hi_IN", 28, "en_US"),
414                     createDummyItem(imeY1, "Y", "E", "ja", 11, "en_US"),
415                     createDummyItem(imeY1, "Y", "Z", "ja", 14, "en_US"),
416                     createDummyItem(imeY1, "Y", "", "ja", 17, "en_US"),
417 
418                     // Subtypes of IME Z.
419                     // Subtypes that has the same locale of the system's.
420                     createDummyItem(imeZ1, "", "E", "en_US", 18, "en_US"),
421                     createDummyItem(imeZ1, "", "Z", "en_US", 21, "en_US"),
422                     createDummyItem(imeZ1, "", "", "en_US", 24, "en_US"),
423                     // Subtypes that has the same language of the system's.
424                     createDummyItem(imeZ1, "", "E", "en", 19, "en_US"),
425                     createDummyItem(imeZ1, "", "Z", "en", 22, "en_US"),
426                     createDummyItem(imeZ1, "", "", "en", 25, "en_US"),
427                     // Subtypes that has different language than the system's.
428                     createDummyItem(imeZ1, "", "A", "hi_IN", 29, "en_US"),
429                     createDummyItem(imeZ1, "", "E", "ja", 20, "en_US"),
430                     createDummyItem(imeZ1, "", "Z", "ja", 23, "en_US"),
431                     createDummyItem(imeZ1, "", "", "ja", 26, "en_US"));
432 
433             // Ensure {@link java.lang.Comparable#compareTo} contracts are satisfied.
434             for (int i = 0; i < items.size(); ++i) {
435                 final ImeSubtypeListItem item1 = items.get(i);
436                 // Ensures sgn(x.compareTo(y)) == -sgn(y.compareTo(x)).
437                 assertTrue(item1 + " has the same order of itself", item1.compareTo(item1) == 0);
438                 // Ensures (x.compareTo(y) > 0 && y.compareTo(z) > 0) implies x.compareTo(z) > 0.
439                 for (int j = i + 1; j < items.size(); ++j) {
440                     final ImeSubtypeListItem item2 = items.get(j);
441                     // Ensures sgn(x.compareTo(y)) == -sgn(y.compareTo(x)).
442                     assertTrue(item1 + " is less than " + item2, item1.compareTo(item2) < 0);
443                     assertTrue(item2 + " is greater than " + item1, item2.compareTo(item1) > 0);
444                 }
445             }
446         }
447 
448         {
449             // Following two items have the same priority.
450             final ImeSubtypeListItem nonSystemLocale1 =
451                     createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
452             final ImeSubtypeListItem nonSystemLocale2 =
453                     createDummyItem(imeX1, "X", "A", "hi_IN", 1, "en_US");
454             assertTrue(nonSystemLocale1.compareTo(nonSystemLocale2) == 0);
455             assertTrue(nonSystemLocale2.compareTo(nonSystemLocale1) == 0);
456             // But those aren't equal to each other.
457             assertFalse(nonSystemLocale1.equals(nonSystemLocale2));
458             assertFalse(nonSystemLocale2.equals(nonSystemLocale1));
459         }
460 
461         {
462             // Check if ComponentName is also taken into account when comparing two items.
463             final ImeSubtypeListItem ime1 = createDummyItem(imeX1, "X", "A", "ja_JP", 0, "en_US");
464             final ImeSubtypeListItem ime2 = createDummyItem(imeX2, "X", "A", "ja_JP", 0, "en_US");
465             assertTrue(ime1.compareTo(ime2) < 0);
466             assertTrue(ime2.compareTo(ime1) > 0);
467             // But those aren't equal to each other.
468             assertFalse(ime1.equals(ime2));
469             assertFalse(ime2.equals(ime1));
470         }
471     }
472 }
473