1 /*
2  * Copyright (C) 2018 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.view.inputmethod.cts;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION;
21 import static android.view.Display.DEFAULT_DISPLAY;
22 import static android.view.View.VISIBLE;
23 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
24 import static android.view.WindowInsets.Type.ime;
25 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
26 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
27 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
28 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
29 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
30 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
31 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
32 import static android.view.inputmethod.InputMethodManager.CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING;
33 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
34 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
35 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
36 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
37 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSyncWithRethrowing;
38 import static android.widget.PopupWindow.INPUT_METHOD_NOT_NEEDED;
39 
40 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
41 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
42 import static com.android.cts.mockime.ImeEventStreamTestUtils.eventMatcher;
43 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
44 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
45 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
46 import static com.android.cts.mockime.ImeEventStreamTestUtils.hideSoftInputMatcher;
47 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
48 import static com.android.cts.mockime.ImeEventStreamTestUtils.showSoftInputMatcher;
49 import static com.android.cts.mockime.ImeEventStreamTestUtils.waitForInputViewLayoutStable;
50 import static com.android.cts.mockime.ImeEventStreamTestUtils.withDescription;
51 
52 import static org.junit.Assert.assertEquals;
53 import static org.junit.Assert.assertFalse;
54 import static org.junit.Assert.assertNotNull;
55 import static org.junit.Assert.assertNull;
56 import static org.junit.Assert.assertTrue;
57 import static org.junit.Assert.fail;
58 import static org.junit.Assume.assumeFalse;
59 import static org.junit.Assume.assumeNotNull;
60 import static org.junit.Assume.assumeTrue;
61 
62 import android.app.AlertDialog;
63 import android.app.Instrumentation;
64 import android.app.Notification;
65 import android.app.NotificationChannel;
66 import android.app.NotificationManager;
67 import android.app.compat.CompatChanges;
68 import android.content.Context;
69 import android.content.Intent;
70 import android.content.pm.PackageManager;
71 import android.content.res.Resources;
72 import android.graphics.Color;
73 import android.os.SystemClock;
74 import android.os.UserHandle;
75 import android.platform.test.annotations.AppModeFull;
76 import android.platform.test.annotations.AppModeInstant;
77 import android.platform.test.annotations.AppModeSdkSandbox;
78 import android.platform.test.annotations.RequiresFlagsDisabled;
79 import android.platform.test.flag.junit.CheckFlagsRule;
80 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
81 import android.server.wm.WindowManagerState;
82 import android.support.test.uiautomator.UiObject2;
83 import android.text.TextUtils;
84 import android.util.Log;
85 import android.util.Pair;
86 import android.view.Gravity;
87 import android.view.KeyEvent;
88 import android.view.View;
89 import android.view.ViewTreeObserver;
90 import android.view.WindowInsets;
91 import android.view.WindowInsetsController;
92 import android.view.WindowManager;
93 import android.view.inputmethod.Flags;
94 import android.view.inputmethod.InputMethod;
95 import android.view.inputmethod.InputMethodManager;
96 import android.view.inputmethod.cts.util.AutoCloseableWrapper;
97 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
98 import android.view.inputmethod.cts.util.MockTestActivityUtil;
99 import android.view.inputmethod.cts.util.RequireImeCompatFlagRule;
100 import android.view.inputmethod.cts.util.TestActivity;
101 import android.view.inputmethod.cts.util.TestActivity2;
102 import android.view.inputmethod.cts.util.TestUtils;
103 import android.view.inputmethod.cts.util.TestWebView;
104 import android.view.inputmethod.cts.util.UnlockScreenRule;
105 import android.widget.EditText;
106 import android.widget.LinearLayout;
107 import android.widget.PopupWindow;
108 import android.widget.TextView;
109 import android.window.OnBackInvokedDispatcher;
110 
111 import androidx.annotation.ColorInt;
112 import androidx.annotation.NonNull;
113 import androidx.test.filters.FlakyTest;
114 import androidx.test.filters.MediumTest;
115 import androidx.test.platform.app.InstrumentationRegistry;
116 import androidx.test.runner.AndroidJUnit4;
117 import androidx.test.uiautomator.By;
118 import androidx.test.uiautomator.BySelector;
119 import androidx.test.uiautomator.UiDevice;
120 import androidx.test.uiautomator.Until;
121 
122 import com.android.compatibility.common.util.CtsTouchUtils;
123 import com.android.compatibility.common.util.SystemUtil;
124 import com.android.cts.mockime.ImeEvent;
125 import com.android.cts.mockime.ImeEventStream;
126 import com.android.cts.mockime.ImeEventStreamTestUtils.DescribedPredicate;
127 import com.android.cts.mockime.ImeLayoutInfo;
128 import com.android.cts.mockime.ImeSettings;
129 import com.android.cts.mockime.MockImeSession;
130 
131 import org.junit.Before;
132 import org.junit.Rule;
133 import org.junit.Test;
134 import org.junit.function.ThrowingRunnable;
135 import org.junit.runner.RunWith;
136 
137 import java.io.IOException;
138 import java.util.ArrayList;
139 import java.util.List;
140 import java.util.Map;
141 import java.util.concurrent.CountDownLatch;
142 import java.util.concurrent.TimeUnit;
143 import java.util.concurrent.atomic.AtomicBoolean;
144 import java.util.concurrent.atomic.AtomicInteger;
145 import java.util.concurrent.atomic.AtomicReference;
146 import java.util.function.Predicate;
147 
148 @MediumTest
149 @RunWith(AndroidJUnit4.class)
150 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
151 public class KeyboardVisibilityControlTest extends EndToEndImeTestBase {
152     @Rule
153     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
154     private static final String TAG = KeyboardVisibilityControlTest.class.getSimpleName();
155     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(6);
156     private static final long START_INPUT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
157     private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
158     private static final long LAYOUT_STABLE_THRESHOLD = TimeUnit.SECONDS.toMillis(3);
159 
160     private static final int NEW_KEYBOARD_HEIGHT = 400;
161     private static final PreBackPressProcedure NO_OP_PRE_BACK_PRESS_PROCEDURE =
162             (instrumentation, editorRef) -> {};
163 
164     private static final String DISABLE_AUTO_ROTATE_CMD =
165             "settings put system accelerometer_rotation 0";
166     private static final String ENABLE_AUTO_ROTATE_CMD =
167             "settings put system accelerometer_rotation 1";
168 
169     private static final String FIXED_TO_USER_ROTATION_CMD = "cmd window fixed-to-user-rotation";
170 
171 
172     @Rule
173     public final UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
174     @Rule
175     public final RequireImeCompatFlagRule mRequireImeCompatFlagRule = new RequireImeCompatFlagRule(
176             FINISH_INPUT_NO_FALLBACK_CONNECTION, true);
177 
178     private Instrumentation mInstrumentation;
179     private CtsTouchUtils mCtsTouchUtils;
180 
181     @Before
setup()182     public void setup() {
183         mInstrumentation = InstrumentationRegistry.getInstrumentation();
184         mCtsTouchUtils = new CtsTouchUtils(mInstrumentation.getTargetContext());
185     }
186 
onFinishInputViewMatcher(boolean expectedFinishingInput)187     private static DescribedPredicate<ImeEvent> onFinishInputViewMatcher(boolean expectedFinishingInput) {
188         Predicate<ImeEvent> matcher = event -> {
189             if (!TextUtils.equals("onFinishInputView", event.getEventName())) {
190                 return false;
191             }
192             final boolean finishingInput = event.getArguments().getBoolean("finishingInput");
193             return finishingInput == expectedFinishingInput;
194         };
195         return withDescription("onFinishInputView(finishingInput=" + expectedFinishingInput + ")",
196                 matcher);
197     }
198 
launchTestActivity(@onNull String focusedMarker, @NonNull String nonFocusedMarker)199     private Pair<EditText, EditText> launchTestActivity(@NonNull String focusedMarker,
200             @NonNull String nonFocusedMarker) {
201         final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
202         final AtomicReference<EditText> nonFocusedEditTextRef = new AtomicReference<>();
203         TestActivity.startSync(activity -> {
204             final LinearLayout layout = new LinearLayout(activity);
205             layout.setOrientation(LinearLayout.VERTICAL);
206 
207             final EditText focusedEditText = new EditText(activity);
208             focusedEditText.setHint("focused editText");
209             focusedEditText.setPrivateImeOptions(focusedMarker);
210             focusedEditText.requestFocus();
211             focusedEditTextRef.set(focusedEditText);
212             layout.addView(focusedEditText);
213 
214             final EditText nonFocusedEditText = new EditText(activity);
215             nonFocusedEditText.setPrivateImeOptions(nonFocusedMarker);
216             nonFocusedEditText.setHint("target editText");
217             nonFocusedEditTextRef.set(nonFocusedEditText);
218             layout.addView(nonFocusedEditText);
219             return layout;
220         });
221         return new Pair<>(focusedEditTextRef.get(), nonFocusedEditTextRef.get());
222     }
223 
launchTestActivity(@onNull String marker)224     private EditText launchTestActivity(@NonNull String marker) {
225         return launchTestActivity(marker, getTestMarker(NON_FOCUSED_EDIT_TEXT_TAG)).first;
226     }
227 
launchTestActivity2(@onNull String marker)228     private EditText launchTestActivity2(@NonNull String marker) {
229         final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
230         new TestActivity.Starter().startSync(activity -> {
231             final LinearLayout layout = new LinearLayout(activity);
232             layout.setOrientation(LinearLayout.VERTICAL);
233 
234             final EditText focusedEditText = new EditText(activity);
235             focusedEditText.setHint("focused editText");
236             focusedEditText.setPrivateImeOptions(marker);
237             focusedEditText.requestFocus();
238             focusedEditTextRef.set(focusedEditText);
239             layout.addView(focusedEditText);
240             return layout;
241         }, TestActivity2.class);
242         return focusedEditTextRef.get();
243     }
244 
245     @Test
testBasicShowHideSoftInput()246     public void testBasicShowHideSoftInput() throws Exception {
247         final InputMethodManager imm = mInstrumentation
248                 .getTargetContext().getSystemService(InputMethodManager.class);
249 
250         try (MockImeSession imeSession = MockImeSession.create(
251                 mInstrumentation.getContext(),
252                 mInstrumentation.getUiAutomation(),
253                 new ImeSettings.Builder())) {
254             final ImeEventStream stream = imeSession.openEventStream();
255 
256             final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
257             final EditText editText = launchTestActivity(marker);
258 
259             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
260             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
261             expectImeInvisible(TIMEOUT);
262 
263             assertTrue("hasActiveInputConnection() must return true if the View has IME focus",
264                     getOnMainSync(() -> imm.hasActiveInputConnection(editText)));
265 
266             // Test showSoftInput() flow
267             assertTrue("showSoftInput must success if the View has IME focus",
268                     getOnMainSync(() -> imm.showSoftInput(editText, 0)));
269 
270             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
271             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
272             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
273                     View.VISIBLE, TIMEOUT);
274             expectImeVisible(TIMEOUT);
275 
276             // Test hideSoftInputFromWindow() flow
277             assertTrue("hideSoftInputFromWindow must success if the View has IME focus",
278                     getOnMainSync(() -> imm.hideSoftInputFromWindow(editText.getWindowToken(), 0)));
279 
280             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
281             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
282             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
283                     View.GONE, TIMEOUT);
284             expectImeInvisible(TIMEOUT);
285         }
286     }
287 
288     @FunctionalInterface
289     private interface PreBackPressProcedure {
run( Instrumentation instrumentation, AtomicReference<EditText> editorRef)290         void run(
291                 Instrumentation instrumentation,
292                 AtomicReference<EditText> editorRef) throws Exception;
293     }
294 
verifyHideImeBackPressed( boolean appRequestsBackCallback, boolean imeRequestsBackCallback, @NonNull PreBackPressProcedure preBackPressProcedure)295     private void verifyHideImeBackPressed(
296             boolean appRequestsBackCallback, boolean imeRequestsBackCallback,
297             @NonNull PreBackPressProcedure preBackPressProcedure) throws Exception {
298         final Instrumentation instrumentation = mInstrumentation;
299         final Context context = instrumentation.getTargetContext();
300         final InputMethodManager imm = context.getSystemService(InputMethodManager.class);
301 
302         // Whether 'OnBackInvokedCallback' or 'onBackPressed' (legacy back) is used is defined by
303         // the 'enableOnBackInvokedCallback' flag in the Application manifest.
304         // Registering a callback is only authorized if the flag is set to true. Since the
305         // WindowOnBackDispatcher is created at the same time as the ViewRootImpl, for test purpose,
306         // we need to manually set the flag on ApplicationInfo before the window is created which
307         // happens during the MockIme creation and TestActivity creation.
308         final boolean onBackCallbackEnabled =
309                 context.getApplicationInfo().isOnBackInvokedCallbackEnabled();
310 
311         try (MockImeSession imeSession = MockImeSession.create(
312                 instrumentation.getContext(),
313                 instrumentation.getUiAutomation(),
314                 new ImeSettings.Builder()
315                         .setOnBackCallbackEnabled(imeRequestsBackCallback)
316         )) {
317             final ImeEventStream stream = imeSession.openEventStream();
318             final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
319             final AtomicInteger backCallbackInvocationCount = new AtomicInteger();
320 
321             if (appRequestsBackCallback) {
322                 context.getApplicationInfo().setEnableOnBackInvokedCallback(true);
323             }
324 
325             final EditText editText = launchTestActivity(marker);
326             final AtomicReference<EditText> editorRef = new AtomicReference<>();
327             editorRef.set(editText);
328             final TestActivity testActivity = (TestActivity) editText.getContext();
329 
330             if (appRequestsBackCallback) {
331                 testActivity.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
332                         OnBackInvokedDispatcher.PRIORITY_DEFAULT, () -> {
333                             backCallbackInvocationCount.getAndIncrement();
334                         });
335             } else {
336                 testActivity.setIgnoreBackKey(true);
337             }
338 
339             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
340             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
341             expectImeInvisible(TIMEOUT);
342 
343             assertTrue("hasActiveInputConnection() must return true if the View has IME focus",
344                     getOnMainSync(() -> imm.hasActiveInputConnection(editText)));
345 
346             // Test showSoftInput() flow
347             assertTrue("showSoftInput must success if the View has IME focus",
348                     getOnMainSync(() -> imm.showSoftInput(editText, 0)));
349 
350             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
351             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
352             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
353                     View.VISIBLE, TIMEOUT);
354             expectImeVisible(TIMEOUT);
355 
356             preBackPressProcedure.run(instrumentation, editorRef);
357 
358             // Pressing back key, expect soft-keyboard will become invisible.
359             instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
360             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
361             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
362             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
363                     View.GONE, TIMEOUT);
364             expectImeInvisible(TIMEOUT);
365 
366             if (appRequestsBackCallback) {
367                 // Verify that IME callback is removed after IME is hidden.
368                 instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
369                 assertEquals(1, backCallbackInvocationCount.get());
370             }
371         } finally {
372             context.getApplicationInfo().setEnableOnBackInvokedCallback(onBackCallbackEnabled);
373         }
374     }
375 
376     @Test
testHideImeAfterBackPressed_legacyAppLegacyIme()377     public void testHideImeAfterBackPressed_legacyAppLegacyIme() throws Exception {
378         verifyHideImeBackPressed(false /* appRequestsBackCallback */,
379                 false /* imeRequestsBackCallback */,
380                 (instrumentation, editorRef) -> {} /* pre back press procedure */);
381     }
382 
383     @Test
testHideImeAfterBackPressed_migratedAppLegacyIme()384     public void testHideImeAfterBackPressed_migratedAppLegacyIme() throws Exception {
385         verifyHideImeBackPressed(true /* appRequestsBackCallback */,
386                 false /* imeRequestsBackCallback */,
387                 NO_OP_PRE_BACK_PRESS_PROCEDURE);
388     }
389 
390     @Test
testHideImeAfterBackPressed_migratedAppMigratedIme()391     public void testHideImeAfterBackPressed_migratedAppMigratedIme() throws Exception {
392         verifyHideImeBackPressed(true /* appRequestsBackCallback */,
393                 true /* imeRequestsBackCallback */,
394                 NO_OP_PRE_BACK_PRESS_PROCEDURE);
395     }
396 
397     @Test
testHideImeAfterBackPressed_legacyAppMigratedIme()398     public void testHideImeAfterBackPressed_legacyAppMigratedIme() throws Exception {
399         verifyHideImeBackPressed(true /* appRequestsBackCallback */,
400                 true /* imeRequestsBackCallback */,
401                 NO_OP_PRE_BACK_PRESS_PROCEDURE);
402     }
403 
404     @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
405     @Test
testHideImeAfterBackPressed_ScreenOffOn()406     public void testHideImeAfterBackPressed_ScreenOffOn() throws Exception {
407         verifyHideImeBackPressed(true /* appRequestsBackCallback */,
408                 true /* imeRequestsBackCallback */,
409                 (instrumentation, editorRef) -> {
410                     TestUtils.turnScreenOff();
411                     TestUtils.turnScreenOn();
412                     TestUtils.unlockScreen();
413                     // Before testing the back procedure, ensure the test activity has the window
414                     // focus and the IME visible after screen-on.
415                     TestUtils.waitOnMainUntil(editorRef.get()::hasWindowFocus, TIMEOUT);
416                     expectImeVisible(TIMEOUT);
417                 } /* pre back press procedure */);
418     }
419 
420     @Test
testHideImeAfterBackPressed_rootViewChanges()421     public void testHideImeAfterBackPressed_rootViewChanges() throws Exception {
422         verifyHideImeBackPressed(true /* appRequestsBackCallback */,
423                 true /* imeRequestsBackCallback */,
424                 (instrumentation, editorRef) -> {
425                     AutoCloseableWrapper<PopupWindow> popupWindowWrapper =
426                             createPopupWindowWrapper(editorRef.get());
427                     instrumentation.waitForIdleSync();
428                     // Verify IME became invisible when the non-ime-focusable PopupWindow is shown.
429                     expectImeInvisible(NOT_EXPECT_TIMEOUT);
430 
431                     runOnMainSync(() -> popupWindowWrapper.get().dismiss());
432                     // Verify IME became visible when the non-ime-focusable PopupWindow has
433                     // dismissed.
434                     expectImeVisible(TIMEOUT);
435                 } /* pre back press procedure */);
436     }
437 
438     @Test
testShowHideSoftInputShouldBeIgnoredOnNonFocusedView()439     public void testShowHideSoftInputShouldBeIgnoredOnNonFocusedView() throws Exception {
440         final InputMethodManager imm = mInstrumentation
441                 .getTargetContext().getSystemService(InputMethodManager.class);
442 
443         try (MockImeSession imeSession = MockImeSession.create(
444                 mInstrumentation.getContext(),
445                 mInstrumentation.getUiAutomation(),
446                 new ImeSettings.Builder())) {
447             final ImeEventStream stream = imeSession.openEventStream();
448 
449             final String focusedMarker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
450             final String nonFocusedMarker = getTestMarker(NON_FOCUSED_EDIT_TEXT_TAG);
451             final Pair<EditText, EditText> editTextPair =
452                     launchTestActivity(focusedMarker, nonFocusedMarker);
453             final EditText nonFocusedEditText = editTextPair.second;
454 
455             expectEvent(stream, editorMatcher("onStartInput", focusedMarker), TIMEOUT);
456 
457             expectImeInvisible(TIMEOUT);
458             assertFalse("hasActiveInputConnection() must return false if the View does not have IME"
459                             + " focus",
460                     getOnMainSync(() -> imm.hasActiveInputConnection(nonFocusedEditText)));
461             assertFalse("showSoftInput must fail if the View does not have IME focus",
462                     getOnMainSync(() -> imm.showSoftInput(nonFocusedEditText, 0)));
463             notExpectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
464 
465             getOnMainSync(() -> imm.hideSoftInputFromWindow(
466                     nonFocusedEditText.getWindowToken(), 0));
467             // IME was never shown, so there should be no hideSoftInput.
468             notExpectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
469             expectImeInvisible(TIMEOUT);
470         }
471     }
472 
473     @Test
testToggleSoftInput()474     public void testToggleSoftInput() throws Exception {
475         final InputMethodManager imm = mInstrumentation
476                 .getTargetContext().getSystemService(InputMethodManager.class);
477 
478         try (MockImeSession imeSession = MockImeSession.create(
479                 mInstrumentation.getContext(),
480                 mInstrumentation.getUiAutomation(),
481                 new ImeSettings.Builder())) {
482             final ImeEventStream stream = imeSession.openEventStream();
483 
484             final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
485             final EditText editText = launchTestActivity(marker);
486 
487             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
488             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
489             expectImeInvisible(TIMEOUT);
490 
491             // Test toggleSoftInputFromWindow() flow
492             runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0));
493 
494             expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
495             expectEvent(stream.copy(), editorMatcher("onStartInputView", marker), TIMEOUT);
496             expectImeVisible(TIMEOUT);
497 
498             // Calling toggleSoftInputFromWindow() must hide the IME.
499             runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0));
500 
501             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
502             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
503             expectImeInvisible(TIMEOUT);
504         }
505     }
506 
507     @Test
508     @FlakyTest(bugId = 294840051)
testShowHideKeyboardOnWebView()509     public void testShowHideKeyboardOnWebView() throws Exception {
510         final PackageManager pm =
511                 mInstrumentation.getContext().getPackageManager();
512         assumeTrue(pm.hasSystemFeature("android.software.webview"));
513 
514         try (MockImeSession imeSession = MockImeSession.create(
515                 mInstrumentation.getContext(),
516                 mInstrumentation.getUiAutomation(),
517                 new ImeSettings.Builder())) {
518             final ImeEventStream stream = imeSession.openEventStream();
519             final String marker = getTestMarker();
520             final UiObject2 inputTextField = TestWebView.launchTestWebViewActivity(
521                     TIMEOUT, marker);
522             assertNotNull("Editor must exists on WebView", inputTextField);
523             expectImeInvisible(TIMEOUT);
524 
525             inputTextField.click();
526             expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
527             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
528             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
529             expectImeVisible(TIMEOUT);
530         }
531     }
532 
533     @Test
534     @FlakyTest(detail = "slow test")
testShowHideKeyboardWithInterval()535     public void testShowHideKeyboardWithInterval() throws Exception {
536         final InputMethodManager imm = mInstrumentation
537                 .getTargetContext().getSystemService(InputMethodManager.class);
538 
539         try (MockImeSession imeSession = MockImeSession.create(
540                 mInstrumentation.getContext(),
541                 mInstrumentation.getUiAutomation(),
542                 new ImeSettings.Builder())) {
543             final ImeEventStream stream = imeSession.openEventStream();
544             final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
545             final EditText editText = launchTestActivity(marker);
546             expectImeInvisible(TIMEOUT);
547 
548             runOnMainSync(() -> imm.showSoftInput(editText, 0));
549             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
550             expectImeVisible(TIMEOUT);
551 
552             // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
553             final List<Integer> intervals = new ArrayList<>();
554             for (int i = 10; i < 100; i += 10) intervals.add(i);
555             for (int i = 100; i < 500; i += 50) intervals.add(i);
556             // Regression test for b/221483132.
557             // WindowInsetsController tries to clean up IME window after IME hide animation is done.
558             // Makes sure that IMM#showSoftInput during IME hide animation cancels the cleanup.
559             for (int intervalMillis : intervals) {
560                 runOnMainSync(() -> imm.hideSoftInputFromWindow(editText.getWindowToken(), 0));
561                 SystemClock.sleep(intervalMillis);
562                 runOnMainSync(() -> imm.showSoftInput(editText, 0));
563                 expectImeVisible(TIMEOUT, "IME should be visible. Interval = " + intervalMillis);
564             }
565         }
566     }
567 
568     /**
569      * Verifies that a hideSoftInputFromWindow call just after a showSoftInput call can succeed.
570      *
571      * <p>This is a regression test for Bug 21727232.</p>
572      */
573     @Test
testShowHideKeyboardImmediately()574     public void testShowHideKeyboardImmediately() throws Exception {
575         final InputMethodManager imm = mInstrumentation
576                 .getTargetContext().getSystemService(InputMethodManager.class);
577 
578         try (MockImeSession imeSession = MockImeSession.create(
579                 mInstrumentation.getContext(),
580                 mInstrumentation.getUiAutomation(),
581                 new ImeSettings.Builder())) {
582             final ImeEventStream stream = imeSession.openEventStream();
583             final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
584             final EditText editText = launchTestActivity(marker);
585 
586             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
587 
588             assertTrue("hasActiveInputConnection() must return true if the View has IME focus",
589                     getOnMainSync(() -> imm.hasActiveInputConnection(editText)));
590 
591             // Issue a command with no effect to wait until MockIme becomes idle
592             expectCommand(stream, imeSession.callGetWindowLayoutInfo(), TIMEOUT);
593             // Copy event stream to check assertion starting from the start of the stream
594             notExpectEvent(imeSession.openEventStream(), editorMatcher("onStartInputView", marker),
595                     0);
596 
597             // Test calling showSoftInput() immediately followed by hideSoftInputFromWindow()
598             runOnMainSyncWithRethrowing(() -> {
599                 assertTrue("showSoftInput must success if the View has IME focus",
600                         imm.showSoftInput(editText, 0 /* flags */));
601                 assertTrue("hideSoftInputFromWindow must success when called right"
602                                 + " after showSoftInput",
603                         imm.hideSoftInputFromWindow(editText.getWindowToken(), 0 /* flags */));
604             });
605 
606             // Assert showSoftInput() flow was observed
607             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
608             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
609 
610             // Assert hideSoftInputFromWindow() flow was observed
611             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
612             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
613             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
614                     View.GONE, TIMEOUT);
615         }
616     }
617 
618     @Test
619     @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_INSETS_CONTROLLER)
testShowSoftInputWithShowForcedFlagWhenAppIsLeaving()620     public void testShowSoftInputWithShowForcedFlagWhenAppIsLeaving() throws Exception {
621         final InputMethodManager imm = mInstrumentation
622                 .getTargetContext().getSystemService(InputMethodManager.class);
623 
624         try (MockImeSession imeSession = MockImeSession.create(
625                 mInstrumentation.getContext(),
626                 mInstrumentation.getUiAutomation(),
627                 new ImeSettings.Builder())) {
628             final ImeEventStream stream = imeSession.openEventStream();
629 
630             // Launch a simple test activity
631             final TestActivity testActivity = TestActivity.startSync(activity -> {
632                 activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
633                 return new LinearLayout(activity);
634             });
635             assertTrue("test activity should be in resume state",
636                     getOnMainSync(testActivity::hasWindowFocus));
637 
638             // Launch a test editor activity
639             final String marker = getTestMarker();
640             final AtomicReference<EditText> ediTextRef = new AtomicReference<>();
641             final TestActivity testEditorActivity =
642                     new TestActivity.Starter().asNewTask().startSync(activity -> {
643                         final LinearLayout layout = new LinearLayout(activity);
644                         layout.setOrientation(LinearLayout.VERTICAL);
645 
646                         final EditText focusedEditText = new EditText(activity);
647                         focusedEditText.setHint("focused editText");
648                         focusedEditText.setPrivateImeOptions(marker);
649                         focusedEditText.requestFocus();
650                         layout.addView(focusedEditText);
651                         ediTextRef.set(focusedEditText);
652                         return layout;
653                     }, TestActivity.class);
654 
655             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
656             notExpectEvent(stream, editorMatcher("onStartInputView", marker), NOT_EXPECT_TIMEOUT);
657             expectImeInvisible(TIMEOUT);
658 
659             assertTrue("hasActiveInputConnection() must return true if the View has IME focus",
660                     getOnMainSync(() -> imm.hasActiveInputConnection(ediTextRef.get())));
661 
662             // Test showSoftInput() flow with adding SHOW_FORCED flag
663             assertTrue("showSoftInput must success if the View has IME focus",
664                     getOnMainSync(() ->
665                             imm.showSoftInput(ediTextRef.get(), InputMethodManager.SHOW_FORCED)));
666 
667             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
668             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
669             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
670                     View.VISIBLE, TIMEOUT);
671             expectImeVisible(TIMEOUT);
672 
673             // Finish testEditorActivity
674             runOnMainSync(testEditorActivity::finish);
675 
676             // Verify soft-keyboard will not visible when enabling the platform compat flag to
677             // clear SHOW_FOCED flag. Otherwise, keeping the legacy behavior of SHOW_FOCED that
678             // soft-keyboard remains visible if there is no explicit hiding request.
679             if (isClearShowForcedFlagEnabled(testActivity.getPackageName())) {
680                 notExpectEvent(stream, eventMatcher("showSoftInput"),
681                         NOT_EXPECT_TIMEOUT);
682                 expectImeInvisible(TIMEOUT);
683             } else {
684                 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT);
685                 expectImeVisible(TIMEOUT);
686             }
687         }
688     }
689 
690     @Test
testFloatingImeHideKeyboardAfterBackPressed()691     public void testFloatingImeHideKeyboardAfterBackPressed() throws Exception {
692         final Instrumentation instrumentation = mInstrumentation;
693         final InputMethodManager imm = instrumentation.getTargetContext().getSystemService(
694                 InputMethodManager.class);
695 
696         // Initial MockIme with floating IME settings.
697         try (MockImeSession imeSession = MockImeSession.create(
698                 instrumentation.getContext(), instrumentation.getUiAutomation(),
699                 getFloatingImeSettings(Color.BLACK))) {
700             final ImeEventStream stream = imeSession.openEventStream();
701             final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
702             final EditText editText = launchTestActivity(marker);
703 
704             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
705             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
706             expectImeInvisible(TIMEOUT);
707 
708             assertTrue("hasActiveInputConnection() must return true if the View has IME focus",
709                     getOnMainSync(() -> imm.hasActiveInputConnection(editText)));
710 
711             // Test showSoftInput() flow
712             assertTrue("showSoftInput must success if the View has IME focus",
713                     getOnMainSync(() -> imm.showSoftInput(editText, 0)));
714 
715             expectEvent(stream, showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
716             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
717             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
718                     View.VISIBLE, TIMEOUT);
719             expectImeVisible(TIMEOUT);
720 
721             // Pressing back key, expect soft-keyboard will become invisible.
722             instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
723             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
724             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
725             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
726                     View.GONE, TIMEOUT);
727             expectImeInvisible(TIMEOUT);
728         }
729     }
730 
731     @Test
testImeVisibilityWhenDismissingDialogWithImeFocused()732     public void testImeVisibilityWhenDismissingDialogWithImeFocused() throws Exception {
733         final Instrumentation instrumentation = mInstrumentation;
734         try (MockImeSession imeSession = MockImeSession.create(
735                 instrumentation.getContext(),
736                 instrumentation.getUiAutomation(),
737                 new ImeSettings.Builder())) {
738             final ImeEventStream stream = imeSession.openEventStream();
739 
740             // Launch a simple test activity
741             final TestActivity testActivity =
742                     new TestActivity.Starter()
743                             .withWindowingMode(WINDOWING_MODE_FULLSCREEN)
744                             .startSync(LinearLayout::new, TestActivity.class);
745 
746             // Launch a dialog
747             final String marker = getTestMarker();
748             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
749             final AtomicReference<AlertDialog> dialogRef = new AtomicReference<>();
750             TestUtils.runOnMainSync(() -> {
751                 final EditText editText = new EditText(testActivity);
752                 editText.setHint("focused editText");
753                 editText.setPrivateImeOptions(marker);
754                 editText.requestFocus();
755                 final AlertDialog dialog = new AlertDialog.Builder(testActivity)
756                         .setView(editText)
757                         .create();
758                 final WindowInsetsController.OnControllableInsetsChangedListener listener =
759                         new WindowInsetsController.OnControllableInsetsChangedListener() {
760                             @Override
761                             public void onControllableInsetsChanged(
762                                     @NonNull WindowInsetsController controller, int typeMask) {
763                                 if ((typeMask & ime()) != 0) {
764                                     editText.getWindowInsetsController()
765                                             .removeOnControllableInsetsChangedListener(this);
766                                     editText.getWindowInsetsController().show(ime());
767                                 }
768                             }
769                         };
770                 dialog.show();
771                 editText.getWindowInsetsController().addOnControllableInsetsChangedListener(
772                         listener);
773                 editTextRef.set(editText);
774                 dialogRef.set(dialog);
775             });
776             TestUtils.waitOnMainUntil(() -> dialogRef.get().isShowing()
777                     && editTextRef.get().hasFocus(), TIMEOUT);
778             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
779             expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT);
780             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
781             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
782                     View.VISIBLE, TIMEOUT);
783             expectImeVisible(TIMEOUT);
784 
785             // Hide keyboard and dismiss dialog.
786             TestUtils.runOnMainSync(() -> {
787                 editTextRef.get().getWindowInsetsController().hide(ime());
788                 dialogRef.get().dismiss();
789             });
790 
791             // Expect onFinishInput called and keyboard should hide successfully.
792             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
793             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
794             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
795                     View.GONE, TIMEOUT);
796             expectImeInvisible(TIMEOUT);
797 
798             // onWindowVisibilityChanged event can be out of sequence. Creating
799             // a copy of the ImeEventStream to handle this event.
800             final ImeEventStream streamCopy = stream.copy();
801 
802             // Expect fallback input connection started and keyboard invisible after activity
803             // focused unless avoidable keyboard startup is desired,
804             // in which case, no fallback will be started.
805             if (!isPreventImeStartup()) {
806                 final ImeEvent onStart = expectEvent(stream, eventMatcher("onStartInput"), TIMEOUT);
807                 assertTrue(onStart.getEnterState().hasFallbackInputConnection());
808             }
809             TestUtils.waitOnMainUntil(testActivity::hasWindowFocus, TIMEOUT);
810             expectEventWithKeyValue(streamCopy, "onWindowVisibilityChanged", "visible",
811                     View.GONE, TIMEOUT);
812             expectImeInvisible(TIMEOUT);
813         }
814     }
815 
816     @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
817     @Test
testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked()818     public void testImeState_Unspecified_EditorDialogLostFocusAfterUnlocked() throws Exception {
819         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_UNSPECIFIED);
820     }
821 
822     @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
823     @Test
testImeState_Visible_EditorDialogLostFocusAfterUnlocked()824     public void testImeState_Visible_EditorDialogLostFocusAfterUnlocked() throws Exception {
825         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_VISIBLE);
826     }
827 
828     @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
829     @Test
testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked()830     public void testImeState_AlwaysVisible_EditorDialogLostFocusAfterUnlocked() throws Exception {
831         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
832     }
833 
834     @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
835     @Test
testImeState_Hidden_EditorDialogLostFocusAfterUnlocked()836     public void testImeState_Hidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
837         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_HIDDEN);
838     }
839 
840     @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
841     @Test
testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked()842     public void testImeState_AlwaysHidden_EditorDialogLostFocusAfterUnlocked() throws Exception {
843         runImeDoesntReshowAfterKeyguardTest(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
844     }
845 
runImeDoesntReshowAfterKeyguardTest(int softInputState)846     private void runImeDoesntReshowAfterKeyguardTest(int softInputState) throws Exception {
847         try (MockImeSession imeSession = MockImeSession.create(
848                 mInstrumentation.getContext(),
849                 mInstrumentation.getUiAutomation(),
850                 new ImeSettings.Builder())) {
851             final ImeEventStream stream = imeSession.openEventStream();
852             // Launch a simple test activity
853             final TestActivity testActivity =
854                     new TestActivity.Starter()
855                             .withWindowingMode(WINDOWING_MODE_FULLSCREEN)
856                             .startSync(LinearLayout::new, TestActivity.class);
857 
858             // Launch a dialog and show keyboard
859             final String marker = getTestMarker();
860             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
861             final AtomicReference<AlertDialog> dialogRef = new AtomicReference<>();
862             TestUtils.runOnMainSync(() -> {
863                 final EditText editText = new EditText(testActivity);
864                 editText.setHint("focused editText");
865                 editText.setPrivateImeOptions(marker);
866                 editText.requestFocus();
867                 final AlertDialog dialog = new AlertDialog.Builder(testActivity)
868                         .setView(editText)
869                         .create();
870                 dialog.getWindow().setSoftInputMode(softInputState);
871                 // Tracking onFocusChange callback for debugging purpose.
872                 editText.setOnFocusChangeListener((v, hasFocus) -> {
873                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
874                         Log.v(TAG, "Editor " + editText + " hasFocus=" + hasFocus, new Throwable());
875                     }
876                 });
877                 dialog.show();
878                 editText.getWindowInsetsController().show(ime());
879                 editTextRef.set(editText);
880                 dialogRef.set(dialog);
881             });
882 
883             try (AutoCloseableWrapper<AlertDialog> dialogCloseWrapper = AutoCloseableWrapper.create(
884                     dialogRef.get(), dialog -> TestUtils.runOnMainSync(dialog::dismiss))) {
885                 TestUtils.waitOnMainUntil(() -> dialogRef.get().isShowing()
886                         && editTextRef.get().hasFocus(), TIMEOUT);
887                 expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
888                 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT);
889                 // Copy the event stream to verify both events in case expectEvent missed the
890                 // event verification if the actual event sequence has flipped.
891                 expectEvent(stream.copy(), editorMatcher("onStartInputView", marker), TIMEOUT);
892                 expectEventWithKeyValue(stream.copy(), "onWindowVisibilityChanged", "visible",
893                         View.VISIBLE, TIMEOUT);
894                 expectImeVisible(TIMEOUT);
895 
896                 TestUtils.turnScreenOff();
897                 // Clear editor focus after screen-off
898                 TestUtils.runOnMainSync(editTextRef.get()::clearFocus);
899 
900                 TestUtils.waitOnMainUntil(() -> editTextRef.get().getWindowVisibility() != VISIBLE,
901                         TIMEOUT);
902                 expectEvent(stream, onFinishInputViewMatcher(true), TIMEOUT);
903                 if (imeSession.isFinishInputNoFallbackConnectionEnabled()) {
904                     // When IME enabled the new app compat behavior to finish input without fallback
905                     // input connection when device interactive state changed,
906                     // we expect onFinishInput happens without any additional fallback input
907                     // connection started and no showShowSoftInput requested.
908                     expectEvent(stream, eventMatcher("onFinishInput"),
909                             TIMEOUT);
910                     notExpectEvent(stream, eventMatcher("showSoftInput"),
911                             NOT_EXPECT_TIMEOUT);
912                 } else {
913                     // For legacy IME, the fallback input connection will started after screen-off.
914                     expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
915                     expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
916                     // Expect showSoftInput comes when system notify InsetsController to apply
917                     // show IME insets after IME input target updated.
918                     expectEvent(stream, eventMatcher("showSoftInput"),
919                             TIMEOUT);
920                     notExpectEvent(stream, hideSoftInputMatcher(), NOT_EXPECT_TIMEOUT);
921                 }
922 
923                 // Verify IME will invisible after device unlocked
924                 TestUtils.turnScreenOn();
925                 TestUtils.unlockScreen();
926                 // Expect hideSoftInput will called by IMMS when the same window
927                 // focused since the editText view focus has been cleared.
928                 TestUtils.waitOnMainUntil(() -> editTextRef.get().hasWindowFocus()
929                         && !editTextRef.get().hasFocus(), TIMEOUT);
930                 expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
931                 if (!imeSession.isFinishInputNoFallbackConnectionEnabled()) {
932                     expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
933                 }
934                 expectImeInvisible(TIMEOUT);
935             }
936         }
937     }
938 
939     @AppModeFull
940     @Test
testImeVisibilityWhenImeTransitionBetweenActivities_Full()941     public void testImeVisibilityWhenImeTransitionBetweenActivities_Full() throws Exception {
942         runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */);
943     }
944 
945     @AppModeInstant
946     @Test
testImeVisibilityWhenImeTransitionBetweenActivities_Instant()947     public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception {
948         runImeVisibilityWhenImeTransitionBetweenActivities(true /* instant */);
949     }
950 
951     @AppModeFull
952     @Test
testImeInvisibleWhenForceStopPkgProcess_Full()953     public void testImeInvisibleWhenForceStopPkgProcess_Full() throws Exception {
954         runImeVisibilityTestWhenForceStopPackage(false /* instant */);
955     }
956 
957     @AppModeInstant
958     @Test
testImeInvisibleWhenForceStopPkgProcess_Instant()959     public void testImeInvisibleWhenForceStopPkgProcess_Instant() throws Exception {
960         runImeVisibilityTestWhenForceStopPackage(true /* instant */);
961     }
962 
963     @Test
testRestoreImeVisibility()964     public void testRestoreImeVisibility() throws Exception {
965         // TODO(b/226110728): Remove after we can send ime restore signal to DisplayAreaOrganizer.
966         assumeFalse(isImeOrganized(DEFAULT_DISPLAY));
967         runRestoreImeVisibility(TestSoftInputMode.UNCHANGED_WITH_BACKWARD_NAV, true);
968     }
969 
970     @Test
testRestoreImeVisibility_noRestoreForAlwaysHidden()971     public void testRestoreImeVisibility_noRestoreForAlwaysHidden() throws Exception {
972         runRestoreImeVisibility(TestSoftInputMode.ALWAYS_HIDDEN_WITH_BACKWARD_NAV, false);
973     }
974 
975     @Test
testRestoreImeVisibility_noRestoreForHiddenWithForwardNav()976     public void testRestoreImeVisibility_noRestoreForHiddenWithForwardNav() throws Exception {
977         runRestoreImeVisibility(TestSoftInputMode.HIDDEN_WITH_FORWARD_NAV, false);
978     }
979 
980     /**
981      * Test case for Bug 225028378.
982      *
983      * <p>This test ensures that showing a non-ime-focusable {@link PopupWindow} with
984      * {@link PopupWindow#INPUT_METHOD_NOT_NEEDED} will be on top of the IME.</p>
985      */
986     @Test
testNonImeFocusablePopupWindow_onTopOfIme()987     public void testNonImeFocusablePopupWindow_onTopOfIme() throws Exception {
988         final Instrumentation instrumentation = mInstrumentation;
989         try (MockImeSession imeSession = MockImeSession.create(
990                 mInstrumentation.getContext(),
991                 mInstrumentation.getUiAutomation(),
992                 new ImeSettings.Builder())) {
993             final ImeEventStream stream = imeSession.openEventStream();
994             final String marker = getTestMarker();
995             final AtomicReference<EditText> editorRef = new AtomicReference<>();
996             new TestActivity.Starter().withWindowingMode(
997                     WINDOWING_MODE_FULLSCREEN).startSync(activity -> {
998                         final LinearLayout layout = new LinearLayout(activity);
999                         layout.setOrientation(LinearLayout.VERTICAL);
1000                         layout.setGravity(Gravity.BOTTOM);
1001                         final EditText editText = new EditText(activity);
1002                         editorRef.set(editText);
1003                         editText.setHint("focused editText");
1004                         editText.setPrivateImeOptions(marker);
1005                         editText.requestFocus();
1006                         layout.addView(editText);
1007                         return layout;
1008                     }, TestActivity.class);
1009             // Show IME.
1010             runOnMainSync(() -> editorRef.get().getContext().getSystemService(
1011                     InputMethodManager.class).showSoftInput(editorRef.get(), 0));
1012 
1013             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1014             expectImeVisible(TIMEOUT);
1015 
1016             // Create then show a non-ime-focusable PopupWindow with INPUT_METHOD_NOT_NEEDED.
1017             try (AutoCloseableWrapper<PopupWindow> popupWindowWrapper =
1018                     createPopupWindowWrapper(editorRef.get())
1019             ) {
1020                 instrumentation.waitForIdleSync();
1021                 // Verify IME became invisible when the non-ime-focusable PopupWindow is shown.
1022                 expectImeInvisible(NOT_EXPECT_TIMEOUT);
1023 
1024                 runOnMainSync(() -> popupWindowWrapper.get().dismiss());
1025                 // Verify IME became visible when the non-ime-focusable PopupWindow has dismissed.
1026                 expectImeVisible(TIMEOUT);
1027             }
1028         }
1029     }
1030 
1031     /**
1032      * Test case for Bug 228766370.
1033      *
1034      * <p>This test ensures that IME will visible on an ime-focusable overlay window when another
1035      * activity behind the overlay that requests to show IME. <p/>
1036      */
1037     @Test
testImeVisibleOnImeFocusableOverlay()1038     public void testImeVisibleOnImeFocusableOverlay() throws Exception {
1039         try (MockImeSession imeSession = MockImeSession.create(
1040                 mInstrumentation.getContext(),
1041                 mInstrumentation.getUiAutomation(),
1042                 new ImeSettings.Builder()
1043                         .setInputViewHeight(NEW_KEYBOARD_HEIGHT)
1044                         .setDrawsBehindNavBar(true))) {
1045             final ImeEventStream stream = imeSession.openEventStream();
1046             TestActivity testActivity = TestActivity.startSync(activity -> {
1047                 final View view = new View(activity);
1048                 view.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
1049                 return view;
1050             });
1051 
1052             // Show an overlay with "IME Focusable" (NOT_FOCUSABLE | ALT_FOCUSABLE_IM) flags.
1053             runOnMainSync(() -> SystemUtil.runWithShellPermissionIdentity(() ->
1054                     testActivity.showOverlayWindow(true /* imeFocusable */)));
1055             mInstrumentation.waitForIdleSync();
1056 
1057             // Start a next activity to expect IME should visible on top of the overlay.
1058             final String marker = getTestMarker();
1059             final AtomicReference<EditText> editorRef = new AtomicReference<>();
1060             new TestActivity.Starter().asNewTask().startSync(activity -> {
1061                 final LinearLayout layout = new LinearLayout(activity);
1062                 layout.setOrientation(LinearLayout.VERTICAL);
1063                 layout.setGravity(Gravity.BOTTOM);
1064                 final EditText editText = new EditText(activity);
1065                 editorRef.set(editText);
1066                 editText.setHint("focused editText");
1067                 editText.setPrivateImeOptions(marker);
1068                 editText.requestFocus();
1069                 layout.addView(editText);
1070                 return layout;
1071             }, TestActivity.class);
1072             // Show IME.
1073             runOnMainSync(() -> editorRef.get().getContext().getSystemService(
1074                     InputMethodManager.class).showSoftInput(editorRef.get(), 0));
1075 
1076             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1077             expectImeVisible(TIMEOUT);
1078         }
1079     }
1080 
1081     private enum TestSoftInputMode {
1082         UNCHANGED_WITH_BACKWARD_NAV,
1083         ALWAYS_HIDDEN_WITH_BACKWARD_NAV,
1084         HIDDEN_WITH_FORWARD_NAV
1085     }
1086 
runRestoreImeVisibility(TestSoftInputMode mode, boolean expectImeVisible)1087     private void runRestoreImeVisibility(TestSoftInputMode mode, boolean expectImeVisible)
1088             throws Exception {
1089         final Instrumentation instrumentation = mInstrumentation;
1090         final WindowManager wm = instrumentation.getContext().getSystemService(WindowManager.class);
1091         // As restoring IME visibility behavior is only available when TaskSnapshot mechanism
1092         // enabled, skip the test when TaskSnapshot is not supported.
1093         assumeTrue("Restoring IME visibility not available when TaskSnapshot unsupported",
1094                 wm.isTaskSnapshotSupported());
1095 
1096         try (MockImeSession imeSession = MockImeSession.create(
1097                 instrumentation.getContext(), instrumentation.getUiAutomation(),
1098                 new ImeSettings.Builder())) {
1099             final ImeEventStream stream = imeSession.openEventStream();
1100             final String markerForActivity1 = getTestMarker(FIRST_EDIT_TEXT_TAG);
1101             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
1102             // Launch a test activity with focusing editText to show keyboard
1103             new TestActivity.Starter().withWindowingMode(
1104                     WINDOWING_MODE_FULLSCREEN).startSync(activity -> {
1105                         final LinearLayout layout = new LinearLayout(activity);
1106                         final EditText editText = new EditText(activity);
1107                         editTextRef.set(editText);
1108                         editText.setHint("focused editText");
1109                         editText.setPrivateImeOptions(markerForActivity1);
1110                         editText.requestFocus();
1111                         layout.addView(editText);
1112                         activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
1113                         if (mode == TestSoftInputMode.ALWAYS_HIDDEN_WITH_BACKWARD_NAV) {
1114                             activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
1115                         }
1116                         return layout;
1117                     }, TestActivity.class);
1118 
1119             expectEvent(stream, editorMatcher("onStartInput", markerForActivity1), TIMEOUT);
1120             expectEvent(stream, editorMatcher("onStartInputView", markerForActivity1), TIMEOUT);
1121             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1122                     View.VISIBLE, TIMEOUT);
1123             expectImeVisible(TIMEOUT);
1124 
1125             // Launch another app task activity to hide keyboard
1126             new TestActivity.Starter().asNewTask().withWindowingMode(
1127                     WINDOWING_MODE_FULLSCREEN).startSync(activity -> {
1128                         activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
1129                         return new LinearLayout(activity);
1130                     }, TestActivity.class);
1131             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
1132             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
1133             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1134                     View.GONE, TIMEOUT);
1135             expectImeInvisible(TIMEOUT);
1136 
1137             if (mode == TestSoftInputMode.HIDDEN_WITH_FORWARD_NAV) {
1138                 // Start new TestActivity on the same task with STATE_HIDDEN softInputMode.
1139                 final String markerForActivity2 = getTestMarker(SECOND_EDIT_TEXT_TAG);
1140                 new TestActivity.Starter().asSameTaskAndClearTop().withWindowingMode(
1141                         WINDOWING_MODE_FULLSCREEN).startSync(activity -> {
1142                             final LinearLayout layout = new LinearLayout(activity);
1143                             final EditText editText = new EditText(activity);
1144                             editText.setHint("focused editText");
1145                             editText.setPrivateImeOptions(markerForActivity2);
1146                             editText.requestFocus();
1147                             layout.addView(editText);
1148                             activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
1149                             return layout;
1150                         }, TestActivity.class);
1151                 expectEvent(stream, editorMatcher("onStartInput", markerForActivity2), TIMEOUT);
1152             } else {
1153                 // Press back key to back to the first test activity
1154                 instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
1155                 expectEvent(stream, editorMatcher("onStartInput", markerForActivity1), TIMEOUT);
1156             }
1157 
1158             // Expect the IME visibility according to expectImeVisible
1159             // The expected result could be:
1160             //  1) The system can restore the IME visibility to show IME up when navigated back to
1161             //     the original app task, even the IME is hidden when switching to the next task.
1162             //  2) The system won't restore the IME visibility in some softInputMode cases.
1163             if (expectImeVisible) {
1164                 expectImeVisible(TIMEOUT);
1165             } else {
1166                 expectImeInvisible(TIMEOUT);
1167             }
1168         }
1169     }
1170 
runImeVisibilityWhenImeTransitionBetweenActivities(boolean instant)1171     private void runImeVisibilityWhenImeTransitionBetweenActivities(boolean instant)
1172             throws Exception {
1173         try (MockImeSession imeSession = MockImeSession.create(
1174                 mInstrumentation.getContext(),
1175                 mInstrumentation.getUiAutomation(),
1176                 new ImeSettings.Builder()
1177                         .setInputViewHeight(NEW_KEYBOARD_HEIGHT)
1178                         .setDrawsBehindNavBar(true))) {
1179             final ImeEventStream stream = imeSession.openEventStream();
1180             final String marker = getTestMarker();
1181 
1182             AtomicReference<EditText> editTextRef = new AtomicReference<>();
1183             // Launch test activity with focusing editor
1184             final TestActivity testActivity =
1185                     new TestActivity.Starter().withWindowingMode(
1186                             WINDOWING_MODE_FULLSCREEN).startSync(activity -> {
1187                                 final LinearLayout layout = new LinearLayout(activity);
1188                                 layout.setOrientation(LinearLayout.VERTICAL);
1189                                 layout.setGravity(Gravity.BOTTOM);
1190                                 final EditText editText = new EditText(activity);
1191                                 editTextRef.set(editText);
1192                                 editText.setHint("focused editText");
1193                                 editText.setPrivateImeOptions(marker);
1194                                 editText.requestFocus();
1195                                 layout.addView(editText);
1196                                 final View decorView = activity.getWindow().getDecorView();
1197                                 decorView.setFitsSystemWindows(true);
1198                                 decorView.getWindowInsetsController().show(ime());
1199                                 return layout;
1200                             }, TestActivity.class);
1201             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
1202             expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT);
1203             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1204             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1205                     View.VISIBLE, TIMEOUT);
1206             expectImeVisible(TIMEOUT);
1207 
1208             // Launch another test activity from another process with popup dialog.
1209             MockTestActivityUtil.launchSync(instant, TIMEOUT,
1210                     Map.of(MockTestActivityUtil.EXTRA_KEY_SHOW_DIALOG, "true"));
1211             BySelector dialogSelector = By.clazz(AlertDialog.class).depth(0);
1212             UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
1213             assertNotNull(uiDevice.wait(Until.hasObject(dialogSelector), TIMEOUT));
1214 
1215             // Dismiss dialog and back to original test activity
1216             MockTestActivityUtil.sendBroadcastAction(MockTestActivityUtil.EXTRA_DISMISS_DIALOG);
1217 
1218             final CountDownLatch imeVisibilityUpdateLatch = new CountDownLatch(1);
1219             AtomicReference<Boolean> imeInsetsVisible = new AtomicReference<>();
1220             TestUtils.runOnMainSync(
1221                     () -> testActivity.getWindow().getDecorView().setOnApplyWindowInsetsListener(
1222                             (v, insets) -> {
1223                                 if (insets.hasInsets()) {
1224                                     imeInsetsVisible.set(insets.isVisible(WindowInsets.Type.ime()));
1225                                     imeVisibilityUpdateLatch.countDown();
1226                                 }
1227                                 return v.onApplyWindowInsets(insets);
1228                             }));
1229             // Verify keyboard visibility should aligned with IME insets visibility.
1230             TestUtils.waitOnMainUntil(
1231                     () -> testActivity.getWindow().getDecorView().getVisibility() == VISIBLE
1232                             && testActivity.getWindow().getDecorView().hasWindowFocus(), TIMEOUT);
1233             assertTrue("Waiting for onApplyWindowInsets timed out",
1234                     imeVisibilityUpdateLatch.await(5, TimeUnit.SECONDS));
1235             // Wait for layout being stable in case insets visibility might not align with the
1236             // input view visibility.
1237             waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
1238 
1239             if (imeInsetsVisible.get()) {
1240                 expectImeVisible(TIMEOUT);
1241             } else {
1242                 expectImeInvisible(TIMEOUT);
1243             }
1244         }
1245     }
1246 
runImeVisibilityTestWhenForceStopPackage(boolean instant)1247     private void runImeVisibilityTestWhenForceStopPackage(boolean instant) throws Exception {
1248         try (MockImeSession imeSession = MockImeSession.create(
1249                 mInstrumentation.getContext(),
1250                 mInstrumentation.getUiAutomation(),
1251                 new ImeSettings.Builder())) {
1252             final ImeEventStream stream = imeSession.openEventStream();
1253             final String marker = getTestMarker();
1254 
1255             // Make sure that MockIme isn't shown in the initial state.
1256             final ImeLayoutInfo lastLayout =
1257                     waitForInputViewLayoutStable(stream, LAYOUT_STABLE_THRESHOLD);
1258             assertNull(lastLayout);
1259             expectImeInvisible(TIMEOUT);
1260             // Flush all the events happened before launching the test Activity.
1261             stream.skipAll();
1262 
1263             // Launch test activity with focusing an editor from remote process and expect the
1264             // IME is visible.
1265             try (AutoCloseable closable = MockTestActivityUtil.launchSync(
1266                     instant, TIMEOUT,
1267                     Map.of(MockTestActivityUtil.EXTRA_KEY_PRIVATE_IME_OPTIONS, marker))) {
1268                 expectEvent(stream, editorMatcher("onStartInput", marker), START_INPUT_TIMEOUT);
1269                 expectImeInvisible(TIMEOUT);
1270 
1271                 // Request showSoftInput, expect the request is valid and soft-keyboard visible.
1272                 MockTestActivityUtil.sendBroadcastAction(
1273                         MockTestActivityUtil.EXTRA_SHOW_SOFT_INPUT);
1274                 expectEvent(stream, eventMatcher("showSoftInput"), TIMEOUT);
1275                 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1276                 expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1277                         View.VISIBLE, TIMEOUT);
1278                 expectImeVisible(TIMEOUT);
1279 
1280                 // Force stop test app package, and then expect IME should be invisible after the
1281                 // remote process stopped by forceStopPackage.
1282                 MockTestActivityUtil.forceStopPackage();
1283                 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
1284                 expectImeInvisible(TIMEOUT);
1285             }
1286         }
1287     }
1288 
1289     /**
1290      * Test case for Bug 254624767.
1291      *
1292      * <p>This test ensures that when the app requests to show/hide IME according to the IME insets
1293      * visibility with {@link WindowInsets#isVisible(int)} during switching apps, verify the system
1294      * dispatches the IME insets visibility to the app correctly during that time. </p>
1295      */
1296     @Test
testImeInsetsInvisibleAfterBackingFromImeHiddenActivity()1297     public void testImeInsetsInvisibleAfterBackingFromImeHiddenActivity() throws Exception {
1298         try (MockImeSession imeSession = MockImeSession.create(
1299                 mInstrumentation.getContext(),
1300                 mInstrumentation.getUiAutomation(),
1301                 new ImeSettings.Builder())) {
1302             final ImeEventStream stream = imeSession.openEventStream();
1303             final String marker = getTestMarker(FOCUSED_EDIT_TEXT_TAG);
1304 
1305             // Launch the first activity
1306             final EditText editText = launchTestActivity(marker);
1307             AtomicReference<CountDownLatch> imeInsetsHiddenLatchRef = new AtomicReference<>();
1308             expectEvent(stream, editorMatcher("onStartInput", marker), START_INPUT_TIMEOUT);
1309             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1310             expectImeInvisible(TIMEOUT);
1311 
1312             TestUtils.runOnMainSync(() -> {
1313                         // Show IME with WindowInsets API and register onApplyWindowInsetsListener
1314                         editText.getRootView().setOnApplyWindowInsetsListener(
1315                                 (v, insets) -> {
1316                                     if (!insets.isVisible(WindowInsets.Type.ime())) {
1317                                         if (imeInsetsHiddenLatchRef.get() != null) {
1318                                             imeInsetsHiddenLatchRef.get().countDown();
1319                                         }
1320                                     }
1321                                     return v.onApplyWindowInsets(insets);
1322                                 });
1323                         editText.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> {
1324                             // Test scenario: emulate the issue app implements to show IME when
1325                             // focusing back if the last requested IME insets visibility is true.
1326                             // And hides IME with clearing the editor focus when the app focus-out.
1327                             if (hasFocus) {
1328                                 final boolean hasImeShown =
1329                                         editText.getRootWindowInsets().isVisible(ime());
1330                                 if (hasImeShown) {
1331                                     editText.getWindowInsetsController().show(ime());
1332                                 }
1333                             } else {
1334                                 editText.getWindowInsetsController().hide(ime());
1335                                 editText.clearFocus();
1336                             }
1337                         });
1338                         editText.getWindowInsetsController().show(ime());
1339                     }
1340             );
1341             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1342             expectImeVisible(TIMEOUT);
1343 
1344             // Launch the second test activity with expecting to hide the IME.
1345             final TestActivity secondActivity = new TestActivity.Starter()
1346                     .asNewTask().startSync(activity -> {
1347                         activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
1348                         return new LinearLayout(activity);
1349                     }, TestActivity.class);
1350             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
1351             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1352                     View.GONE, TIMEOUT);
1353             expectImeInvisible(TIMEOUT);
1354 
1355             // Back to first activity
1356             imeInsetsHiddenLatchRef.set(new CountDownLatch(1));
1357             runOnMainSync(secondActivity::onBackPressed);
1358 
1359             // Verify the visibility of IME insets should be hidden in onApplyWindowInsets and
1360             // expect the first activity hides the IME according to the received IME insets
1361             // visibility when backing from the second activity.
1362             imeInsetsHiddenLatchRef.get().await(5, TimeUnit.SECONDS);
1363             notExpectEvent(stream, editorMatcher("onStartInputView", marker), NOT_EXPECT_TIMEOUT);
1364             expectImeInvisible(TIMEOUT);
1365         }
1366     }
1367 
1368     /**
1369      * Test case for Bug 256739702.
1370      *
1371      * <p>This test ensures that when "android:screenSize|Orientation" is not set and the keyboard
1372      * is shown implicitly in fullscreen mode, it will not disappear after the user rotates screen.
1373      */
1374     @Test
testRotateScreenWithKeyboardShownImplicitly()1375     public void testRotateScreenWithKeyboardShownImplicitly() throws Exception {
1376         // Test only when both portrait and landscape mode are supported.
1377         final PackageManager pm = mInstrumentation.getTargetContext().getPackageManager();
1378         assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT));
1379         assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE));
1380         final boolean isFixedToUserRotation =
1381                 "enabled".equals(SystemUtil.runShellCommand(FIXED_TO_USER_ROTATION_CMD).trim());
1382         assumeFalse("Device shouldn't have fixed rotation.", isFixedToUserRotation);
1383 
1384         final InputMethodManager imm = mInstrumentation
1385                 .getTargetContext().getSystemService(InputMethodManager.class);
1386         // Disable auto-rotate screen and set the screen orientation to portrait mode.
1387         setAutoRotateScreen(false);
1388         final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
1389         uiDevice.setOrientationPortrait();
1390         mInstrumentation.waitForIdleSync();
1391 
1392         // Set FullscreenModePolicy as OS_DEFAULT to call the original
1393         // InputMethodService#onEvaluateFullscreenMode()
1394         try (MockImeSession imeSession = MockImeSession.create(
1395                 mInstrumentation.getContext(),
1396                 mInstrumentation.getUiAutomation(),
1397                 new ImeSettings.Builder().setFullscreenModePolicy(
1398                         ImeSettings.FullscreenModePolicy.OS_DEFAULT))) {
1399             final ImeEventStream stream = imeSession.openEventStream();
1400 
1401             final String marker = getTestMarker();
1402             // TestActivity2 is identical to TestActivity but doesn't handle any configChanges.
1403             final EditText editText = launchTestActivity2(marker);
1404 
1405             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
1406             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1407             expectImeInvisible(TIMEOUT);
1408             assertTrue("hasActiveInputConnection() must return true if the View has IME focus",
1409                     getOnMainSync(() -> imm.hasActiveInputConnection(editText)));
1410             assumeFalse("onEvaluateFullscreenMode() should be false for portrait",
1411                     expectCommand(
1412                             stream, imeSession.callGetOnEvaluateFullscreenMode(), TIMEOUT)
1413                             .getReturnBooleanValue());
1414 
1415             // Call ShowSoftInput() implicitly
1416             assertTrue("showSoftInput must success if the View has IME focus",
1417                     getOnMainSync(() -> imm.showSoftInput(editText,
1418                             InputMethodManager.SHOW_IMPLICIT)));
1419 
1420             expectEvent(stream, showSoftInputMatcher(0), TIMEOUT);
1421             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1422             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1423                     View.VISIBLE, TIMEOUT);
1424             expectImeVisible(TIMEOUT);
1425 
1426             // Rotate screen to landscape.
1427             uiDevice.setOrientationLandscape();
1428             mInstrumentation.waitForIdleSync();
1429             expectImeVisible(TIMEOUT);
1430             assertTrue("IME should be in fullscreen mode",
1431                     getOnMainSync(() -> imm.isFullscreenMode()));
1432         }
1433         setAutoRotateScreen(true);
1434     }
1435 
1436     /**
1437      * Test case for Bug 222064495.
1438      *
1439      * <p>Test that the IME should be hidden when the IME layering target overlay with
1440      * "NOT_FOCUSABLE | ALT_FOCUSABLE_IM" flags popup during pressing the recents key to the
1441      * overview screen.</b>
1442      */
1443     @Test
testImeHiddenWhenImeLayeringTargetDelayedToShowInAppSwitch()1444     public void testImeHiddenWhenImeLayeringTargetDelayedToShowInAppSwitch() throws Exception {
1445         assumeTrue(hasRecentsScreen());
1446 
1447         try (MockImeSession imeSession = MockImeSession.create(
1448                 mInstrumentation.getContext(),
1449                 mInstrumentation.getUiAutomation(),
1450                 new ImeSettings.Builder())) {
1451             final ImeEventStream stream = imeSession.openEventStream();
1452             final String marker = getTestMarker();
1453 
1454             final TestActivity testActivity = TestActivity.startSync(activity -> {
1455                 final LinearLayout layout = new LinearLayout(activity);
1456                 layout.setOrientation(LinearLayout.VERTICAL);
1457 
1458                 final EditText editText = new EditText(activity);
1459                 layout.addView(editText);
1460                 editText.setHint("focused editText");
1461                 editText.setPrivateImeOptions(marker);
1462                 editText.requestFocus();
1463                 activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
1464                 return layout;
1465             });
1466 
1467             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1468             expectImeVisible(TIMEOUT);
1469 
1470             // Intentionally showing an overlay with "IME Focusable" (NOT_FOCUSABLE |
1471             // ALT_FOCUSABLE_IM) flags during pressing the recents key to the overview screen.
1472             testActivity.getWindow().getDecorView().postDelayed(
1473                     () -> SystemUtil.runWithShellPermissionIdentity(() ->
1474                     testActivity.showOverlayWindow(true /* imeFocusable */)), 100);
1475             mInstrumentation.sendKeyDownUpSync(
1476                     KeyEvent.KEYCODE_RECENT_APPS);
1477 
1478             // Expect the IME should hidden by the IME not attachable on the activity when the
1479             // overlay popup.
1480             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
1481             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1482                     View.GONE, TIMEOUT);
1483             expectImeInvisible(TIMEOUT);
1484         } finally {
1485             // Back to home to clean up states after the test finished.
1486             UiDevice.getInstance(mInstrumentation).pressHome();
1487         }
1488     }
1489 
1490     /**
1491      * Test the IME visibility when in split-screen mode, switching the focus to the app task with
1492      * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_HIDDEN} flag from the app showing the
1493      * IME will expect to be hidden.
1494      */
1495     @Test
testImeHiddenWhenFocusToAppWithStateHiddenFlagInMultiWindowMode()1496     public void testImeHiddenWhenFocusToAppWithStateHiddenFlagInMultiWindowMode() throws Exception {
1497         assumeTrue(TestUtils.supportsSplitScreenMultiWindow());
1498 
1499         try (MockImeSession imeSession = MockImeSession.create(
1500                 mInstrumentation.getContext(),
1501                 mInstrumentation.getUiAutomation(),
1502                 new ImeSettings.Builder())) {
1503             final ImeEventStream stream = imeSession.openEventStream();
1504             final String marker = getTestMarker();
1505 
1506             // Launch an editor activity to be on the split primary task.
1507             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
1508             final TestActivity splitPrimaryActivity = TestActivity.startSync(activity -> {
1509                 final LinearLayout layout = new LinearLayout(activity);
1510                 layout.setOrientation(LinearLayout.VERTICAL);
1511                 final EditText editText = new EditText(activity);
1512                 editTextRef.set(editText);
1513                 layout.addView(editText);
1514                 editText.setHint("focused editText");
1515                 editText.setPrivateImeOptions(marker);
1516                 editText.requestFocus();
1517                 return layout;
1518             });
1519             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
1520             notExpectEvent(stream, editorMatcher("onStartInputView", marker), NOT_EXPECT_TIMEOUT);
1521             expectImeInvisible(TIMEOUT);
1522 
1523             // Launch another activity with SOFT_INPUT_STATE_HIDDEN flag to be on the split
1524             // secondary task, expect the IME won't receive onStartInputView and invisible.
1525             final TestActivity splitSecondaryActivity = new TestActivity.Starter()
1526                     .asMultipleTask()
1527                     .withAdditionalFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
1528                     .startSync(splitPrimaryActivity, activity -> {
1529                         activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
1530                         return new LinearLayout(activity);
1531                     }, TestActivity2.class);
1532             notExpectEvent(stream, eventMatcher("onStartInputView"),
1533                     NOT_EXPECT_TIMEOUT);
1534             expectImeInvisible(TIMEOUT);
1535 
1536             // Double tap the editor on the split primary task to focus the window and show the IME.
1537             mCtsTouchUtils.emulateDoubleTapOnViewCenter(mInstrumentation,
1538                     null, editTextRef.get());
1539             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1540             expectImeVisible(TIMEOUT);
1541 
1542             // Tap on the split secondary task to switch focus and expect the IME will be hidden.
1543             mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation,
1544                     null, splitSecondaryActivity.getWindow().getDecorView());
1545             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
1546             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
1547             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
1548                     View.GONE, TIMEOUT);
1549             expectImeInvisible(TIMEOUT);
1550         }
1551     }
1552 
1553     /**
1554      * Test case for Bug 226689544.
1555      *
1556      * Test to verify that the IME is visible, after the following actions:
1557      * 1. Open primary activity.
1558      * 2. Open second activity from the first activity in split screen.
1559      * 3. Open and show a dialog with editText in the second activity.
1560      * 4. Focus/click on first activity, then click on the editText.
1561      */
1562     @Test
1563     @FlakyTest
testIMEVisibleInSplitScreenAfterGainingFocus()1564     public void testIMEVisibleInSplitScreenAfterGainingFocus() throws Exception {
1565         assumeTrue(TestUtils.supportsSplitScreenMultiWindow());
1566 
1567         try (MockImeSession imeSession = MockImeSession.create(
1568                 mInstrumentation.getContext(),
1569                 mInstrumentation.getUiAutomation(),
1570                 new ImeSettings.Builder())) {
1571             final ImeEventStream stream = imeSession.openEventStream();
1572             final String marker = getTestMarker();
1573 
1574             final TestActivity splitPrimaryActivity = TestActivity.startSync(LinearLayout::new);
1575 
1576             final AtomicReference<AlertDialog> dialogRef = new AtomicReference<>();
1577             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
1578             try {
1579                 // Launch another activity with SOFT_INPUT_STATE_UNCHANGED flag to be on the split
1580                 // secondary task as well as on its dialog, so that the IME is not visible by
1581                 // default on large screens, when showing the dialog.
1582                 new TestActivity.Starter()
1583                         .asMultipleTask()
1584                         .withAdditionalFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
1585                         .startSync(splitPrimaryActivity, activity -> {
1586                             activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNCHANGED);
1587 
1588                             final EditText editText = new EditText(activity);
1589                             editText.setHint("focused editText");
1590                             editText.setPrivateImeOptions(marker);
1591                             editText.requestFocus();
1592                             final AlertDialog dialog = new AlertDialog.Builder(activity)
1593                                     .setTitle("DialogWithEditText")
1594                                     .setCancelable(false)
1595                                     .setView(editText)
1596                                     .create();
1597                             dialog.getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNCHANGED);
1598                             dialog.show();
1599                             dialogRef.set(dialog);
1600                             editTextRef.set(editText);
1601                             return new LinearLayout(activity);
1602                         }, TestActivity2.class);
1603 
1604                 View decor = splitPrimaryActivity.getWindow().getDecorView();
1605                 CountDownLatch latch = new CountDownLatch(1);
1606                 ViewTreeObserver observer = decor.getViewTreeObserver();
1607                 observer.addOnDrawListener(() -> {
1608                     if (splitPrimaryActivity.isInMultiWindowMode()) {
1609                         // check activity in multi-window mode after relayoutWindow.
1610                         latch.countDown();
1611                     }
1612                 });
1613 
1614                 latch.await(LAYOUT_STABLE_THRESHOLD, TimeUnit.MILLISECONDS);
1615 
1616                 // Tap on the first activity to change focus
1617                 mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation,
1618                         null, splitPrimaryActivity.getWindow().getDecorView());
1619 
1620                 notExpectEvent(stream, eventMatcher("onStartInputView"),
1621                         NOT_EXPECT_TIMEOUT);
1622                 expectImeInvisible(TIMEOUT);
1623 
1624                 // Tap on the edit text in the split dialog to show the IME.
1625                 mCtsTouchUtils.emulateDoubleTapOnViewCenter(mInstrumentation,
1626                         null, editTextRef.get());
1627 
1628                 expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
1629                 expectImeVisible(TIMEOUT);
1630             } finally {
1631                 // dismiss dialog, in case it wasn't closed properly
1632                 if (dialogRef.get() != null) {
1633                     dialogRef.get().dismiss();
1634                 }
1635             }
1636         }
1637     }
1638 
1639     /**
1640      * A regression Test for Bug 283342812
1641      *
1642      * 1. Open primary activity.
1643      * 2. Open second activity with 2 editText views from the first activity in split screen.
1644      * 3. Focus the 1st editor and invoke {@link WindowInsetsController#show} to make IME visible.
1645      * 4. Press the back key to make IME invisible.
1646      * 5. Focus the 2nd editor and invoke {@link WindowInsetsController#show} to make IME visible.
1647      * 6. Finish the primary activity to exit split screen mode.
1648      * 7. Test step 3-5 again to ensure it passes after exiting split screen mode.
1649      */
1650     @Test
1651     @FlakyTest
testIMEVisibleInSplitScreenWithWindowInsetsApi()1652     public void testIMEVisibleInSplitScreenWithWindowInsetsApi() throws Throwable {
1653         assumeTrue(TestUtils.supportsSplitScreenMultiWindow());
1654 
1655         try (MockImeSession imeSession = MockImeSession.create(
1656                 mInstrumentation.getContext(),
1657                 mInstrumentation.getUiAutomation(),
1658                 new ImeSettings.Builder())) {
1659             final ImeEventStream stream = imeSession.openEventStream();
1660             final TestActivity splitPrimaryActivity = TestActivity.startSync(LinearLayout::new);
1661 
1662             // Launch another test activity in split-screen with 2 editor views
1663             final AtomicReference<EditText> editText1Ref = new AtomicReference<>();
1664             final AtomicReference<EditText> editText2Ref = new AtomicReference<>();
1665             final String editText1Marker = getTestMarker(FIRST_EDIT_TEXT_TAG);
1666             final String editText2Marker = getTestMarker(SECOND_EDIT_TEXT_TAG);
1667             final TestActivity testActivity2 = new TestActivity.Starter()
1668                     .asMultipleTask()
1669                     .withAdditionalFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT)
1670                     .startSync(splitPrimaryActivity, activity -> {
1671                         LinearLayout layout = new LinearLayout(activity);
1672                         activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_HIDDEN);
1673 
1674                         final EditText editText1 = new EditText(activity);
1675                         editText1.setHint("This is editText1");
1676                         editText1.setPrivateImeOptions(editText1Marker);
1677                         editText1Ref.set(editText1);
1678 
1679                         final EditText editText2 = new EditText(activity);
1680                         editText2.setHint("This is editText2");
1681                         editText2.setPrivateImeOptions(editText2Marker);
1682                         editText2Ref.set(editText2);
1683 
1684                         layout.addView(editText1);
1685                         layout.addView(editText2);
1686                         return layout;
1687                     }, TestActivity2.class);
1688 
1689             notExpectEvent(stream, eventMatcher("onStartInputView"),
1690                     NOT_EXPECT_TIMEOUT);
1691             expectImeInvisible(TIMEOUT);
1692 
1693             // Tap on the test activity to change focus
1694             mCtsTouchUtils.emulateTapOnViewCenter(mInstrumentation,
1695                     null, testActivity2.getWindow().getDecorView());
1696 
1697             ThrowingRunnable testProcedureForTestActivity2 = () -> {
1698                 // Focus the 1st editor and show the IME with WindowInsets API.
1699                 testActivity2.runOnUiThread(() -> {
1700                     editText1Ref.get().requestFocus();
1701                     editText1Ref.get()
1702                             .getWindowInsetsController().show(WindowInsets.Type.ime());
1703                 });
1704                 expectEvent(stream, editorMatcher("onStartInputView", editText1Marker), TIMEOUT);
1705                 expectImeVisible(TIMEOUT);
1706 
1707                 // Press the back key to make the IME invisible.
1708                 mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
1709                 expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
1710                 expectImeInvisible(TIMEOUT);
1711 
1712                 // Focus the 2nd editor and show the IME with WindowInsets API.
1713                 testActivity2.runOnUiThread(() -> {
1714                     editText2Ref.get().requestFocus();
1715                     editText2Ref.get()
1716                             .getWindowInsetsController().show(WindowInsets.Type.ime());
1717                 });
1718                 expectEvent(stream, editorMatcher("onStartInputView", editText2Marker), TIMEOUT);
1719                 expectImeVisible(TIMEOUT);
1720             };
1721             testProcedureForTestActivity2.run();
1722 
1723             // Finish the primary activity to exit split-screen mode.
1724             splitPrimaryActivity.runOnUiThread(splitPrimaryActivity::finish);
1725             TestUtils.waitOnMainUntil(() -> {
1726                 final View decorView = testActivity2.getWindow().getDecorView();
1727                 return decorView.hasWindowFocus() && decorView.getVisibility() == VISIBLE;
1728             }, TIMEOUT, "Activity should visible & focused when exiting split-screen mode");
1729 
1730             // Rerun the test procedure to ensure it passes after exiting split-screen mode.
1731             testProcedureForTestActivity2.run();
1732         }
1733     }
1734 
1735     /**
1736      * A regression Test for Bug 226033399.
1737      *
1738      * <p>This test verifies that the keyboard remains visible when a notification comes.
1739      * This test runs only when the screen is big enough. If the screen is small, it may make sense
1740      * to dismiss the keyboard while a notification is being displayed. We also skip this test on
1741      * non-phone, non-tablet form factors.
1742      */
1743     // Instant apps cannot post notification.
1744     @AppModeFull
1745     @Test
testIMEVisibleWhenNotificationComes()1746     public void testIMEVisibleWhenNotificationComes() throws Throwable {
1747         final Context targetContext = mInstrumentation.getTargetContext();
1748         final PackageManager pm = targetContext.getPackageManager();
1749         // Exclude major known non-phone, non-tablet form factors which have different notification
1750         // UIs.
1751         assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY));
1752         assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
1753         assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
1754         final NotificationManager notificationManager =
1755                 targetContext.getSystemService(NotificationManager.class);
1756         assumeNotNull(notificationManager);
1757         // Usually notification permission should be auto-granted by the test runner.
1758         // Skip the test if notification is disabled for some other reason.
1759         assumeTrue(notificationManager.areNotificationsEnabled());
1760 
1761         final InputMethodManager imm = targetContext.getSystemService(InputMethodManager.class);
1762         final int notificationId = 12345;
1763         try (MockImeSession imeSession = MockImeSession.create(
1764                 mInstrumentation.getContext(),
1765                 mInstrumentation.getUiAutomation(),
1766                 new ImeSettings.Builder())) {
1767             final ImeEventStream stream = imeSession.openEventStream();
1768 
1769             final String marker = getTestMarker();
1770             final EditText editText = launchTestActivity(marker);
1771 
1772             // Skip the test if the screen size is small.
1773             final int smallestScreenWidthDp =
1774                     editText.getContext().getResources().getConfiguration().smallestScreenWidthDp;
1775             Log.d(TAG, "smallestScreenWidthDp = " + smallestScreenWidthDp);
1776             assumeTrue(smallestScreenWidthDp >= 400);
1777 
1778             // 1. Show keyboard.
1779             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
1780             assertTrue("showSoftInput must success if the View has IME focus",
1781                     getOnMainSync(() -> imm.showSoftInput(editText, 0)));
1782             expectImeVisible(TIMEOUT);
1783 
1784             // 2. Post a notification and verify that the keyboard is still visible.
1785             final NotificationChannel channel = new NotificationChannel("test" /* id */,
1786                     "Test Channel" /* name */, NotificationManager.IMPORTANCE_HIGH);
1787             notificationManager.createNotificationChannel(channel);
1788             final String notificationTitle = "notification-" + marker;
1789             notificationManager.notify(
1790                     notificationId,
1791                     new Notification.Builder(targetContext, channel.getId())
1792                             .setContentTitle(notificationTitle)
1793                             .setContentText("testIMEVisibleWhenNotificationComes")
1794                             .setSmallIcon(android.R.drawable.ic_info)
1795                             .build());
1796             UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
1797             // Wait until the notification is visible. If TIMEOUT has passed and the notification
1798             // is not visible, it's fine - the keyboard should remain visible in that case too.
1799             uiDevice.wait(Until.hasObject(By.text(notificationTitle)), TIMEOUT);
1800             expectImeVisible(TIMEOUT);
1801 
1802             // 3. Dismiss the notification and verify that the keyboard is still visible.
1803             notificationManager.cancel(notificationId);
1804             uiDevice.wait(Until.gone(By.text(notificationTitle)), TIMEOUT);
1805             expectImeVisible(TIMEOUT);
1806         } finally {
1807             // Make sure to dismiss the notification even if the test failed.
1808             notificationManager.cancel(notificationId);
1809         }
1810     }
1811 
setAutoRotateScreen(boolean enable)1812     private void setAutoRotateScreen(boolean enable) {
1813         try {
1814             final Instrumentation instrumentation = mInstrumentation;
1815             SystemUtil.runShellCommand(instrumentation, enable ? ENABLE_AUTO_ROTATE_CMD :
1816                     DISABLE_AUTO_ROTATE_CMD);
1817             instrumentation.waitForIdleSync();
1818         } catch (IOException io) {
1819             fail("Couldn't enable/disable auto-rotate screen");
1820         }
1821     }
1822 
getFloatingImeSettings(@olorInt int navigationBarColor)1823     private static ImeSettings.Builder getFloatingImeSettings(@ColorInt int navigationBarColor) {
1824         final ImeSettings.Builder builder = new ImeSettings.Builder();
1825         builder.setWindowFlags(0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
1826         // As documented, Window#setNavigationBarColor() is actually ignored when the IME window
1827         // does not have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS.  We are calling setNavigationBarColor()
1828         // to ensure it.
1829         builder.setNavigationBarColor(navigationBarColor);
1830         return builder;
1831     }
1832 
1833     /**
1834      * Whether enabling a compatibility flag to clear {@link InputMethodManager#SHOW_FORCED} flag
1835      * for the given {@code packageName} of the app when it's leaving.
1836      *
1837      * @return {@code true} if the compatibility flag is enabled.
1838      */
isClearShowForcedFlagEnabled(String packageName)1839     private static boolean isClearShowForcedFlagEnabled(String packageName) {
1840         AtomicBoolean result = new AtomicBoolean();
1841         runWithShellPermissionIdentity(() -> result.set(
1842                 CompatChanges.isChangeEnabled(CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING, packageName,
1843                         UserHandle.CURRENT)));
1844         return result.get();
1845     }
1846 
1847     /** Whether the IME DisplayArea is organized by WM Shell. */
isImeOrganized(int displayId)1848     private static boolean isImeOrganized(int displayId) {
1849         final WindowManagerState wmState = new WindowManagerState();
1850         wmState.computeState();
1851         WindowManagerState.DisplayArea imeContainer =  wmState.getImeContainer(displayId);
1852         assertNotNull("ImeContainer not found for display id: " + displayId, imeContainer);
1853         return imeContainer.isOrganized();
1854     }
1855 
createPopupWindowWrapper( @onNull EditText editor)1856     private static AutoCloseableWrapper<PopupWindow> createPopupWindowWrapper(
1857             @NonNull EditText editor) {
1858         return AutoCloseableWrapper.create(
1859                 TestUtils.getOnMainSync(() -> {
1860                     final PopupWindow popup = new PopupWindow(editor.getContext());
1861                     popup.setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
1862                     final TextView textView = new TextView(editor.getContext());
1863                     textView.setText("Popup");
1864                     popup.setContentView(textView);
1865                     popup.setWidth(MATCH_PARENT);
1866                     popup.setHeight(MATCH_PARENT);
1867                     // Show the popup window.
1868                     popup.showAsDropDown(textView);
1869                     return popup;
1870                 }), popup -> TestUtils.runOnMainSync(popup::dismiss));
1871     }
1872 
1873     /**
1874      * Whether the device has supported the recents screen.
1875      */
hasRecentsScreen()1876     private boolean hasRecentsScreen() {
1877         try {
1878             Context context = mInstrumentation.getContext();
1879             return context.getResources().getBoolean(
1880                     Resources.getSystem().getIdentifier("config_hasRecents", "bool", "android"));
1881         } catch (Resources.NotFoundException e) {
1882             return false;
1883         }
1884     }
1885 }
1886