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