• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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