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