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 org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.when;
26 
27 import android.app.Instrumentation;
28 import android.content.Context;
29 import android.content.pm.ActivityInfo;
30 import android.graphics.Bitmap;
31 import android.graphics.Bitmap.Config;
32 import android.graphics.Color;
33 import android.graphics.ColorSpace;
34 import android.graphics.Rect;
35 import android.graphics.SurfaceTexture;
36 import android.os.Debug;
37 import android.os.Debug.MemoryInfo;
38 import android.util.Half;
39 import android.util.Log;
40 import android.view.PixelCopy;
41 import android.view.Surface;
42 import android.view.View;
43 import android.view.Window;
44 import android.view.WindowManager;
45 
46 import androidx.test.InstrumentationRegistry;
47 import androidx.test.filters.LargeTest;
48 import androidx.test.filters.MediumTest;
49 import androidx.test.rule.ActivityTestRule;
50 import androidx.test.runner.AndroidJUnit4;
51 
52 import com.android.compatibility.common.util.SynchronousPixelCopy;
53 
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.rules.TestRule;
58 import org.junit.runner.Description;
59 import org.junit.runner.RunWith;
60 import org.junit.runners.model.Statement;
61 
62 import java.nio.ByteBuffer;
63 import java.nio.ByteOrder;
64 import java.util.concurrent.CountDownLatch;
65 import java.util.concurrent.TimeUnit;
66 
67 @MediumTest
68 @RunWith(AndroidJUnit4.class)
69 public class PixelCopyTest {
70     private static final String TAG = "PixelCopyTests";
71 
72     @Rule
73     public ActivityTestRule<PixelCopyGLProducerCtsActivity> mGLSurfaceViewActivityRule =
74             new ActivityTestRule<>(PixelCopyGLProducerCtsActivity.class, false, false);
75 
76     @Rule
77     public ActivityTestRule<PixelCopyVideoSourceActivity> mVideoSourceActivityRule =
78             new ActivityTestRule<>(PixelCopyVideoSourceActivity.class, false, false);
79 
80     @Rule
81     public ActivityTestRule<PixelCopyViewProducerActivity> mWindowSourceActivityRule =
82             new ActivityTestRule<>(PixelCopyViewProducerActivity.class, false, false);
83 
84     @Rule
85     public ActivityTestRule<PixelCopyWideGamutViewProducerActivity>
86             mWideGamutWindowSourceActivityRule = new ActivityTestRule<>(
87                     PixelCopyWideGamutViewProducerActivity.class, false, false);
88 
89     @Rule
90     public ActivityTestRule<PixelCopyViewProducerDialogActivity> mDialogSourceActivityRule =
91             new ActivityTestRule<>(PixelCopyViewProducerDialogActivity.class, false, false);
92 
93     @Rule
94     public SurfaceTextureRule mSurfaceRule = new SurfaceTextureRule();
95 
96     private Instrumentation mInstrumentation;
97     private SynchronousPixelCopy mCopyHelper;
98 
99     @Before
setup()100     public void setup() {
101         mInstrumentation = InstrumentationRegistry.getInstrumentation();
102         assertNotNull(mInstrumentation);
103         mCopyHelper = new SynchronousPixelCopy();
104     }
105 
106     @Test(expected = IllegalArgumentException.class)
testNullDest()107     public void testNullDest() {
108         Bitmap dest = null;
109         mCopyHelper.request(mSurfaceRule.getSurface(), dest);
110     }
111 
112     @Test(expected = IllegalArgumentException.class)
testRecycledDest()113     public void testRecycledDest() {
114         Bitmap dest = Bitmap.createBitmap(5, 5, Config.ARGB_8888);
115         dest.recycle();
116         mCopyHelper.request(mSurfaceRule.getSurface(), dest);
117     }
118 
119     @Test
testNoSourceData()120     public void testNoSourceData() {
121         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
122         int result = mCopyHelper.request(mSurfaceRule.getSurface(), dest);
123         assertEquals(PixelCopy.ERROR_SOURCE_NO_DATA, result);
124     }
125 
126     @Test(expected = IllegalArgumentException.class)
testEmptySourceRectSurface()127     public void testEmptySourceRectSurface() {
128         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
129         mCopyHelper.request(mSurfaceRule.getSurface(), new Rect(), dest);
130     }
131 
132     @Test(expected = IllegalArgumentException.class)
testEmptySourceRectWindow()133     public void testEmptySourceRectWindow() {
134         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
135         mCopyHelper.request(mock(Window.class), new Rect(), dest);
136     }
137 
138     @Test(expected = IllegalArgumentException.class)
testInvalidSourceRectSurface()139     public void testInvalidSourceRectSurface() {
140         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
141         mCopyHelper.request(mSurfaceRule.getSurface(), new Rect(10, 10, 0, 0), dest);
142     }
143 
144     @Test(expected = IllegalArgumentException.class)
testInvalidSourceRectWindow()145     public void testInvalidSourceRectWindow() {
146         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
147         mCopyHelper.request(mock(Window.class), new Rect(10, 10, 0, 0), dest);
148     }
149 
150     @Test(expected = IllegalArgumentException.class)
testNoDecorView()151     public void testNoDecorView() {
152         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
153         Window mockWindow = mock(Window.class);
154         mCopyHelper.request(mockWindow, dest);
155     }
156 
157     @Test(expected = IllegalArgumentException.class)
testNoViewRoot()158     public void testNoViewRoot() {
159         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
160         Window mockWindow = mock(Window.class);
161         View view = new View(mInstrumentation.getTargetContext());
162         when(mockWindow.peekDecorView()).thenReturn(view);
163         mCopyHelper.request(mockWindow, dest);
164     }
165 
waitForGlProducerActivity()166     private PixelCopyGLProducerCtsActivity waitForGlProducerActivity() {
167         CountDownLatch swapFence = new CountDownLatch(2);
168 
169         PixelCopyGLProducerCtsActivity activity =
170                 mGLSurfaceViewActivityRule.launchActivity(null);
171         activity.setSwapFence(swapFence);
172 
173         try {
174             while (!swapFence.await(5, TimeUnit.MILLISECONDS)) {
175                 activity.getView().requestRender();
176             }
177         } catch (InterruptedException ex) {
178             fail("Interrupted, error=" + ex.getMessage());
179         }
180         return activity;
181     }
182 
183     @Test
testGlProducerFullsize()184     public void testGlProducerFullsize() {
185         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
186         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
187         int result = mCopyHelper.request(activity.getView(), bitmap);
188         assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
189         assertEquals(100, bitmap.getWidth());
190         assertEquals(100, bitmap.getHeight());
191         assertEquals(Config.ARGB_8888, bitmap.getConfig());
192         assertBitmapQuadColor(bitmap,
193                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
194     }
195 
196     @Test
testGlProducerCropTopLeft()197     public void testGlProducerCropTopLeft() {
198         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
199         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
200         int result = mCopyHelper.request(activity.getView(), new Rect(0, 0, 50, 50), bitmap);
201         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
202         assertBitmapQuadColor(bitmap,
203                 Color.RED, Color.RED, Color.RED, Color.RED);
204     }
205 
206     @Test
testGlProducerCropCenter()207     public void testGlProducerCropCenter() {
208         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
209         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
210         int result = mCopyHelper.request(activity.getView(), new Rect(25, 25, 75, 75), bitmap);
211         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
212         assertBitmapQuadColor(bitmap,
213                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
214     }
215 
216     @Test
testGlProducerCropBottomHalf()217     public void testGlProducerCropBottomHalf() {
218         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
219         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
220         int result = mCopyHelper.request(activity.getView(), new Rect(0, 50, 100, 100), bitmap);
221         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
222         assertBitmapQuadColor(bitmap,
223                 Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
224     }
225 
226     @Test
testGlProducerCropClamping()227     public void testGlProducerCropClamping() {
228         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
229         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
230         int result = mCopyHelper.request(activity.getView(), new Rect(50, -50, 150, 50), bitmap);
231         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
232         assertBitmapQuadColor(bitmap,
233                 Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN);
234     }
235 
236     @Test
testGlProducerScaling()237     public void testGlProducerScaling() {
238         // Since we only sample mid-pixel of each qudrant, filtering
239         // quality isn't tested
240         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
241         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
242         int result = mCopyHelper.request(activity.getView(), bitmap);
243         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
244         // Make sure nothing messed with the bitmap
245         assertEquals(20, bitmap.getWidth());
246         assertEquals(20, bitmap.getHeight());
247         assertEquals(Config.ARGB_8888, bitmap.getConfig());
248         assertBitmapQuadColor(bitmap,
249                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
250     }
251 
252     @Test
testReuseBitmap()253     public void testReuseBitmap() {
254         // Since we only sample mid-pixel of each qudrant, filtering
255         // quality isn't tested
256         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
257         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
258         int result = mCopyHelper.request(activity.getView(), bitmap);
259         // Make sure nothing messed with the bitmap
260         assertEquals(20, bitmap.getWidth());
261         assertEquals(20, bitmap.getHeight());
262         assertEquals(Config.ARGB_8888, bitmap.getConfig());
263         assertBitmapQuadColor(bitmap,
264                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
265         int generationId = bitmap.getGenerationId();
266         result = mCopyHelper.request(activity.getView(), bitmap);
267         // Make sure nothing messed with the bitmap
268         assertEquals(20, bitmap.getWidth());
269         assertEquals(20, bitmap.getHeight());
270         assertEquals(Config.ARGB_8888, bitmap.getConfig());
271         assertBitmapQuadColor(bitmap,
272                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
273         assertNotEquals(generationId, bitmap.getGenerationId());
274     }
275 
waitForWindowProducerActivity()276     private Window waitForWindowProducerActivity() {
277         PixelCopyViewProducerActivity activity =
278                 mWindowSourceActivityRule.launchActivity(null);
279         activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
280         return activity.getWindow();
281     }
282 
makeWindowRect(int left, int top, int right, int bottom)283     private Rect makeWindowRect(int left, int top, int right, int bottom) {
284         Rect r = new Rect(left, top, right, bottom);
285         mWindowSourceActivityRule.getActivity().normalizedToSurface(r);
286         return r;
287     }
288 
289     @Test
testWindowProducer()290     public void testWindowProducer() {
291         Bitmap bitmap;
292         Window window = waitForWindowProducerActivity();
293         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
294         do {
295             Rect src = makeWindowRect(0, 0, 100, 100);
296             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
297             int result = mCopyHelper.request(window, src, bitmap);
298             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
299             assertEquals(Config.ARGB_8888, bitmap.getConfig());
300             assertBitmapQuadColor(bitmap,
301                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
302             assertBitmapEdgeColor(bitmap, Color.YELLOW);
303         } while (activity.rotate());
304     }
305 
306     @Test
testWindowProducerCropTopLeft()307     public void testWindowProducerCropTopLeft() {
308         Window window = waitForWindowProducerActivity();
309         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
310         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
311         do {
312             int result = mCopyHelper.request(window, makeWindowRect(0, 0, 50, 50), bitmap);
313             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
314             assertBitmapQuadColor(bitmap,
315                     Color.RED, Color.RED, Color.RED, Color.RED);
316         } while (activity.rotate());
317     }
318 
319     @Test
testWindowProducerCropCenter()320     public void testWindowProducerCropCenter() {
321         Window window = waitForWindowProducerActivity();
322         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
323         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
324         do {
325             int result = mCopyHelper.request(window, makeWindowRect(25, 25, 75, 75), bitmap);
326             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
327             assertBitmapQuadColor(bitmap,
328                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
329         } while (activity.rotate());
330     }
331 
332     @Test
testWindowProducerCropBottomHalf()333     public void testWindowProducerCropBottomHalf() {
334         Window window = waitForWindowProducerActivity();
335         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
336         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
337         do {
338             int result = mCopyHelper.request(window, makeWindowRect(0, 50, 100, 100), bitmap);
339             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
340             assertBitmapQuadColor(bitmap,
341                     Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
342         } while (activity.rotate());
343     }
344 
345     @Test
testWindowProducerScaling()346     public void testWindowProducerScaling() {
347         // Since we only sample mid-pixel of each qudrant, filtering
348         // quality isn't tested
349         Window window = waitForWindowProducerActivity();
350         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
351         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
352         do {
353             int result = mCopyHelper.request(window, bitmap);
354             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
355             // Make sure nothing messed with the bitmap
356             assertEquals(20, bitmap.getWidth());
357             assertEquals(20, bitmap.getHeight());
358             assertEquals(Config.ARGB_8888, bitmap.getConfig());
359             assertBitmapQuadColor(bitmap,
360                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
361         } while (activity.rotate());
362     }
363 
364     @Test
testWindowProducerCopyToRGBA16F()365     public void testWindowProducerCopyToRGBA16F() {
366         Window window = waitForWindowProducerActivity();
367         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
368 
369         Bitmap bitmap;
370         do {
371             Rect src = makeWindowRect(0, 0, 100, 100);
372             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16);
373             int result = mCopyHelper.request(window, src, bitmap);
374             // On OpenGL ES 2.0 devices a copy to RGBA_F16 can fail because there's
375             // not support for float textures
376             if (result != PixelCopy.ERROR_DESTINATION_INVALID) {
377                 assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
378                 assertEquals(Config.RGBA_F16, bitmap.getConfig());
379                 assertBitmapQuadColor(bitmap,
380                         Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
381                 assertBitmapEdgeColor(bitmap, Color.YELLOW);
382             }
383         } while (activity.rotate());
384     }
385 
waitForWideGamutWindowProducerActivity()386     private Window waitForWideGamutWindowProducerActivity() {
387         PixelCopyWideGamutViewProducerActivity activity =
388                 mWideGamutWindowSourceActivityRule.launchActivity(null);
389         activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
390         return activity.getWindow();
391     }
392 
makeWideGamutWindowRect(int left, int top, int right, int bottom)393     private Rect makeWideGamutWindowRect(int left, int top, int right, int bottom) {
394         Rect r = new Rect(left, top, right, bottom);
395         mWideGamutWindowSourceActivityRule.getActivity().offsetForContent(r);
396         return r;
397     }
398 
399     @Test
testWideGamutWindowProducerCopyToRGBA8888()400     public void testWideGamutWindowProducerCopyToRGBA8888() {
401         Window window = waitForWideGamutWindowProducerActivity();
402         assertEquals(
403                 ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT, window.getAttributes().getColorMode());
404 
405         // Early out if the device does not support wide color gamut rendering
406         if (!window.isWideColorGamut()) {
407             return;
408         }
409 
410         PixelCopyWideGamutViewProducerActivity activity =
411                 mWideGamutWindowSourceActivityRule.getActivity();
412 
413         Bitmap bitmap;
414         do {
415             Rect src = makeWideGamutWindowRect(0, 0, 128, 128);
416             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
417             int result = mCopyHelper.request(window, src, bitmap);
418 
419             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
420             assertEquals(Config.ARGB_8888, bitmap.getConfig());
421 
422             assertEquals("Top left", Color.RED, bitmap.getPixel(32, 32));
423             assertEquals("Top right", Color.GREEN, bitmap.getPixel(96, 32));
424             assertEquals("Bottom left", Color.BLUE, bitmap.getPixel(32, 96));
425             assertEquals("Bottom right", Color.YELLOW, bitmap.getPixel(96, 96));
426         } while (activity.rotate());
427     }
428 
429     @Test
testWideGamutWindowProducerCopyToRGBA16F()430     public void testWideGamutWindowProducerCopyToRGBA16F() {
431         Window window = waitForWideGamutWindowProducerActivity();
432         assertEquals(
433                 ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT, window.getAttributes().getColorMode());
434 
435         // Early out if the device does not support wide color gamut rendering
436         if (!window.isWideColorGamut()) {
437             return;
438         }
439 
440         PixelCopyWideGamutViewProducerActivity activity =
441                 mWideGamutWindowSourceActivityRule.getActivity();
442         final WindowManager windowManager = (WindowManager) activity.getSystemService(
443                 Context.WINDOW_SERVICE);
444         final ColorSpace colorSpace = windowManager.getDefaultDisplay()
445                 .getPreferredWideGamutColorSpace();
446         final ColorSpace.Connector proPhotoToDisplayWideColorSpace = ColorSpace.connect(
447                 ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB), colorSpace);
448         final ColorSpace.Connector displayWideColorSpaceToExtendedSrgb = ColorSpace.connect(
449                 colorSpace, ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
450 
451         final float[] intermediateRed = proPhotoToDisplayWideColorSpace.transform(1.0f, 0.0f, 0.0f);
452         final float[] intermediateGreen = proPhotoToDisplayWideColorSpace
453                 .transform(0.0f, 1.0f, 0.0f);
454         final float[] intermediateBlue = proPhotoToDisplayWideColorSpace
455                 .transform(0.0f, 0.0f, 1.0f);
456         final float[] intermediateYellow = proPhotoToDisplayWideColorSpace
457                 .transform(1.0f, 1.0f, 0.0f);
458 
459         final float[] expectedRed = displayWideColorSpaceToExtendedSrgb.transform(intermediateRed);
460         final float[] expectedGreen = displayWideColorSpaceToExtendedSrgb
461                 .transform(intermediateGreen);
462         final float[] expectedBlue = displayWideColorSpaceToExtendedSrgb
463                 .transform(intermediateBlue);
464         final float[] expectedYellow = displayWideColorSpaceToExtendedSrgb
465                 .transform(intermediateYellow);
466 
467         Bitmap bitmap;
468         int i = 0;
469         do {
470             Rect src = makeWideGamutWindowRect(0, 0, 128, 128);
471             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16, true,
472                     ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
473             int result = mCopyHelper.request(window, src, bitmap);
474 
475             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
476             assertEquals(Config.RGBA_F16, bitmap.getConfig());
477 
478             ByteBuffer dst = ByteBuffer.allocateDirect(bitmap.getAllocationByteCount());
479             bitmap.copyPixelsToBuffer(dst);
480             dst.rewind();
481             dst.order(ByteOrder.LITTLE_ENDIAN);
482 
483             // ProPhoto RGB red in scRGB-nl
484             assertEqualsRgba16f("Top left",     bitmap, 32, 32, dst, expectedRed[0],
485                     expectedRed[1], expectedRed[2], 1.0f);
486             // ProPhoto RGB green in scRGB-nl
487             assertEqualsRgba16f("Top right",    bitmap, 96, 32, dst, expectedGreen[0],
488                     expectedGreen[1], expectedGreen[2], 1.0f);
489             // ProPhoto RGB blue in scRGB-nl
490             assertEqualsRgba16f("Bottom left",  bitmap, 32, 96, dst, expectedBlue[0],
491                     expectedBlue[1], expectedBlue[2], 1.0f);
492             // ProPhoto RGB yellow in scRGB-nl
493             assertEqualsRgba16f("Bottom right", bitmap, 96, 96, dst, expectedYellow[0],
494                     expectedYellow[1], expectedYellow[2], 1.0f);
495         } while (activity.rotate());
496     }
497 
waitForDialogProducerActivity()498     private Window waitForDialogProducerActivity() {
499         PixelCopyViewProducerActivity activity =
500                 mDialogSourceActivityRule.launchActivity(null);
501         activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
502         return activity.getWindow();
503     }
504 
makeDialogRect(int left, int top, int right, int bottom)505     private Rect makeDialogRect(int left, int top, int right, int bottom) {
506         Rect r = new Rect(left, top, right, bottom);
507         mDialogSourceActivityRule.getActivity().normalizedToSurface(r);
508         return r;
509     }
510 
511     @Test
testDialogProducer()512     public void testDialogProducer() {
513         Bitmap bitmap;
514         Window window = waitForDialogProducerActivity();
515         PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
516         do {
517             Rect src = makeDialogRect(0, 0, 100, 100);
518             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
519             int result = mCopyHelper.request(window, src, bitmap);
520             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
521             assertEquals(Config.ARGB_8888, bitmap.getConfig());
522             assertBitmapQuadColor(bitmap,
523                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
524             assertBitmapEdgeColor(bitmap, Color.YELLOW);
525         } while (activity.rotate());
526     }
527 
528     @Test
testDialogProducerCropTopLeft()529     public void testDialogProducerCropTopLeft() {
530         Window window = waitForDialogProducerActivity();
531         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
532         PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
533         do {
534             int result = mCopyHelper.request(window, makeDialogRect(0, 0, 50, 50), bitmap);
535             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
536             assertBitmapQuadColor(bitmap,
537                     Color.RED, Color.RED, Color.RED, Color.RED);
538         } while (activity.rotate());
539     }
540 
541     @Test
testDialogProducerCropCenter()542     public void testDialogProducerCropCenter() {
543         Window window = waitForDialogProducerActivity();
544         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
545         PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
546         do {
547             int result = mCopyHelper.request(window, makeDialogRect(25, 25, 75, 75), bitmap);
548             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
549             assertBitmapQuadColor(bitmap,
550                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
551         } while (activity.rotate());
552     }
553 
554     @Test
testDialogProducerCropBottomHalf()555     public void testDialogProducerCropBottomHalf() {
556         Window window = waitForDialogProducerActivity();
557         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
558         PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
559         do {
560             int result = mCopyHelper.request(window, makeDialogRect(0, 50, 100, 100), bitmap);
561             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
562             assertBitmapQuadColor(bitmap,
563                     Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
564         } while (activity.rotate());
565     }
566 
567     @Test
testDialogProducerScaling()568     public void testDialogProducerScaling() {
569         // Since we only sample mid-pixel of each qudrant, filtering
570         // quality isn't tested
571         Window window = waitForDialogProducerActivity();
572         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
573         PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
574         do {
575             int result = mCopyHelper.request(window, bitmap);
576             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
577             // Make sure nothing messed with the bitmap
578             assertEquals(20, bitmap.getWidth());
579             assertEquals(20, bitmap.getHeight());
580             assertEquals(Config.ARGB_8888, bitmap.getConfig());
581             assertBitmapQuadColor(bitmap,
582                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
583         } while (activity.rotate());
584     }
585 
586     @Test
testDialogProducerCopyToRGBA16F()587     public void testDialogProducerCopyToRGBA16F() {
588         Window window = waitForDialogProducerActivity();
589         PixelCopyViewProducerActivity activity = mDialogSourceActivityRule.getActivity();
590 
591         Bitmap bitmap;
592         do {
593             Rect src = makeDialogRect(0, 0, 100, 100);
594             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16);
595             int result = mCopyHelper.request(window, src, bitmap);
596             // On OpenGL ES 2.0 devices a copy to RGBA_F16 can fail because there's
597             // not support for float textures
598             if (result != PixelCopy.ERROR_DESTINATION_INVALID) {
599                 assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
600                 assertEquals(Config.RGBA_F16, bitmap.getConfig());
601                 assertBitmapQuadColor(bitmap,
602                         Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
603                 assertBitmapEdgeColor(bitmap, Color.YELLOW);
604             }
605         } while (activity.rotate());
606     }
607 
assertEqualsRgba16f(String message, Bitmap bitmap, int x, int y, ByteBuffer dst, float r, float g, float b, float a)608     private static void assertEqualsRgba16f(String message, Bitmap bitmap, int x, int y,
609             ByteBuffer dst, float r, float g, float b, float a) {
610         int index = y * bitmap.getRowBytes() + (x << 3);
611         short cR = dst.getShort(index);
612         short cG = dst.getShort(index + 2);
613         short cB = dst.getShort(index + 4);
614         short cA = dst.getShort(index + 6);
615 
616         assertEquals(message, r, Half.toFloat(cR), 0.01);
617         assertEquals(message, g, Half.toFloat(cG), 0.01);
618         assertEquals(message, b, Half.toFloat(cB), 0.01);
619         assertEquals(message, a, Half.toFloat(cA), 0.01);
620     }
621 
runGcAndFinalizersSync()622     private void runGcAndFinalizersSync() {
623         final CountDownLatch fence = new CountDownLatch(1);
624         new Object() {
625             @Override
626             protected void finalize() throws Throwable {
627                 try {
628                     fence.countDown();
629                 } finally {
630                     super.finalize();
631                 }
632             }
633         };
634         try {
635             do {
636                 Runtime.getRuntime().gc();
637                 Runtime.getRuntime().runFinalization();
638             } while (!fence.await(100, TimeUnit.MILLISECONDS));
639         } catch (InterruptedException ex) {
640             throw new RuntimeException(ex);
641         }
642         Runtime.getRuntime().gc();
643     }
644 
assertNotLeaking(int iteration, MemoryInfo start, MemoryInfo end)645     private void assertNotLeaking(int iteration, MemoryInfo start, MemoryInfo end) {
646         Debug.getMemoryInfo(end);
647         if (end.getTotalPss() - start.getTotalPss() > 2000 /* kB */) {
648             runGcAndFinalizersSync();
649             Debug.getMemoryInfo(end);
650             if (end.getTotalPss() - start.getTotalPss() > 2000 /* kB */) {
651                 // Guarded by if so we don't continually generate garbage for the
652                 // assertion string.
653                 assertEquals("Memory leaked, iteration=" + iteration,
654                         start.getTotalPss(), end.getTotalPss(),
655                         2000 /* kb */);
656             }
657         }
658     }
659 
660     @Test
661     @LargeTest
testNotLeaking()662     public void testNotLeaking() {
663         try {
664             CountDownLatch swapFence = new CountDownLatch(2);
665 
666             PixelCopyGLProducerCtsActivity activity =
667                     mGLSurfaceViewActivityRule.launchActivity(null);
668             activity.setSwapFence(swapFence);
669 
670             while (!swapFence.await(5, TimeUnit.MILLISECONDS)) {
671                 activity.getView().requestRender();
672             }
673 
674             // Test a fullsize copy
675             Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
676 
677             MemoryInfo meminfoStart = new MemoryInfo();
678             MemoryInfo meminfoEnd = new MemoryInfo();
679 
680             for (int i = 0; i < 1000; i++) {
681                 if (i == 2) {
682                     // Not really the "start" but by having done a couple
683                     // we've fully initialized any state that may be required,
684                     // so memory usage should be stable now
685                     runGcAndFinalizersSync();
686                     Debug.getMemoryInfo(meminfoStart);
687                 }
688                 if (i % 100 == 5) {
689                     assertNotLeaking(i, meminfoStart, meminfoEnd);
690                 }
691                 int result = mCopyHelper.request(activity.getView(), bitmap);
692                 assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
693                 // Make sure nothing messed with the bitmap
694                 assertEquals(100, bitmap.getWidth());
695                 assertEquals(100, bitmap.getHeight());
696                 assertEquals(Config.ARGB_8888, bitmap.getConfig());
697                 assertBitmapQuadColor(bitmap,
698                         Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
699             }
700 
701             assertNotLeaking(1000, meminfoStart, meminfoEnd);
702 
703         } catch (InterruptedException e) {
704             fail("Interrupted, error=" + e.getMessage());
705         }
706     }
707 
708     @Test
testVideoProducer()709     public void testVideoProducer() throws InterruptedException {
710         PixelCopyVideoSourceActivity activity =
711                 mVideoSourceActivityRule.launchActivity(null);
712         if (!activity.canPlayVideo()) {
713             Log.i(TAG, "Skipping testVideoProducer, video codec isn't supported");
714             return;
715         }
716         // This returns when the video has been prepared and playback has
717         // been started, it doesn't necessarily means a frame has actually been
718         // produced. There sadly isn't a callback for that.
719         // So we'll try for up to 900ms after this event to acquire a frame, otherwise
720         // it's considered a timeout.
721         activity.waitForPlaying();
722         assertTrue("Failed to start video playback", activity.canPlayVideo());
723         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
724         int copyResult = PixelCopy.ERROR_SOURCE_NO_DATA;
725         for (int i = 0; i < 30; i++) {
726             copyResult = mCopyHelper.request(activity.getVideoView(), bitmap);
727             if (copyResult != PixelCopy.ERROR_SOURCE_NO_DATA) {
728                 break;
729             }
730             Thread.sleep(30);
731         }
732         assertEquals(PixelCopy.SUCCESS, copyResult);
733         // A large threshold is used because decoder accuracy is covered in the
734         // media CTS tests, so we are mainly interested in verifying that rotation
735         // and YUV->RGB conversion were handled properly.
736         assertBitmapQuadColor(bitmap, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, 30);
737 
738         // Test that cropping works.
739         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(0, 0, 50, 50), bitmap);
740         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
741         assertBitmapQuadColor(bitmap,
742                 Color.RED, Color.RED, Color.RED, Color.RED, 30);
743 
744         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(25, 25, 75, 75), bitmap);
745         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
746         assertBitmapQuadColor(bitmap,
747                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, 30);
748 
749         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(0, 50, 100, 100), bitmap);
750         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
751         assertBitmapQuadColor(bitmap,
752                 Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK, 30);
753 
754         // Test that clamping works
755         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(50, -50, 150, 50), bitmap);
756         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
757         assertBitmapQuadColor(bitmap,
758                 Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN, 30);
759     }
760 
getPixelFloatPos(Bitmap bitmap, float xpos, float ypos)761     private static int getPixelFloatPos(Bitmap bitmap, float xpos, float ypos) {
762         return bitmap.getPixel((int) (bitmap.getWidth() * xpos), (int) (bitmap.getHeight() * ypos));
763     }
764 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight)765     public static void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
766                 int bottomLeft, int bottomRight) {
767         // Just quickly sample 4 pixels in the various regions.
768         assertEquals("Top left " + Integer.toHexString(topLeft) + ", actual= "
769                 + Integer.toHexString(getPixelFloatPos(bitmap, .25f, .25f)),
770                 topLeft, getPixelFloatPos(bitmap, .25f, .25f));
771         assertEquals("Top right", topRight, getPixelFloatPos(bitmap, .75f, .25f));
772         assertEquals("Bottom left", bottomLeft, getPixelFloatPos(bitmap, .25f, .75f));
773         assertEquals("Bottom right", bottomRight, getPixelFloatPos(bitmap, .75f, .75f));
774     }
775 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight, int threshold)776     private void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
777             int bottomLeft, int bottomRight, int threshold) {
778         // Just quickly sample 4 pixels in the various regions.
779         assertTrue("Top left", pixelsAreSame(topLeft, getPixelFloatPos(bitmap, .25f, .25f),
780                 threshold));
781         assertTrue("Top right", pixelsAreSame(topRight, getPixelFloatPos(bitmap, .75f, .25f),
782                 threshold));
783         assertTrue("Bottom left", pixelsAreSame(bottomLeft, getPixelFloatPos(bitmap, .25f, .75f),
784                 threshold));
785         assertTrue("Bottom right", pixelsAreSame(bottomRight, getPixelFloatPos(bitmap, .75f, .75f),
786                 threshold));
787     }
788 
assertBitmapEdgeColor(Bitmap bitmap, int edgeColor)789     private void assertBitmapEdgeColor(Bitmap bitmap, int edgeColor) {
790         // Just quickly sample a few pixels on the edge and assert
791         // they are edge color, then assert that just inside the edge is a different color
792         assertBitmapColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 1);
793         assertBitmapNotColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 2);
794 
795         assertBitmapColor("Left edge", bitmap, edgeColor, 1, bitmap.getHeight() / 2);
796         assertBitmapNotColor("Left edge", bitmap, edgeColor, 2, bitmap.getHeight() / 2);
797 
798         assertBitmapColor("Bottom edge", bitmap, edgeColor,
799                 bitmap.getWidth() / 2, bitmap.getHeight() - 2);
800         assertBitmapNotColor("Bottom edge", bitmap, edgeColor,
801                 bitmap.getWidth() / 2, bitmap.getHeight() - 3);
802 
803         assertBitmapColor("Right edge", bitmap, edgeColor,
804                 bitmap.getWidth() - 2, bitmap.getHeight() / 2);
805         assertBitmapNotColor("Right edge", bitmap, edgeColor,
806                 bitmap.getWidth() - 3, bitmap.getHeight() / 2);
807     }
808 
pixelsAreSame(int ideal, int given, int threshold)809     private boolean pixelsAreSame(int ideal, int given, int threshold) {
810         int error = Math.abs(Color.red(ideal) - Color.red(given));
811         error += Math.abs(Color.green(ideal) - Color.green(given));
812         error += Math.abs(Color.blue(ideal) - Color.blue(given));
813         return (error < threshold);
814     }
815 
assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y)816     private void assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y) {
817         int pixel = bitmap.getPixel(x, y);
818         if (!pixelsAreSame(color, pixel, 10)) {
819             fail(debug + "; expected=" + Integer.toHexString(color) + ", actual="
820                     + Integer.toHexString(pixel));
821         }
822     }
823 
assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y)824     private void assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y) {
825         int pixel = bitmap.getPixel(x, y);
826         if (pixelsAreSame(color, pixel, 10)) {
827             fail(debug + "; actual=" + Integer.toHexString(pixel)
828                     + " shouldn't have matched " + Integer.toHexString(color));
829         }
830     }
831 
832     private static class SurfaceTextureRule implements TestRule {
833         private SurfaceTexture mSurfaceTexture = null;
834         private Surface mSurface = null;
835 
createIfNecessary()836         private void createIfNecessary() {
837             mSurfaceTexture = new SurfaceTexture(false);
838             mSurface = new Surface(mSurfaceTexture);
839         }
840 
getSurface()841         public Surface getSurface() {
842             createIfNecessary();
843             return mSurface;
844         }
845 
846         @Override
apply(Statement base, Description description)847         public Statement apply(Statement base, Description description) {
848             return new CreateSurfaceTextureStatement(base);
849         }
850 
851         private class CreateSurfaceTextureStatement extends Statement {
852 
853             private final Statement mBase;
854 
CreateSurfaceTextureStatement(Statement base)855             public CreateSurfaceTextureStatement(Statement base) {
856                 mBase = base;
857             }
858 
859             @Override
evaluate()860             public void evaluate() throws Throwable {
861                 try {
862                     mBase.evaluate();
863                 } finally {
864                     try {
865                         if (mSurface != null) mSurface.release();
866                     } catch (Throwable t) {}
867                     try {
868                         if (mSurfaceTexture != null) mSurfaceTexture.release();
869                     } catch (Throwable t) {}
870                 }
871             }
872         }
873     }
874 }
875