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