• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.server.wm;
18 
19 import static android.graphics.PixelFormat.TRANSLUCENT;
20 import static android.server.wm.ShellCommandHelper.executeShellCommand;
21 import static android.view.KeyEvent.ACTION_DOWN;
22 import static android.view.KeyEvent.KEYCODE_BACK;
23 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
24 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
25 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE;
26 import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
27 import static android.view.WindowInsets.Type.ime;
28 import static android.view.WindowInsets.Type.navigationBars;
29 import static android.view.WindowInsets.Type.statusBars;
30 import static android.view.WindowInsets.Type.systemBars;
31 import static android.view.WindowInsets.Type.systemGestures;
32 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
33 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
34 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
35 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
36 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
37 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
38 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
39 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
40 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
41 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
42 
43 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
44 
45 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
46 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
47 
48 import static com.google.common.truth.Truth.assertWithMessage;
49 
50 import static org.hamcrest.Matchers.is;
51 import static org.hamcrest.Matchers.notNullValue;
52 import static org.hamcrest.Matchers.nullValue;
53 import static org.junit.Assert.assertEquals;
54 import static org.junit.Assert.assertFalse;
55 import static org.junit.Assert.assertTrue;
56 import static org.junit.Assume.assumeFalse;
57 import static org.junit.Assume.assumeThat;
58 import static org.junit.Assume.assumeTrue;
59 
60 import android.app.Activity;
61 import android.app.AlertDialog;
62 import android.app.Instrumentation;
63 import android.content.Context;
64 import android.content.pm.PackageManager;
65 import android.content.res.Resources;
66 import android.os.Bundle;
67 import android.os.SystemClock;
68 import android.platform.test.annotations.Presubmit;
69 import android.view.InputDevice;
70 import android.view.MotionEvent;
71 import android.view.View;
72 import android.view.ViewGroup;
73 import android.view.Window;
74 import android.view.WindowInsets;
75 import android.view.WindowInsetsAnimation;
76 import android.view.WindowInsetsController;
77 import android.view.WindowManager;
78 import android.widget.EditText;
79 import android.widget.LinearLayout;
80 import android.widget.TextView;
81 
82 import androidx.annotation.Nullable;
83 import androidx.test.filters.FlakyTest;
84 
85 import com.android.compatibility.common.util.PollingCheck;
86 import com.android.cts.mockime.ImeEventStream;
87 import com.android.cts.mockime.ImeSettings;
88 import com.android.cts.mockime.MockImeSession;
89 
90 import org.junit.Rule;
91 import org.junit.Test;
92 import org.junit.rules.ErrorCollector;
93 
94 import java.util.ArrayList;
95 import java.util.List;
96 import java.util.concurrent.CountDownLatch;
97 import java.util.concurrent.TimeUnit;
98 import java.util.function.Supplier;
99 
100 /**
101  * Test whether WindowInsetsController controls window insets as expected.
102  *
103  * Build/Install/Run:
104  *     atest CtsWindowManagerDeviceTestCases:WindowInsetsControllerTests
105  */
106 @Presubmit
107 @android.server.wm.annotation.Group2
108 public class WindowInsetsControllerTests extends WindowManagerTestBase {
109 
110     private final static long TIMEOUT = 1000; // milliseconds
111     private final static long TIMEOUT_UPDATING_INPUT_WINDOW = 500; // milliseconds
112     private final static long TIME_SLICE = 50; // milliseconds
113     private final static AnimationCallback ANIMATION_CALLBACK = new AnimationCallback();
114 
115     private static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOGS =
116             "am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS";
117 
118     @Rule
119     public final ErrorCollector mErrorCollector = new ErrorCollector();
120 
121     @Test
testHide()122     public void testHide() {
123         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
124 
125         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
126         final View rootView = activity.getWindow().getDecorView();
127 
128         testHideInternal(rootView, statusBars());
129         testHideInternal(rootView, navigationBars());
130     }
131 
testHideInternal(View rootView, int types)132     private void testHideInternal(View rootView, int types) {
133         if (rootView.getRootWindowInsets().isVisible(types)) {
134             getInstrumentation().runOnMainSync(() -> {
135                 rootView.getWindowInsetsController().hide(types);
136             });
137             PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
138         }
139     }
140 
141     @Test
testShow()142     public void testShow() {
143         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
144 
145         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
146         final View rootView = activity.getWindow().getDecorView();
147 
148         testShowInternal(rootView, statusBars());
149         testShowInternal(rootView, navigationBars());
150     }
151 
testShowInternal(View rootView, int types)152     private void testShowInternal(View rootView, int types) {
153         if (rootView.getRootWindowInsets().isVisible(types)) {
154             getInstrumentation().runOnMainSync(() -> {
155                 rootView.getWindowInsetsController().hide(types);
156             });
157             PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
158             getInstrumentation().runOnMainSync(() -> {
159                 rootView.getWindowInsetsController().show(types);
160             });
161             PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
162         }
163     }
164 
testTopAppHidesStatusBarInternal(Activity activity, View rootView, Runnable hidingStatusBar)165     private void testTopAppHidesStatusBarInternal(Activity activity, View rootView,
166             Runnable hidingStatusBar) {
167         if (rootView.getRootWindowInsets().isVisible(statusBars())) {
168 
169             // The top-fullscreen-app window hides status bar.
170             getInstrumentation().runOnMainSync(hidingStatusBar);
171             PollingCheck.waitFor(TIMEOUT,
172                     () -> !rootView.getRootWindowInsets().isVisible(statusBars()));
173 
174             // Add a non-fullscreen window on top of the fullscreen window.
175             // The new focused window doesn't hide status bar.
176             getInstrumentation().runOnMainSync(
177                     () -> activity.getWindowManager().addView(
178                             new View(activity),
179                             new WindowManager.LayoutParams(1 /* w */, 1 /* h */, TYPE_APPLICATION,
180                                     0 /* flags */, TRANSLUCENT)));
181 
182             // Check if status bar stays invisible.
183             for (long time = TIMEOUT; time >= 0; time -= TIME_SLICE) {
184                 assertFalse(rootView.getRootWindowInsets().isVisible(statusBars()));
185                 SystemClock.sleep(TIME_SLICE);
186             }
187         }
188     }
189 
190     @Test
testTopAppHidesStatusBarByMethod()191     public void testTopAppHidesStatusBarByMethod() {
192         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
193 
194         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
195         final View rootView = activity.getWindow().getDecorView();
196 
197         testTopAppHidesStatusBarInternal(activity, rootView,
198                 () -> rootView.getWindowInsetsController().hide(statusBars()));
199     }
200 
201     @Test
testTopAppHidesStatusBarByWindowFlag()202     public void testTopAppHidesStatusBarByWindowFlag() {
203         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
204 
205         final TestActivity activity = startActivity(TestActivity.class);
206         final View rootView = activity.getWindow().getDecorView();
207 
208         testTopAppHidesStatusBarInternal(activity, rootView,
209                 () -> activity.getWindow().addFlags(FLAG_FULLSCREEN));
210     }
211 
212     @Test
testTopAppHidesStatusBarBySystemUiFlag()213     public void testTopAppHidesStatusBarBySystemUiFlag() {
214         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
215 
216         final TestActivity activity = startActivity(TestActivity.class);
217         final View rootView = activity.getWindow().getDecorView();
218 
219         testTopAppHidesStatusBarInternal(activity, rootView,
220                 () -> rootView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN));
221     }
222 
223     @Test
testImeShowAndHide()224     public void testImeShowAndHide() throws Exception {
225         final Instrumentation instrumentation = getInstrumentation();
226         assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
227                 nullValue());
228         final MockImeSession imeSession = MockImeHelper.createManagedMockImeSession(this);
229         final ImeEventStream stream = imeSession.openEventStream();
230         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
231         expectEvent(stream, editorMatcher("onStartInput", activity.mEditTextMarker), TIMEOUT);
232 
233         final View rootView = activity.getWindow().getDecorView();
234         getInstrumentation().runOnMainSync(() -> rootView.getWindowInsetsController().show(ime()));
235         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(ime()));
236         getInstrumentation().runOnMainSync(() -> rootView.getWindowInsetsController().hide(ime()));
237         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(ime()));
238     }
239 
240     @Test
testImeForceShowingNavigationBar()241     public void testImeForceShowingNavigationBar() throws Exception {
242         final Instrumentation instrumentation = getInstrumentation();
243         assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
244                 nullValue());
245         final Resources resources = instrumentation.getContext().getResources();
246         final boolean isHideNavBarForKeyboardEnabled = resources.getBoolean(
247                 resources.getIdentifier("config_hideNavBarForKeyboard", "bool", "android"));
248         assumeFalse("Device is configured to not show navigation bar for keyboard",
249                 isHideNavBarForKeyboardEnabled);
250         final MockImeSession imeSession = MockImeHelper.createManagedMockImeSession(this);
251         final ImeEventStream stream = imeSession.openEventStream();
252         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
253         expectEvent(stream, editorMatcher("onStartInput", activity.mEditTextMarker), TIMEOUT);
254 
255         final View rootView = activity.getWindow().getDecorView();
256         assumeTrue(rootView.getRootWindowInsets().isVisible(navigationBars()));
257         getInstrumentation().runOnMainSync(
258                 () -> rootView.getWindowInsetsController().hide(navigationBars()));
259         PollingCheck.waitFor(TIMEOUT,
260                 () -> !rootView.getRootWindowInsets().isVisible(navigationBars()));
261         getInstrumentation().runOnMainSync(() -> rootView.getWindowInsetsController().show(ime()));
262         PollingCheck.waitFor(TIMEOUT,
263                 () -> rootView.getRootWindowInsets().isVisible(ime() | navigationBars()));
264         getInstrumentation().runOnMainSync(() -> rootView.getWindowInsetsController().hide(ime()));
265         PollingCheck.waitFor(TIMEOUT,
266                 () -> !rootView.getRootWindowInsets().isVisible(ime())
267                         && !rootView.getRootWindowInsets().isVisible(navigationBars()));
268     }
269 
270     @Test
testSetSystemBarsAppearance()271     public void testSetSystemBarsAppearance() {
272         final TestActivity activity = startActivity(TestActivity.class);
273         final View rootView = activity.getWindow().getDecorView();
274         final WindowInsetsController controller = rootView.getWindowInsetsController();
275         getInstrumentation().runOnMainSync(() -> {
276             // Set APPEARANCE_LIGHT_STATUS_BARS.
277             controller.setSystemBarsAppearance(
278                     APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
279 
280             // Clear APPEARANCE_LIGHT_NAVIGATION_BARS.
281             controller.setSystemBarsAppearance(
282                     0 /* appearance */, APPEARANCE_LIGHT_NAVIGATION_BARS);
283         });
284         waitForIdle();
285 
286         // We must have APPEARANCE_LIGHT_STATUS_BARS, but not APPEARANCE_LIGHT_NAVIGATION_BARS.
287         assertEquals(APPEARANCE_LIGHT_STATUS_BARS,
288                 controller.getSystemBarsAppearance()
289                         & (APPEARANCE_LIGHT_STATUS_BARS | APPEARANCE_LIGHT_NAVIGATION_BARS));
290 
291         final boolean[] onPreDrawCalled = { false };
292         rootView.getViewTreeObserver().addOnPreDrawListener(() -> {
293             onPreDrawCalled[0] = true;
294             return true;
295         });
296 
297         // Clear APPEARANCE_LIGHT_NAVIGATION_BARS again.
298         getInstrumentation().runOnMainSync(() -> controller.setSystemBarsAppearance(
299                 0 /* appearance */, APPEARANCE_LIGHT_NAVIGATION_BARS));
300         waitForIdle();
301 
302         assertFalse("Setting the same appearance must not cause a new traversal",
303                 onPreDrawCalled[0]);
304     }
305 
306     @Test
testSetSystemBarsBehavior_default()307     public void testSetSystemBarsBehavior_default() throws InterruptedException {
308         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
309 
310         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
311         final View rootView = activity.getWindow().getDecorView();
312 
313         // Assume we have the bars and they can be visible.
314         final int types = statusBars();
315         assumeTrue(rootView.getRootWindowInsets().isVisible(types));
316 
317         rootView.getWindowInsetsController().setSystemBarsBehavior(BEHAVIOR_DEFAULT);
318 
319         hideInsets(rootView, types);
320 
321         // Tapping on display cannot show bars.
322         tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
323         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
324 
325         // Wait for status bar invisible from InputDispatcher. Otherwise, the following
326         // dragFromTopToCenter might expand notification shade.
327         SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
328 
329         // Swiping from top of display can show bars.
330         dragFromTopToCenter(rootView);
331         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
332     }
333 
334     @Test
testSetSystemBarsBehavior_showTransientBarsBySwipe()335     public void testSetSystemBarsBehavior_showTransientBarsBySwipe() throws InterruptedException {
336         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
337 
338         final TestActivity activity = startActivity(TestActivity.class);
339         final View rootView = activity.getWindow().getDecorView();
340 
341         // Assume we have the bars and they can be visible.
342         final int types = statusBars();
343         assumeTrue(rootView.getRootWindowInsets().isVisible(types));
344 
345         rootView.getWindowInsetsController().setSystemBarsBehavior(
346                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
347 
348         hideInsets(rootView, types);
349 
350         // Tapping on display cannot show bars.
351         tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
352         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
353 
354         // Wait for status bar invisible from InputDispatcher. Otherwise, the following
355         // dragFromTopToCenter might expand notification shade.
356         SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
357 
358         // Swiping from top of display can show transient bars, but apps cannot detect that.
359         dragFromTopToCenter(rootView);
360         // Make sure status bar stays invisible.
361         for (long time = TIMEOUT; time >= 0; time -= TIME_SLICE) {
362             assertFalse(rootView.getRootWindowInsets().isVisible(types));
363             SystemClock.sleep(TIME_SLICE);
364         }
365     }
366 
367     @Test
testSetSystemBarsBehavior_systemGesture_default()368     public void testSetSystemBarsBehavior_systemGesture_default() throws InterruptedException {
369         final TestActivity activity = startActivity(TestActivity.class);
370         final View rootView = activity.getWindow().getDecorView();
371 
372         // Assume the current navigation mode has the back gesture.
373         assumeTrue(rootView.getRootWindowInsets().getInsets(systemGestures()).left > 0);
374         assumeTrue(canTriggerBackGesture(rootView));
375 
376         rootView.getWindowInsetsController().setSystemBarsBehavior(BEHAVIOR_DEFAULT);
377         hideInsets(rootView, systemBars());
378 
379         // Test if the back gesture can be triggered while system bars are hidden with the behavior.
380         assertTrue(canTriggerBackGesture(rootView));
381     }
382 
383     @Test
testSetSystemBarsBehavior_systemGesture_showTransientBarsBySwipe()384     public void testSetSystemBarsBehavior_systemGesture_showTransientBarsBySwipe()
385             throws InterruptedException {
386         final TestActivity activity = startActivity(TestActivity.class);
387         final View rootView = activity.getWindow().getDecorView();
388 
389         // Assume the current navigation mode has the back gesture.
390         assumeTrue(rootView.getRootWindowInsets().getInsets(systemGestures()).left > 0);
391         assumeTrue(canTriggerBackGesture(rootView));
392 
393         rootView.getWindowInsetsController().setSystemBarsBehavior(
394                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
395         hideInsets(rootView, systemBars());
396 
397         // Test if the back gesture can be triggered while system bars are hidden with the behavior.
398         assertFalse(canTriggerBackGesture(rootView));
399     }
400 
canTriggerBackGesture(View rootView)401     private boolean canTriggerBackGesture(View rootView) throws InterruptedException {
402         final boolean[] hasBack = { false };
403         final CountDownLatch latch = new CountDownLatch(1);
404         rootView.findFocus().setOnKeyListener((v, keyCode, event) -> {
405             if (keyCode == KEYCODE_BACK && event.getAction() == ACTION_DOWN) {
406                 hasBack[0] = true;
407                 latch.countDown();
408                 return true;
409             }
410             return false;
411         });
412         dragFromLeftToCenter(rootView);
413         latch.await(1, TimeUnit.SECONDS);
414         return hasBack[0];
415     }
416 
417     @Test
testSystemUiVisibilityCallbackCausedByInsets()418     public void testSystemUiVisibilityCallbackCausedByInsets() {
419         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
420 
421         final TestActivity activity = startActivity(TestActivity.class);
422         final View controlTarget = activity.getWindow().getDecorView();
423 
424         // Assume we have at least one visible system bar.
425         assumeTrue(controlTarget.getRootWindowInsets().isVisible(statusBars()) ||
426                 controlTarget.getRootWindowInsets().isVisible(navigationBars()));
427 
428         final int[] targetSysUiVis = new int[1];
429         final View nonControlTarget = new View(mTargetContext);
430         final int[] nonTargetSysUiVis = new int[1];
431         final WindowManager.LayoutParams nonTargetAttrs =
432                 new WindowManager.LayoutParams(TYPE_APPLICATION);
433         nonTargetAttrs.flags = FLAG_NOT_FOCUSABLE;
434         getInstrumentation().runOnMainSync(() -> {
435             controlTarget.setOnSystemUiVisibilityChangeListener(
436                     visibility -> targetSysUiVis[0] = visibility);
437             nonControlTarget.setOnSystemUiVisibilityChangeListener(
438                     visibility -> nonTargetSysUiVis[0] = visibility);
439             activity.getWindowManager().addView(nonControlTarget, nonTargetAttrs);
440         });
441         waitForIdle();
442         testSysUiVisCallbackCausedByInsets(statusBars(), SYSTEM_UI_FLAG_FULLSCREEN,
443                 controlTarget, targetSysUiVis, nonTargetSysUiVis);
444         testSysUiVisCallbackCausedByInsets(navigationBars(), SYSTEM_UI_FLAG_HIDE_NAVIGATION,
445                 controlTarget, targetSysUiVis, nonTargetSysUiVis);
446     }
447 
testSysUiVisCallbackCausedByInsets(int insetsType, int sysUiFlag, View target, int[] targetSysUiVis, int[] nonTargetSysUiVis)448     private void testSysUiVisCallbackCausedByInsets(int insetsType, int sysUiFlag, View target,
449             int[] targetSysUiVis, int[] nonTargetSysUiVis) {
450         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
451         if (target.getRootWindowInsets().isVisible(insetsType)) {
452 
453             // Controlled by methods
454             getInstrumentation().runOnMainSync(
455                     () -> target.getWindowInsetsController().hide(insetsType));
456             PollingCheck.waitFor(TIMEOUT, () ->
457                     targetSysUiVis[0] == sysUiFlag && targetSysUiVis[0] == nonTargetSysUiVis[0]);
458             getInstrumentation().runOnMainSync(
459                     () -> target.getWindowInsetsController().show(insetsType));
460             PollingCheck.waitFor(TIMEOUT, () ->
461                     targetSysUiVis[0] == 0 && targetSysUiVis[0] == nonTargetSysUiVis[0]);
462 
463             // Controlled by legacy flags
464             getInstrumentation().runOnMainSync(
465                     () -> target.setSystemUiVisibility(sysUiFlag));
466             PollingCheck.waitFor(TIMEOUT, () ->
467                     targetSysUiVis[0] == sysUiFlag && targetSysUiVis[0] == nonTargetSysUiVis[0]);
468             getInstrumentation().runOnMainSync(
469                     () -> target.setSystemUiVisibility(0));
470             PollingCheck.waitFor(TIMEOUT, () ->
471                     targetSysUiVis[0] == 0 && targetSysUiVis[0] == nonTargetSysUiVis[0]);
472         }
473     }
474 
475     @Test
testSystemUiVisibilityCallbackCausedByAppearance()476     public void testSystemUiVisibilityCallbackCausedByAppearance() {
477         final TestActivity activity = startActivity(TestActivity.class);
478         final View controlTarget = activity.getWindow().getDecorView();
479 
480         // Assume we have at least one visible system bar.
481         assumeTrue(controlTarget.getRootWindowInsets().isVisible(statusBars()) ||
482                 controlTarget.getRootWindowInsets().isVisible(navigationBars()));
483 
484         final int[] targetSysUiVis = new int[1];
485         getInstrumentation().runOnMainSync(() -> {
486             controlTarget.setOnSystemUiVisibilityChangeListener(
487                     visibility -> targetSysUiVis[0] = visibility);
488         });
489         waitForIdle();
490         final int sysUiFlag = SYSTEM_UI_FLAG_LOW_PROFILE;
491         getInstrumentation().runOnMainSync(() -> controlTarget.setSystemUiVisibility(sysUiFlag));
492         PollingCheck.waitFor(TIMEOUT, () -> targetSysUiVis[0] == sysUiFlag);
493         getInstrumentation().runOnMainSync(() -> controlTarget.setSystemUiVisibility(0));
494         PollingCheck.waitFor(TIMEOUT, () -> targetSysUiVis[0] == 0);
495     }
496 
497     @Test
testSetSystemUiVisibilityAfterCleared_showBarsBySwipe()498     public void testSetSystemUiVisibilityAfterCleared_showBarsBySwipe() throws Exception {
499         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
500 
501         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
502         final View rootView = activity.getWindow().getDecorView();
503 
504         // Assume we have the bars and they can be visible.
505         final int types = statusBars();
506         assumeTrue(rootView.getRootWindowInsets().isVisible(types));
507 
508         final int targetFlags = SYSTEM_UI_FLAG_IMMERSIVE | SYSTEM_UI_FLAG_FULLSCREEN;
509 
510         // Use flags to hide status bar.
511         ANIMATION_CALLBACK.reset();
512         getInstrumentation().runOnMainSync(() -> {
513             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
514             rootView.setSystemUiVisibility(targetFlags);
515         });
516         ANIMATION_CALLBACK.waitForFinishing();
517         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
518 
519         // Wait for status bar invisible from InputDispatcher. Otherwise, the following
520         // dragFromTopToCenter might expand notification shade.
521         SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
522 
523         // Swiping from top of display can show bars.
524         ANIMATION_CALLBACK.reset();
525         dragFromTopToCenter(rootView);
526         ANIMATION_CALLBACK.waitForFinishing();
527         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types)
528             && rootView.getSystemUiVisibility() != targetFlags);
529 
530         // Use flags to hide status bar again.
531         ANIMATION_CALLBACK.reset();
532         getInstrumentation().runOnMainSync(() -> {
533             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
534             rootView.setSystemUiVisibility(targetFlags);
535         });
536         ANIMATION_CALLBACK.waitForFinishing();
537         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
538 
539         // Wait for status bar invisible from InputDispatcher. Otherwise, the following
540         // dragFromTopToCenter might expand notification shade.
541         SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
542 
543         // Swiping from top of display can show bars.
544         ANIMATION_CALLBACK.reset();
545         dragFromTopToCenter(rootView);
546         ANIMATION_CALLBACK.waitForFinishing();
547         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
548 
549         // The swipe action brings down the notification shade which causes subsequent tests to
550         // fail.
551         if (isAutomotive(mContext)) {
552             // Bring system to a known state before requesting to close system dialogs.
553             launchHomeActivity();
554             broadcastCloseSystemDialogs();
555         }
556     }
557 
558     @Test
testSetSystemUiVisibilityAfterCleared_showBarsByApp()559     public void testSetSystemUiVisibilityAfterCleared_showBarsByApp() throws Exception {
560         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
561 
562         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
563         final View rootView = activity.getWindow().getDecorView();
564 
565         // Assume we have the bars and they can be visible.
566         final int types = statusBars();
567         assumeTrue(rootView.getRootWindowInsets().isVisible(types));
568 
569         // Use the flag to hide status bar.
570         ANIMATION_CALLBACK.reset();
571         getInstrumentation().runOnMainSync(() -> {
572             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
573             rootView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
574         });
575         ANIMATION_CALLBACK.waitForFinishing();
576         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
577 
578         // Clearing the flag can show status bar.
579         getInstrumentation().runOnMainSync(() -> {
580             rootView.setSystemUiVisibility(0);
581         });
582         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
583 
584         // Use the flag to hide status bar again.
585         ANIMATION_CALLBACK.reset();
586         getInstrumentation().runOnMainSync(() -> {
587             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
588             rootView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
589         });
590         ANIMATION_CALLBACK.waitForFinishing();
591         PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
592 
593         // Clearing the flag can show status bar.
594         getInstrumentation().runOnMainSync(() -> {
595             rootView.setSystemUiVisibility(0);
596         });
597         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
598     }
599 
600     @Test
testHideOnCreate()601     public void testHideOnCreate() throws Exception {
602         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
603 
604         final TestHideOnCreateActivity activity =
605                 startActivityInWindowingModeFullScreen(TestHideOnCreateActivity.class);
606         final View rootView = activity.getWindow().getDecorView();
607         ANIMATION_CALLBACK.waitForFinishing();
608         PollingCheck.waitFor(TIMEOUT,
609                 () -> !rootView.getRootWindowInsets().isVisible(statusBars())
610                         && !rootView.getRootWindowInsets().isVisible(navigationBars()));
611     }
612 
613     @Test
testShowImeOnCreate()614     public void testShowImeOnCreate() throws Exception {
615         final Instrumentation instrumentation = getInstrumentation();
616         assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
617                 nullValue());
618         MockImeHelper.createManagedMockImeSession(this);
619         final TestShowOnCreateActivity activity = startActivity(TestShowOnCreateActivity.class);
620         final View rootView = activity.getWindow().getDecorView();
621         ANIMATION_CALLBACK.waitForFinishing();
622         PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(ime()));
623     }
624 
625     @Test
testShowImeOnCreate_doesntCauseImeToReappearWhenDialogIsShown()626     public void testShowImeOnCreate_doesntCauseImeToReappearWhenDialogIsShown() throws Exception {
627         final Instrumentation instrumentation = getInstrumentation();
628         assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
629                 nullValue());
630         try (MockImeSession imeSession = MockImeSession.create(instrumentation.getContext(),
631                 instrumentation.getUiAutomation(), new ImeSettings.Builder())) {
632             final TestShowOnCreateActivity activity =
633                     startActivityInWindowingModeFullScreen(TestShowOnCreateActivity.class);
634             final View rootView = activity.getWindow().getDecorView();
635             PollingCheck.waitFor(TIMEOUT,
636                     () -> rootView.getRootWindowInsets().isVisible(ime()));
637             ANIMATION_CALLBACK.waitForFinishing();
638             ANIMATION_CALLBACK.reset();
639             getInstrumentation().runOnMainSync(() ->  {
640                 rootView.getWindowInsetsController().hide(ime());
641             });
642             PollingCheck.waitFor(TIMEOUT,
643                     () -> !rootView.getRootWindowInsets().isVisible(ime()));
644             ANIMATION_CALLBACK.waitForFinishing();
645             getInstrumentation().runOnMainSync(() ->  {
646                 activity.showAltImDialog();
647             });
648 
649             for (long time = TIMEOUT; time >= 0; time -= TIME_SLICE) {
650                 assertFalse("IME visible when it shouldn't be",
651                         rootView.getRootWindowInsets().isVisible(ime()));
652                 SystemClock.sleep(TIME_SLICE);
653             }
654         }
655     }
656 
657     @Test
testShowIme_immediatelyAfterDetachAndReattach()658     public void testShowIme_immediatelyAfterDetachAndReattach() throws Exception {
659         final Instrumentation instrumentation = getInstrumentation();
660         assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
661                 nullValue());
662         MockImeHelper.createManagedMockImeSession(this);
663         final TestActivity activity = startActivity(TestActivity.class);
664         final View rootView = activity.getWindow().getDecorView();
665 
666         PollingCheck.waitFor(TIMEOUT, () -> getOnMainSync(rootView::hasWindowFocus));
667 
668         View editor = getOnMainSync(rootView::findFocus);
669         ViewGroup parent = (ViewGroup) getOnMainSync(editor::getParent);
670 
671         getInstrumentation().runOnMainSync(() -> {
672             parent.removeView(editor);
673         });
674 
675         // Wait until checkFocus() is dispatched
676         getInstrumentation().waitForIdleSync();
677 
678         getInstrumentation().runOnMainSync(() -> {
679             parent.addView(editor);
680             editor.requestFocus();
681             editor.getWindowInsetsController().show(ime());
682         });
683 
684         PollingCheck.waitFor(TIMEOUT, () -> getOnMainSync(
685                 () -> rootView.getRootWindowInsets().isVisible(ime())),
686                 "Expected IME to become visible but didn't.");
687     }
688 
689     @Test
testInsetsDispatch()690     public void testInsetsDispatch() throws Exception {
691         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
692 
693         // Start an activity which hides system bars in fullscreen mode,
694         // otherwise, it might not be able to hide system bars in other windowing modes.
695         final TestHideOnCreateActivity activity = startActivityInWindowingModeFullScreen(
696                 TestHideOnCreateActivity.class);
697         final View rootView = activity.getWindow().getDecorView();
698         ANIMATION_CALLBACK.waitForFinishing();
699         PollingCheck.waitFor(TIMEOUT,
700                 () -> !rootView.getRootWindowInsets().isVisible(statusBars())
701                         && !rootView.getRootWindowInsets().isVisible(navigationBars()));
702 
703         // Add a dialog which hides system bars before the dialog is added to the system while the
704         // system bar was hidden previously, and collect the window insets that the dialog receives.
705         final ArrayList<WindowInsets> windowInsetsList = new ArrayList<>();
706         getInstrumentation().runOnMainSync(() -> {
707             final AlertDialog dialog = new AlertDialog.Builder(activity).create();
708             final Window dialogWindow = dialog.getWindow();
709             dialogWindow.getDecorView().setOnApplyWindowInsetsListener((view, insets) -> {
710                 windowInsetsList.add(insets);
711                 return view.onApplyWindowInsets(insets);
712             });
713             dialogWindow.getInsetsController().hide(statusBars() | navigationBars());
714             dialog.show();
715         });
716         getInstrumentation().waitForIdleSync();
717 
718         // The dialog must never receive any of visible insets of system bars.
719         for (WindowInsets windowInsets : windowInsetsList) {
720             assertFalse(windowInsets.isVisible(statusBars()));
721             assertFalse(windowInsets.isVisible(navigationBars()));
722         }
723     }
724 
725     @Test
testWindowInsetsController_availableAfterAddView()726     public void testWindowInsetsController_availableAfterAddView() throws Exception {
727         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
728 
729         final TestHideOnCreateActivity activity =
730                 startActivityInWindowingModeFullScreen(TestHideOnCreateActivity.class);
731         final View rootView = activity.getWindow().getDecorView();
732         ANIMATION_CALLBACK.waitForFinishing();
733         PollingCheck.waitFor(TIMEOUT,
734                 () -> !rootView.getRootWindowInsets().isVisible(statusBars())
735                         && !rootView.getRootWindowInsets().isVisible(navigationBars()));
736 
737         final View childWindow = new View(activity);
738         getInstrumentation().runOnMainSync(() -> {
739             activity.getWindowManager().addView(childWindow,
740                     new WindowManager.LayoutParams(TYPE_APPLICATION));
741             mErrorCollector.checkThat(childWindow.getWindowInsetsController(), is(notNullValue()));
742         });
743         getInstrumentation().waitForIdleSync();
744         getInstrumentation().runOnMainSync(() -> {
745             activity.getWindowManager().removeView(childWindow);
746         });
747 
748     }
749 
750     @Test
testDispatchApplyWindowInsetsCount_systemBars()751     public void testDispatchApplyWindowInsetsCount_systemBars() throws InterruptedException {
752         assumeFalse(isCar() && remoteInsetsControllerControlsSystemBars());
753 
754         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
755         final View rootView = activity.getWindow().getDecorView();
756         getInstrumentation().waitForIdleSync();
757 
758         // Assume we have at least one visible system bar.
759         assumeTrue(rootView.getRootWindowInsets().isVisible(statusBars())
760                 || rootView.getRootWindowInsets().isVisible(navigationBars()));
761 
762         getInstrumentation().runOnMainSync(() -> {
763             // This makes the window frame stable while changing the system bar visibility.
764             final WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
765             attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
766             activity.getWindow().setAttributes(attrs);
767         });
768         getInstrumentation().waitForIdleSync();
769 
770         final int[] dispatchApplyWindowInsetsCount = {0};
771         rootView.setOnApplyWindowInsetsListener((v, insets) -> {
772             dispatchApplyWindowInsetsCount[0]++;
773             return v.onApplyWindowInsets(insets);
774         });
775 
776         // One hide-system-bar call...
777         ANIMATION_CALLBACK.reset();
778         getInstrumentation().runOnMainSync(() -> {
779             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
780             rootView.getWindowInsetsController().hide(systemBars());
781         });
782         ANIMATION_CALLBACK.waitForFinishing();
783 
784         // ... should only trigger one dispatchApplyWindowInsets
785         assertEquals(1, dispatchApplyWindowInsetsCount[0]);
786 
787         // One show-system-bar call...
788         dispatchApplyWindowInsetsCount[0] = 0;
789         ANIMATION_CALLBACK.reset();
790         getInstrumentation().runOnMainSync(() -> {
791             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
792             rootView.getWindowInsetsController().show(systemBars());
793         });
794         ANIMATION_CALLBACK.waitForFinishing();
795 
796         // ... should only trigger one dispatchApplyWindowInsets
797         assertEquals(1, dispatchApplyWindowInsetsCount[0]);
798     }
799 
800     @FlakyTest(bugId = 297000797)
801     @Test
testDispatchApplyWindowInsetsCount_ime()802     public void testDispatchApplyWindowInsetsCount_ime() throws Exception {
803         assumeFalse("Automotive is to skip this test until showing and hiding certain insets "
804                 + "simultaneously in a single request is supported", isAutomotive(mContext));
805         assumeThat(MockImeSession.getUnavailabilityReason(getInstrumentation().getContext()),
806                 nullValue());
807 
808         MockImeHelper.createManagedMockImeSession(this);
809         final TestActivity activity = startActivityInWindowingModeFullScreen(TestActivity.class);
810         final View rootView = activity.getWindow().getDecorView();
811         getInstrumentation().waitForIdleSync();
812 
813         final int[] dispatchApplyWindowInsetsCount = {0};
814         final StringBuilder insetsSb = new StringBuilder();
815         rootView.setOnApplyWindowInsetsListener((v, insets) -> {
816             dispatchApplyWindowInsetsCount[0]++;
817             insetsSb.append("\n").append(insets);
818             return v.onApplyWindowInsets(insets);
819         });
820 
821         // One show-ime call...
822         ANIMATION_CALLBACK.reset();
823         getInstrumentation().runOnMainSync(() -> {
824             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
825             rootView.getWindowInsetsController().show(ime());
826         });
827         ANIMATION_CALLBACK.waitForFinishing();
828 
829         // ... should only trigger one dispatchApplyWindowInsets
830         assertWithMessage("insets should be dispatched exactly once, received: " + insetsSb)
831                 .that(dispatchApplyWindowInsetsCount[0]).isEqualTo(1);
832 
833         // One hide-ime call...
834         dispatchApplyWindowInsetsCount[0] = 0;
835         insetsSb.setLength(0);
836         ANIMATION_CALLBACK.reset();
837         getInstrumentation().runOnMainSync(() -> {
838             rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
839             rootView.getWindowInsetsController().hide(ime());
840         });
841         ANIMATION_CALLBACK.waitForFinishing();
842 
843         // ... should only trigger one dispatchApplyWindowInsets
844         assertWithMessage("insets should be dispatched exactly once, received: " + insetsSb)
845                 .that(dispatchApplyWindowInsetsCount[0]).isEqualTo(1);
846     }
847 
broadcastCloseSystemDialogs()848     private static void broadcastCloseSystemDialogs() {
849         executeShellCommand(AM_BROADCAST_CLOSE_SYSTEM_DIALOGS);
850     }
851 
isAutomotive(Context context)852     private static boolean isAutomotive(Context context) {
853         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
854     }
855 
hideInsets(View view, int types)856     private static void hideInsets(View view, int types) throws InterruptedException {
857         ANIMATION_CALLBACK.reset();
858         getInstrumentation().runOnMainSync(() -> {
859             view.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
860             view.getWindowInsetsController().hide(types);
861         });
862         ANIMATION_CALLBACK.waitForFinishing();
863         PollingCheck.waitFor(TIMEOUT, () -> !view.getRootWindowInsets().isVisible(types));
864     }
865 
tapOnDisplay(float x, float y)866     private void tapOnDisplay(float x, float y) {
867         dragOnDisplay(x, y, x, y);
868     }
869 
dragFromTopToCenter(View view)870     private void dragFromTopToCenter(View view) {
871         dragOnDisplay(view.getWidth() / 2f, 0 /* downY */,
872                 view.getWidth() / 2f, view.getHeight() / 2f);
873     }
874 
dragFromLeftToCenter(View view)875     private void dragFromLeftToCenter(View view) {
876         dragOnDisplay(0 /* downX */, view.getHeight() / 2f,
877                 view.getWidth() / 2f, view.getHeight() / 2f);
878     }
879 
dragOnDisplay(float downX, float downY, float upX, float upY)880     private void dragOnDisplay(float downX, float downY, float upX, float upY) {
881         final long downTime = SystemClock.elapsedRealtime();
882 
883         // down event
884         MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
885                 downX, downY, 0 /* metaState */);
886         sendPointerSync(event);
887         event.recycle();
888 
889         // move event
890         event = MotionEvent.obtain(downTime, downTime + 1, MotionEvent.ACTION_MOVE,
891                 (downX + upX) / 2f, (downY + upY) / 2f, 0 /* metaState */);
892         sendPointerSync(event);
893         event.recycle();
894 
895         // up event
896         event = MotionEvent.obtain(downTime, downTime + 2, MotionEvent.ACTION_UP,
897                 upX, upY, 0 /* metaState */);
898         sendPointerSync(event);
899         event.recycle();
900     }
901 
sendPointerSync(MotionEvent event)902     private void sendPointerSync(MotionEvent event) {
903         event.setSource(event.getSource() | InputDevice.SOURCE_CLASS_POINTER);
904         // Use UiAutomation to inject into TestActivity because it is started and owned by the
905         // Shell, which has a different uid than this instrumentation.
906         getInstrumentation().getUiAutomation().injectInputEvent(event, true);
907     }
908 
909     private static class AnimationCallback extends WindowInsetsAnimation.Callback {
910 
911         private static final long ANIMATION_TIMEOUT = 5000; // milliseconds
912 
913         private boolean mFinished = false;
914 
AnimationCallback()915         AnimationCallback() {
916             super(DISPATCH_MODE_CONTINUE_ON_SUBTREE);
917         }
918 
919         @Override
onProgress(WindowInsets insets, List<WindowInsetsAnimation> runningAnimations)920         public WindowInsets onProgress(WindowInsets insets,
921                 List<WindowInsetsAnimation> runningAnimations) {
922             return insets;
923         }
924 
925         @Override
onEnd(WindowInsetsAnimation animation)926         public void onEnd(WindowInsetsAnimation animation) {
927             synchronized (this) {
928                 mFinished = true;
929                 notify();
930             }
931         }
932 
waitForFinishing()933         void waitForFinishing() throws InterruptedException {
934             synchronized (this) {
935                 if (!mFinished) {
936                     wait(ANIMATION_TIMEOUT);
937                 }
938             }
939         }
940 
reset()941         void reset() {
942             synchronized (this) {
943                 mFinished = false;
944             }
945         }
946     }
947 
setViews(Activity activity, @Nullable String privateImeOptions)948     private static View setViews(Activity activity, @Nullable String privateImeOptions) {
949         LinearLayout layout = new LinearLayout(activity);
950         View text = new TextView(activity);
951         EditText editor = new EditText(activity);
952         editor.setPrivateImeOptions(privateImeOptions);
953         layout.addView(text);
954         layout.addView(editor);
955         activity.setContentView(layout);
956         editor.requestFocus();
957         return layout;
958     }
959 
960     public static class TestActivity extends FocusableActivity {
961         final String mEditTextMarker =
962                 getClass().getName() + "/" + SystemClock.elapsedRealtimeNanos();
963 
964         @Override
onCreate(Bundle savedInstanceState)965         protected void onCreate(Bundle savedInstanceState) {
966             super.onCreate(savedInstanceState);
967             setViews(this, mEditTextMarker);
968             getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
969         }
970     }
971 
972     public static class TestHideOnCreateActivity extends FocusableActivity {
973 
974         @Override
onCreate(Bundle savedInstanceState)975         protected void onCreate(Bundle savedInstanceState) {
976             super.onCreate(savedInstanceState);
977             View layout = setViews(this, null /* privateImeOptions */);
978             ANIMATION_CALLBACK.reset();
979             getWindow().getDecorView().setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
980             getWindow().getInsetsController().hide(statusBars());
981             layout.getWindowInsetsController().hide(navigationBars());
982         }
983     }
984 
985     public static class TestShowOnCreateActivity extends FocusableActivity {
986         @Override
onCreate(Bundle savedInstanceState)987         protected void onCreate(Bundle savedInstanceState) {
988             super.onCreate(savedInstanceState);
989             setViews(this, null /* privateImeOptions */);
990             ANIMATION_CALLBACK.reset();
991             getWindow().getDecorView().setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
992             getWindow().getInsetsController().show(ime());
993         }
994 
showAltImDialog()995         void showAltImDialog() {
996             AlertDialog dialog = new AlertDialog.Builder(this)
997                     .setTitle("TestDialog")
998                     .create();
999             dialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
1000             dialog.show();
1001         }
1002     }
1003 
getOnMainSync(Supplier<R> f)1004     private <R> R getOnMainSync(Supplier<R> f) {
1005         final Object[] result = new Object[1];
1006         getInstrumentation().runOnMainSync(() -> result[0] = f.get());
1007         //noinspection unchecked
1008         return (R) result[0];
1009     }
1010 }
1011