1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.inputmethod; 18 19import android.content.pm.ApplicationInfo; 20import android.content.pm.ResolveInfo; 21import android.content.pm.ServiceInfo; 22import android.test.InstrumentationTestCase; 23import android.test.suitebuilder.annotation.SmallTest; 24import android.view.inputmethod.InputMethodInfo; 25import android.view.inputmethod.InputMethodSubtype; 26import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; 27 28import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; 29import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; 30 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.List; 34 35public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase { 36 private static final String DUMMY_PACKAGE_NAME = "dymmy package name"; 37 private static final String DUMMY_SETTING_ACTIVITY_NAME = ""; 38 private static final boolean DUMMY_IS_AUX_IME = false; 39 private static final boolean DUMMY_FORCE_DEFAULT = false; 40 private static final int DUMMY_IS_DEFAULT_RES_ID = 0; 41 private static final String SYSTEM_LOCALE = "en_US"; 42 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; 43 44 private static InputMethodSubtype createDummySubtype(final String locale) { 45 final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); 46 return builder.setSubtypeNameResId(0) 47 .setSubtypeIconResId(0) 48 .setSubtypeLocale(locale) 49 .setIsAsciiCapable(true) 50 .build(); 51 } 52 53 private static void addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items, 54 String imeName, String imeLabel, List<String> subtypeLocales, 55 boolean supportsSwitchingToNextInputMethod) { 56 final ResolveInfo ri = new ResolveInfo(); 57 final ServiceInfo si = new ServiceInfo(); 58 final ApplicationInfo ai = new ApplicationInfo(); 59 ai.packageName = DUMMY_PACKAGE_NAME; 60 ai.enabled = true; 61 si.applicationInfo = ai; 62 si.enabled = true; 63 si.packageName = DUMMY_PACKAGE_NAME; 64 si.name = imeName; 65 si.exported = true; 66 si.nonLocalizedLabel = imeLabel; 67 ri.serviceInfo = si; 68 List<InputMethodSubtype> subtypes = null; 69 if (subtypeLocales != null) { 70 subtypes = new ArrayList<>(); 71 for (String subtypeLocale : subtypeLocales) { 72 subtypes.add(createDummySubtype(subtypeLocale)); 73 } 74 } 75 final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, 76 DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, 77 DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod); 78 if (subtypes == null) { 79 items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, 80 NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); 81 } else { 82 for (int i = 0; i < subtypes.size(); ++i) { 83 final String subtypeLocale = subtypeLocales.get(i); 84 items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, 85 SYSTEM_LOCALE)); 86 } 87 } 88 } 89 90 private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { 91 final List<ImeSubtypeListItem> items = new ArrayList<>(); 92 addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), 93 true /* supportsSwitchingToNextInputMethod*/); 94 addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", 95 Arrays.asList("en_UK", "hi"), 96 false /* supportsSwitchingToNextInputMethod*/); 97 addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, 98 false /* supportsSwitchingToNextInputMethod*/); 99 addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"), 100 true /* supportsSwitchingToNextInputMethod*/); 101 addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", 102 Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); 103 return items; 104 } 105 106 private static List<ImeSubtypeListItem> createDisabledImeSubtypes() { 107 final List<ImeSubtypeListItem> items = new ArrayList<>(); 108 addDummyImeSubtypeListItems(items, 109 "UnknownIme", "UnknownIme", 110 Arrays.asList("en_US", "hi"), 111 true /* supportsSwitchingToNextInputMethod*/); 112 addDummyImeSubtypeListItems(items, 113 "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme", 114 Arrays.asList("en_US"), 115 false /* supportsSwitchingToNextInputMethod*/); 116 addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", 117 "UnknownSubtypeUnawareIme", null, 118 false /* supportsSwitchingToNextInputMethod*/); 119 return items; 120 } 121 122 private void assertNextInputMethod(final ControllerImpl controller, 123 final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem, 124 final ImeSubtypeListItem nextItem, final ImeSubtypeListItem prevItem) { 125 InputMethodSubtype subtype = null; 126 if (currentItem.mSubtypeName != null) { 127 subtype = createDummySubtype(currentItem.mSubtypeName.toString()); 128 } 129 final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, 130 currentItem.mImi, subtype, true /* forward */); 131 assertEquals(nextItem, nextIme); 132 final ImeSubtypeListItem prevIme = controller.getNextInputMethod(onlyCurrentIme, 133 currentItem.mImi, subtype, false /* forward */); 134 assertEquals(prevItem, prevIme); 135 } 136 137 private void assertRotationOrder(final ControllerImpl controller, 138 final boolean onlyCurrentIme, 139 final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) { 140 final int N = expectedRotationOrderOfImeSubtypeList.length; 141 for (int i = 0; i < N; i++) { 142 final int currentIndex = i; 143 final int prevIndex = (currentIndex + N - 1) % N; 144 final int nextIndex = (currentIndex + 1) % N; 145 final ImeSubtypeListItem currentItem = 146 expectedRotationOrderOfImeSubtypeList[currentIndex]; 147 final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex]; 148 final ImeSubtypeListItem prevItem = expectedRotationOrderOfImeSubtypeList[prevIndex]; 149 assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem, prevItem); 150 } 151 } 152 153 private void onUserAction(final ControllerImpl controller, 154 final ImeSubtypeListItem subtypeListItem) { 155 InputMethodSubtype subtype = null; 156 if (subtypeListItem.mSubtypeName != null) { 157 subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString()); 158 } 159 controller.onUserActionLocked(subtypeListItem.mImi, subtype); 160 } 161 162 @SmallTest 163 public void testControllerImpl() throws Exception { 164 final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); 165 final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0); 166 final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); 167 final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2); 168 final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3); 169 170 final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); 171 final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); 172 final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); 173 final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); 174 final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); 175 final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); 176 final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); 177 final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); 178 179 final ControllerImpl controller = ControllerImpl.createFrom( 180 null /* currentInstance */, enabledItems); 181 182 // switching-aware loop 183 assertRotationOrder(controller, false /* onlyCurrentIme */, 184 latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); 185 186 // switching-unaware loop 187 assertRotationOrder(controller, false /* onlyCurrentIme */, 188 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, 189 switchUnawareJapaneseIme_ja_JP); 190 191 // test onlyCurrentIme == true 192 assertRotationOrder(controller, true /* onlyCurrentIme */, 193 latinIme_en_US, latinIme_fr); 194 assertRotationOrder(controller, true /* onlyCurrentIme */, 195 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); 196 assertNextInputMethod(controller, true /* onlyCurrentIme */, 197 subtypeUnawareIme, null, null); 198 assertNextInputMethod(controller, true /* onlyCurrentIme */, 199 japaneseIme_ja_JP, null, null); 200 assertNextInputMethod(controller, true /* onlyCurrentIme */, 201 switchUnawareJapaneseIme_ja_JP, null, null); 202 203 // Make sure that disabled IMEs are not accepted. 204 assertNextInputMethod(controller, false /* onlyCurrentIme */, 205 disabledIme_en_US, null, null); 206 assertNextInputMethod(controller, false /* onlyCurrentIme */, 207 disabledIme_hi, null, null); 208 assertNextInputMethod(controller, false /* onlyCurrentIme */, 209 disabledSwitchingUnawareIme, null, null); 210 assertNextInputMethod(controller, false /* onlyCurrentIme */, 211 disabledSubtypeUnawareIme, null, null); 212 assertNextInputMethod(controller, true /* onlyCurrentIme */, 213 disabledIme_en_US, null, null); 214 assertNextInputMethod(controller, true /* onlyCurrentIme */, 215 disabledIme_hi, null, null); 216 assertNextInputMethod(controller, true /* onlyCurrentIme */, 217 disabledSwitchingUnawareIme, null, null); 218 assertNextInputMethod(controller, true /* onlyCurrentIme */, 219 disabledSubtypeUnawareIme, null, null); 220 } 221 222 @SmallTest 223 public void testControllerImplWithUserAction() throws Exception { 224 final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); 225 final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); 226 final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); 227 final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); 228 final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); 229 final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); 230 final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); 231 final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); 232 233 final ControllerImpl controller = ControllerImpl.createFrom( 234 null /* currentInstance */, enabledItems); 235 236 // === switching-aware loop === 237 assertRotationOrder(controller, false /* onlyCurrentIme */, 238 latinIme_en_US, latinIme_fr, japaneseIme_ja_JP); 239 // Then notify that a user did something for latinIme_fr. 240 onUserAction(controller, latinIme_fr); 241 assertRotationOrder(controller, false /* onlyCurrentIme */, 242 latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); 243 // Then notify that a user did something for latinIme_fr again. 244 onUserAction(controller, latinIme_fr); 245 assertRotationOrder(controller, false /* onlyCurrentIme */, 246 latinIme_fr, latinIme_en_US, japaneseIme_ja_JP); 247 // Then notify that a user did something for japaneseIme_ja_JP. 248 onUserAction(controller, latinIme_fr); 249 assertRotationOrder(controller, false /* onlyCurrentIme */, 250 japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); 251 // Check onlyCurrentIme == true. 252 assertNextInputMethod(controller, true /* onlyCurrentIme */, 253 japaneseIme_ja_JP, null, null); 254 assertRotationOrder(controller, true /* onlyCurrentIme */, 255 latinIme_fr, latinIme_en_US); 256 assertRotationOrder(controller, true /* onlyCurrentIme */, 257 latinIme_en_US, latinIme_fr); 258 259 // === switching-unaware loop === 260 assertRotationOrder(controller, false /* onlyCurrentIme */, 261 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, 262 switchUnawareJapaneseIme_ja_JP); 263 // User action should be ignored for switching unaware IMEs. 264 onUserAction(controller, switchingUnawarelatinIme_hi); 265 assertRotationOrder(controller, false /* onlyCurrentIme */, 266 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, 267 switchUnawareJapaneseIme_ja_JP); 268 // User action should be ignored for switching unaware IMEs. 269 onUserAction(controller, switchUnawareJapaneseIme_ja_JP); 270 assertRotationOrder(controller, false /* onlyCurrentIme */, 271 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, 272 switchUnawareJapaneseIme_ja_JP); 273 // Check onlyCurrentIme == true. 274 assertRotationOrder(controller, true /* onlyCurrentIme */, 275 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); 276 assertNextInputMethod(controller, true /* onlyCurrentIme */, 277 subtypeUnawareIme, null, null); 278 assertNextInputMethod(controller, true /* onlyCurrentIme */, 279 switchUnawareJapaneseIme_ja_JP, null, null); 280 281 // Rotation order should be preserved when created with the same subtype list. 282 final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes(); 283 final ControllerImpl newController = ControllerImpl.createFrom(controller, 284 sameEnabledItems); 285 assertRotationOrder(newController, false /* onlyCurrentIme */, 286 japaneseIme_ja_JP, latinIme_fr, latinIme_en_US); 287 assertRotationOrder(newController, false /* onlyCurrentIme */, 288 switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme, 289 switchUnawareJapaneseIme_ja_JP); 290 291 // Rotation order should be initialized when created with a different subtype list. 292 final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList( 293 latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK, 294 switchUnawareJapaneseIme_ja_JP); 295 final ControllerImpl anotherController = ControllerImpl.createFrom(controller, 296 differentEnabledItems); 297 assertRotationOrder(anotherController, false /* onlyCurrentIme */, 298 latinIme_en_US, latinIme_fr); 299 assertRotationOrder(anotherController, false /* onlyCurrentIme */, 300 switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP); 301 } 302 303 @SmallTest 304 public void testImeSubtypeListItem() throws Exception { 305 final List<ImeSubtypeListItem> items = new ArrayList<>(); 306 addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", 307 Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"), 308 true /* supportsSwitchingToNextInputMethod*/); 309 final ImeSubtypeListItem item_en_US = items.get(0); 310 final ImeSubtypeListItem item_fr = items.get(1); 311 final ImeSubtypeListItem item_en = items.get(2); 312 final ImeSubtypeListItem item_enn = items.get(3); 313 final ImeSubtypeListItem item_e = items.get(4); 314 final ImeSubtypeListItem item_EN_US = items.get(5); 315 316 assertTrue(item_en_US.mIsSystemLocale); 317 assertFalse(item_fr.mIsSystemLocale); 318 assertFalse(item_en.mIsSystemLocale); 319 assertFalse(item_en.mIsSystemLocale); 320 assertFalse(item_enn.mIsSystemLocale); 321 assertFalse(item_e.mIsSystemLocale); 322 assertFalse(item_EN_US.mIsSystemLocale); 323 324 assertTrue(item_en_US.mIsSystemLanguage); 325 assertFalse(item_fr.mIsSystemLanguage); 326 assertTrue(item_en.mIsSystemLanguage); 327 assertFalse(item_enn.mIsSystemLocale); 328 assertFalse(item_e.mIsSystemLocale); 329 assertFalse(item_EN_US.mIsSystemLocale); 330 } 331} 332