InputMethodTest.java revision 59cccf93d28648a77860a30349e52b7eeb98436c
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 17package android.os; 18 19import android.content.Context; 20import android.content.pm.ApplicationInfo; 21import android.content.pm.ResolveInfo; 22import android.content.pm.ServiceInfo; 23import android.test.InstrumentationTestCase; 24import android.test.suitebuilder.annotation.SmallTest; 25import android.view.inputmethod.InputMethodInfo; 26import android.view.inputmethod.InputMethodSubtype; 27import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; 28 29import com.android.internal.inputmethod.InputMethodUtils; 30 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.HashSet; 34import java.util.List; 35import java.util.Locale; 36 37public class InputMethodTest extends InstrumentationTestCase { 38 private static final boolean IS_AUX = true; 39 private static final boolean IS_DEFAULT = true; 40 private static final boolean IS_AUTO = true; 41 private static final boolean IS_ASCII_CAPABLE = true; 42 private static final boolean IS_SYSTEM_READY = true; 43 private static final ArrayList<InputMethodSubtype> NO_SUBTYPE = null; 44 private static final Locale LOCALE_EN_US = new Locale("en", "US"); 45 private static final Locale LOCALE_EN_GB = new Locale("en", "GB"); 46 private static final Locale LOCALE_EN_IN = new Locale("en", "IN"); 47 private static final Locale LOCALE_HI = new Locale("hi"); 48 private static final Locale LOCALE_JA_JP = new Locale("ja", "JP"); 49 private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; 50 private static final String SUBTYPE_MODE_VOICE = "voice"; 51 52 @SmallTest 53 public void testVoiceImes() throws Exception { 54 // locale: en_US 55 assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US, !IS_SYSTEM_READY, 56 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 57 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); 58 assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, !IS_SYSTEM_READY, 59 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 60 "DummyDefaultEnKeyboardIme"); 61 assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US, IS_SYSTEM_READY, 62 "DummyDefaultAutoVoiceIme", "DummyDefaultEnKeyboardIme"); 63 assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US, IS_SYSTEM_READY, 64 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 65 "DummyDefaultEnKeyboardIme"); 66 67 // locale: en_GB 68 assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, !IS_SYSTEM_READY, 69 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 70 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); 71 assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, !IS_SYSTEM_READY, 72 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 73 "DummyDefaultEnKeyboardIme"); 74 assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB, IS_SYSTEM_READY, 75 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); 76 assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB, IS_SYSTEM_READY, 77 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 78 "DummyDefaultEnKeyboardIme"); 79 80 // locale: ja_JP 81 assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, !IS_SYSTEM_READY, 82 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 83 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); 84 assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, !IS_SYSTEM_READY, 85 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 86 "DummyDefaultEnKeyboardIme"); 87 assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP, IS_SYSTEM_READY, 88 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 89 "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme"); 90 assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP, IS_SYSTEM_READY, 91 "DummyNonDefaultAutoVoiceIme0", "DummyNonDefaultAutoVoiceIme1", 92 "DummyDefaultEnKeyboardIme"); 93 } 94 95 @SmallTest 96 public void testKeyboardImes() throws Exception { 97 // locale: en_US 98 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_US, !IS_SYSTEM_READY, 99 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 100 "com.android.apps.inputmethod.hindi"); 101 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_US, IS_SYSTEM_READY, 102 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 103 "com.android.apps.inputmethod.hindi"); 104 105 // locale: en_GB 106 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_GB, !IS_SYSTEM_READY, 107 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 108 "com.android.apps.inputmethod.hindi"); 109 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_GB, IS_SYSTEM_READY, 110 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 111 "com.android.apps.inputmethod.hindi"); 112 113 // locale: en_IN 114 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_IN, !IS_SYSTEM_READY, 115 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 116 "com.android.apps.inputmethod.hindi"); 117 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_EN_IN, IS_SYSTEM_READY, 118 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 119 "com.android.apps.inputmethod.hindi"); 120 121 // locale: hi 122 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_HI, !IS_SYSTEM_READY, 123 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 124 "com.android.apps.inputmethod.hindi"); 125 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_HI, IS_SYSTEM_READY, 126 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 127 "com.android.apps.inputmethod.hindi"); 128 129 // locale: ja_JP 130 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_JA_JP, !IS_SYSTEM_READY, 131 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 132 "com.android.apps.inputmethod.hindi"); 133 assertDefaultEnabledImes(getSamplePreinstalledImes(), LOCALE_JA_JP, IS_SYSTEM_READY, 134 "com.android.apps.inputmethod.voice", "com.android.apps.inputmethod.latin", 135 "com.android.apps.inputmethod.hindi", "com.android.apps.inputmethod.japanese"); 136 } 137 138 @SmallTest 139 public void testParcelable() throws Exception { 140 final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes(); 141 final List<InputMethodInfo> clonedList = cloneViaParcel(originalList); 142 assertNotNull(clonedList); 143 final List<InputMethodInfo> clonedClonedList = cloneViaParcel(clonedList); 144 assertNotNull(clonedClonedList); 145 assertEquals(originalList, clonedList); 146 assertEquals(clonedList, clonedClonedList); 147 assertEquals(originalList.size(), clonedList.size()); 148 assertEquals(clonedList.size(), clonedClonedList.size()); 149 for (int imeIndex = 0; imeIndex < originalList.size(); ++imeIndex) { 150 verifyEquality(originalList.get(imeIndex), clonedList.get(imeIndex)); 151 verifyEquality(clonedList.get(imeIndex), clonedClonedList.get(imeIndex)); 152 } 153 } 154 155 private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes, 156 final Locale systemLocale, final boolean isSystemReady, String... imeNames) { 157 final Context context = getInstrumentation().getTargetContext(); 158 assertEquals(new HashSet<String>(Arrays.asList(imeNames)), 159 getPackageNames(callGetDefaultEnabledImesUnderWithLocale(context, 160 isSystemReady, preinstalledImes, systemLocale))); 161 } 162 163 private static List<InputMethodInfo> cloneViaParcel(final List<InputMethodInfo> list) { 164 Parcel p = null; 165 try { 166 p = Parcel.obtain(); 167 p.writeTypedList(list); 168 p.setDataPosition(0); 169 return p.createTypedArrayList(InputMethodInfo.CREATOR); 170 } finally { 171 if (p != null) { 172 p.recycle(); 173 } 174 } 175 } 176 177 private static ArrayList<InputMethodInfo> callGetDefaultEnabledImesUnderWithLocale( 178 final Context context, final boolean isSystemReady, 179 final ArrayList<InputMethodInfo> imis, final Locale locale) { 180 final Locale initialLocale = context.getResources().getConfiguration().locale; 181 try { 182 context.getResources().getConfiguration().setLocale(locale); 183 return InputMethodUtils.getDefaultEnabledImes(context, isSystemReady, imis); 184 } finally { 185 context.getResources().getConfiguration().setLocale(initialLocale); 186 } 187 } 188 189 private HashSet<String> getPackageNames(final ArrayList<InputMethodInfo> imis) { 190 final HashSet<String> packageNames = new HashSet<>(); 191 for (final InputMethodInfo imi : imis) { 192 final String actualPackageName = imi.getPackageName(); 193 packageNames.add(actualPackageName); 194 } 195 return packageNames; 196 } 197 198 private static void verifyEquality(InputMethodInfo expected, InputMethodInfo actual) { 199 assertEquals(expected, actual); 200 assertEquals(expected.getSubtypeCount(), actual.getSubtypeCount()); 201 for (int subtypeIndex = 0; subtypeIndex < expected.getSubtypeCount(); ++subtypeIndex) { 202 final InputMethodSubtype expectedSubtype = expected.getSubtypeAt(subtypeIndex); 203 final InputMethodSubtype actualSubtype = actual.getSubtypeAt(subtypeIndex); 204 assertEquals(expectedSubtype, actualSubtype); 205 assertEquals(expectedSubtype.hashCode(), actualSubtype.hashCode()); 206 } 207 } 208 209 private static InputMethodInfo createDummyInputMethodInfo(String packageName, String name, 210 CharSequence label, boolean isAuxIme, boolean isDefault, 211 List<InputMethodSubtype> subtypes) { 212 final ResolveInfo ri = new ResolveInfo(); 213 final ServiceInfo si = new ServiceInfo(); 214 final ApplicationInfo ai = new ApplicationInfo(); 215 ai.packageName = packageName; 216 ai.enabled = true; 217 ai.flags |= ApplicationInfo.FLAG_SYSTEM; 218 si.applicationInfo = ai; 219 si.enabled = true; 220 si.packageName = packageName; 221 si.name = name; 222 si.exported = true; 223 si.nonLocalizedLabel = label; 224 ri.serviceInfo = si; 225 return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault); 226 } 227 228 private static InputMethodSubtype createDummyInputMethodSubtype(String locale, String mode, 229 boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, 230 boolean isAsciiCapable) { 231 return new InputMethodSubtypeBuilder() 232 .setSubtypeNameResId(0) 233 .setSubtypeIconResId(0) 234 .setSubtypeLocale(locale) 235 .setSubtypeMode(mode) 236 .setSubtypeExtraValue("") 237 .setIsAuxiliary(isAuxiliary) 238 .setOverridesImplicitlyEnabledSubtype(overridesImplicitlyEnabledSubtype) 239 .setIsAsciiCapable(isAsciiCapable) 240 .build(); 241 } 242 243 private static ArrayList<InputMethodInfo> getImesWithDefaultVoiceIme() { 244 ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>(); 245 { 246 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 247 subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX, IS_AUTO, 248 !IS_ASCII_CAPABLE)); 249 subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX, 250 !IS_AUTO, !IS_ASCII_CAPABLE)); 251 preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultAutoVoiceIme", 252 "dummy.voice0", "DummyVoice0", IS_AUX, IS_DEFAULT, subtypes)); 253 } 254 preinstalledImes.addAll(getImesWithoutDefaultVoiceIme()); 255 return preinstalledImes; 256 } 257 258 private static ArrayList<InputMethodInfo> getImesWithoutDefaultVoiceIme() { 259 ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>(); 260 { 261 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 262 subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX, IS_AUTO, 263 !IS_ASCII_CAPABLE)); 264 subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX, 265 !IS_AUTO, !IS_ASCII_CAPABLE)); 266 preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme0", 267 "dummy.voice1", "DummyVoice1", IS_AUX, !IS_DEFAULT, subtypes)); 268 } 269 { 270 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 271 subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX, IS_AUTO, 272 !IS_ASCII_CAPABLE)); 273 subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX, 274 !IS_AUTO, !IS_ASCII_CAPABLE)); 275 preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme1", 276 "dummy.voice2", "DummyVoice2", IS_AUX, !IS_DEFAULT, subtypes)); 277 } 278 { 279 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 280 subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX, 281 !IS_AUTO, !IS_ASCII_CAPABLE)); 282 preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultVoiceIme2", 283 "dummy.voice3", "DummyVoice3", IS_AUX, !IS_DEFAULT, subtypes)); 284 } 285 { 286 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 287 subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 288 !IS_AUTO, IS_ASCII_CAPABLE)); 289 preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultEnKeyboardIme", 290 "dummy.keyboard0", "DummyKeyboard0", !IS_AUX, IS_DEFAULT, subtypes)); 291 } 292 return preinstalledImes; 293 } 294 295 private static ArrayList<InputMethodInfo> getSamplePreinstalledImes() { 296 ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>(); 297 298 // a dummy Voice IME 299 { 300 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 301 subtypes.add(createDummyInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX, 302 IS_AUTO, !IS_ASCII_CAPABLE)); 303 preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.voice", 304 "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, IS_DEFAULT, 305 subtypes)); 306 } 307 308 // a dummy Hindi IME 309 { 310 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 311 // TODO: This subtype should be marked as IS_ASCII_CAPABLE 312 subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 313 !IS_AUTO, !IS_ASCII_CAPABLE)); 314 subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 315 !IS_AUTO, !IS_ASCII_CAPABLE)); 316 preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.hindi", 317 "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, IS_DEFAULT, 318 subtypes)); 319 } 320 321 // a dummy Pinyin IME 322 { 323 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 324 subtypes.add(createDummyInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 325 !IS_AUTO, !IS_ASCII_CAPABLE)); 326 preinstalledImes.add(createDummyInputMethodInfo("ccom.android.apps.inputmethod.pinyin", 327 "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, IS_DEFAULT, 328 subtypes)); 329 } 330 331 // a dummy Korian IME 332 { 333 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 334 subtypes.add(createDummyInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 335 !IS_AUTO, !IS_ASCII_CAPABLE)); 336 preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.korean", 337 "com.android.apps.inputmethod.korean", "DummyKorianIme", !IS_AUX, IS_DEFAULT, 338 subtypes)); 339 } 340 341 // a dummy Latin IME 342 { 343 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 344 subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 345 !IS_AUTO, IS_ASCII_CAPABLE)); 346 subtypes.add(createDummyInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 347 !IS_AUTO, IS_ASCII_CAPABLE)); 348 subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 349 !IS_AUTO, IS_ASCII_CAPABLE)); 350 subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 351 !IS_AUTO, IS_ASCII_CAPABLE)); 352 preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.latin", 353 "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT, 354 subtypes)); 355 } 356 357 // a dummy Japanese IME 358 { 359 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 360 subtypes.add(createDummyInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 361 !IS_AUTO, !IS_ASCII_CAPABLE)); 362 subtypes.add(createDummyInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX, 363 !IS_AUTO, !IS_ASCII_CAPABLE)); 364 preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.japanese", 365 "com.android.apps.inputmethod.japanese", "DummyJapaneseIme", !IS_AUX, 366 IS_DEFAULT, subtypes)); 367 } 368 369 return preinstalledImes; 370 } 371} 372