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