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 
17 package android.view.cts;
18 
19 import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
20 import static android.opengl.GLES20.GL_SCISSOR_TEST;
21 import static android.opengl.GLES20.glClear;
22 import static android.opengl.GLES20.glClearColor;
23 import static android.opengl.GLES20.glEnable;
24 import static android.opengl.GLES20.glScissor;
25 import static android.view.WindowInsets.Type.captionBar;
26 import static android.view.WindowInsets.Type.systemBars;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertNotNull;
30 import static org.junit.Assert.assertTrue;
31 import static org.junit.Assert.fail;
32 
33 import android.Manifest;
34 import android.app.Instrumentation;
35 import android.graphics.Bitmap;
36 import android.graphics.Canvas;
37 import android.graphics.Color;
38 import android.graphics.ColorSpace;
39 import android.graphics.Matrix;
40 import android.graphics.Paint;
41 import android.graphics.PixelFormat;
42 import android.graphics.Point;
43 import android.graphics.Rect;
44 import android.graphics.RectF;
45 import android.graphics.SurfaceTexture;
46 import android.hardware.DataSpace;
47 import android.media.Image;
48 import android.media.ImageWriter;
49 import android.platform.test.annotations.AppModeSdkSandbox;
50 import android.util.Half;
51 import android.view.PixelCopy;
52 import android.view.Surface;
53 import android.view.SurfaceHolder;
54 import android.view.SurfaceView;
55 import android.view.TextureView;
56 import android.view.View;
57 import android.view.ViewGroup;
58 import android.view.Window;
59 import android.view.WindowInsets;
60 import android.view.cts.util.BitmapDumper;
61 
62 import androidx.test.InstrumentationRegistry;
63 import androidx.test.filters.MediumTest;
64 import androidx.test.rule.ActivityTestRule;
65 
66 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
67 import com.android.compatibility.common.util.SynchronousPixelCopy;
68 import com.android.compatibility.common.util.WidgetTestUtils;
69 
70 import org.junit.Assert;
71 import org.junit.Before;
72 import org.junit.Rule;
73 import org.junit.Test;
74 import org.junit.rules.TestName;
75 import org.junit.runner.RunWith;
76 
77 import java.nio.ByteBuffer;
78 import java.nio.ByteOrder;
79 import java.util.concurrent.CountDownLatch;
80 import java.util.concurrent.TimeUnit;
81 import java.util.concurrent.TimeoutException;
82 
83 import junitparams.JUnitParamsRunner;
84 import junitparams.Parameters;
85 
86 @MediumTest
87 @RunWith(JUnitParamsRunner.class)
88 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
89 public class TextureViewTest {
90 
91     static final int EGL_GL_COLORSPACE_SRGB_KHR = 0x3089;
92     static final int EGL_GL_COLORSPACE_LINEAR_KHR = 0x308A;
93     static final int EGL_GL_COLORSPACE_DISPLAY_P3_EXT = 0x3363;
94     static final int EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT = 0x3362;
95     static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
96     static final int EGL_GL_COLORSPACE_SCRGB_EXT = 0x3351;
97     static final int EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT = 0x3350;
98 
99     private Instrumentation mInstrumentation;
100 
101     @Before
setup()102     public void setup() {
103         mInstrumentation = InstrumentationRegistry.getInstrumentation();
104         assertNotNull(mInstrumentation);
105     }
106 
107     @Rule(order = 0)
108     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
109             androidx.test.platform.app.InstrumentationRegistry
110                     .getInstrumentation().getUiAutomation(),
111             Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
112 
113     @Rule(order = 1)
114     public ActivityTestRule<TextureViewCtsActivity> mActivityRule =
115             new ActivityTestRule<>(TextureViewCtsActivity.class, false, false);
116 
117     @Rule(order = 1)
118     public ActivityTestRule<SDRTestActivity> mSDRActivityRule =
119             new ActivityTestRule<>(SDRTestActivity.class, false, false);
120 
121     @Rule
122     public TestName mTestName = new TestName();
123 
124     @Test
testFirstFrames()125     public void testFirstFrames() throws Throwable {
126         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
127         activity.waitForEnterAnimationComplete();
128 
129         final Point center = new Point();
130         final Window[] windowRet = new Window[1];
131         mActivityRule.runOnUiThread(() -> {
132             View content = activity.findViewById(android.R.id.content);
133             int[] outLocation = new int[2];
134             content.getLocationInWindow(outLocation);
135             center.x = outLocation[0] + (content.getWidth() / 2);
136             center.y = outLocation[1] + (content.getHeight() / 2);
137             windowRet[0] = activity.getWindow();
138         });
139         final Window window = windowRet[0];
140         assertTrue(center.x > 0);
141         assertTrue(center.y > 0);
142         waitForColor(window, center, Color.WHITE);
143         activity.waitForSurface();
144         activity.initGl();
145         int updatedCount;
146         // If the caption bar is present, the surface update counts increase by 1
147         int extraSurfaceOffset =
148                 window.getDecorView().getRootWindowInsets().getInsets(captionBar()).top == 0
149                 ? 0 : 1;
150         updatedCount = activity.waitForSurfaceUpdateCount(0 + extraSurfaceOffset);
151         assertEquals(0 + extraSurfaceOffset, updatedCount);
152         activity.drawColor(Color.GREEN);
153         updatedCount = activity.waitForSurfaceUpdateCount(1 + extraSurfaceOffset);
154         assertEquals(1 + extraSurfaceOffset, updatedCount);
155         assertEquals(Color.WHITE, getPixel(window, center));
156         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
157                 activity.findViewById(android.R.id.content), () -> activity.removeCover());
158 
159         int color = waitForChange(window, center, Color.WHITE);
160         assertEquals(Color.GREEN, color);
161         activity.drawColor(Color.BLUE);
162         updatedCount = activity.waitForSurfaceUpdateCount(2 + extraSurfaceOffset);
163         assertEquals(2 + extraSurfaceOffset, updatedCount);
164         color = waitForChange(window, center, color);
165         assertEquals(Color.BLUE, color);
166     }
167 
168     @Test
testScaling()169     public void testScaling() throws Throwable {
170         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
171         activity.drawFrame(TextureViewTest::drawGlQuad);
172         final Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
173         mActivityRule.runOnUiThread(() -> {
174             activity.getTextureView().getBitmap(bitmap);
175         });
176         assertBitmapQuadColor(bitmap,
177                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
178     }
179 
180     @Test
testRotateScale()181     public void testRotateScale() throws Throwable {
182         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
183         final TextureView textureView = activity.getTextureView();
184         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, activity.getTextureView(), null);
185         Matrix rotate = new Matrix();
186         rotate.setRotate(180, textureView.getWidth() / 2, textureView.getHeight() / 2);
187         activity.drawFrame(rotate, TextureViewTest::drawGlQuad);
188         final Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
189         mActivityRule.runOnUiThread(() -> {
190             activity.getTextureView().getBitmap(bitmap);
191         });
192         // Verify the matrix did not rotate content of getTextureView.getBitmap().
193         assertBitmapQuadColor(bitmap,
194                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
195 
196         // Remove cover and calculate TextureView position on the screen.
197         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
198                 activity.findViewById(android.R.id.content), () -> activity.removeCover());
199         final Rect viewPos = new Rect();
200         mActivityRule.runOnUiThread(() -> {
201             int[] outLocation = new int[2];
202             textureView.getLocationInWindow(outLocation);
203             viewPos.left = outLocation[0];
204             viewPos.top = outLocation[1];
205             viewPos.right = viewPos.left + textureView.getWidth();
206             viewPos.bottom = viewPos.top + textureView.getHeight();
207         });
208 
209         // Capture the portion of the screen that contains the texture view only.
210         Window window = activity.getWindow();
211         Bitmap screenshot = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
212         int result = new SynchronousPixelCopy().request(window, viewPos, screenshot);
213         assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
214         // Verify the matrix rotated the TextureView content drawn on the screen.
215         assertBitmapQuadColor(screenshot,
216                 Color.BLACK, Color.BLUE, Color.GREEN, Color.RED);
217     }
218 
219     @Test
testTransformScale()220     public void testTransformScale() throws Throwable {
221         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
222         final TextureView textureView = activity.getTextureView();
223         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, activity.getTextureView(), null);
224         Matrix transform = new Matrix();
225         final float translateY = 100.0f;
226         final float scaleY = 0.25f;
227         float[] values = {1, 0, 0, 0, scaleY, translateY, 0, 0, 1};
228         transform.setValues(values);
229         activity.drawFrame(transform, TextureViewTest::drawGlQuad);
230         final Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
231         mActivityRule.runOnUiThread(() -> {
232             activity.getTextureView().getBitmap(bitmap);
233         });
234         // Verify the matrix did not affect the content of getTextureView.getBitmap().
235         assertBitmapQuadColor(bitmap,
236                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
237 
238         // Remove cover and calculate TextureView position on the screen.
239         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
240                 activity.findViewById(android.R.id.content), () -> activity.removeCover());
241         final Rect viewPos = new Rect();
242         mActivityRule.runOnUiThread(() -> {
243             int[] outLocation = new int[2];
244             textureView.getLocationInWindow(outLocation);
245             viewPos.left = outLocation[0];
246             viewPos.top = outLocation[1];
247             viewPos.right = viewPos.left + textureView.getWidth();
248             viewPos.bottom = viewPos.top + textureView.getHeight();
249         });
250 
251         // Capture the portion of the screen that contains the texture view only.
252         Window window = activity.getWindow();
253         Bitmap screenshot = Bitmap.createBitmap(viewPos.width(), viewPos.height(),
254                 Bitmap.Config.ARGB_8888);
255         int result = new SynchronousPixelCopy().request(window, viewPos, screenshot);
256         assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
257         // Verify the matrix scaled and translated the TextureView content drawn on the screen.
258         // "texturePos" has SurfaceTexture position inside the TextureView.
259         final Rect texturePos = new Rect(0, (int) translateY, viewPos.width(),
260                 (int) (viewPos.height() * scaleY + translateY));
261 
262         // Areas not covered by the texture are black, because FrameLayout background is set to
263         // Color.BLACK in TextureViewCtsActivity.onCreate.
264         assertEquals("above texture", Color.BLACK,
265                 screenshot.getPixel(10, texturePos.top - 10));
266         assertEquals("below texture", Color.BLACK,
267                 screenshot.getPixel(10, texturePos.bottom + 10));
268         assertEquals("top left", Color.RED,
269                 screenshot.getPixel(texturePos.left + 10, texturePos.top + 10));
270         assertEquals("top right", Color.GREEN,
271                 screenshot.getPixel(texturePos.right - 10, texturePos.top + 10));
272         assertEquals("Bottom left", Color.BLUE,
273                 screenshot.getPixel(texturePos.left + 10, texturePos.bottom - 10));
274         assertEquals("Bottom right", Color.BLACK,
275                 screenshot.getPixel(texturePos.right - 10, texturePos.bottom - 10));
276     }
277 
278     // TODO(b/229173479): understand why DCI_P3 and BT2020 do not match with certain colors.
279     // TODO(b/230400473): Add in BT2020 and BT709 and BT601 once SurfaceFlinger reliably color
280     // converts.
testDataSpaces()281     private static Object[] testDataSpaces() {
282         return new Integer[]{
283             DataSpace.DATASPACE_SCRGB_LINEAR,
284             DataSpace.DATASPACE_SRGB,
285             DataSpace.DATASPACE_SCRGB,
286             DataSpace.DATASPACE_DISPLAY_P3,
287             DataSpace.DATASPACE_ADOBE_RGB,
288             DataSpace.DATASPACE_DCI_P3,
289             DataSpace.DATASPACE_SRGB_LINEAR
290         };
291     }
292 
293     @Test
294     @Parameters(method = "testDataSpaces")
testSDRFromSurfaceViewAndTextureView(int dataSpace)295     public void testSDRFromSurfaceViewAndTextureView(int dataSpace) throws Throwable {
296         final int grayishYellow = 0xFFBABAB9;
297         long converted = Color.convert(grayishYellow, ColorSpace.getFromDataSpace(dataSpace));
298 
299         final SDRTestActivity activity =
300                 mSDRActivityRule.launchActivity(/*startIntent*/ null);
301         activity.waitForEnterAnimationComplete();
302 
303         TextureView textureView = activity.getTextureView();
304         // SurfaceView and TextureView dimensions are the same so we reuse variables
305         int width = textureView.getWidth();
306         int height = textureView.getHeight();
307 
308         // paint surfaceView layer
309         SurfaceView surfaceView = activity.getSurfaceView();
310         final CountDownLatch latch = new CountDownLatch(1);
311         surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
312             @Override
313             public void surfaceCreated(SurfaceHolder holder) {
314                 ImageWriter writer = new ImageWriter
315                         .Builder(holder.getSurface())
316                         .setHardwareBufferFormat(PixelFormat.RGBA_8888)
317                         .setDataSpace(dataSpace)
318                         .build();
319                 // spawn a thread here to iterate 10 times from image dequeue to queue
320                 // so that we can be stalled until the first frame has been displayed.
321                 new Thread(() -> {
322                     Bitmap bitmap = null;
323                     for (int i = 0; i < 10; i++) {
324                         Image image = writer.dequeueInputImage();
325                         assertEquals(dataSpace, image.getDataSpace());
326                         Image.Plane plane = image.getPlanes()[0];
327                         // only make bitmap the first time to improve the performation
328                         // if the bitmap is large.
329                         if (bitmap == null) {
330                             bitmap = Bitmap.createBitmap(plane.getRowStride() / 4,
331                                 image.getHeight(),
332                                 Bitmap.Config.ARGB_8888, true,
333                                 ColorSpace.getFromDataSpace(dataSpace));
334                             Canvas canvas = new Canvas(bitmap);
335                             canvas.drawColor(converted);
336                         }
337                         bitmap.copyPixelsToBuffer(plane.getBuffer());
338                         writer.queueInputImage(image);
339                     }
340                     latch.countDown();
341                 }).start();
342             }
343 
344             @Override
345             public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
346 
347             @Override
348             public void surfaceDestroyed(SurfaceHolder holder) {}
349         });
350 
351         WidgetTestUtils.runOnMainAndDrawSync(mSDRActivityRule, surfaceView, () -> {
352             ((ViewGroup) surfaceView.getParent()).removeView(surfaceView);
353             activity.setContentView(surfaceView);
354         });
355 
356         assertTrue(latch.await(5, TimeUnit.SECONDS));
357 
358         Bitmap surfaceViewScreenshot = mInstrumentation
359                 .getUiAutomation()
360                 .takeScreenshot(activity.getWindow());
361 
362         WidgetTestUtils.runOnMainAndDrawSync(mSDRActivityRule, textureView, () -> {
363             ((ViewGroup) textureView.getParent()).removeView(textureView);
364             activity.setContentView(textureView);
365         });
366 
367          // paint textureView layer
368         SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
369         Surface surface = new Surface(surfaceTexture);
370         assertTrue(surface.isValid());
371 
372         ImageWriter writer = new ImageWriter
373                 .Builder(surface)
374                 .setHardwareBufferFormat(PixelFormat.RGBA_8888)
375                 .setDataSpace(dataSpace)
376                 .build();
377         Image image = writer.dequeueInputImage();
378         assertEquals(dataSpace, image.getDataSpace());
379         Image.Plane plane = image.getPlanes()[0];
380         Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4, image.getHeight(),
381                 Bitmap.Config.ARGB_8888, true, ColorSpace.getFromDataSpace(dataSpace));
382         Canvas canvas = new Canvas(bitmap);
383         canvas.drawColor(converted);
384         bitmap.copyPixelsToBuffer(plane.getBuffer());
385         writer.queueInputImage(image);
386 
387         final Bitmap textureViewScreenshot =
388                 Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
389         // surfaceViewScreenshot colorspace depends on SF colormode selection,
390         // i.e., Display_P3 or sRGB, therefore, change textureViewScreenshot's bitmap
391         // colorspace to be aligned with it
392         textureViewScreenshot.setColorSpace(surfaceViewScreenshot.getColorSpace());
393 
394         WidgetTestUtils.runOnMainAndDrawSync(
395                 mSDRActivityRule, textureView, () -> textureView.getBitmap(textureViewScreenshot));
396 
397         WindowInsets rootWindowInsets = activity.getWindow().getDecorView().getRootWindowInsets();
398 
399         int extraSurfaceTopOffset = rootWindowInsets.getInsets(systemBars()).top;
400         int extraSurfaceRightOffset = rootWindowInsets.getInsets(systemBars()).right;
401         int extraSurfaceBottomOffset = rootWindowInsets.getInsets(systemBars()).bottom;
402         int extraSurfaceLeftOffset = rootWindowInsets.getInsets(systemBars()).left;
403 
404         // sample 5 pixels on the edge for bitmap comparison.
405         // TextureView and SurfaceView use different shaders, so compare these two with tolerance.
406         // TODO(b/229173479): These shaders shouldn't be very different. Figure out why we need
407         // this tolerance in the first place.
408         final int threshold = 3;
409         try {
410             assertPixelsAreSame(surfaceViewScreenshot.getPixel(width / 2, extraSurfaceTopOffset),
411                     textureViewScreenshot.getPixel(width / 2, 0), threshold);
412             assertPixelsAreSame(surfaceViewScreenshot.getPixel(extraSurfaceLeftOffset, height / 2),
413                     textureViewScreenshot.getPixel(0, height / 2), threshold);
414             assertPixelsAreSame(surfaceViewScreenshot.getPixel(width / 2, height / 2),
415                     textureViewScreenshot.getPixel(width / 2, height / 2), threshold);
416             assertPixelsAreSame(
417                     surfaceViewScreenshot.getPixel(width / 2,
418                             height - 1 - extraSurfaceBottomOffset),
419                     textureViewScreenshot.getPixel(width / 2,
420                             height - 1),
421                     threshold);
422             assertPixelsAreSame(
423                     surfaceViewScreenshot.getPixel(width - 1 - extraSurfaceRightOffset, height / 2),
424                     textureViewScreenshot.getPixel(width - 1, height / 2), threshold);
425         } catch (AssertionError err) {
426             BitmapDumper.dumpBitmap(textureViewScreenshot,
427                     mTestName.getMethodName() + "_textureView", "TextureViewTest");
428             BitmapDumper.dumpBitmap(surfaceViewScreenshot,
429                     mTestName.getMethodName() + "_surfaceView", "TextureViewTest");
430             throw err;
431         }
432     }
433 
434     @Test
testCropRect()435     public void testCropRect() throws Throwable {
436         final TextureViewCtsActivity activity = mActivityRule.launchActivity(/*startIntent*/ null);
437         activity.waitForSurface();
438         final TextureView textureView = activity.getTextureView();
439         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, textureView, () -> {
440             activity.removeCover();
441             // This test is sensitive to GPU sampling precision, so cap the size of the textureview
442             // to a known small value that will not have float precision issues when being
443             // sampled, specifically when running on GPUs limited to fp16 precision
444             ViewGroup.LayoutParams params = textureView.getLayoutParams();
445             params.width = 100;
446             params.height = 100;
447             textureView.setLayoutParams(params);
448         });
449         int textureWidth = textureView.getWidth();
450         int textureHeight = textureView.getHeight();
451         assertEquals(100, textureWidth);
452         assertEquals(100, textureHeight);
453         SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
454         Surface surface = new Surface(surfaceTexture);
455         assertTrue(surface.isValid());
456         ImageWriter writer = ImageWriter.newInstance(surface, /*maxImages*/ 1);
457         Image image = writer.dequeueInputImage();
458         assertEquals(100, image.getWidth());
459         assertEquals(100, image.getHeight());
460         Image.Plane plane = image.getPlanes()[0];
461         Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4, image.getHeight(),
462                 Bitmap.Config.ARGB_8888);
463         Canvas canvas = new Canvas(bitmap);
464         Paint paint = new Paint();
465         paint.setAntiAlias(false);
466         paint.setColor(Color.YELLOW);
467         canvas.drawRect(0f, 0f, textureWidth, textureHeight, paint);
468         paint.setColor(Color.BLACK);
469         canvas.drawRect(2f, 2f, textureWidth - 2f, textureHeight - 2f, paint);
470 
471         image.setCropRect(new Rect(1, 1, textureWidth - 1, textureHeight - 1));
472         bitmap.copyPixelsToBuffer(plane.getBuffer());
473         writer.queueInputImage(image);
474         waitForDraw(textureView);
475 
476         final Rect viewPos = new Rect();
477         mActivityRule.runOnUiThread(() -> {
478             int[] outLocation = new int[2];
479             textureView.getLocationInSurface(outLocation);
480             viewPos.left = outLocation[0];
481             viewPos.top = outLocation[1];
482             viewPos.right = viewPos.left + textureView.getWidth();
483             viewPos.bottom = viewPos.top + textureView.getHeight();
484         });
485         SynchronousPixelCopy pixelCopy = new SynchronousPixelCopy();
486         // Capture the portion of the screen that contains the texture view only.
487         Window window = activity.getWindow();
488         bitmap = Bitmap.createBitmap(viewPos.width(), viewPos.height(),
489                 Bitmap.Config.ARGB_8888);
490         int result = pixelCopy.request(window, viewPos, bitmap);
491         assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
492         assertBitmapEdgeColor(bitmap, Color.YELLOW);
493     }
494 
495     // TODO(b/220361081) replace with runOnMainAndDrawSync once we have the
496     // runOnMainAndDrawSync updated to use the registerFrameCommitCallback
waitForDraw(final View view)497     private void waitForDraw(final View view) throws Throwable {
498         final CountDownLatch latch = new CountDownLatch(1);
499         mActivityRule.runOnUiThread(() -> {
500             view.getViewTreeObserver().registerFrameCommitCallback(latch::countDown);
501             view.invalidate();
502         });
503         assertTrue(latch.await(1, TimeUnit.SECONDS));
504     }
505 
pixelsAreSame(int ideal, int given, int threshold)506     private boolean pixelsAreSame(int ideal, int given, int threshold) {
507         int error = Math.abs(Color.red(ideal) - Color.red(given));
508         error += Math.abs(Color.green(ideal) - Color.green(given));
509         error += Math.abs(Color.blue(ideal) - Color.blue(given));
510         return (error < threshold);
511     }
512 
assertPixelsAreSame(int ideal, int given, int threshold)513     private void assertPixelsAreSame(int ideal, int given, int threshold) {
514         if (!pixelsAreSame(ideal, given, threshold)) {
515             fail("expected=" + Integer.toHexString(ideal) + ", actual="
516                     + Integer.toHexString(given));
517         }
518     }
519 
520     @Test
testGetBitmap_8888_P3()521     public void testGetBitmap_8888_P3() throws Throwable {
522         testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_EXT, ColorSpace.get(ColorSpace.Named.DISPLAY_P3),
523                 false, false, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
524     }
525 
526     @Test
testGetBitmap_8888_PassthroughP3()527     public void testGetBitmap_8888_PassthroughP3() throws Throwable {
528         testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT,
529                 ColorSpace.get(ColorSpace.Named.DISPLAY_P3), false, true,
530                 new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
531     }
532 
533     @Test
testGetBitmap_FP16_PassthroughP3()534     public void testGetBitmap_FP16_PassthroughP3() throws Throwable {
535         testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT,
536                 ColorSpace.get(ColorSpace.Named.DISPLAY_P3), true, true,
537                 new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
538     }
539 
540     @Test
testGetBitmap_FP16_LinearP3()541     public void testGetBitmap_FP16_LinearP3() throws Throwable {
542         ColorSpace.Rgb displayP3 = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
543         ColorSpace.Rgb linearDisplayP3 = new ColorSpace.Rgb(
544                 "Display P3 Linear",
545                 displayP3.getTransform(),
546                 displayP3.getWhitePoint(),
547                 x -> x,
548                 x -> x,
549                 0.0f, 1.0f
550         );
551 
552         testGetBitmap(EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT, linearDisplayP3, true,
553                 true, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
554     }
555 
556     @Test
testGetBitmap_FP16_ExtendedSRGB()557     public void testGetBitmap_FP16_ExtendedSRGB() throws Throwable {
558         // isLinear is "true", because the spec says
559         // GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_LINEAR for EGL_GL_COLORSPACE_SCRGB_EXT.
560         // See https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_scrgb.txt.
561         testGetBitmap(EGL_GL_COLORSPACE_SCRGB_EXT,
562                 ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), true,
563                 true, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
564     }
565 
566     @Test
testGetBitmap_FP16_LinearExtendedSRGB()567     public void testGetBitmap_FP16_LinearExtendedSRGB() throws Throwable {
568         testGetBitmap(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT,
569                 ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), true,
570                 true, new FP16Compare(ColorSpace.Named.EXTENDED_SRGB));
571     }
572 
573     @Test
testGet565Bitmap_SRGB()574     public void testGet565Bitmap_SRGB() throws Throwable {
575         testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.get(ColorSpace.Named.SRGB),
576                 false, false, new SRGBCompare(Bitmap.Config.RGB_565));
577     }
578 
579     @Test
testGetBitmap_SRGB()580     public void testGetBitmap_SRGB() throws Throwable {
581         testGetBitmap(EGL_GL_COLORSPACE_SRGB_KHR, ColorSpace.get(ColorSpace.Named.SRGB),
582                 false, false, new SRGBCompare(Bitmap.Config.ARGB_8888));
583     }
584 
585     @Test
testGetBitmap_SRGBLinear()586     public void testGetBitmap_SRGBLinear() throws Throwable {
587         testGetBitmap(EGL_GL_COLORSPACE_LINEAR_KHR, ColorSpace.get(ColorSpace.Named.LINEAR_SRGB),
588                 false, true, new SRGBCompare(Bitmap.Config.ARGB_8888));
589     }
590 
591     /**
592      *  Test that verifies TextureView is drawn with bilerp sampling, when the matrix is not
593      *  an integer translate or identity.
594      */
595     @Test
testSamplingWithTransform()596     public void testSamplingWithTransform() throws Throwable {
597         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
598         final TextureView textureView = activity.getTextureView();
599         final int viewWidth = textureView.getWidth();
600         final int viewHeight = textureView.getHeight();
601         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, activity.getTextureView(), null);
602         // Remove cover and calculate TextureView position on the screen.
603         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
604                 activity.findViewById(android.R.id.content), () -> activity.removeCover());
605 
606         float[][] matrices = {
607             {1, 0, 0, 0, 1, 0, 0, 0, 1},            // identity matrix
608             {1, 0, 0, 0, 1, 10.3f, 0, 0, 1},        // translation matrix with a fractional offset
609             {1, 0, 0, 0, 0.75f, 0, 0, 0, 1},        // scaling matrix
610             {1, 0, 0, 0, 1, 10f, 0, 0, 1},          // translation matrix with an integer offset
611             {0, -1, viewWidth, 1, 0, 0, 0, 0, 1},   // 90 rotation matrix + integer translate X
612             {0, 1, 0, -1, 0, viewWidth, 0, 0, 1},   // 270 rotation matrix + integer translate Y
613             {-1, 0, viewWidth, 0, 1, 0, 0, 0, 1},   // H flip matrix + integer translate X
614             {1, 0, 0, 0, -1, viewHeight, 0, 0, 1},  // V flip matrix + integer translate Y
615             {-1, 0, viewWidth, 0, -1, viewHeight, 0, 0, 1}, // 180 rotation + integer translate X Y
616             {0, -1, viewWidth - 10.3f, 1, 0, 0, 0, 0, 1},  // 90 rotation matrix with a fractional
617                                                            // offset
618         };
619         boolean[] nearestSampling = {
620             true,  // nearest sampling for identity
621             false, // bilerp sampling for fractional translate
622             false, // bilerp sampling for scaling
623             true,  // nearest sampling for integer translate
624             true,  // nearest sampling for 90 rotation with integer translate
625             true,  // nearest sampling for 270 rotation with integer translate
626             true,  // nearest sampling for H flip with integer translate
627             true,  // nearest sampling for V flip with integer translate
628             true,  // nearest sampling for 180 rotation with integer translate
629             false, // bilerp sampling for 90 rotation matrix with a fractional offset
630         };
631         for (int i = 0; i < nearestSampling.length; i++) {
632 
633             Matrix transform = new Matrix();
634             transform.setValues(matrices[i]);
635 
636             // Test draws a set of black & white alternating lines.
637             activity.drawFrame(transform, TextureViewTest::drawGlBlackWhiteLines);
638 
639             final Rect viewPos = new Rect();
640             mActivityRule.runOnUiThread(() -> {
641                 int[] outLocation = new int[2];
642                 textureView.getLocationInWindow(outLocation);
643                 viewPos.left = outLocation[0];
644                 viewPos.top = outLocation[1];
645                 viewPos.right = viewPos.left + textureView.getWidth();
646                 viewPos.bottom = viewPos.top + textureView.getHeight();
647             });
648 
649             // Capture the portion of the screen that contains the texture view only.
650             Window window = activity.getWindow();
651             Bitmap screenshot = Bitmap.createBitmap(viewPos.width(), viewPos.height(),
652                     Bitmap.Config.ARGB_8888);
653             int result = new SynchronousPixelCopy().request(window, viewPos, screenshot);
654             assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
655 
656             // "texturePos" has SurfaceTexture position inside the TextureView.
657             RectF texturePosF = new RectF(0, 0, viewPos.width(), viewPos.height());
658             transform.mapRect(texturePosF);
659             // Clip parts outside TextureView.
660             // Matrices are picked, so that the drawing area is not empty.
661             assertTrue("empty test area",
662                     texturePosF.intersect(0, 0, viewPos.width(), viewPos.height()));
663             Rect texturePos = new Rect((int) Math.ceil(texturePosF.left),
664                     (int) Math.ceil(texturePosF.top), (int) Math.floor(texturePosF.right),
665                     (int) Math.floor(texturePosF.bottom));
666 
667             int[] pixels = new int[texturePos.width() * texturePos.height()];
668             screenshot.getPixels(pixels, 0, texturePos.width(), texturePos.left, texturePos.top,
669                     texturePos.width(), texturePos.height());
670 
671             boolean success = true;
672             int failPosition = 0;
673             if (nearestSampling[i]) {
674                 // Check all pixels are either black or white.
675                 for (int j = 0; j < pixels.length; j++) {
676                     if (pixels[j] != Color.BLACK && pixels[j] != Color.WHITE) {
677                         success = false;
678                         failPosition = j;
679                         break;
680                     }
681                 }
682             } else {
683                 // Check that a third of pixels are not black nor white, because bilerp sampling
684                 // changed pure black/white to a variety of gray intermediates.
685                 int nonBlackWhitePixels = 0;
686                 for (int j = 0; j < pixels.length; j++) {
687                     if (pixels[j] != Color.BLACK && pixels[j] != Color.WHITE) {
688                         nonBlackWhitePixels++;
689                     } else {
690                         failPosition = j;
691                     }
692                 }
693                 if (nonBlackWhitePixels < pixels.length / 3) {
694                     success = false;
695                 }
696             }
697             assertTrue("Unexpected color at position " + failPosition + " = "
698                     + Integer.toHexString(pixels[failPosition]) + " " + transform.toString(),
699                     success);
700         }
701     }
702 
703     interface CompareFunction {
getConfig()704         Bitmap.Config getConfig();
getColorSpace()705         ColorSpace getColorSpace();
verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap)706         void verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap);
707     }
708 
709     private class FP16Compare implements CompareFunction {
710         private ColorSpace mDstColorSpace;
711 
FP16Compare(ColorSpace.Named namedCS)712         FP16Compare(ColorSpace.Named namedCS) {
713             mDstColorSpace = ColorSpace.get(namedCS);
714         }
715 
getConfig()716         public Bitmap.Config getConfig() {
717             return Bitmap.Config.RGBA_F16;
718         }
719 
getColorSpace()720         public ColorSpace getColorSpace() {
721             return mDstColorSpace;
722         }
723 
verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap)724         public void verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap) {
725             // read pixels into buffer and compare using colorspace connector
726             ByteBuffer buffer = ByteBuffer.allocate(dstBitmap.getAllocationByteCount());
727             buffer.order(ByteOrder.LITTLE_ENDIAN);
728             dstBitmap.copyPixelsToBuffer(buffer);
729             Half alpha = Half.valueOf(buffer.getShort(6));
730             assertEquals(1.0f, alpha.floatValue(), 0.0f);
731 
732             final ColorSpace dstSpace = getColorSpace();
733             float[] expectedColor = ColorSpace.connect(srcColorSpace, dstSpace).transform(srcColor);
734             float[] outputColor = {
735                     Half.valueOf(buffer.getShort(0)).floatValue(),
736                     Half.valueOf(buffer.getShort(2)).floatValue(),
737                     Half.valueOf(buffer.getShort(4)).floatValue() };
738 
739             assertEquals(expectedColor[0], outputColor[0], 0.01f);
740             assertEquals(expectedColor[1], outputColor[1], 0.01f);
741             assertEquals(expectedColor[2], outputColor[2], 0.01f);
742         }
743     }
744 
745     private class SRGBCompare implements CompareFunction {
746         private Bitmap.Config mConfig;
747 
SRGBCompare(Bitmap.Config config)748         SRGBCompare(Bitmap.Config config) {
749             mConfig = config;
750         }
751 
getConfig()752         public Bitmap.Config getConfig() {
753             return mConfig;
754         }
755 
getColorSpace()756         public ColorSpace getColorSpace() {
757             return ColorSpace.get(ColorSpace.Named.SRGB);
758         }
759 
verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap)760         public void verify(float[] srcColor, ColorSpace srcColorSpace, Bitmap dstBitmap) {
761             int color = dstBitmap.getPixel(0, 0);
762             assertEquals(1.0f, Color.alpha(color) / 255.0f, 0.0f);
763             assertEquals(srcColor[0], Color.red(color) / 255.0f, 0.01f);
764             assertEquals(srcColor[1], Color.green(color) / 255.0f, 0.01f);
765             assertEquals(srcColor[2], Color.blue(color) / 255.0f, 0.01f);
766         }
767     }
768 
769     // isFramebufferLinear is true, when GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_LINEAR.
770     // It is false, when GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_SRGB.
testGetBitmap(int eglColorSpace, ColorSpace colorSpace, boolean useHalfFloat, boolean isFramebufferLinear, CompareFunction compareFunction)771     private void testGetBitmap(int eglColorSpace, ColorSpace colorSpace,
772             boolean useHalfFloat, boolean isFramebufferLinear,
773             CompareFunction compareFunction) throws Throwable {
774         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
775         activity.waitForSurface();
776 
777         try {
778             activity.initGl(eglColorSpace, useHalfFloat);
779         } catch (RuntimeException e) {
780             // failure to init GL with the right colorspace is not a TextureView failure as some
781             // devices may not support 16-bits or the colorspace extension
782             if (!activity.initGLExtensionUnsupported()) {
783                 fail("Unable to initGL : " + e);
784             }
785             return;
786         }
787 
788         final float[] inputColor = { 1.0f, 128 / 255.0f, 0.0f};
789 
790         int updatedCount;
791         updatedCount = activity.waitForSurfaceUpdateCount(0);
792         assertEquals(0, updatedCount);
793         activity.drawColor(inputColor[0], inputColor[1], inputColor[2], 1.0f);
794         updatedCount = activity.waitForSurfaceUpdateCount(1);
795         assertEquals(1, updatedCount);
796 
797         final Bitmap bitmap = activity.getContents(compareFunction.getConfig(),
798                 compareFunction.getColorSpace());
799 
800         // If GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING is GL_SRGB, then glClear will treat the input
801         // color as linear and write a converted sRGB color into the framebuffer.
802         if (isFramebufferLinear) {
803             compareFunction.verify(inputColor, colorSpace, bitmap);
804         } else {
805             ColorSpace.Connector connector;
806             connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB),
807                     ColorSpace.get(ColorSpace.Named.SRGB));
808             float[] outputColor = connector.transform(inputColor);
809             compareFunction.verify(outputColor, colorSpace, bitmap);
810         }
811     }
812 
drawGlQuad(int width, int height)813     private static void drawGlQuad(int width, int height) {
814         int cx = width / 2;
815         int cy = height / 2;
816 
817         glEnable(GL_SCISSOR_TEST);
818 
819         glScissor(0, cy, cx, height - cy);
820         clearColor(Color.RED);
821 
822         glScissor(cx, cy, width - cx, height - cy);
823         clearColor(Color.GREEN);
824 
825         glScissor(0, 0, cx, cy);
826         clearColor(Color.BLUE);
827 
828         glScissor(cx, 0, width - cx, cy);
829         clearColor(Color.BLACK);
830     }
831 
drawGlBlackWhiteLines(int width, int height)832     private static void drawGlBlackWhiteLines(int width, int height) {
833         final int lineHeight = 1;
834         glEnable(GL_SCISSOR_TEST);
835         for (int y = 0; y < height / lineHeight; y++) {
836             glScissor(0, lineHeight * y, width, lineHeight);
837             clearColor((y % 2 == 0) ? Color.BLACK : Color.WHITE);
838         }
839     }
840 
clearColor(int color)841     private static void clearColor(int color) {
842         glClearColor(Color.red(color) / 255.0f,
843                 Color.green(color) / 255.0f,
844                 Color.blue(color) / 255.0f,
845                 Color.alpha(color) / 255.0f);
846         glClear(GL_COLOR_BUFFER_BIT);
847     }
848 
getPixel(Window window, Point point)849     private int getPixel(Window window, Point point) {
850         Bitmap screenshot = Bitmap.createBitmap(window.getDecorView().getWidth(),
851                 window.getDecorView().getHeight(), Bitmap.Config.ARGB_8888);
852         int result = new SynchronousPixelCopy().request(window, screenshot);
853         assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
854         int pixel = screenshot.getPixel(point.x, point.y);
855         screenshot.recycle();
856         return pixel;
857     }
858 
waitForColor(Window window, Point point, int color)859     private void waitForColor(Window window, Point point, int color)
860             throws InterruptedException, TimeoutException {
861         for (int i = 0; i < 20; i++) {
862             int pixel = getPixel(window, point);
863             if (pixel == color) {
864                 return;
865             }
866             Thread.sleep(16);
867         }
868         throw new TimeoutException();
869     }
870 
waitForChange(Window window, Point point, int color)871     private int waitForChange(Window window, Point point, int color)
872             throws InterruptedException, TimeoutException {
873         for (int i = 0; i < 30; i++) {
874             int pixel = getPixel(window, point);
875             if (pixel != color) {
876                 return pixel;
877             }
878             Thread.sleep(16);
879         }
880         throw new TimeoutException();
881     }
882 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight)883     private void assertBitmapQuadColor(Bitmap bitmap,
884             int topLeft, int topRight, int bottomLeft, int bottomRight) {
885         PixelCopyTest.assertBitmapQuadColor(mTestName.getMethodName(), "TextureViewTest",
886                 bitmap, topLeft, topRight, bottomLeft, bottomRight);
887     }
888 
assertBitmapEdgeColor(Bitmap bitmap, int edgeColor)889     private void assertBitmapEdgeColor(Bitmap bitmap, int edgeColor) {
890         // Just quickly sample a few pixels on the edge and assert
891         // they are edge color, then assert that just inside the edge is a different color
892         assertBitmapColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 0);
893         assertBitmapNotColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 2);
894 
895         assertBitmapColor("Left edge", bitmap, edgeColor, 0, bitmap.getHeight() / 2);
896         assertBitmapNotColor("Left edge", bitmap, edgeColor, 2, bitmap.getHeight() / 2);
897 
898         assertBitmapColor("Bottom edge", bitmap, edgeColor,
899                 bitmap.getWidth() / 2, bitmap.getHeight() - 1);
900         assertBitmapNotColor("Bottom edge", bitmap, edgeColor,
901                 bitmap.getWidth() / 2, bitmap.getHeight() - 3);
902 
903         assertBitmapColor("Right edge", bitmap, edgeColor,
904                 bitmap.getWidth() - 1, bitmap.getHeight() / 2);
905         assertBitmapNotColor("Right edge", bitmap, edgeColor,
906                 bitmap.getWidth() - 3, bitmap.getHeight() / 2);
907     }
908 
failBitmap(Bitmap bitmap, String message)909     private void failBitmap(Bitmap bitmap, String message) {
910         BitmapDumper.dumpBitmap(bitmap, mTestName.getMethodName(), "TextureViewTest");
911         Assert.fail(message);
912     }
913 
assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y)914     private void assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y) {
915         int pixel = bitmap.getPixel(x, y);
916         if (!pixelsAreSame(color, pixel, 10)) {
917             failBitmap(bitmap, debug + "; expected=" + Integer.toHexString(color) + ", actual="
918                     + Integer.toHexString(pixel));
919         }
920     }
921 
assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y)922     private void assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y) {
923         int pixel = bitmap.getPixel(x, y);
924         if (pixelsAreSame(color, pixel, 10)) {
925             failBitmap(bitmap, debug + "; actual=" + Integer.toHexString(pixel)
926                     + " shouldn't have matched " + Integer.toHexString(color));
927         }
928     }
929 }
930