2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.internal.inputmethod;
19 import android.content.pm.ApplicationInfo;
20 import android.content.pm.ResolveInfo;
21 import android.content.pm.ServiceInfo;
22 import android.test.InstrumentationTestCase;
23 import android.test.suitebuilder.annotation.SmallTest;
24 import android.view.inputmethod.InputMethodInfo;
25 import android.view.inputmethod.InputMethodSubtype;
26 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
28 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
29 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
35 public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase {
36 private static final String DUMMY_PACKAGE_NAME = "dummy package name";
37 private static final String DUMMY_IME_LABEL = "dummy ime label";
38 private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
39 private static final boolean DUMMY_IS_AUX_IME = false;
40 private static final boolean DUMMY_FORCE_DEFAULT = false;
41 private static final int DUMMY_IS_DEFAULT_RES_ID = 0;
42 private static final String SYSTEM_LOCALE = "en_US";
43 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
45 private static InputMethodSubtype createDummySubtype(final String locale) {
46 final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
47 return builder.setSubtypeNameResId(0)
48 .setSubtypeIconResId(0)
49 .setSubtypeLocale(locale)
50 .setIsAsciiCapable(true)
54 private static void addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items,
55 String imeName, String imeLabel, List<String> subtypeLocales,
56 boolean supportsSwitchingToNextInputMethod) {
57 final ResolveInfo ri = new ResolveInfo();
58 final ServiceInfo si = new ServiceInfo();
59 final ApplicationInfo ai = new ApplicationInfo();
60 ai.packageName = DUMMY_PACKAGE_NAME;
62 si.applicationInfo = ai;
64 si.packageName = DUMMY_PACKAGE_NAME;
67 si.nonLocalizedLabel = imeLabel;
69 List<InputMethodSubtype> subtypes = null;
70 if (subtypeLocales != null) {
71 subtypes = new ArrayList<>();
72 for (String subtypeLocale : subtypeLocales) {
73 subtypes.add(createDummySubtype(subtypeLocale));
76 final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
77 DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
78 DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod,
79 false /* supportsDismissingWindow */);
80 if (subtypes == null) {
81 items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
82 NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
84 for (int i = 0; i < subtypes.size(); ++i) {
85 final String subtypeLocale = subtypeLocales.get(i);
86 items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
92 private static ImeSubtypeListItem createDummyItem(String imeName,
93 String subtypeName, String subtypeLocale, int subtypeIndex, String systemLocale) {
94 final ResolveInfo ri = new ResolveInfo();
95 final ServiceInfo si = new ServiceInfo();
96 final ApplicationInfo ai = new ApplicationInfo();
97 ai.packageName = DUMMY_PACKAGE_NAME;
99 si.applicationInfo = ai;
101 si.packageName = DUMMY_PACKAGE_NAME;
104 si.nonLocalizedLabel = DUMMY_IME_LABEL;
106 ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
107 subtypes.add(new InputMethodSubtypeBuilder()
108 .setSubtypeNameResId(0)
109 .setSubtypeIconResId(0)
110 .setSubtypeLocale(subtypeLocale)
111 .setIsAsciiCapable(true)
113 final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
114 DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
115 DUMMY_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */,
116 false /* supportsDismissingWindow */);
117 return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
121 private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
122 final List<ImeSubtypeListItem> items = new ArrayList<>();
123 addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
124 true /* supportsSwitchingToNextInputMethod*/);
125 addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
126 Arrays.asList("en_UK", "hi"),
127 false /* supportsSwitchingToNextInputMethod*/);
128 addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null,
129 false /* supportsSwitchingToNextInputMethod*/);
130 addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"),
131 true /* supportsSwitchingToNextInputMethod*/);
132 addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme",
133 Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
137 private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
138 final List<ImeSubtypeListItem> items = new ArrayList<>();
139 addDummyImeSubtypeListItems(items,
140 "UnknownIme", "UnknownIme",
141 Arrays.asList("en_US", "hi"),
142 true /* supportsSwitchingToNextInputMethod*/);
143 addDummyImeSubtypeListItems(items,
144 "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme",
145 Arrays.asList("en_US"),
146 false /* supportsSwitchingToNextInputMethod*/);
147 addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme",
148 "UnknownSubtypeUnawareIme", null,
149 false /* supportsSwitchingToNextInputMethod*/);
153 private void assertNextInputMethod(final ControllerImpl controller,
154 final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem,
155 final ImeSubtypeListItem nextItem, final ImeSubtypeListItem prevItem) {
156 InputMethodSubtype subtype = null;
157 if (currentItem.mSubtypeName != null) {
158 subtype = createDummySubtype(currentItem.mSubtypeName.toString());
160 final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
161 currentItem.mImi, subtype, true /* forward */);
162 assertEquals(nextItem, nextIme);
163 final ImeSubtypeListItem prevIme = controller.getNextInputMethod(onlyCurrentIme,
164 currentItem.mImi, subtype, false /* forward */);
165 assertEquals(prevItem, prevIme);
168 private void assertRotationOrder(final ControllerImpl controller,
169 final boolean onlyCurrentIme,
170 final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
171 final int N = expectedRotationOrderOfImeSubtypeList.length;
172 for (int i = 0; i < N; i++) {
173 final int currentIndex = i;
174 final int prevIndex = (currentIndex + N - 1) % N;
175 final int nextIndex = (currentIndex + 1) % N;
176 final ImeSubtypeListItem currentItem =
177 expectedRotationOrderOfImeSubtypeList[currentIndex];
178 final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
179 final ImeSubtypeListItem prevItem = expectedRotationOrderOfImeSubtypeList[prevIndex];
180 assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem, prevItem);
184 private void onUserAction(final ControllerImpl controller,
185 final ImeSubtypeListItem subtypeListItem) {
186 InputMethodSubtype subtype = null;
187 if (subtypeListItem.mSubtypeName != null) {
188 subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString());
190 controller.onUserActionLocked(subtypeListItem.mImi, subtype);
194 public void testControllerImpl() throws Exception {
195 final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
196 final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0);
197 final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1);
198 final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2);
199 final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3);
201 final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
202 final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
203 final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
204 final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
205 final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
206 final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
207 final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
208 final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
210 final ControllerImpl controller = ControllerImpl.createFrom(
211 null /* currentInstance */, enabledItems);
213 // switching-aware loop
214 assertRotationOrder(controller, false /* onlyCurrentIme */,
215 latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
217 // switching-unaware loop
218 assertRotationOrder(controller, false /* onlyCurrentIme */,
219 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
220 switchUnawareJapaneseIme_ja_JP);
222 // test onlyCurrentIme == true
223 assertRotationOrder(controller, true /* onlyCurrentIme */,
224 latinIme_en_US, latinIme_fr);
225 assertRotationOrder(controller, true /* onlyCurrentIme */,
226 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
227 assertNextInputMethod(controller, true /* onlyCurrentIme */,
228 subtypeUnawareIme, null, null);
229 assertNextInputMethod(controller, true /* onlyCurrentIme */,
230 japaneseIme_ja_JP, null, null);
231 assertNextInputMethod(controller, true /* onlyCurrentIme */,
232 switchUnawareJapaneseIme_ja_JP, null, null);
234 // Make sure that disabled IMEs are not accepted.
235 assertNextInputMethod(controller, false /* onlyCurrentIme */,
236 disabledIme_en_US, null, null);
237 assertNextInputMethod(controller, false /* onlyCurrentIme */,
238 disabledIme_hi, null, null);
239 assertNextInputMethod(controller, false /* onlyCurrentIme */,
240 disabledSwitchingUnawareIme, null, null);
241 assertNextInputMethod(controller, false /* onlyCurrentIme */,
242 disabledSubtypeUnawareIme, null, null);
243 assertNextInputMethod(controller, true /* onlyCurrentIme */,
244 disabledIme_en_US, null, null);
245 assertNextInputMethod(controller, true /* onlyCurrentIme */,
246 disabledIme_hi, null, null);
247 assertNextInputMethod(controller, true /* onlyCurrentIme */,
248 disabledSwitchingUnawareIme, null, null);
249 assertNextInputMethod(controller, true /* onlyCurrentIme */,
250 disabledSubtypeUnawareIme, null, null);
254 public void testControllerImplWithUserAction() throws Exception {
255 final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
256 final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
257 final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
258 final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
259 final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
260 final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
261 final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
262 final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
264 final ControllerImpl controller = ControllerImpl.createFrom(
265 null /* currentInstance */, enabledItems);
267 // === switching-aware loop ===
268 assertRotationOrder(controller, false /* onlyCurrentIme */,
269 latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
270 // Then notify that a user did something for latinIme_fr.
271 onUserAction(controller, latinIme_fr);
272 assertRotationOrder(controller, false /* onlyCurrentIme */,
273 latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
274 // Then notify that a user did something for latinIme_fr again.
275 onUserAction(controller, latinIme_fr);
276 assertRotationOrder(controller, false /* onlyCurrentIme */,
277 latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
278 // Then notify that a user did something for japaneseIme_ja_JP.
279 onUserAction(controller, latinIme_fr);
280 assertRotationOrder(controller, false /* onlyCurrentIme */,
281 japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
282 // Check onlyCurrentIme == true.
283 assertNextInputMethod(controller, true /* onlyCurrentIme */,
284 japaneseIme_ja_JP, null, null);
285 assertRotationOrder(controller, true /* onlyCurrentIme */,
286 latinIme_fr, latinIme_en_US);
287 assertRotationOrder(controller, true /* onlyCurrentIme */,
288 latinIme_en_US, latinIme_fr);
290 // === switching-unaware loop ===
291 assertRotationOrder(controller, false /* onlyCurrentIme */,
292 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
293 switchUnawareJapaneseIme_ja_JP);
294 // User action should be ignored for switching unaware IMEs.
295 onUserAction(controller, switchingUnawarelatinIme_hi);
296 assertRotationOrder(controller, false /* onlyCurrentIme */,
297 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
298 switchUnawareJapaneseIme_ja_JP);
299 // User action should be ignored for switching unaware IMEs.
300 onUserAction(controller, switchUnawareJapaneseIme_ja_JP);
301 assertRotationOrder(controller, false /* onlyCurrentIme */,
302 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
303 switchUnawareJapaneseIme_ja_JP);
304 // Check onlyCurrentIme == true.
305 assertRotationOrder(controller, true /* onlyCurrentIme */,
306 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
307 assertNextInputMethod(controller, true /* onlyCurrentIme */,
308 subtypeUnawareIme, null, null);
309 assertNextInputMethod(controller, true /* onlyCurrentIme */,
310 switchUnawareJapaneseIme_ja_JP, null, null);
312 // Rotation order should be preserved when created with the same subtype list.
313 final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
314 final ControllerImpl newController = ControllerImpl.createFrom(controller,
316 assertRotationOrder(newController, false /* onlyCurrentIme */,
317 japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
318 assertRotationOrder(newController, false /* onlyCurrentIme */,
319 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
320 switchUnawareJapaneseIme_ja_JP);
322 // Rotation order should be initialized when created with a different subtype list.
323 final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList(
324 latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK,
325 switchUnawareJapaneseIme_ja_JP);
326 final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
327 differentEnabledItems);
328 assertRotationOrder(anotherController, false /* onlyCurrentIme */,
329 latinIme_en_US, latinIme_fr);
330 assertRotationOrder(anotherController, false /* onlyCurrentIme */,
331 switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP);
335 public void testImeSubtypeListItem() throws Exception {
336 final List<ImeSubtypeListItem> items = new ArrayList<>();
337 addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
338 Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
339 true /* supportsSwitchingToNextInputMethod*/);
340 final ImeSubtypeListItem item_en_US = items.get(0);
341 final ImeSubtypeListItem item_fr = items.get(1);
342 final ImeSubtypeListItem item_en = items.get(2);
343 final ImeSubtypeListItem item_enn = items.get(3);
344 final ImeSubtypeListItem item_e = items.get(4);
345 final ImeSubtypeListItem item_EN_US = items.get(5);
347 assertTrue(item_en_US.mIsSystemLocale);
348 assertFalse(item_fr.mIsSystemLocale);
349 assertFalse(item_en.mIsSystemLocale);
350 assertFalse(item_en.mIsSystemLocale);
351 assertFalse(item_enn.mIsSystemLocale);
352 assertFalse(item_e.mIsSystemLocale);
353 assertFalse(item_EN_US.mIsSystemLocale);
355 assertTrue(item_en_US.mIsSystemLanguage);
356 assertFalse(item_fr.mIsSystemLanguage);
357 assertTrue(item_en.mIsSystemLanguage);
358 assertFalse(item_enn.mIsSystemLocale);
359 assertFalse(item_e.mIsSystemLocale);
360 assertFalse(item_EN_US.mIsSystemLocale);
364 public void testImeSubtypeListComparator() throws Exception {
366 final List<ImeSubtypeListItem> items = Arrays.asList(
367 createDummyItem("X", "A", "en_US", 0, "en_US"),
368 createDummyItem("X", "A", "en", 1, "en_US"),
369 createDummyItem("X", "A", "ja", 2, "en_US"),
370 createDummyItem("X", "Z", "en_US", 3, "en_US"),
371 createDummyItem("X", "Z", "en", 4, "en_US"),
372 createDummyItem("X", "Z", "ja", 5, "en_US"),
373 createDummyItem("X", "", "en_US", 6, "en_US"),
374 createDummyItem("X", "", "en", 7, "en_US"),
375 createDummyItem("X", "", "ja", 8, "en_US"),
376 createDummyItem("Y", "A", "en_US", 9, "en_US"),
377 createDummyItem("Y", "A", "en", 10, "en_US"),
378 createDummyItem("Y", "A", "ja", 11, "en_US"),
379 createDummyItem("Y", "Z", "en_US", 12, "en_US"),
380 createDummyItem("Y", "Z", "en", 13, "en_US"),
381 createDummyItem("Y", "Z", "ja", 14, "en_US"),
382 createDummyItem("Y", "", "en_US", 15, "en_US"),
383 createDummyItem("Y", "", "en", 16, "en_US"),
384 createDummyItem("Y", "", "ja", 17, "en_US"),
385 createDummyItem("", "A", "en_US", 18, "en_US"),
386 createDummyItem("", "A", "en", 19, "en_US"),
387 createDummyItem("", "A", "ja", 20, "en_US"),
388 createDummyItem("", "Z", "en_US", 21, "en_US"),
389 createDummyItem("", "Z", "en", 22, "en_US"),
390 createDummyItem("", "Z", "ja", 23, "en_US"),
391 createDummyItem("", "", "en_US", 24, "en_US"),
392 createDummyItem("", "", "en", 25, "en_US"),
393 createDummyItem("", "", "ja", 26, "en_US"));
395 for (int i = 0; i < items.size(); ++i) {
396 assertEquals(0, items.get(i).compareTo(items.get(i)));
397 for (int j = i + 1; j < items.size(); ++j) {
398 assertTrue(items.get(i).compareTo(items.get(j)) < 0);
399 assertTrue(items.get(j).compareTo(items.get(i)) > 0);
405 // Following two items have the same priority.
406 final ImeSubtypeListItem nonSystemLocale1 =
407 createDummyItem("X", "A", "ja_JP", 0, "en_us");
408 final ImeSubtypeListItem nonSystemLocale2 =
409 createDummyItem("X", "A", "hi_IN", 1, "en_us");
410 assertEquals(0, nonSystemLocale1.compareTo(nonSystemLocale2));
411 assertEquals(0, nonSystemLocale2.compareTo(nonSystemLocale1));