1 /* 2 * Copyright (C) 2021 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 package android.view.surfacecontrol.cts; 17 18 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 19 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 20 import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; 21 import static android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop; 22 import static android.view.SurfaceControl.JankData.JANK_APPLICATION; 23 import static android.view.cts.surfacevalidator.BitmapPixelChecker.validateScreenshot; 24 25 import static androidx.test.core.app.ActivityScenario.launch; 26 27 import static com.google.common.truth.Truth.assertThat; 28 import static com.google.common.truth.Truth.assertWithMessage; 29 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assert.fail; 32 import static org.junit.Assume.assumeFalse; 33 34 import android.app.Activity; 35 import android.content.Context; 36 import android.content.pm.ActivityInfo; 37 import android.content.pm.PackageManager; 38 import android.graphics.Canvas; 39 import android.graphics.Color; 40 import android.graphics.Insets; 41 import android.graphics.Rect; 42 import android.platform.test.annotations.RequiresFlagsEnabled; 43 import android.platform.test.flag.junit.CheckFlagsRule; 44 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 45 import android.server.wm.IgnoreOrientationRequestSession; 46 import android.server.wm.WindowManagerStateHelper; 47 import android.util.Log; 48 import android.view.AttachedSurfaceControl; 49 import android.view.Gravity; 50 import android.view.Surface; 51 import android.view.SurfaceControl; 52 import android.view.SurfaceControlViewHost; 53 import android.view.SurfaceHolder; 54 import android.view.SurfaceView; 55 import android.view.View; 56 import android.view.ViewTreeObserver; 57 import android.view.cts.surfacevalidator.BitmapPixelChecker; 58 import android.widget.FrameLayout; 59 60 import androidx.annotation.NonNull; 61 import androidx.test.core.app.ActivityScenario; 62 import androidx.test.filters.SmallTest; 63 import androidx.test.platform.app.InstrumentationRegistry; 64 65 import com.android.window.flags.Flags; 66 67 import org.junit.After; 68 import org.junit.Assert; 69 import org.junit.Assume; 70 import org.junit.Before; 71 import org.junit.Rule; 72 import org.junit.Test; 73 import org.junit.rules.TestName; 74 75 import java.util.concurrent.CountDownLatch; 76 import java.util.concurrent.TimeUnit; 77 import java.util.function.IntConsumer; 78 79 @SmallTest 80 public class AttachedSurfaceControlTest { 81 private static final String TAG = "AttachedSurfaceControlTest"; 82 private IgnoreOrientationRequestSession mOrientationSession; 83 private WindowManagerStateHelper mWmState; 84 85 private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER; 86 87 @Rule 88 public TestName mName = new TestName(); 89 90 @Rule 91 public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 92 93 94 private static class TransformHintListener implements 95 AttachedSurfaceControl.OnBufferTransformHintChangedListener { 96 Activity activity; 97 int expectedOrientation; 98 CountDownLatch latch = new CountDownLatch(1); 99 IntConsumer hintConsumer; 100 TransformHintListener(Activity activity, int expectedOrientation, IntConsumer hintConsumer)101 TransformHintListener(Activity activity, int expectedOrientation, 102 IntConsumer hintConsumer) { 103 this.activity = activity; 104 this.expectedOrientation = expectedOrientation; 105 this.hintConsumer = hintConsumer; 106 } 107 108 @Override onBufferTransformHintChanged(int hint)109 public void onBufferTransformHintChanged(int hint) { 110 int orientation = activity.getResources().getConfiguration().orientation; 111 Log.d(TAG, "onBufferTransformHintChanged: orientation actual=" + orientation 112 + " expected=" + expectedOrientation + " transformHint=" + hint); 113 Assert.assertEquals("Failed to switch orientation hint=" + hint, orientation, 114 expectedOrientation); 115 116 // Check the callback value matches the call to get the transform hint. 117 int actualTransformHint = 118 activity.getWindow().getRootSurfaceControl().getBufferTransformHint(); 119 Assert.assertEquals( 120 "Callback " + hint + " doesn't match transform hint=" + actualTransformHint, 121 hint, 122 actualTransformHint); 123 hintConsumer.accept(hint); 124 latch.countDown(); 125 activity.getWindow().getRootSurfaceControl() 126 .removeOnBufferTransformHintChangedListener(this); 127 } 128 } 129 130 @Before setup()131 public void setup() throws InterruptedException { 132 mOrientationSession = new IgnoreOrientationRequestSession(false /* enable */); 133 mWmState = new WindowManagerStateHelper(); 134 } 135 supportRotationCheck()136 private void supportRotationCheck() { 137 PackageManager pm = 138 InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); 139 boolean supportsRotation = pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT) 140 && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE); 141 mWmState.computeState(); 142 final boolean isFixedToUserRotation = mWmState.isFixedToUserRotation(); 143 Assume.assumeTrue(supportsRotation && !isFixedToUserRotation); 144 } 145 146 @After teardown()147 public void teardown() { 148 if (mOrientationSession != null) { 149 mOrientationSession.close(); 150 } 151 } 152 153 @Test testOnBufferTransformHintChangedListener()154 public void testOnBufferTransformHintChangedListener() throws InterruptedException { 155 supportRotationCheck(); 156 157 try (ActivityScenario<?> scenario = launch(HandleConfigurationActivity.class)) { 158 Activity activity = awaitActivityStart(scenario); 159 160 final int[] transformHintResult = new int[2]; 161 final CountDownLatch[] firstCallback = new CountDownLatch[1]; 162 final CountDownLatch[] secondCallback = new CountDownLatch[1]; 163 mWmState.computeState(); 164 assumeFalse("Skipping test: display area is ignoring orientation request", 165 mWmState.isTaskDisplayAreaIgnoringOrientationRequest( 166 activity.getComponentName())); 167 int requestedOrientation = getRequestedOrientation(activity); 168 TransformHintListener listener = new TransformHintListener(activity, 169 requestedOrientation, hint -> transformHintResult[0] = hint); 170 firstCallback[0] = listener.latch; 171 activity.getWindow().getRootSurfaceControl() 172 .addOnBufferTransformHintChangedListener(listener); 173 setRequestedOrientation(activity, requestedOrientation); 174 // Check we get a callback since the orientation has changed and we expect transform 175 // hint to change. 176 Assert.assertTrue(firstCallback[0].await(10, TimeUnit.SECONDS)); 177 178 requestedOrientation = getRequestedOrientation(activity); 179 TransformHintListener secondListener = new TransformHintListener(activity, 180 requestedOrientation, hint -> transformHintResult[1] = hint); 181 secondCallback[0] = secondListener.latch; 182 activity.getWindow().getRootSurfaceControl() 183 .addOnBufferTransformHintChangedListener(secondListener); 184 setRequestedOrientation(activity, requestedOrientation); 185 // Check we get a callback since the orientation has changed and we expect transform 186 // hint to change. 187 Assert.assertTrue(secondCallback[0].await(10, TimeUnit.SECONDS)); 188 189 // If the app orientation was changed, we should get a different transform hint 190 Assert.assertNotEquals(transformHintResult[0], transformHintResult[1]); 191 } 192 } 193 getRequestedOrientation(Activity activity)194 private int getRequestedOrientation(Activity activity) { 195 int currentOrientation = activity.getResources().getConfiguration().orientation; 196 return currentOrientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT 197 : ORIENTATION_LANDSCAPE; 198 } 199 setRequestedOrientation(Activity activity, int requestedOrientation)200 private void setRequestedOrientation(Activity activity, 201 /* @Configuration.Orientation */ int requestedOrientation) { 202 /* @ActivityInfo.ScreenOrientation */ 203 Log.d(TAG, "setRequestedOrientation: requestedOrientation=" + requestedOrientation); 204 int screenOrientation = 205 requestedOrientation == ORIENTATION_LANDSCAPE 206 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 207 : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 208 activity.setRequestedOrientation(screenOrientation); 209 } 210 211 @Test testOnBufferTransformHintChangesFromLandToSea()212 public void testOnBufferTransformHintChangesFromLandToSea() throws InterruptedException { 213 supportRotationCheck(); 214 215 try (ActivityScenario<?> scenario = launch(HandleConfigurationActivity.class)) { 216 Activity activity = awaitActivityStart(scenario); 217 218 final int[] transformHintResult = new int[2]; 219 final CountDownLatch[] firstCallback = new CountDownLatch[1]; 220 final CountDownLatch[] secondCallback = new CountDownLatch[1]; 221 mWmState.computeState(); 222 assumeFalse("Skipping test: display area is ignoring orientation request", 223 mWmState.isTaskDisplayAreaIgnoringOrientationRequest( 224 activity.getComponentName())); 225 if (activity.getResources().getConfiguration().orientation 226 != ORIENTATION_LANDSCAPE) { 227 Log.d(TAG, "Request landscape orientation"); 228 TransformHintListener listener = new TransformHintListener(activity, 229 ORIENTATION_LANDSCAPE, hint -> { 230 transformHintResult[0] = hint; 231 Log.d(TAG, "firstListener fired with hint =" + hint); 232 }); 233 firstCallback[0] = listener.latch; 234 activity.getWindow().getRootSurfaceControl() 235 .addOnBufferTransformHintChangedListener(listener); 236 setRequestedOrientation(activity, ORIENTATION_LANDSCAPE); 237 Assert.assertTrue(firstCallback[0].await(10, TimeUnit.SECONDS)); 238 } else { 239 transformHintResult[0] = 240 activity.getWindow().getRootSurfaceControl().getBufferTransformHint(); 241 Log.d(TAG, "Skipped request landscape orientation: hint=" + transformHintResult[0]); 242 } 243 244 TransformHintListener secondListener = new TransformHintListener(activity, 245 ORIENTATION_LANDSCAPE, hint -> { 246 transformHintResult[1] = hint; 247 Log.d(TAG, "secondListener fired with hint =" + hint); 248 }); 249 secondCallback[0] = secondListener.latch; 250 activity.getWindow().getRootSurfaceControl() 251 .addOnBufferTransformHintChangedListener(secondListener); 252 Log.d(TAG, "Requesting reverse landscape"); 253 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); 254 255 Assert.assertTrue(secondCallback[0].await(10, TimeUnit.SECONDS)); 256 Assert.assertNotEquals(transformHintResult[0], transformHintResult[1]); 257 } 258 } 259 260 private static class GreenAnchorViewWithInsets extends View { 261 SurfaceControl mSurfaceControl; 262 final Surface mSurface; 263 264 private final Rect mChildBoundingInsets; 265 266 private final CountDownLatch mDrawCompleteLatch = new CountDownLatch(1); 267 268 private boolean mChildScAttached; 269 GreenAnchorViewWithInsets(Context c, Rect insets)270 GreenAnchorViewWithInsets(Context c, Rect insets) { 271 super(c, null, 0, 0); 272 mSurfaceControl = new SurfaceControl.Builder() 273 .setName("SurfaceAnchorView") 274 .setBufferSize(100, 100) 275 .build(); 276 mSurface = new Surface(mSurfaceControl); 277 Canvas canvas = mSurface.lockHardwareCanvas(); 278 canvas.drawColor(Color.GREEN); 279 mSurface.unlockCanvasAndPost(canvas); 280 mChildBoundingInsets = insets; 281 282 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 283 @Override 284 public boolean onPreDraw() { 285 attachChildSc(); 286 getViewTreeObserver().removeOnPreDrawListener(this); 287 return true; 288 } 289 }); 290 } 291 292 @Override onAttachedToWindow()293 protected void onAttachedToWindow() { 294 super.onAttachedToWindow(); 295 attachChildSc(); 296 } 297 attachChildSc()298 private void attachChildSc() { 299 if (mChildScAttached) { 300 return; 301 } 302 // This should be called even if buildReparentTransaction fails the first time since 303 // the second call will come from preDrawListener which is called after bounding insets 304 // are updated in VRI. 305 getRootSurfaceControl().setChildBoundingInsets(mChildBoundingInsets); 306 307 SurfaceControl.Transaction t = 308 getRootSurfaceControl().buildReparentTransaction(mSurfaceControl); 309 310 if (t == null) { 311 // TODO (b/286406553) SurfaceControl was not yet setup. Wait until the draw request 312 // to attach since the SurfaceControl will be created by that point. This can be 313 // cleaned up when the bug is fixed. 314 return; 315 } 316 317 t.setLayer(mSurfaceControl, 1).setVisibility(mSurfaceControl, true); 318 t.addTransactionCommittedListener(Runnable::run, mDrawCompleteLatch::countDown); 319 getRootSurfaceControl().applyTransactionOnDraw(t); 320 mChildScAttached = true; 321 } 322 323 @Override onDetachedFromWindow()324 protected void onDetachedFromWindow() { 325 new SurfaceControl.Transaction().reparent(mSurfaceControl, null).apply(); 326 mSurfaceControl.release(); 327 mSurface.release(); 328 mChildScAttached = false; 329 330 super.onDetachedFromWindow(); 331 } 332 waitForDrawn()333 public void waitForDrawn() { 334 try { 335 assertTrue("Failed to wait for frame to draw", 336 mDrawCompleteLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 337 } catch (InterruptedException e) { 338 fail(); 339 } 340 } 341 } 342 343 @Test testCropWithChildBoundingInsets()344 public void testCropWithChildBoundingInsets() throws Throwable { 345 try (ActivityScenario<TestActivity> scenario = launch(TestActivity.class)) { 346 final GreenAnchorViewWithInsets[] view = new GreenAnchorViewWithInsets[1]; 347 Activity activity = awaitActivityStart(scenario, a -> { 348 FrameLayout parentLayout = a.getParentLayout(); 349 GreenAnchorViewWithInsets anchorView = new GreenAnchorViewWithInsets(a, 350 new Rect(0, 10, 0, 0)); 351 parentLayout.addView(anchorView, 352 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP)); 353 354 view[0] = anchorView; 355 }); 356 357 view[0].waitForDrawn(); 358 // Do not include system insets because the child SC is not laid out in the system 359 // insets 360 validateScreenshot(mName, activity, 361 new BitmapPixelChecker(Color.GREEN, new Rect(0, 10, 100, 100)), 362 9000 /* expectedMatchingPixels */, Insets.NONE); 363 } 364 } 365 366 private static class ScvhSurfaceView extends SurfaceView implements SurfaceHolder.Callback { 367 CountDownLatch mReadyLatch = new CountDownLatch(1); 368 SurfaceControlViewHost mScvh; 369 final View mView; 370 ScvhSurfaceView(Context context, View view)371 ScvhSurfaceView(Context context, View view) { 372 super(context); 373 getHolder().addCallback(this); 374 mView = view; 375 } 376 377 @Override surfaceCreated(@onNull SurfaceHolder surfaceHolder)378 public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) { 379 mView.getViewTreeObserver().addOnWindowAttachListener( 380 new ViewTreeObserver.OnWindowAttachListener() { 381 @Override 382 public void onWindowAttached() { 383 mReadyLatch.countDown(); 384 } 385 386 @Override 387 public void onWindowDetached() { 388 } 389 }); 390 mScvh = new SurfaceControlViewHost(getContext(), getDisplay(), getHostToken()); 391 mScvh.setView(mView, getWidth(), getHeight()); 392 setChildSurfacePackage(mScvh.getSurfacePackage()); 393 } 394 395 @Override surfaceChanged(@onNull SurfaceHolder surfaceHolder, int i, int i1, int i2)396 public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) { 397 398 } 399 400 @Override surfaceDestroyed(@onNull SurfaceHolder surfaceHolder)401 public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) { 402 403 } 404 waitForReady()405 public void waitForReady() throws InterruptedException { 406 assertTrue("Failed to wait for ScvhSurfaceView to get added", 407 mReadyLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 408 } 409 } 410 411 @Test 412 @RequiresFlagsEnabled(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) testGetHostToken()413 public void testGetHostToken() throws Throwable { 414 try (ActivityScenario<TestActivity> scenario = launch(TestActivity.class)) { 415 final ScvhSurfaceView[] scvhSurfaceView = new ScvhSurfaceView[1]; 416 final View[] view = new View[1]; 417 Activity activity = awaitActivityStart(scenario, a -> { 418 view[0] = new View(a); 419 FrameLayout parentLayout = a.getParentLayout(); 420 scvhSurfaceView[0] = new ScvhSurfaceView(a, view[0]); 421 parentLayout.addView(scvhSurfaceView[0]); 422 }); 423 424 final AttachedSurfaceControl attachedSurfaceControl = 425 scvhSurfaceView[0].getRootSurfaceControl(); 426 assertThat(attachedSurfaceControl.getInputTransferToken()) 427 .isNotEqualTo(null); 428 } 429 } 430 431 /** 432 * Ensure the synced transaction is applied even if the view isn't visible and won't draw a 433 * frame. 434 */ 435 @Test testSyncTransactionViewNotVisible()436 public void testSyncTransactionViewNotVisible() throws Throwable { 437 try (ActivityScenario<TestActivity> scenario = launch(TestActivity.class)) { 438 final ScvhSurfaceView[] scvhSurfaceView = new ScvhSurfaceView[1]; 439 final View[] view = new View[1]; 440 Activity activity = awaitActivityStart(scenario, a -> { 441 view[0] = new View(a); 442 FrameLayout parentLayout = a.getParentLayout(); 443 scvhSurfaceView[0] = new ScvhSurfaceView(a, view[0]); 444 parentLayout.addView(scvhSurfaceView[0], 445 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP)); 446 }); 447 448 scvhSurfaceView[0].waitForReady(); 449 450 CountDownLatch committedLatch = new CountDownLatch(1); 451 activity.runOnUiThread(() -> { 452 view[0].setVisibility(View.INVISIBLE); 453 SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); 454 transaction.addTransactionCommittedListener(Runnable::run, 455 committedLatch::countDown); 456 view[0].getRootSurfaceControl().applyTransactionOnDraw(transaction); 457 }); 458 459 assertTrue("Failed to receive transaction committed callback for scvh with no view", 460 committedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 461 } 462 } 463 464 /** 465 * Ensure the synced transaction is applied even if there was nothing new to draw 466 */ 467 @Test testSyncTransactionNothingToDraw()468 public void testSyncTransactionNothingToDraw() throws Throwable { 469 try (ActivityScenario<TestActivity> scenario = launch(TestActivity.class)) { 470 final View[] view = new View[1]; 471 Activity activity = awaitActivityStart(scenario, a -> { 472 view[0] = new View(a); 473 FrameLayout parentLayout = a.getParentLayout(); 474 parentLayout.addView(view[0], 475 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP)); 476 }); 477 478 CountDownLatch committedLatch = new CountDownLatch(1); 479 activity.runOnUiThread(() -> { 480 SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); 481 transaction.addTransactionCommittedListener(Runnable::run, 482 committedLatch::countDown); 483 view[0].getRootSurfaceControl().applyTransactionOnDraw(transaction); 484 view[0].requestLayout(); 485 }); 486 487 assertTrue("Failed to receive transaction committed callback for scvh with no view", 488 committedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)); 489 } 490 } 491 492 /** 493 * Tests the jank classification API. 494 */ 495 @Test 496 @RequiresFlagsEnabled(Flags.FLAG_JANK_API) testRegisterOnJankDataListener()497 public void testRegisterOnJankDataListener() throws Throwable { 498 try (ActivityScenario<TestActivity> scenario = launch(TestActivity.class)) { 499 TestActivity activity = awaitActivityStart(scenario); 500 501 final AttachedSurfaceControl asc = activity.getWindow().getRootSurfaceControl(); 502 final CountDownLatch animEnd = new CountDownLatch(1); 503 final CountDownLatch dataReceived = new CountDownLatch(1); 504 final int[] jankCount = new int[] { 0 }; 505 506 SurfaceControl.OnJankDataListenerRegistration listenerRegistration = 507 asc.registerOnJankDataListener(activity.getMainExecutor(), data -> { 508 for (SurfaceControl.JankData frame : data) { 509 assertWithMessage("durations should be positive") 510 .that(frame.getScheduledAppFrameTimeNanos()) 511 .isGreaterThan(0); 512 assertWithMessage("durations should be positive") 513 .that(frame.getActualAppFrameTimeNanos()) 514 .isGreaterThan(0); 515 if ((frame.getJankType() & JANK_APPLICATION) != 0) { 516 assertWithMessage("missed frame timeline mismatch") 517 .that(frame.getActualAppFrameTimeNanos()) 518 .isGreaterThan(frame.getScheduledAppFrameTimeNanos()); 519 jankCount[0]++; 520 } 521 } 522 dataReceived.countDown(); 523 }); 524 525 activity.runOnUiThread(() -> activity.startJankyAnimation(animEnd)); 526 527 assertWithMessage("Failed to wait for animation to end") 528 .that(animEnd.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)) 529 .isTrue(); 530 531 listenerRegistration.flush(); 532 533 assertWithMessage("Failed to receive any jank data callbacks") 534 .that(dataReceived.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)) 535 .isTrue(); 536 537 listenerRegistration.removeAfter(0); 538 539 assertWithMessage("No frames were marked as janky") 540 .that(jankCount[0]) 541 .isGreaterThan(0); 542 } 543 } 544 awaitActivityStart(ActivityScenario<A> scenario)545 private static <A extends Activity> A awaitActivityStart(ActivityScenario<A> scenario) 546 throws InterruptedException { 547 return awaitActivityStart(scenario, null); 548 } 549 awaitActivityStart(ActivityScenario<A> scenario, ActivityScenario.ActivityAction<A> action)550 private static <A extends Activity> A awaitActivityStart(ActivityScenario<A> scenario, 551 ActivityScenario.ActivityAction<A> action) throws InterruptedException { 552 CountDownLatch activityReady = new CountDownLatch(1); 553 Activity[] activity = new Activity[1]; 554 scenario.onActivity(a -> { 555 activity[0] = a; 556 activityReady.countDown(); 557 }); 558 559 assertWithMessage("Failed to wait for activity to start") 560 .that(activityReady.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS)) 561 .isTrue(); 562 563 waitForWindowOnTop(activity[0].getWindow()); 564 565 if (action != null) { 566 scenario.onActivity(action); 567 } 568 569 return (A) activity[0]; 570 } 571 } 572