1 /* 2 * Copyright (C) 2016 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.uirendering.cts.testclasses; 17 18 import static org.junit.Assert.assertFalse; 19 import static org.junit.Assert.assertThrows; 20 import static org.junit.Assert.assertTrue; 21 22 import android.animation.ObjectAnimator; 23 import android.graphics.Bitmap; 24 import android.graphics.Bitmap.Config; 25 import android.graphics.Canvas; 26 import android.graphics.Color; 27 import android.graphics.HardwareBufferRenderer; 28 import android.graphics.Rect; 29 import android.graphics.RenderNode; 30 import android.hardware.DataSpace; 31 import android.hardware.HardwareBuffer; 32 import android.media.Image; 33 import android.media.ImageWriter; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.SystemClock; 37 import android.uirendering.cts.R; 38 import android.uirendering.cts.bitmapverifiers.ColorVerifier; 39 import android.uirendering.cts.bitmapverifiers.RectVerifier; 40 import android.uirendering.cts.testinfrastructure.ActivityTestBase; 41 import android.uirendering.cts.testinfrastructure.CanvasClient; 42 import android.uirendering.cts.testinfrastructure.DrawActivity; 43 import android.uirendering.cts.testinfrastructure.ViewInitializer; 44 import android.uirendering.cts.util.BitmapAsserter; 45 import android.view.Display; 46 import android.view.Gravity; 47 import android.view.PixelCopy; 48 import android.view.SurfaceControl; 49 import android.view.SurfaceHolder; 50 import android.view.SurfaceView; 51 import android.view.View; 52 import android.view.animation.LinearInterpolator; 53 import android.widget.FrameLayout; 54 55 import androidx.test.filters.LargeTest; 56 import androidx.test.runner.AndroidJUnit4; 57 58 import com.android.compatibility.common.util.SynchronousPixelCopy; 59 import com.android.compatibility.common.util.WidgetTestUtils; 60 import com.android.graphics.hwui.flags.Flags; 61 62 import org.junit.Assert; 63 import org.junit.Assume; 64 import org.junit.Test; 65 import org.junit.runner.RunWith; 66 67 import java.io.IOException; 68 import java.util.concurrent.CountDownLatch; 69 import java.util.concurrent.Executors; 70 import java.util.concurrent.TimeUnit; 71 72 @LargeTest 73 @RunWith(AndroidJUnit4.class) 74 public class SurfaceViewTests extends ActivityTestBase { 75 76 static final DrawCallback sGreenCanvasCallback = makeCanvasCallback( 77 (canvas, width, height) -> canvas.drawColor(Color.GREEN)); 78 static final DrawCallback sWhiteCanvasCallback = makeCanvasCallback( 79 (canvas, width, height) -> canvas.drawColor(Color.WHITE)); 80 static final DrawCallback sRedCanvasCallback = makeCanvasCallback( 81 (canvas, width, height) -> canvas.drawColor(Color.RED)); 82 makeCanvasCallback(CanvasClient canvasClient)83 private static DrawCallback makeCanvasCallback(CanvasClient canvasClient) { 84 return new DrawCallback((surfaceHolder, width, height) -> { 85 Canvas canvas = surfaceHolder.lockCanvas(); 86 canvasClient.draw(canvas, width, height); 87 surfaceHolder.unlockCanvasAndPost(canvas); 88 }); 89 } 90 makeHardwareBufferRendererCallback(int color, int dataspace)91 private static DrawCallback makeHardwareBufferRendererCallback(int color, int dataspace) { 92 return new DrawCallback((surfaceHolder, width, height) -> { 93 ImageWriter writer = new ImageWriter.Builder(surfaceHolder.getSurface()) 94 .setWidthAndHeight(width, height) 95 .setHardwareBufferFormat(HardwareBuffer.RGBA_8888) 96 .setDataSpace(dataspace) 97 .setUsage(HardwareBuffer.USAGE_GPU_COLOR_OUTPUT 98 | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 99 | HardwareBuffer.USAGE_COMPOSER_OVERLAY) 100 .build(); 101 Image image = writer.dequeueInputImage(); 102 HardwareBufferRenderer renderer = new HardwareBufferRenderer(image.getHardwareBuffer()); 103 RenderNode node = new RenderNode("content"); 104 node.setPosition(0, 0, width, height); 105 Canvas canvas = node.beginRecording(); 106 canvas.drawColor(color); 107 node.endRecording(); 108 renderer.setContentRoot(node); 109 renderer.obtainRenderRequest().draw(Runnable::run, result -> { 110 try { 111 image.setFence(result.getFence()); 112 } catch (IOException e) { 113 throw new RuntimeException(e); 114 } 115 writer.queueInputImage(image); 116 }); 117 }); 118 } 119 120 private static class DrawCallback implements SurfaceHolder.Callback { 121 interface SurfaceDrawer { 122 void draw(SurfaceHolder holder, int width, int height); 123 } 124 private SurfaceDrawer mSurfaceDrawer; 125 private CountDownLatch mFirstDrawLatch; 126 127 DrawCallback(SurfaceDrawer surfaceDrawer) { 128 mSurfaceDrawer = surfaceDrawer; 129 } 130 131 @Override 132 public void surfaceCreated(SurfaceHolder holder) { 133 } 134 135 @Override 136 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 137 mSurfaceDrawer.draw(holder, width, height); 138 139 if (mFirstDrawLatch != null) { 140 mFirstDrawLatch.countDown(); 141 } 142 } 143 144 @Override 145 public void surfaceDestroyed(SurfaceHolder holder) { 146 } 147 148 public void setFence(CountDownLatch fence) { 149 mFirstDrawLatch = fence; 150 } 151 } 152 153 static ObjectAnimator createInfiniteAnimator(Object target, String prop, 154 float start, float end) { 155 ObjectAnimator a = ObjectAnimator.ofFloat(target, prop, start, end); 156 a.setRepeatMode(ObjectAnimator.REVERSE); 157 a.setRepeatCount(ObjectAnimator.INFINITE); 158 a.setDuration(200); 159 a.setInterpolator(new LinearInterpolator()); 160 a.start(); 161 return a; 162 } 163 164 private final Screenshotter mScreenshotter = testPositionInfo -> { 165 Bitmap source = getInstrumentation().getUiAutomation().takeScreenshot( 166 getActivity().getWindow()); 167 return Bitmap.createBitmap(source, 168 testPositionInfo.surfaceOffset.x, testPositionInfo.surfaceOffset.y, 169 TEST_WIDTH, TEST_HEIGHT); 170 }; 171 172 @Test 173 public void testMovingWhiteSurfaceView() { 174 // A moving SurfaceViews with white content against a white background should be invisible 175 CountDownLatch latch = new CountDownLatch(1); 176 sWhiteCanvasCallback.setFence(latch); 177 ViewInitializer initializer = new ViewInitializer() { 178 ObjectAnimator mAnimator; 179 @Override 180 public void initializeView(View view) { 181 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 182 mAnimator = createInfiniteAnimator(root, "translationY", 0, 50); 183 184 SurfaceView surfaceViewA = new SurfaceView(view.getContext()); 185 surfaceViewA.getHolder().addCallback(sWhiteCanvasCallback); 186 root.addView(surfaceViewA, new FrameLayout.LayoutParams( 187 90, 40, Gravity.START | Gravity.TOP)); 188 } 189 @Override 190 public void teardownView() { 191 mAnimator.cancel(); 192 } 193 }; 194 createTest() 195 .addLayout(R.layout.frame_layout, initializer, true, latch) 196 .withScreenshotter(mScreenshotter) 197 .runWithAnimationVerifier(new ColorVerifier(Color.WHITE, 0 /* zero tolerance */)); 198 } 199 200 private static class SurfaceViewHelper implements ViewInitializer, Screenshotter, SurfaceHolder.Callback { 201 private final CanvasClient mCanvasClient; 202 private final CountDownLatch mFence = new CountDownLatch(1); 203 private SurfaceView mSurfaceView = null; 204 private boolean mHasSurface = false; 205 206 public SurfaceViewHelper(CanvasClient canvasClient) { 207 mCanvasClient = canvasClient; 208 } 209 210 @Override 211 public Bitmap takeScreenshot(TestPositionInfo testPositionInfo) { 212 SynchronousPixelCopy copy = new SynchronousPixelCopy(); 213 Bitmap dest = Bitmap.createBitmap( 214 TEST_WIDTH, TEST_HEIGHT, Config.ARGB_8888); 215 Rect srcRect = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT); 216 int copyResult = copy.request(mSurfaceView, srcRect, dest); 217 Assert.assertEquals(PixelCopy.SUCCESS, copyResult); 218 return dest; 219 } 220 221 @Override 222 public void initializeView(View view) { 223 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 224 mSurfaceView = new SurfaceView(view.getContext()); 225 mSurfaceView.getHolder().addCallback(this); 226 onSurfaceViewCreated(mSurfaceView); 227 root.addView(mSurfaceView, new FrameLayout.LayoutParams( 228 FrameLayout.LayoutParams.MATCH_PARENT, 229 FrameLayout.LayoutParams.MATCH_PARENT)); 230 } 231 232 public SurfaceView getSurfaceView() { 233 return mSurfaceView; 234 } 235 236 237 public boolean hasSurface() { 238 return mHasSurface; 239 } 240 241 242 public void onSurfaceViewCreated(SurfaceView surfaceView) { 243 244 } 245 246 @Override 247 public void surfaceCreated(SurfaceHolder holder) { 248 mHasSurface = true; 249 } 250 251 @Override 252 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 253 // TODO: Remove the post() which is a temporary workaround for b/32484713 254 mSurfaceView.post(() -> { 255 Canvas canvas = holder.lockHardwareCanvas(); 256 mCanvasClient.draw(canvas, width, height); 257 holder.unlockCanvasAndPost(canvas); 258 mFence.countDown(); 259 }); 260 } 261 262 @Override 263 public void surfaceDestroyed(SurfaceHolder holder) { 264 mHasSurface = false; 265 } 266 267 public CountDownLatch getFence() { 268 return mFence; 269 } 270 } 271 272 @Test 273 public void testSurfaceHolderHardwareCanvas() { 274 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 275 Assert.assertNotNull(canvas); 276 Assert.assertTrue(canvas.isHardwareAccelerated()); 277 canvas.drawColor(Color.GREEN); 278 }); 279 createTest() 280 .addLayout(R.layout.frame_layout, helper, true, helper.getFence()) 281 .withScreenshotter(helper) 282 .runWithVerifier(new ColorVerifier(Color.GREEN, 0 /* zero tolerance */)); 283 } 284 285 @Test 286 public void testSurfaceViewHolePunchWithLayer() { 287 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 288 Assert.assertNotNull(canvas); 289 Assert.assertTrue(canvas.isHardwareAccelerated()); 290 canvas.drawColor(Color.GREEN); 291 } 292 ) { 293 @Override 294 public void onSurfaceViewCreated(SurfaceView surfaceView) { 295 surfaceView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 296 } 297 }; 298 createTest() 299 .addLayout(R.layout.frame_layout, helper, true, helper.getFence()) 300 .withScreenshotter(helper) 301 .runWithVerifier(new ColorVerifier(Color.GREEN, 0 /* zero tolerance */)); 302 303 } 304 305 @Test 306 public void surfaceViewMediaLayer() { 307 // Add a shared latch which will fire after both callbacks are complete. 308 CountDownLatch latch = new CountDownLatch(2); 309 sGreenCanvasCallback.setFence(latch); 310 sRedCanvasCallback.setFence(latch); 311 312 ViewInitializer initializer = new ViewInitializer() { 313 @Override 314 public void initializeView(View view) { 315 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 316 SurfaceView surfaceViewA = new SurfaceView(view.getContext()); 317 surfaceViewA.setZOrderMediaOverlay(true); 318 surfaceViewA.getHolder().addCallback(sRedCanvasCallback); 319 320 root.addView(surfaceViewA, new FrameLayout.LayoutParams( 321 FrameLayout.LayoutParams.MATCH_PARENT, 322 FrameLayout.LayoutParams.MATCH_PARENT)); 323 324 SurfaceView surfaceViewB = new SurfaceView(view.getContext()); 325 surfaceViewB.getHolder().addCallback(sGreenCanvasCallback); 326 327 root.addView(surfaceViewB, new FrameLayout.LayoutParams( 328 FrameLayout.LayoutParams.MATCH_PARENT, 329 FrameLayout.LayoutParams.MATCH_PARENT)); 330 } 331 }; 332 333 createTest() 334 .addLayout(R.layout.frame_layout, initializer, true, latch) 335 .withScreenshotter(mScreenshotter) 336 // The red layer is the media overlay, so it must be on top. 337 .runWithVerifier(new ColorVerifier(Color.RED, 0 /* zero tolerance */)); 338 } 339 340 @Test 341 public void surfaceViewBlendZAbove() { 342 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 343 Assert.assertNotNull(canvas); 344 Assert.assertTrue(canvas.isHardwareAccelerated()); 345 canvas.drawColor(Color.BLACK); 346 } 347 ) { 348 @Override 349 public void onSurfaceViewCreated(SurfaceView surfaceView) { 350 surfaceView.setAlpha(0.25f); 351 surfaceView.setZOrderOnTop(true); 352 } 353 }; 354 createTest() 355 .addLayout(R.layout.frame_layout, helper, true, helper.getFence()) 356 .withScreenshotter(mScreenshotter) 357 .runWithVerifier(new ColorVerifier( 358 Color.rgb(191, 191, 191), 1 /* blending tolerance */)); 359 } 360 361 @Test 362 public void surfaceViewBlendZBelow() { 363 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 364 Assert.assertNotNull(canvas); 365 Assert.assertTrue(canvas.isHardwareAccelerated()); 366 canvas.drawColor(Color.BLACK); 367 } 368 ) { 369 @Override 370 public void onSurfaceViewCreated(SurfaceView surfaceView) { 371 surfaceView.setAlpha(0.25f); 372 } 373 }; 374 createTest() 375 .addLayout(R.layout.frame_layout, helper, true, helper.getFence()) 376 .withScreenshotter(mScreenshotter) 377 .runWithVerifier(new ColorVerifier( 378 Color.rgb(191, 191, 191), 1 /* blending tolerance */)); 379 } 380 381 @Test 382 public void surfaceViewSurfaceLifecycleFollowsVisibilityByDefault() { 383 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 384 Assert.assertNotNull(canvas); 385 canvas.drawColor(Color.BLACK); 386 }); 387 388 DrawActivity activity = getActivity(); 389 try { 390 activity.enqueueRenderSpecAndWait(R.layout.frame_layout, null, helper, false, false); 391 assertTrue(helper.hasSurface()); 392 activity.runOnUiThread(() -> helper.getSurfaceView().setVisibility(View.INVISIBLE)); 393 activity.waitForRedraw(); 394 assertFalse(helper.hasSurface()); 395 } finally { 396 activity.reset(); 397 } 398 } 399 400 @Test 401 public void surfaceViewSurfaceLifecycleFollowsVisibility() { 402 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 403 Assert.assertNotNull(canvas); 404 canvas.drawColor(Color.BLACK); 405 }); 406 407 DrawActivity activity = getActivity(); 408 try { 409 activity.enqueueRenderSpecAndWait(R.layout.frame_layout, null, helper, false, false); 410 assertTrue(helper.hasSurface()); 411 activity.runOnUiThread(() -> { 412 helper.getSurfaceView() 413 .setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY); 414 helper.getSurfaceView().setVisibility(View.INVISIBLE); 415 }); 416 activity.waitForRedraw(); 417 assertFalse(helper.hasSurface()); 418 } finally { 419 activity.reset(); 420 } 421 } 422 423 @Test 424 public void surfaceViewSurfaceLifecycleFollowsAttachment() { 425 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 426 Assert.assertNotNull(canvas); 427 canvas.drawColor(Color.BLACK); 428 }); 429 430 DrawActivity activity = getActivity(); 431 try { 432 activity.enqueueRenderSpecAndWait(R.layout.frame_layout, null, helper, false, false); 433 assertTrue(helper.hasSurface()); 434 activity.runOnUiThread(() -> { 435 helper.getSurfaceView() 436 .setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT); 437 helper.getSurfaceView().setVisibility(View.INVISIBLE); 438 }); 439 activity.waitForRedraw(); 440 assertTrue(helper.hasSurface()); 441 } finally { 442 activity.reset(); 443 } 444 } 445 446 @Test 447 public void surfaceViewSurfaceLifecycleFollowsAttachmentWithOverlaps() { 448 // Add a shared latch which will fire after both callbacks are complete. 449 CountDownLatch latch = new CountDownLatch(2); 450 sGreenCanvasCallback.setFence(latch); 451 sRedCanvasCallback.setFence(latch); 452 453 ViewInitializer initializer = new ViewInitializer() { 454 @Override 455 public void initializeView(View view) { 456 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 457 SurfaceView surfaceViewA = new SurfaceView(view.getContext()); 458 surfaceViewA.setVisibility(View.VISIBLE); 459 surfaceViewA.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY); 460 surfaceViewA.getHolder().addCallback(sRedCanvasCallback); 461 462 root.addView(surfaceViewA, new FrameLayout.LayoutParams( 463 FrameLayout.LayoutParams.MATCH_PARENT, 464 FrameLayout.LayoutParams.MATCH_PARENT)); 465 466 SurfaceView surfaceViewB = new SurfaceView(view.getContext()); 467 surfaceViewB.setVisibility(View.INVISIBLE); 468 surfaceViewB.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT); 469 surfaceViewB.getHolder().addCallback(sGreenCanvasCallback); 470 471 root.addView(surfaceViewB, new FrameLayout.LayoutParams( 472 FrameLayout.LayoutParams.MATCH_PARENT, 473 FrameLayout.LayoutParams.MATCH_PARENT)); 474 } 475 }; 476 477 createTest() 478 .addLayout(R.layout.frame_layout, initializer, true, latch) 479 .withScreenshotter(mScreenshotter) 480 .runWithVerifier(new ColorVerifier(Color.RED, 0 /* zero tolerance */)); 481 } 482 483 @Test 484 public void surfaceViewSurfaceLifecycleChangesFromFollowsAttachmentToFollowsVisibility() { 485 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 486 Assert.assertNotNull(canvas); 487 canvas.drawColor(Color.BLACK); 488 }); 489 490 DrawActivity activity = getActivity(); 491 try { 492 activity.enqueueRenderSpecAndWait(R.layout.frame_layout, null, helper, false, false); 493 assertTrue(helper.hasSurface()); 494 activity.runOnUiThread(() -> { 495 helper.getSurfaceView() 496 .setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT); 497 helper.getSurfaceView().setVisibility(View.INVISIBLE); 498 }); 499 activity.waitForRedraw(); 500 assertTrue(helper.hasSurface()); 501 activity.runOnUiThread(() -> { 502 helper.getSurfaceView() 503 .setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY); 504 }); 505 activity.waitForRedraw(); 506 assertFalse(helper.hasSurface()); 507 } finally { 508 activity.reset(); 509 } 510 } 511 512 @Test 513 public void surfaceViewSurfaceLifecycleChangesFromFollowsVisibilityToFollowsAttachment() { 514 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 515 Assert.assertNotNull(canvas); 516 canvas.drawColor(Color.BLACK); 517 }); 518 519 DrawActivity activity = getActivity(); 520 try { 521 activity.enqueueRenderSpecAndWait(R.layout.frame_layout, null, helper, false, false); 522 assertTrue(helper.hasSurface()); 523 activity.runOnUiThread(() -> { 524 helper.getSurfaceView() 525 .setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY); 526 helper.getSurfaceView().setVisibility(View.INVISIBLE); 527 }); 528 activity.waitForRedraw(); 529 assertFalse(helper.hasSurface()); 530 activity.runOnUiThread(() -> { 531 helper.getSurfaceView() 532 .setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT); 533 }); 534 activity.waitForRedraw(); 535 assertTrue(helper.hasSurface()); 536 } finally { 537 activity.reset(); 538 } 539 } 540 541 @Test 542 public void surfaceViewAppliesTransactionsToFrame() 543 throws InterruptedException { 544 SurfaceControl blueLayer = new SurfaceControl.Builder() 545 .setName("SurfaceViewTests") 546 .setHidden(false) 547 .build(); 548 SurfaceViewHelper helper = new SurfaceViewHelper((canvas, width, height) -> { 549 Assert.assertNotNull(canvas); 550 canvas.drawColor(Color.RED); 551 }); 552 553 DrawActivity activity = getActivity(); 554 try { 555 TestPositionInfo testInfo = activity 556 .enqueueRenderSpecAndWait(R.layout.frame_layout, null, helper, true, false); 557 assertTrue(helper.hasSurface()); 558 helper.getFence().await(3, TimeUnit.SECONDS); 559 CountDownLatch latch = new CountDownLatch(1); 560 CountDownLatch transactionCommitted = new CountDownLatch(1); 561 activity.runOnUiThread(() -> { 562 SurfaceControl.Transaction transaction = new SurfaceControl.Transaction() 563 .reparent(blueLayer, helper.getSurfaceView().getSurfaceControl()) 564 .setLayer(blueLayer, 1) 565 .addTransactionCommittedListener(Runnable::run, 566 transactionCommitted::countDown); 567 568 int width = helper.getSurfaceView().getWidth(); 569 int height = helper.getSurfaceView().getHeight(); 570 long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 571 | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT 572 | HardwareBuffer.USAGE_COMPOSER_OVERLAY; 573 HardwareBuffer buffer = HardwareBuffer.create( 574 width, height, HardwareBuffer.RGBA_8888, 1, usage); 575 HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); 576 RenderNode node = new RenderNode("content"); 577 node.setPosition(0, 0, width, height); 578 Canvas canvas = node.beginRecording(); 579 canvas.drawColor(Color.BLUE); 580 node.endRecording(); 581 renderer.setContentRoot(node); 582 Handler handler = new Handler(Looper.getMainLooper()); 583 renderer.obtainRenderRequest().draw(Executors.newSingleThreadExecutor(), result -> { 584 handler.post(() -> { 585 transaction.setBuffer(blueLayer, buffer, result.getFence()); 586 helper.getSurfaceView().applyTransactionToFrame(transaction); 587 latch.countDown(); 588 }); 589 }); 590 }); 591 592 assertTrue(latch.await(5, TimeUnit.SECONDS)); 593 // Wait for an additional second to ensure that the transaction reparenting the blue 594 // layer is not applied. 595 assertFalse(transactionCommitted.await(1, TimeUnit.SECONDS)); 596 Bitmap screenshot = mScreenshotter.takeScreenshot(testInfo); 597 598 BitmapAsserter.assertBitmapIsVerified( 599 screenshot, new ColorVerifier(Color.RED, 2), ""); 600 activity.runOnUiThread(() -> { 601 SurfaceHolder holder = helper.getSurfaceView().getHolder(); 602 Canvas canvas = holder.lockHardwareCanvas(); 603 canvas.drawColor(Color.GREEN); 604 holder.unlockCanvasAndPost(canvas); 605 }); 606 assertTrue(transactionCommitted.await(1, TimeUnit.SECONDS)); 607 screenshot = mScreenshotter.takeScreenshot(testInfo); 608 // Now that a new frame was drawn, the blue layer should be overlaid now. 609 BitmapAsserter.assertBitmapIsVerified( 610 screenshot, new ColorVerifier(Color.BLUE, 2), ""); 611 } finally { 612 activity.reset(); 613 } 614 } 615 616 // Regression test for b/269113414 617 @Test 618 public void surfaceViewOffscreenDoesNotPeekThrough() throws InterruptedException { 619 620 // Add a shared latch which will fire after both callbacks are complete. 621 CountDownLatch latch = new CountDownLatch(2); 622 sGreenCanvasCallback.setFence(latch); 623 sRedCanvasCallback.setFence(latch); 624 625 DrawActivity activity = getActivity(); 626 627 SurfaceView surfaceViewRed = new SurfaceView(activity); 628 surfaceViewRed.getHolder().addCallback(sRedCanvasCallback); 629 SurfaceView surfaceViewGreen = new SurfaceView(activity); 630 surfaceViewGreen.setZOrderMediaOverlay(true); 631 surfaceViewGreen.getHolder().addCallback(sGreenCanvasCallback); 632 633 int width = activity.getWindow().getDecorView().getWidth(); 634 int height = activity.getWindow().getDecorView().getHeight(); 635 636 ViewInitializer initializer = (View view) -> { 637 FrameLayout root = view.findViewById(R.id.frame_layout); 638 root.addView(surfaceViewRed, new FrameLayout.LayoutParams( 639 FrameLayout.LayoutParams.MATCH_PARENT, 640 FrameLayout.LayoutParams.MATCH_PARENT)); 641 642 root.addView(surfaceViewGreen, new FrameLayout.LayoutParams( 643 FrameLayout.LayoutParams.MATCH_PARENT, 644 FrameLayout.LayoutParams.MATCH_PARENT)); 645 }; 646 647 try { 648 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 649 R.layout.frame_layout, null, initializer, true, false); 650 assertTrue(latch.await(5, TimeUnit.SECONDS)); 651 652 // Layout the SurfaceView way offscreen which would cause it to get quick rejected. 653 WidgetTestUtils.runOnMainAndDrawSync(surfaceViewGreen, () -> { 654 surfaceViewGreen.layout( 655 width * 2, 656 height * 2, 657 width * 2 + TEST_WIDTH, 658 height * 2 + TEST_HEIGHT); 659 }); 660 waitForScreenshottable(); 661 Bitmap screenshot = mScreenshotter.takeScreenshot(testInfo); 662 BitmapAsserter.assertBitmapIsVerified( 663 screenshot, 664 new ColorVerifier(Color.RED, 0), 665 "Verifying red SurfaceControl"); 666 } finally { 667 activity.reset(); 668 } 669 } 670 671 @Test 672 public void surfaceViewRespectsClipBounds() throws InterruptedException { 673 Assume.assumeTrue(Flags.clipSurfaceviews()); 674 675 Rect clipRect = new Rect(20, 20, 70, 70); 676 CountDownLatch latch = new CountDownLatch(1); 677 sRedCanvasCallback.setFence(latch); 678 ViewInitializer initializer = (View view) -> { 679 FrameLayout root = view.findViewById(R.id.frame_layout); 680 root.setBackgroundColor(Color.GREEN); 681 SurfaceView surfaceView = new SurfaceView(view.getContext()); 682 surfaceView.setZOrderOnTop(true); 683 surfaceView.setClipBounds(clipRect); 684 surfaceView.getHolder().addCallback(sRedCanvasCallback); 685 root.addView(surfaceView, new FrameLayout.LayoutParams( 686 FrameLayout.LayoutParams.MATCH_PARENT, 687 FrameLayout.LayoutParams.MATCH_PARENT)); 688 }; 689 690 DrawActivity activity = getActivity(); 691 try { 692 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 693 R.layout.frame_layout, null, initializer, true, false); 694 assertTrue(latch.await(5, TimeUnit.SECONDS)); 695 696 waitForScreenshottable(); 697 Bitmap screenshot = mScreenshotter.takeScreenshot(testInfo); 698 BitmapAsserter.assertBitmapIsVerified( 699 screenshot, 700 new RectVerifier(Color.GREEN, Color.RED, clipRect), 701 "Verifying red clipped SurfaceView"); 702 } finally { 703 activity.reset(); 704 } 705 } 706 707 @Test 708 public void surfaceViewRespectsParentClipBounds() throws InterruptedException { 709 Assume.assumeTrue(Flags.clipSurfaceviews()); 710 711 CountDownLatch latch = new CountDownLatch(1); 712 sRedCanvasCallback.setFence(latch); 713 ViewInitializer initializer = (View view) -> { 714 FrameLayout root = view.findViewById(R.id.frame_layout); 715 root.setBackgroundColor(Color.GREEN); 716 SurfaceView surfaceView = new SurfaceView(view.getContext()); 717 surfaceView.setZOrderOnTop(true); 718 surfaceView.getHolder().addCallback(sRedCanvasCallback); 719 surfaceView.setTranslationX(20); 720 surfaceView.setTranslationY(30); 721 root.addView(surfaceView, new FrameLayout.LayoutParams( 722 FrameLayout.LayoutParams.MATCH_PARENT, 723 FrameLayout.LayoutParams.MATCH_PARENT)); 724 }; 725 726 DrawActivity activity = getActivity(); 727 try { 728 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 729 R.layout.frame_layout, null, initializer, true, false); 730 assertTrue(latch.await(5, TimeUnit.SECONDS)); 731 732 733 waitForScreenshottable(); 734 Bitmap screenshot = mScreenshotter.takeScreenshot(testInfo); 735 BitmapAsserter.assertBitmapIsVerified( 736 screenshot, 737 new RectVerifier(Color.GREEN, Color.RED, 738 new Rect(20, 30, TEST_WIDTH, TEST_HEIGHT)), 739 "Verifying red clipped SurfaceView"); 740 } finally { 741 activity.reset(); 742 } 743 } 744 745 @Test 746 public void surfaceViewDisabledClip() throws InterruptedException { 747 Assume.assumeTrue(Flags.clipSurfaceviews()); 748 749 CountDownLatch latch = new CountDownLatch(1); 750 sRedCanvasCallback.setFence(latch); 751 ViewInitializer initializer = (View view) -> { 752 FrameLayout root = view.findViewById(R.id.frame_layout); 753 root.setBackgroundColor(Color.GREEN); 754 SurfaceView surfaceView = new SurfaceView(view.getContext()); 755 surfaceView.setZOrderOnTop(true); 756 root.setClipChildren(false); 757 surfaceView.getHolder().addCallback(sRedCanvasCallback); 758 root.addView(surfaceView, new FrameLayout.LayoutParams( 759 FrameLayout.LayoutParams.MATCH_PARENT, 760 FrameLayout.LayoutParams.MATCH_PARENT)); 761 }; 762 763 DrawActivity activity = getActivity(); 764 try { 765 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 766 R.layout.frame_layout, null, initializer, true, false); 767 assertTrue(latch.await(5, TimeUnit.SECONDS)); 768 769 770 waitForScreenshottable(); 771 Bitmap screenshot = mScreenshotter.takeScreenshot(testInfo); 772 BitmapAsserter.assertBitmapIsVerified( 773 screenshot, 774 new ColorVerifier(Color.RED), 775 "Verifying unclipped SurfaceView"); 776 } finally { 777 activity.reset(); 778 } 779 } 780 781 @Test 782 public void surfaceViewScaledClipZAbove() throws InterruptedException { 783 Assume.assumeTrue(Flags.clipSurfaceviews()); 784 785 Rect clipRect = new Rect(21, 20, 60, 60); 786 CountDownLatch latch = new CountDownLatch(1); 787 sRedCanvasCallback.setFence(latch); 788 ViewInitializer initializer = (View view) -> { 789 FrameLayout root = view.findViewById(R.id.frame_layout); 790 root.setBackgroundColor(Color.GREEN); 791 SurfaceView surfaceView = new SurfaceView(view.getContext()); 792 surfaceView.setZOrderOnTop(true); 793 surfaceView.setClipBounds(clipRect); 794 surfaceView.getHolder().setFixedSize(30, 250); 795 surfaceView.getHolder().addCallback(sRedCanvasCallback); 796 root.addView(surfaceView, new FrameLayout.LayoutParams( 797 FrameLayout.LayoutParams.MATCH_PARENT, 798 FrameLayout.LayoutParams.MATCH_PARENT)); 799 }; 800 801 DrawActivity activity = getActivity(); 802 try { 803 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 804 R.layout.frame_layout, null, initializer, true, false); 805 assertTrue(latch.await(5, TimeUnit.SECONDS)); 806 807 808 waitForScreenshottable(); 809 Bitmap screenshot = mScreenshotter.takeScreenshot(testInfo); 810 BitmapAsserter.assertBitmapIsVerified( 811 screenshot, 812 new RectVerifier(Color.GREEN, Color.RED, clipRect), 813 "Verifying red clipped SurfaceView"); 814 } finally { 815 activity.reset(); 816 } 817 } 818 819 @Test 820 public void surfaceViewScaledClipZBelow() throws InterruptedException { 821 Assume.assumeTrue(Flags.clipSurfaceviews()); 822 823 Rect clipRect = new Rect(20, 20, 60, 60); 824 CountDownLatch latch = new CountDownLatch(1); 825 sRedCanvasCallback.setFence(latch); 826 ViewInitializer initializer = (View view) -> { 827 FrameLayout root = view.findViewById(R.id.frame_layout); 828 root.setBackgroundColor(Color.GREEN); 829 SurfaceView surfaceView = new SurfaceView(view.getContext()); 830 surfaceView.setZOrderOnTop(false); 831 surfaceView.setClipBounds(clipRect); 832 surfaceView.getHolder().setFixedSize(30, 250); 833 surfaceView.getHolder().addCallback(sRedCanvasCallback); 834 root.addView(surfaceView, new FrameLayout.LayoutParams( 835 FrameLayout.LayoutParams.MATCH_PARENT, 836 FrameLayout.LayoutParams.MATCH_PARENT)); 837 }; 838 839 DrawActivity activity = getActivity(); 840 try { 841 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 842 R.layout.frame_layout, null, initializer, true, false); 843 assertTrue(latch.await(5, TimeUnit.SECONDS)); 844 845 846 waitForScreenshottable(); 847 Bitmap screenshot = mScreenshotter.takeScreenshot(testInfo); 848 BitmapAsserter.assertBitmapIsVerified( 849 screenshot, 850 new RectVerifier(Color.GREEN, Color.RED, clipRect), 851 "Verifying red clipped SurfaceView"); 852 } finally { 853 activity.reset(); 854 } 855 } 856 857 private static class SurfaceViewHolder implements ViewInitializer { 858 private SurfaceView mSurfaceView; 859 private final DrawCallback mCallback; 860 861 SurfaceViewHolder(DrawCallback callback) { 862 mCallback = callback; 863 } 864 public void initializeView(View view) { 865 FrameLayout root = (FrameLayout) view.findViewById(R.id.frame_layout); 866 mSurfaceView = new SurfaceView(view.getContext()); 867 mSurfaceView.getHolder().addCallback(mCallback); 868 869 root.addView(mSurfaceView, new FrameLayout.LayoutParams( 870 FrameLayout.LayoutParams.MATCH_PARENT, 871 FrameLayout.LayoutParams.MATCH_PARENT)); 872 } 873 SurfaceView getSurfaceView() { 874 return mSurfaceView; 875 } 876 } 877 878 private float getStableHdrSdrRatio(Display display) { 879 float ratio = -1f; 880 float incomingRatio = display.getHdrSdrRatio(); 881 long startMillis = SystemClock.uptimeMillis(); 882 try { 883 do { 884 ratio = incomingRatio; 885 TimeUnit.MILLISECONDS.sleep(500); 886 incomingRatio = display.getHdrSdrRatio(); 887 // Bail if the ratio settled or if it's been way too long. 888 } while (Math.abs(ratio - incomingRatio) > 0.01 889 && SystemClock.uptimeMillis() - startMillis < 10000); 890 } catch (InterruptedException e) { 891 throw new RuntimeException(e); 892 } 893 return ratio; 894 } 895 896 @Test 897 public void surfaceViewDesiredHdrHeadroom() throws InterruptedException { 898 Assume.assumeTrue(Flags.limitedHdr()); 899 900 CountDownLatch latch = new CountDownLatch(1); 901 DrawCallback callback = makeHardwareBufferRendererCallback( 902 Color.GREEN, DataSpace.DATASPACE_BT2020_HLG); 903 callback.setFence(latch); 904 905 SurfaceViewHolder initializer = new SurfaceViewHolder(callback); 906 907 DrawActivity activity = getActivity(); 908 909 try { 910 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 911 R.layout.frame_layout, null, initializer, true, false); 912 assertTrue(latch.await(5, TimeUnit.SECONDS)); 913 waitForScreenshottable(); 914 915 SurfaceView surfaceView = initializer.getSurfaceView(); 916 917 getInstrumentation().runOnMainSync(() -> { 918 // Boundary conditions should throw 919 assertThrows(IllegalArgumentException.class, 920 () -> surfaceView.setDesiredHdrHeadroom(0.5f)); 921 assertThrows(IllegalArgumentException.class, 922 () -> surfaceView.setDesiredHdrHeadroom(-1f)); 923 assertThrows(IllegalArgumentException.class, 924 () -> surfaceView.setDesiredHdrHeadroom(Float.NaN)); 925 assertThrows(IllegalArgumentException.class, 926 () -> surfaceView.setDesiredHdrHeadroom(1000000f)); 927 }); 928 929 Display display = activity.getDisplay(); 930 931 if (display.isHdrSdrRatioAvailable()) { 932 float ratio = getStableHdrSdrRatio(display); 933 // cut the headroom in half, wait for it to settle, then check that we're 934 // upper-bounded. Only do that if we have some headroom to slice in half, 935 // since otherwise we're not testing much 936 Assume.assumeTrue(ratio > 1.02f); 937 float newRatio = 1.f + (ratio - 1.f) / 2; 938 getInstrumentation().runOnMainSync(() -> { 939 surfaceView.setDesiredHdrHeadroom(newRatio); 940 }); 941 assertTrue("Headroom restriction is not respected", 942 getStableHdrSdrRatio(display) <= (newRatio + 0.01)); 943 getInstrumentation().runOnMainSync(() -> { 944 surfaceView.setDesiredHdrHeadroom(0.f); 945 }); 946 assertTrue("Removed headroom restriction is not respected", 947 getStableHdrSdrRatio(display) > newRatio); 948 } 949 950 } finally { 951 activity.reset(); 952 } 953 } 954 955 @Test 956 public void surfaceViewDesiredHdrHeadroomPreservesWithReconfiguration() throws InterruptedException { 957 Assume.assumeTrue(Flags.limitedHdr()); 958 959 CountDownLatch latch = new CountDownLatch(1); 960 DrawCallback callback = makeHardwareBufferRendererCallback( 961 Color.GREEN, DataSpace.DATASPACE_BT2020_HLG); 962 callback.setFence(latch); 963 964 SurfaceViewHolder initializer = new SurfaceViewHolder(callback); 965 966 DrawActivity activity = getActivity(); 967 968 try { 969 TestPositionInfo testInfo = activity.enqueueRenderSpecAndWait( 970 R.layout.frame_layout, null, initializer, true, false); 971 assertTrue(latch.await(5, TimeUnit.SECONDS)); 972 waitForScreenshottable(); 973 974 SurfaceView surfaceView = initializer.getSurfaceView(); 975 surfaceView.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_VISIBILITY); 976 Display display = activity.getDisplay(); 977 978 if (display.isHdrSdrRatioAvailable()) { 979 float ratio = getStableHdrSdrRatio(display); 980 // cut the headroom in half, wait for it to settle, then check that we're 981 // upper-bounded. Only do that if we have some headroom to slice in half, 982 // since otherwise we're not testing much 983 Assume.assumeTrue(ratio > 1.02f); 984 float newRatio = 1.f + (ratio - 1.f) / 2; 985 getInstrumentation().runOnMainSync(() -> { 986 surfaceView.setDesiredHdrHeadroom(newRatio); 987 }); 988 assertTrue("Headroom restriction is not respected", 989 getStableHdrSdrRatio(display) <= (newRatio + 0.01)); 990 getInstrumentation().runOnMainSync(() -> { 991 surfaceView.setVisibility(View.INVISIBLE); 992 surfaceView.setVisibility(View.VISIBLE); 993 }); 994 assertTrue("Headroom restriction got removed", 995 getStableHdrSdrRatio(display) <= (newRatio + 0.01)); 996 } 997 998 } finally { 999 activity.reset(); 1000 } 1001 } 1002 } 1003