1 /* 2 * Copyright (C) 2017 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 android.inputmethodservice.cts.hostside; 18 19 import static android.inputmethodservice.cts.common.BusyWaitUtils.pollingCheck; 20 import static android.inputmethodservice.cts.common.DeviceEventConstants.ACTION_DEVICE_EVENT; 21 import static android.inputmethodservice.cts.common.DeviceEventConstants.DeviceEventType.TEST_START; 22 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_SENDER; 23 import static android.inputmethodservice.cts.common.DeviceEventConstants.EXTRA_EVENT_TYPE; 24 import static android.inputmethodservice.cts.common.DeviceEventConstants.RECEIVER_COMPONENT; 25 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assume.assumeFalse; 28 import static org.junit.Assume.assumeTrue; 29 30 import android.inputmethodservice.cts.common.ComponentNameUtils; 31 import android.inputmethodservice.cts.common.EditTextAppConstants; 32 import android.inputmethodservice.cts.common.EventProviderConstants.EventTableConstants; 33 import android.inputmethodservice.cts.common.Ime1Constants; 34 import android.inputmethodservice.cts.common.Ime2Constants; 35 import android.inputmethodservice.cts.common.test.DeviceTestConstants; 36 import android.inputmethodservice.cts.common.test.ShellCommandUtils; 37 import android.inputmethodservice.cts.common.test.TestInfo; 38 import android.platform.test.annotations.AppModeFull; 39 import android.platform.test.annotations.AppModeInstant; 40 41 import com.android.compatibility.common.util.FeatureUtil; 42 import com.android.tradefed.log.LogUtil; 43 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 44 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 45 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; 46 import com.android.tradefed.util.RunUtil; 47 48 import org.junit.After; 49 import org.junit.Before; 50 import org.junit.Test; 51 import org.junit.function.ThrowingRunnable; 52 import org.junit.runner.RunWith; 53 54 import java.util.concurrent.TimeUnit; 55 import java.util.concurrent.TimeoutException; 56 57 /** 58 * Test general lifecycle events around InputMethodService. 59 */ 60 @RunWith(DeviceJUnit4ClassRunner.class) 61 public class InputMethodServiceLifecycleTest extends BaseHostJUnit4Test { 62 63 private static final long WAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(1); 64 private static final long PACKAGE_OP_TIMEOUT = TimeUnit.SECONDS.toMillis(15); 65 private static final long POLLING_INTERVAL = 100; 66 private static final String COMPAT_CHANGE_DO_NOT_DOWNSCALE_TO_1080P_ON_TV = 67 "DO_NOT_DOWNSCALE_TO_1080P_ON_TV"; 68 69 /** 70 * {@code true} if {@link #tearDown()} needs to be fully executed. 71 * 72 * <p>When {@link #setUp()} is interrupted by {@link org.junit.AssumptionViolatedException} 73 * before the actual setup tasks are executed, all the corresponding cleanup tasks should also 74 * be skipped.</p> 75 * 76 * <p>Once JUnit 5 becomes available in Android, we can remove this by moving the assumption 77 * checks into a non-static {@link org.junit.BeforeClass} method.</p> 78 */ 79 private boolean mNeedsTearDown = false; 80 81 /** 82 * Set up test case. 83 */ 84 @Before setUp()85 public void setUp() throws Exception { 86 // Skip whole tests when DUT has no android.software.input_methods feature. 87 assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS)); 88 mNeedsTearDown = true; 89 90 cleanUpTestImes(); 91 installPackage(DeviceTestConstants.APK, "-r"); 92 shell(ShellCommandUtils.deleteContent(EventTableConstants.CONTENT_URI)); 93 } 94 95 /** 96 * Tear down test case. 97 */ 98 @After tearDown()99 public void tearDown() throws Exception { 100 if (!mNeedsTearDown) { 101 return; 102 } 103 shell(ShellCommandUtils.resetImes()); 104 } 105 106 /** 107 * Install an app apk file synchronously. 108 * 109 * <p>This methods waits until package is available in PackageManger</p> 110 * 111 * <p>Note: For installing IME APKs use {@link #installImePackageSync(String, String)} 112 * instead.</p> 113 * @param apkFileName App apk to install 114 * @param packageName packageName of the installed apk 115 * @param options adb shell install options. 116 * @throws Exception 117 */ installPackageSync( String apkFileName, String packageName, String... options)118 private void installPackageSync( 119 String apkFileName, String packageName, String... options) throws Exception { 120 installPackage(apkFileName, options); 121 pollingCheck(() -> 122 shell(ShellCommandUtils.listPackage(packageName)).contains(packageName), 123 PACKAGE_OP_TIMEOUT, 124 packageName + " should be installed."); 125 } 126 127 /** 128 * Install IME packages synchronously. 129 * 130 * <p>This method verifies that IME is available in IMMS.</p> 131 * @param apkFileName IME apk to install 132 * @param imeId of the IME being installed. 133 * @param forceQueryable True to enable ime becoming visible on the device. 134 * @throws Exception 135 */ installImePackageSync(String apkFileName, String imeId, boolean forceQueryable)136 private void installImePackageSync(String apkFileName, String imeId, boolean forceQueryable) 137 throws Exception { 138 final DeviceTestRunOptions options = new DeviceTestRunOptions(null /* unused */); 139 options.setApkFileName(apkFileName); 140 options.setInstallArgs("-r"); 141 options.setForceQueryable(forceQueryable); 142 installPackage(options); 143 waitUntilImesAreAvailable(imeId); 144 145 // Compatibility scaling may affect how watermarks are rendered in such a way so that we 146 // won't be able to detect them on screenshots. 147 disableAppCompatScalingForPackageIfNeeded(ComponentNameUtils.retrievePackageName(imeId)); 148 } 149 150 /** 151 * @see #installImePackageSync(String, String, boolean) 152 */ installImePackageSync(String apkFileName, String imeId)153 private void installImePackageSync(String apkFileName, String imeId) throws Exception { 154 installImePackageSync(apkFileName, imeId, true /* forceQueryable */); 155 } 156 disableAppCompatScalingForPackageIfNeeded(String packageName)157 private void disableAppCompatScalingForPackageIfNeeded(String packageName) throws Exception { 158 if (FeatureUtil.isTV(getDevice())) { 159 // On 4K TV devices packages that target API levels below S run in a compat mode where 160 // they render UI to a 1080p surface which then gets scaled up x2 (to the device's 161 // "native" 4K resolution). 162 // When a test IME package runs in such compatibility mode, the watermarks it renders 163 // would be scaled up x2 as well, thus we won't be able detect them on (4K) screenshots 164 // we take during tests. 165 // Note, that this command will have no effect if the device is not a 4K TV, or if the 166 // package's "targetSdk" level is S or above. 167 shell(ShellCommandUtils.enableCompatChange( 168 COMPAT_CHANGE_DO_NOT_DOWNSCALE_TO_1080P_ON_TV, packageName)); 169 } 170 } 171 installPossibleInstantPackage( String apkFileName, String packageName, boolean instant)172 private void installPossibleInstantPackage( 173 String apkFileName, String packageName, boolean instant) throws Exception { 174 if (instant) { 175 installPackageSync(apkFileName, packageName, "-r", "--instant"); 176 } else { 177 installPackageSync(apkFileName, packageName, "-r"); 178 } 179 } 180 testSwitchIme(boolean instant)181 private void testSwitchIme(boolean instant) throws Exception { 182 sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2); 183 installPossibleInstantPackage( 184 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 185 shell(ShellCommandUtils.waitForBroadcastBarrier()); 186 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 187 installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID); 188 shell(ShellCommandUtils.waitForBroadcastBarrier()); 189 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 190 shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID)); 191 waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID); 192 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 193 194 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2)); 195 } 196 197 /** 198 * Test IME switching APIs for full (non-instant) apps. 199 */ 200 @AppModeFull 201 @Test testSwitchImeFull()202 public void testSwitchImeFull() throws Exception { 203 testSwitchIme(false); 204 } 205 206 /** 207 * Test IME switching APIs for instant apps. 208 */ 209 @AppModeInstant 210 @Test testSwitchImeInstant()211 public void testSwitchImeInstant() throws Exception { 212 testSwitchIme(true); 213 } 214 testUninstallCurrentIme(boolean instant)215 private void testUninstallCurrentIme(boolean instant) throws Exception { 216 sendTestStartEvent(DeviceTestConstants.TEST_CREATE_IME1); 217 installPossibleInstantPackage( 218 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 219 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 220 shell(ShellCommandUtils.waitForBroadcastBarrier()); 221 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 222 waitUntilImesAreEnabled(Ime1Constants.IME_ID); 223 224 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 225 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_CREATE_IME1)); 226 227 uninstallPackageSyncIfExists(Ime1Constants.PACKAGE); 228 shell(ShellCommandUtils.waitForBroadcastBarrier()); 229 assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, WAIT_TIMEOUT); 230 } 231 232 /** 233 * Test uninstalling the currently selected IME for full (non-instant) apps. 234 */ 235 @AppModeFull 236 @Test testUninstallCurrentImeFull()237 public void testUninstallCurrentImeFull() throws Exception { 238 testUninstallCurrentIme(false); 239 } 240 241 /** 242 * Test uninstalling the currently selected IME for instant apps. 243 */ 244 @AppModeInstant 245 @Test testUninstallCurrentImeInstant()246 public void testUninstallCurrentImeInstant() throws Exception { 247 testUninstallCurrentIme(true); 248 } 249 testDisableCurrentIme(boolean instant)250 private void testDisableCurrentIme(boolean instant) throws Exception { 251 sendTestStartEvent(DeviceTestConstants.TEST_CREATE_IME1); 252 installPossibleInstantPackage( 253 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 254 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 255 shell(ShellCommandUtils.waitForBroadcastBarrier()); 256 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 257 waitUntilImesAreEnabled(Ime1Constants.IME_ID); 258 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 259 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_CREATE_IME1)); 260 261 shell(ShellCommandUtils.disableIme(Ime1Constants.IME_ID)); 262 shell(ShellCommandUtils.waitForBroadcastBarrier()); 263 assertImeNotSelectedInSecureSettings(Ime1Constants.IME_ID, WAIT_TIMEOUT); 264 } 265 266 /** 267 * Test disabling the currently selected IME for full (non-instant) apps. 268 */ 269 @AppModeFull 270 @Test testDisableCurrentImeFull()271 public void testDisableCurrentImeFull() throws Exception { 272 testDisableCurrentIme(false); 273 } 274 275 /** 276 * Test disabling the currently selected IME for instant apps. 277 */ 278 @AppModeInstant 279 @Test testDisableCurrentImeInstant()280 public void testDisableCurrentImeInstant() throws Exception { 281 testDisableCurrentIme(true); 282 } 283 testSwitchInputMethod(boolean instant)284 private void testSwitchInputMethod(boolean instant) throws Exception { 285 sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_INPUTMETHOD); 286 installPossibleInstantPackage( 287 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 288 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 289 installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID); 290 shell(ShellCommandUtils.waitForBroadcastBarrier()); 291 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 292 shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID)); 293 waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID); 294 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 295 296 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_INPUTMETHOD)); 297 } 298 299 /** 300 * Test "InputMethodService#switchInputMethod" API for full (non-instant) apps. 301 */ 302 @AppModeFull 303 @Test testSwitchInputMethodFull()304 public void testSwitchInputMethodFull() throws Exception { 305 testSwitchInputMethod(false); 306 } 307 308 /** 309 * Test "InputMethodService#switchInputMethod" API for instant apps. 310 */ 311 @AppModeInstant 312 @Test testSwitchInputMethodInstant()313 public void testSwitchInputMethodInstant() throws Exception { 314 testSwitchInputMethod(true); 315 } 316 testSwitchToNextInput(boolean instant, boolean imeForceQueryable)317 private void testSwitchToNextInput(boolean instant, boolean imeForceQueryable) 318 throws Exception { 319 sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_NEXT_INPUT); 320 installPossibleInstantPackage( 321 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 322 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID, imeForceQueryable); 323 installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID, imeForceQueryable); 324 shell(ShellCommandUtils.waitForBroadcastBarrier()); 325 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 326 // Make sure that there is at least one more IME that specifies 327 // supportsSwitchingToNextInputMethod="true" 328 shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID)); 329 waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID); 330 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 331 332 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_NEXT_INPUT)); 333 } 334 335 /** 336 * Test "InputMethodService#switchToNextInputMethod" API for full (non-instant) apps. 337 */ 338 @AppModeFull 339 @Test testSwitchToNextInputFull()340 public void testSwitchToNextInputFull() throws Exception { 341 testSwitchToNextInput(false, true /* imeForceQueryable */); 342 } 343 344 /** 345 * Test "InputMethodService#switchToNextInputMethod" API for instant apps. 346 */ 347 @AppModeInstant 348 @Test testSwitchToNextInputInstant()349 public void testSwitchToNextInputInstant() throws Exception { 350 testSwitchToNextInput(true, true /* imeForceQueryable */); 351 } 352 353 /** 354 * Test "InputMethodService#switchToNextInputMethod" API for full (non-instant) apps. 355 */ 356 @AppModeFull 357 @Test testSwitchToNextInputFull_callerCannotSeeTargetInput()358 public void testSwitchToNextInputFull_callerCannotSeeTargetInput() throws Exception { 359 testSwitchToNextInput(false, false /* imeForceQueryable */); 360 } 361 362 /** 363 * Test "InputMethodService#switchToNextInputMethod" API for instant apps. 364 */ 365 @AppModeInstant 366 @Test testSwitchToNextInputInstant_callerCannotSeeTargetInput()367 public void testSwitchToNextInputInstant_callerCannotSeeTargetInput() throws Exception { 368 testSwitchToNextInput(true, false /* imeForceQueryable */); 369 } 370 testSwitchToPreviousInput(boolean instant, boolean imeForceQueryable)371 private void testSwitchToPreviousInput(boolean instant, boolean imeForceQueryable) 372 throws Exception { 373 sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_PREVIOUS_INPUT); 374 installPossibleInstantPackage( 375 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 376 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID, imeForceQueryable); 377 installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID, imeForceQueryable); 378 shell(ShellCommandUtils.waitForBroadcastBarrier()); 379 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 380 shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID)); 381 waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID); 382 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 383 384 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_SWITCH_PREVIOUS_INPUT)); 385 } 386 387 /** 388 * Test "InputMethodService#switchToPreviousInputMethod" API for full (non-instant) apps. 389 */ 390 @AppModeFull 391 @Test testSwitchToPreviousInputFull()392 public void testSwitchToPreviousInputFull() throws Exception { 393 testSwitchToPreviousInput(false, true /* imeForceQueryable */); 394 } 395 396 /** 397 * Test "InputMethodService#switchToPreviousInputMethod" API for instant apps. 398 */ 399 @AppModeInstant 400 @Test testSwitchToPreviousInputInstant()401 public void testSwitchToPreviousInputInstant() throws Exception { 402 testSwitchToPreviousInput(true, true /* imeForceQueryable */); 403 } 404 405 /** 406 * Test "InputMethodService#switchToPreviousInputMethod" API for full (non-instant) apps. 407 */ 408 @AppModeFull 409 @Test testSwitchToPreviousInputFull_callerCannotSeeTargetInput()410 public void testSwitchToPreviousInputFull_callerCannotSeeTargetInput() throws Exception { 411 testSwitchToPreviousInput(false, false /* imeForceQueryable */); 412 } 413 414 /** 415 * Test "InputMethodService#switchToPreviousInputMethod" API for instant apps. 416 */ 417 @AppModeInstant 418 @Test testSwitchToPreviousInputInstant_callerCannotSeeTargetInput()419 public void testSwitchToPreviousInputInstant_callerCannotSeeTargetInput() throws Exception { 420 testSwitchToPreviousInput(true, false /* imeForceQueryable */); 421 } 422 testInputUnbindsOnImeStopped(boolean instant)423 private void testInputUnbindsOnImeStopped(boolean instant) throws Exception { 424 sendTestStartEvent(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED); 425 installPossibleInstantPackage( 426 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 427 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 428 installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID); 429 shell(ShellCommandUtils.waitForBroadcastBarrier()); 430 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 431 shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID)); 432 waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID); 433 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 434 435 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_IME_STOPPED)); 436 } 437 438 /** 439 * Test if uninstalling the currently selected IME then selecting another IME triggers standard 440 * startInput/bindInput sequence for full (non-instant) apps. 441 */ 442 @AppModeFull 443 @Test testInputUnbindsOnImeStoppedFull()444 public void testInputUnbindsOnImeStoppedFull() throws Exception { 445 testInputUnbindsOnImeStopped(false); 446 } 447 448 /** 449 * Test if uninstalling the currently selected IME then selecting another IME triggers standard 450 * startInput/bindInput sequence for instant apps. 451 */ 452 @AppModeInstant 453 @Test testInputUnbindsOnImeStoppedInstant()454 public void testInputUnbindsOnImeStoppedInstant() throws Exception { 455 testInputUnbindsOnImeStopped(true); 456 } 457 testInputUnbindsOnAppStop(boolean instant)458 private void testInputUnbindsOnAppStop(boolean instant) throws Exception { 459 sendTestStartEvent(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED); 460 installPossibleInstantPackage( 461 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 462 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 463 shell(ShellCommandUtils.waitForBroadcastBarrier()); 464 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 465 waitUntilImesAreEnabled(Ime1Constants.IME_ID); 466 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 467 468 assertTrue(runDeviceTestMethod(DeviceTestConstants.TEST_INPUT_UNBINDS_ON_APP_STOPPED)); 469 } 470 471 /** 472 * Test if uninstalling the currently running IME client triggers 473 * "InputMethodService#onUnbindInput" callback for full (non-instant) apps. 474 */ 475 @AppModeFull 476 @Test testInputUnbindsOnAppStopFull()477 public void testInputUnbindsOnAppStopFull() throws Exception { 478 testInputUnbindsOnAppStop(false); 479 } 480 481 /** 482 * Test if uninstalling the currently running IME client triggers 483 * "InputMethodService#onUnbindInput" callback for instant apps. 484 */ 485 @AppModeInstant 486 @Test testInputUnbindsOnAppStopInstant()487 public void testInputUnbindsOnAppStopInstant() throws Exception { 488 testInputUnbindsOnAppStop(true); 489 } 490 testImeVisibilityAfterImeSwitching(boolean instant)491 private void testImeVisibilityAfterImeSwitching(boolean instant) throws Exception { 492 runWithRetries(3, () -> { 493 sendTestStartEvent(DeviceTestConstants.TEST_SWITCH_IME1_TO_IME2); 494 installPossibleInstantPackage( 495 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 496 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 497 installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID); 498 shell(ShellCommandUtils.waitForBroadcastBarrier()); 499 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 500 shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID)); 501 waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID); 502 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 503 504 assertTrue(runDeviceTestMethod( 505 DeviceTestConstants.TEST_IME_VISIBILITY_AFTER_IME_SWITCHING)); 506 }); 507 } 508 509 /** 510 * Test if IMEs remain to be visible after switching to other IMEs for full (non-instant) apps. 511 * 512 * <p>Regression test for Bug 152876819.</p> 513 */ 514 @AppModeFull 515 @Test testImeVisibilityAfterImeSwitchingFull()516 public void testImeVisibilityAfterImeSwitchingFull() throws Exception { 517 testImeVisibilityAfterImeSwitching(false); 518 } 519 520 /** 521 * Test if IMEs remain to be visible after switching to other IMEs for instant apps. 522 * 523 * <p>Regression test for Bug 152876819.</p> 524 */ 525 @AppModeInstant 526 @Test testImeVisibilityAfterImeSwitchingInstant()527 public void testImeVisibilityAfterImeSwitchingInstant() throws Exception { 528 testImeVisibilityAfterImeSwitching(true); 529 } 530 testImeSwitchingWithoutWindowFocusAfterDisplayOffOn(boolean instant)531 private void testImeSwitchingWithoutWindowFocusAfterDisplayOffOn(boolean instant) 532 throws Exception { 533 // Skip whole tests when DUT has com.google.android.tv.operator_tier feature. 534 assumeFalse(hasDeviceFeature(ShellCommandUtils.FEATURE_TV_OPERATOR_TIER)); 535 sendTestStartEvent( 536 DeviceTestConstants.TEST_IME_SWITCHING_WITHOUT_WINDOW_FOCUS_AFTER_DISPLAY_OFF_ON); 537 installPossibleInstantPackage( 538 EditTextAppConstants.APK, EditTextAppConstants.PACKAGE, instant); 539 installImePackageSync(Ime1Constants.APK, Ime1Constants.IME_ID); 540 installImePackageSync(Ime2Constants.APK, Ime2Constants.IME_ID); 541 shell(ShellCommandUtils.waitForBroadcastBarrier()); 542 shell(ShellCommandUtils.enableIme(Ime1Constants.IME_ID)); 543 shell(ShellCommandUtils.enableIme(Ime2Constants.IME_ID)); 544 waitUntilImesAreEnabled(Ime1Constants.IME_ID, Ime2Constants.IME_ID); 545 shell(ShellCommandUtils.setCurrentImeSync(Ime1Constants.IME_ID)); 546 547 assertTrue(runDeviceTestMethod( 548 DeviceTestConstants.TEST_IME_SWITCHING_WITHOUT_WINDOW_FOCUS_AFTER_DISPLAY_OFF_ON)); 549 } 550 551 /** 552 * Test IME switching while another window (e.g. IME switcher dialog) is focused on top of the 553 * IME target window after turning off/on the screen. 554 * 555 * <p>Regression test for Bug 160391516.</p> 556 */ 557 @AppModeFull 558 @Test testImeSwitchingWithoutWindowFocusAfterDisplayOffOnFull()559 public void testImeSwitchingWithoutWindowFocusAfterDisplayOffOnFull() throws Exception { 560 testImeSwitchingWithoutWindowFocusAfterDisplayOffOn(false); 561 } 562 563 /** 564 * Test IME switching while another window (e.g. IME switcher dialog) is focused on top of the 565 * IME target window after turning off/on the screen. 566 * 567 * <p>Regression test for Bug 160391516.</p> 568 */ 569 @AppModeInstant 570 @Test testImeSwitchingWithoutWindowFocusAfterDisplayOffOnInstant()571 public void testImeSwitchingWithoutWindowFocusAfterDisplayOffOnInstant() throws Exception { 572 testImeSwitchingWithoutWindowFocusAfterDisplayOffOn(true); 573 } 574 sendTestStartEvent(TestInfo deviceTest)575 private void sendTestStartEvent(TestInfo deviceTest) throws Exception { 576 final String sender = deviceTest.getTestName(); 577 // {@link EventType#EXTRA_EVENT_TIME} will be recorded at device side. 578 shell(ShellCommandUtils.broadcastIntent( 579 ACTION_DEVICE_EVENT, RECEIVER_COMPONENT, 580 "--es", EXTRA_EVENT_SENDER, sender, 581 "--es", EXTRA_EVENT_TYPE, TEST_START.name())); 582 } 583 runDeviceTestMethod(TestInfo deviceTest)584 private boolean runDeviceTestMethod(TestInfo deviceTest) throws Exception { 585 return runDeviceTests(deviceTest.testPackage, deviceTest.testClass, deviceTest.testMethod); 586 } 587 shell(String command)588 private String shell(String command) throws Exception { 589 return getDevice().executeShellCommand(command).trim(); 590 } 591 cleanUpTestImes()592 private void cleanUpTestImes() throws Exception { 593 uninstallPackageSyncIfExists(Ime1Constants.PACKAGE); 594 uninstallPackageSyncIfExists(Ime2Constants.PACKAGE); 595 } 596 uninstallPackageSyncIfExists(String packageName)597 private void uninstallPackageSyncIfExists(String packageName) throws Exception { 598 if (isPackageInstalled(getDevice(), packageName)) { 599 uninstallPackage(getDevice(), packageName); 600 pollingCheck(() -> !isPackageInstalled(getDevice(), packageName), PACKAGE_OP_TIMEOUT, 601 packageName + " should be uninstalled."); 602 } 603 } 604 605 /** 606 * Makes sure that the given IME is not in the stored in the secure settings as the current IME. 607 * 608 * @param imeId IME ID to be monitored 609 * @param timeout timeout in millisecond 610 */ assertImeNotSelectedInSecureSettings(String imeId, long timeout)611 private void assertImeNotSelectedInSecureSettings(String imeId, long timeout) throws Exception { 612 while (true) { 613 if (timeout < 0) { 614 throw new TimeoutException(imeId + " is still the current IME even after " 615 + timeout + " msec."); 616 } 617 if (!imeId.equals(shell(ShellCommandUtils.getCurrentIme()))) { 618 break; 619 } 620 RunUtil.getDefault().sleep(POLLING_INTERVAL); 621 timeout -= POLLING_INTERVAL; 622 } 623 } 624 625 /** 626 * Wait until IMEs are available in IMMS. 627 * @throws Exception 628 */ waitUntilImesAreAvailable(String... imeIds)629 private void waitUntilImesAreAvailable(String... imeIds) throws Exception { 630 waitUntilImesAreAvailableOrEnabled(false, imeIds); 631 } 632 633 /** 634 * Wait until IMEs are enabled in IMMS. 635 * @throws Exception 636 */ waitUntilImesAreEnabled(String... imeIds)637 private void waitUntilImesAreEnabled(String... imeIds) throws Exception { 638 waitUntilImesAreAvailableOrEnabled(true, imeIds); 639 } 640 641 /** 642 * Call a function multiple times consecutively, if assertion in it fails first. 643 * 644 * <p>Retry running a provided action multiple times, if an {@link AssertionError} is thrown in 645 * a previous run. Only throws the error when the action failed at all previous consecutive 646 * runs. Other types of exceptions are not suppressed.</p> 647 * 648 * @param maxTries maximal amount of attempt that should be performed before throwing the 649 * {@link AssertionError}, if applicable 650 * @param action the action to perform 651 */ runWithRetries(int maxTries, ThrowingRunnable action)652 private static void runWithRetries(int maxTries, ThrowingRunnable action) throws Exception { 653 for (int attempt = 1; true; attempt++) { 654 try { 655 action.run(); 656 return; 657 } catch (AssertionError e) { 658 if (attempt < maxTries) { 659 LogUtil.CLog.i("Attempt " + attempt + " failed; retrying", e); 660 } else { 661 throw e; 662 } 663 } catch (Throwable e) { 664 throw new Exception(e); 665 } 666 } 667 } 668 669 waitUntilImesAreAvailableOrEnabled( boolean shouldBeEnabled, String... imeIds)670 private void waitUntilImesAreAvailableOrEnabled( 671 boolean shouldBeEnabled, String... imeIds) throws Exception { 672 final String cmd = shouldBeEnabled 673 ? ShellCommandUtils.getEnabledImes() : ShellCommandUtils.getAvailableImes(); 674 for (String imeId : imeIds) { 675 pollingCheck(() -> shell(cmd).contains(imeId), PACKAGE_OP_TIMEOUT, 676 imeId + " should be " + (shouldBeEnabled ? "enabled." : "available.")); 677 } 678 } 679 } 680