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