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