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