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