1 /*
2  * Copyright (C) 2021 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 com.android.inputmethod.stresstest;
18 
19 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
20 
21 import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_HIDE_ON_CREATE;
22 import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_SHOW_ON_CREATE;
23 import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
24 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity;
25 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
26 import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE;
27 import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE;
28 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
29 import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
30 import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
31 import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown;
32 import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify;
33 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
34 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
35 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
36 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil;
37 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
38 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
39 
40 import static com.google.common.truth.Truth.assertThat;
41 
42 import static org.junit.Assume.assumeFalse;
43 import static org.junit.Assume.assumeTrue;
44 
45 import android.app.Instrumentation;
46 import android.content.Intent;
47 import android.os.Build;
48 import android.os.SystemClock;
49 import android.platform.test.annotations.RootPermissionTest;
50 import android.platform.test.rule.UnlockScreenRule;
51 import android.support.test.uiautomator.UiDevice;
52 import android.util.Log;
53 import android.view.WindowManager;
54 import android.widget.EditText;
55 
56 import androidx.test.platform.app.InstrumentationRegistry;
57 
58 import org.junit.Rule;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 import org.junit.runners.Parameterized;
62 
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.Collections;
66 import java.util.List;
67 
68 @RootPermissionTest
69 @RunWith(Parameterized.class)
70 public final class ImeOpenCloseStressTest {
71 
72     private static final String TAG = "ImeOpenCloseStressTest";
73     private static final int NUM_TEST_ITERATIONS = 10;
74 
75     @Rule(order = 0) public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
76     @Rule(order = 1) public ImeStressTestRule mImeStressTestRule =
77             new ImeStressTestRule(true /* useSimpleTestIme */);
78     @Rule(order = 2) public ScreenCaptureRule mScreenCaptureRule =
79             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
80 
81     private final Instrumentation mInstrumentation;
82     private final int mSoftInputFlags;
83     private final int mWindowFocusFlags;
84 
85     @Parameterized.Parameters(
86             name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}")
windowAndSoftInputFlagParameters()87     public static List<Object[]> windowAndSoftInputFlagParameters() {
88         return getWindowAndSoftInputFlagParameters();
89     }
90 
ImeOpenCloseStressTest( int windowFocusFlags, int softInputVisibility, int softInputAdjustment)91     public ImeOpenCloseStressTest(
92             int windowFocusFlags, int softInputVisibility, int softInputAdjustment) {
93         mSoftInputFlags = softInputVisibility | softInputAdjustment;
94         mWindowFocusFlags = windowFocusFlags;
95         mInstrumentation = InstrumentationRegistry.getInstrumentation();
96     }
97 
98     @Test
testShowHideWithInputMethodManager_waitingVisibilityChange()99     public void testShowHideWithInputMethodManager_waitingVisibilityChange() {
100         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
101         TestActivity activity = TestActivity.start(intent);
102         // Request focus after app starts to avoid triggering auto-show behavior.
103         requestFocusAndVerify(activity);
104 
105         // Test only once if window flags set to save time.
106         int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
107         for (int i = 0; i < iterNum; i++) {
108             String msgPrefix = "Iteration #" + i + " ";
109             Log.i(TAG, msgPrefix + "start");
110             callOnMainSync(activity::showImeWithInputMethodManager);
111             verifyShowBehavior(activity);
112 
113             callOnMainSync(activity::hideImeWithInputMethodManager);
114             verifyHideBehavior(activity);
115         }
116     }
117 
118     @Test
testShowHideWithInputMethodManager_waitingAnimationEnd()119     public void testShowHideWithInputMethodManager_waitingAnimationEnd() {
120         assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
121 
122         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
123         TestActivity activity = TestActivity.start(intent);
124         // Request focus after app starts to avoid triggering auto-show behavior.
125         requestFocusAndVerify(activity);
126 
127         activity.enableAnimationMonitoring();
128         EditText editText = activity.getEditText();
129         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
130             String msgPrefix = "Iteration #" + i + " ";
131             Log.i(TAG, msgPrefix + "start");
132             callOnMainSync(activity::showImeWithInputMethodManager);
133             waitOnMainUntil(
134                     msgPrefix + "IME should have been shown",
135                     () -> !activity.isAnimating() && isImeShown(editText));
136 
137             callOnMainSync(activity::hideImeWithInputMethodManager);
138             waitOnMainUntil(
139                     msgPrefix + "IME should have been hidden",
140                     () -> !activity.isAnimating() && !isImeShown(editText));
141         }
142     }
143 
144     @Test
testShowHideWithInputMethodManager_intervalAfterHide()145     public void testShowHideWithInputMethodManager_intervalAfterHide() {
146         // Regression test for b/221483132
147         assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
148 
149         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
150         TestActivity activity = TestActivity.start(intent);
151         // Request focus after app starts to avoid triggering auto-show behavior.
152         requestFocusAndVerify(activity);
153 
154         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
155         List<Integer> intervals = new ArrayList<>();
156         for (int i = 10; i < 100; i += 10) intervals.add(i);
157         for (int i = 100; i < 1000; i += 50) intervals.add(i);
158         for (int intervalMillis : intervals) {
159             String msgPrefix = "Interval = " + intervalMillis + " ";
160             Log.i(TAG, msgPrefix + " start");
161             callOnMainSync(activity::hideImeWithInputMethodManager);
162             SystemClock.sleep(intervalMillis);
163 
164             callOnMainSync(activity::showImeWithInputMethodManager);
165             verifyShowBehavior(activity);
166         }
167     }
168 
169     @Test
testShowHideWithInputMethodManager_inSameFrame()170     public void testShowHideWithInputMethodManager_inSameFrame() {
171         assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
172         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
173         TestActivity activity = TestActivity.start(intent);
174         // Request focus after app starts to avoid triggering auto-show behavior.
175         requestFocusAndVerify(activity);
176 
177         // hidden -> show -> hide
178         mInstrumentation.runOnMainSync(
179                 () -> {
180                     Log.i(TAG, "Calling showIme() and hideIme()");
181                     activity.showImeWithInputMethodManager();
182                     activity.hideImeWithInputMethodManager();
183                 });
184         // Wait until IMMS / IMS handles messages.
185         SystemClock.sleep(1000);
186         mInstrumentation.waitForIdleSync();
187         verifyHideBehavior(activity);
188 
189         mInstrumentation.runOnMainSync(activity::showImeWithInputMethodManager);
190         verifyShowBehavior(activity);
191         mInstrumentation.waitForIdleSync();
192 
193         // shown -> hide -> show
194         mInstrumentation.runOnMainSync(
195                 () -> {
196                     Log.i(TAG, "Calling hideIme() and showIme()");
197                     activity.hideImeWithInputMethodManager();
198                     activity.showImeWithInputMethodManager();
199                 });
200         // Wait until IMMS / IMS handles messages.
201         SystemClock.sleep(1000);
202         mInstrumentation.waitForIdleSync();
203         verifyShowBehavior(activity);
204     }
205 
206     /**
207      * Test IME hidden by calling show and hide IME consecutively with
208      * {@link android.view.inputmethod.InputMethodManager} APIs in
209      * {@link android.app.Activity#onCreate}.
210      *
211      * <p> Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED}
212      * window flag to avoid some softInputMode visibility flags may take presence over
213      * {@link android.view.inputmethod.InputMethodManager} APIs (e.g. use showSoftInput to show
214      * IME in {@link android.app.Activity#onCreate} but being hidden by
215      * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN} window flag after the
216      * activity window focused).</p>
217      */
218     @Test
testShowHideWithInputMethodManager_onCreate()219     public void testShowHideWithInputMethodManager_onCreate() {
220         if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) {
221             return;
222         }
223         // Show and hide with InputMethodManager at onCreate()
224         Intent intent =
225                 createIntent(
226                         mWindowFocusFlags,
227                         mSoftInputFlags,
228                         Arrays.asList(
229                                 REQUEST_FOCUS_ON_CREATE,
230                                 INPUT_METHOD_MANAGER_SHOW_ON_CREATE,
231                                 INPUT_METHOD_MANAGER_HIDE_ON_CREATE));
232         TestActivity activity = TestActivity.start(intent);
233 
234         verifyHideBehavior(activity);
235     }
236 
237     @Test
testShowWithInputMethodManager_notRequestFocus()238     public void testShowWithInputMethodManager_notRequestFocus() {
239         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
240         TestActivity activity = TestActivity.start(intent);
241 
242         // Show InputMethodManager without requesting focus
243         callOnMainSync(activity::showImeWithInputMethodManager);
244 
245         int windowFlags = activity.getWindow().getAttributes().flags;
246         EditText editText = activity.getEditText();
247         if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
248             verifyWindowAndViewFocus(
249                     editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
250         } else {
251             verifyWindowAndViewFocus(
252                     editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
253         }
254         // The Ime should always be hidden because view never gains focus.
255         verifyImeIsAlwaysHidden(editText);
256     }
257 
258     @Test
testShowHideWithWindowInsetsController_waitingVisibilityChange()259     public void testShowHideWithWindowInsetsController_waitingVisibilityChange() {
260         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
261         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
262         TestActivity activity = TestActivity.start(intent);
263         // Request focus after app starts to avoid triggering auto-show behavior.
264         requestFocusAndVerify(activity);
265 
266         // Test only once if window flags set to save time.
267         int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
268         for (int i = 0; i < iterNum; i++) {
269             String msgPrefix = "Iteration #" + i + " ";
270             Log.i(TAG, msgPrefix + "start");
271             mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
272             verifyShowBehavior(activity);
273             mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
274             verifyHideBehavior(activity);
275         }
276     }
277 
278     @Test
testShowHideWithWindowInsetsController_waitingAnimationEnd()279     public void testShowHideWithWindowInsetsController_waitingAnimationEnd() {
280         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
281         assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
282         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
283         TestActivity activity = TestActivity.start(intent);
284         // Request focus after app starts to avoid triggering auto-show behavior.
285         requestFocusAndVerify(activity);
286 
287         activity.enableAnimationMonitoring();
288         EditText editText = activity.getEditText();
289         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
290             String msgPrefix = "Iteration #" + i + " ";
291             Log.i(TAG, msgPrefix + "start");
292             mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
293             waitOnMainUntil(
294                     msgPrefix + "IME should have been shown",
295                     () -> !activity.isAnimating() && isImeShown(editText));
296 
297             mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
298             waitOnMainUntil(
299                     msgPrefix + "IME should have been hidden",
300                     () -> !activity.isAnimating() && !isImeShown(editText));
301         }
302     }
303 
304     @Test
testShowHideWithWindowInsetsController_intervalAfterHide()305     public void testShowHideWithWindowInsetsController_intervalAfterHide() {
306         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
307         assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
308         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
309         TestActivity activity = TestActivity.start(intent);
310         // Request focus after app starts to avoid triggering auto-show behavior.
311         requestFocusAndVerify(activity);
312 
313         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
314         List<Integer> intervals = new ArrayList<>();
315         for (int i = 10; i < 100; i += 10) intervals.add(i);
316         for (int i = 100; i < 1000; i += 50) intervals.add(i);
317         for (int intervalMillis : intervals) {
318             String msgPrefix = "Interval = " + intervalMillis + " ";
319             Log.i(TAG, msgPrefix + " start");
320             mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
321             SystemClock.sleep(intervalMillis);
322 
323             mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
324             verifyShowBehavior(activity);
325         }
326     }
327 
328     @Test
testShowHideWithWindowInsetsController_inSameFrame()329     public void testShowHideWithWindowInsetsController_inSameFrame() {
330         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
331         assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
332         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
333         TestActivity activity = TestActivity.start(intent);
334         // Request focus after app starts to avoid triggering auto-show behavior.
335         requestFocusAndVerify(activity);
336 
337         // hidden -> show -> hide
338         mInstrumentation.runOnMainSync(
339                 () -> {
340                     Log.i(TAG, "Calling showIme() and hideIme()");
341                     activity.showImeWithWindowInsetsController();
342                     activity.hideImeWithWindowInsetsController();
343                 });
344         // Wait until IMMS / IMS handles messages.
345         SystemClock.sleep(1000);
346         mInstrumentation.waitForIdleSync();
347         verifyHideBehavior(activity);
348 
349         mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
350         verifyShowBehavior(activity);
351         mInstrumentation.waitForIdleSync();
352 
353         // shown -> hide -> show
354         mInstrumentation.runOnMainSync(
355                 () -> {
356                     Log.i(TAG, "Calling hideIme() and showIme()");
357                     activity.hideImeWithWindowInsetsController();
358                     activity.showImeWithWindowInsetsController();
359                 });
360         // Wait until IMMS / IMS handles messages.
361         SystemClock.sleep(1000);
362         mInstrumentation.waitForIdleSync();
363         verifyShowBehavior(activity);
364     }
365 
366     @Test
testShowWithWindowInsetsController_onCreate_requestFocus()367     public void testShowWithWindowInsetsController_onCreate_requestFocus() {
368         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
369         // Show with InputMethodManager at onCreate()
370         Intent intent =
371                 createIntent(
372                         mWindowFocusFlags,
373                         mSoftInputFlags,
374                         Arrays.asList(
375                                 REQUEST_FOCUS_ON_CREATE, WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
376         TestActivity activity = TestActivity.start(intent);
377 
378         verifyShowBehavior(activity);
379     }
380 
381     @Test
testShowWithWindowInsetsController_onCreate_notRequestFocus()382     public void testShowWithWindowInsetsController_onCreate_notRequestFocus() {
383         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
384         // Show and hide with WindowInsetsController at onCreate()
385         Intent intent =
386                 createIntent(
387                         mWindowFocusFlags,
388                         mSoftInputFlags,
389                         Collections.singletonList(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE));
390         TestActivity activity = TestActivity.start(intent);
391 
392         // Ime is shown but with a fallback InputConnection
393         verifyShowBehaviorNotRequestFocus(activity);
394     }
395 
396     @Test
testShowWithWindowInsetsController_afterStart_notRequestFocus()397     public void testShowWithWindowInsetsController_afterStart_notRequestFocus() {
398         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
399         // Show and hide with WindowInsetsController at onCreate()
400         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
401         TestActivity activity = TestActivity.start(intent);
402         mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
403 
404         // Ime is shown but with a fallback InputConnection
405         verifyShowBehaviorNotRequestFocus(activity);
406     }
407 
408     /**
409      * Test IME hidden by calling show and hide IME consecutively with
410      * {@link android.view.WindowInsetsController} APIs in
411      * {@link android.app.Activity#onCreate}.
412      *
413      * <p> Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED}
414      * window flag to avoid some softInputMode visibility flags may take presence over
415      * {@link android.view.WindowInsetsController} APIs (e.g. use showSoftInput to show
416      * IME in {@link android.app.Activity#onCreate} but being hidden by
417      * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN} window flag after the
418      * activity window focused).</p>
419      */
420     @Test
testHideWithWindowInsetsController_onCreate_requestFocus()421     public void testHideWithWindowInsetsController_onCreate_requestFocus() {
422         assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
423         if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) {
424             return;
425         }
426         // Show and hide with WindowInsetsController at onCreate()
427         Intent intent =
428                 createIntent(
429                         mWindowFocusFlags,
430                         mSoftInputFlags,
431                         Arrays.asList(
432                                 REQUEST_FOCUS_ON_CREATE,
433                                 WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE,
434                                 WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE));
435         TestActivity activity = TestActivity.start(intent);
436 
437         verifyHideBehavior(activity);
438     }
439 
440     @Test
testScreenOffOn()441     public void testScreenOffOn() throws Exception {
442         Intent intent1 =
443                 createIntent(
444                         mWindowFocusFlags,
445                         mSoftInputFlags,
446                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
447         TestActivity activity = TestActivity.start(intent1);
448         // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
449         callOnMainSync(activity::showImeWithInputMethodManager);
450 
451         Thread.sleep(1000);
452         verifyShowBehavior(activity);
453 
454         UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
455 
456         if (uiDevice.isScreenOn()) {
457             uiDevice.sleep();
458         }
459         Thread.sleep(1000);
460         if (!uiDevice.isScreenOn()) {
461             uiDevice.wakeUp();
462         }
463 
464         verifyShowBehavior(activity);
465     }
466 
467     // TODO: Add tests for activities that don't handle the rotation.
468     @Test
testRotateScreenWithKeyboardOn()469     public void testRotateScreenWithKeyboardOn() throws Exception {
470         Intent intent =
471                 createIntent(
472                         mWindowFocusFlags,
473                         mSoftInputFlags,
474                         Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
475         TestActivity activity = TestActivity.start(intent);
476         // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
477         callOnMainSync(activity::showImeWithInputMethodManager);
478         Thread.sleep(2000);
479         verifyShowBehavior(activity);
480 
481         UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
482 
483         uiDevice.setOrientationRight();
484         uiDevice.waitForIdle();
485         Thread.sleep(1000);
486         Log.i(TAG, "Rotate screen right");
487         assertThat(uiDevice.isNaturalOrientation()).isFalse();
488         verifyRotateBehavior(activity);
489 
490         uiDevice.setOrientationLeft();
491         uiDevice.waitForIdle();
492         Thread.sleep(1000);
493         Log.i(TAG, "Rotate screen left");
494         assertThat(uiDevice.isNaturalOrientation()).isFalse();
495         verifyRotateBehavior(activity);
496 
497         uiDevice.setOrientationNatural();
498         uiDevice.waitForIdle();
499     }
500 
verifyShowBehavior(TestActivity activity)501     private static void verifyShowBehavior(TestActivity activity) {
502         if (hasUnfocusableWindowFlags(activity)) {
503             verifyImeAlwaysHiddenWithWindowFlagSet(activity);
504             return;
505         }
506         EditText editText = activity.getEditText();
507 
508         verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
509         waitOnMainUntilImeIsShown(editText);
510     }
511 
verifyHideBehavior(TestActivity activity)512     private static void verifyHideBehavior(TestActivity activity) {
513         if (hasUnfocusableWindowFlags(activity)) {
514             verifyImeAlwaysHiddenWithWindowFlagSet(activity);
515             return;
516         }
517         EditText editText = activity.getEditText();
518 
519         verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true);
520         waitOnMainUntilImeIsHidden(editText);
521     }
522 
verifyShowBehaviorNotRequestFocus(TestActivity activity)523     private static void verifyShowBehaviorNotRequestFocus(TestActivity activity) {
524         int windowFlags = activity.getWindow().getAttributes().flags;
525         EditText editText = activity.getEditText();
526 
527         if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) {
528             verifyWindowAndViewFocus(
529                     editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false);
530             verifyImeIsAlwaysHidden(editText);
531         } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
532                 || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) {
533             verifyWindowAndViewFocus(
534                     editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
535             verifyImeIsAlwaysHidden(editText);
536         } else {
537             verifyWindowAndViewFocus(
538                     editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false);
539             // Ime is shown but with a fallback InputConnection
540             waitOnMainUntilImeIsShown(editText);
541         }
542     }
543 
verifyRotateBehavior(TestActivity activity)544     private static void verifyRotateBehavior(TestActivity activity) {
545         // Get the new TestActivity after recreation.
546         TestActivity newActivity = TestActivity.getLastCreatedInstance();
547         assertThat(newActivity).isNotNull();
548         assertThat(newActivity).isNotEqualTo(activity);
549 
550         EditText newEditText = newActivity.getEditText();
551         int softInputMode = newActivity.getWindow().getAttributes().softInputMode;
552         int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
553 
554         if (hasUnfocusableWindowFlags(newActivity)) {
555             verifyImeAlwaysHiddenWithWindowFlagSet(newActivity);
556             return;
557         }
558 
559         if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
560             // After rotation, the keyboard would be hidden only when the flag is
561             // SOFT_INPUT_STATE_ALWAYS_HIDDEN. However, SOFT_INPUT_STATE_HIDDEN is different because
562             // it requires appending SOFT_INPUT_IS_FORWARD_NAVIGATION flag, which won't be added
563             // when rotating the devices (rotating doesn't navigate forward to the next app window.)
564             verifyWindowAndViewFocus(newEditText, /*expectWindowFocus*/ true, /*expectViewFocus*/
565                     true);
566             waitOnMainUntilImeIsHidden(newEditText);
567 
568         } else {
569             // Other cases, keyboard would be shown.
570             verifyWindowAndViewFocus(newEditText, /*expectWindowFocus*/ true, /*expectViewFocus*/
571                     true);
572             waitOnMainUntilImeIsShown(newEditText);
573         }
574     }
575 }
576