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 com.android.compatibility.common.util.SynchronousPixelCopy.copySurface;
20 import static com.android.compatibility.common.util.SynchronousPixelCopy.copyWindow;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotEquals;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertTrue;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.when;
28 
29 import android.Manifest;
30 import android.app.Activity;
31 import android.app.ActivityOptions;
32 import android.app.Instrumentation;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.ActivityInfo;
36 import android.graphics.Bitmap;
37 import android.graphics.Bitmap.Config;
38 import android.graphics.Canvas;
39 import android.graphics.Color;
40 import android.graphics.ColorSpace;
41 import android.graphics.Paint;
42 import android.graphics.PixelFormat;
43 import android.graphics.Rect;
44 import android.graphics.SurfaceTexture;
45 import android.hardware.HardwareBuffer;
46 import android.media.Image;
47 import android.media.ImageReader;
48 import android.media.ImageWriter;
49 import android.os.Debug;
50 import android.platform.test.annotations.AppModeSdkSandbox;
51 import android.server.wm.SetRequestedOrientationRule;
52 import android.util.Half;
53 import android.util.Log;
54 import android.util.Pair;
55 import android.view.PixelCopy;
56 import android.view.Surface;
57 import android.view.View;
58 import android.view.Window;
59 import android.view.WindowManager;
60 import android.view.cts.util.BitmapDumper;
61 
62 import androidx.test.InstrumentationRegistry;
63 import androidx.test.filters.LargeTest;
64 import androidx.test.filters.MediumTest;
65 import androidx.test.rule.ActivityTestRule;
66 import androidx.test.runner.AndroidJUnit4;
67 
68 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
69 import com.android.compatibility.common.util.SynchronousPixelCopy;
70 
71 import org.junit.Assert;
72 import org.junit.Before;
73 import org.junit.ClassRule;
74 import org.junit.Rule;
75 import org.junit.Test;
76 import org.junit.rules.ExternalResource;
77 import org.junit.rules.TestName;
78 import org.junit.rules.TestRule;
79 import org.junit.runner.Description;
80 import org.junit.runner.RunWith;
81 import org.junit.runners.model.Statement;
82 
83 import java.io.File;
84 import java.nio.ByteBuffer;
85 import java.nio.ByteOrder;
86 import java.util.ArrayList;
87 import java.util.concurrent.CountDownLatch;
88 import java.util.concurrent.TimeUnit;
89 import java.util.function.Function;
90 
91 @MediumTest
92 @RunWith(AndroidJUnit4.class)
93 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
94 public class PixelCopyTest {
95     private static final String TAG = "PixelCopyTests";
96 
97     @ClassRule
98     public static SetRequestedOrientationRule mSetRequestedOrientationRule =
99             new SetRequestedOrientationRule();
100 
101     @Rule(order = 0)
102     public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
103             androidx.test.platform.app.InstrumentationRegistry
104                     .getInstrumentation().getUiAutomation(),
105             Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
106 
107     @Rule(order = 1)
108     public ActivityTestRule<PixelCopyGLProducerCtsActivity> mGLSurfaceViewActivityRule =
109             new ActivityTestRule<>(PixelCopyGLProducerCtsActivity.class, false, false);
110 
111     @Rule(order = 1)
112     public ActivityTestRule<PixelCopyVideoSourceActivity> mVideoSourceActivityRule =
113             new ActivityTestRule<>(PixelCopyVideoSourceActivity.class, false, false);
114 
115     public static class FullscreenActivityRule extends ExternalResource {
116         private final ArrayList<Activity> mActivities = new ArrayList<>();
117 
launch(Class<T> klass)118         public <T extends Activity> T launch(Class<T> klass) {
119             final Pair<Intent, ActivityOptions> args =
120                     SetRequestedOrientationRule.buildFullScreenLaunchArgs(klass);
121             final T activity = (T) InstrumentationRegistry.getInstrumentation()
122                     .startActivitySync(args.first, args.second.toBundle());
123             mActivities.add(activity);
124             return activity;
125         }
126 
127         @Override
after()128         protected void after() {
129             if (mActivities.isEmpty()) return;
130             InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
131                 for (final Activity activity : mActivities) {
132                     activity.finish();
133                 }
134             });
135         }
136     }
137 
138     @Rule
139     public FullscreenActivityRule mFullscreenActivityRule = new FullscreenActivityRule();
140 
141     @Rule
142     public SurfaceTextureRule mSurfaceRule = new SurfaceTextureRule();
143 
144     @Rule
145     public TestName mTestName = new TestName();
146 
147     private Instrumentation mInstrumentation;
148     private SynchronousPixelCopy mCopyHelper;
149 
150     @Before
setup()151     public void setup() {
152         mInstrumentation = InstrumentationRegistry.getInstrumentation();
153         assertNotNull(mInstrumentation);
154         mCopyHelper = new SynchronousPixelCopy();
155     }
156 
157     @Test(expected = IllegalArgumentException.class)
testNullDest()158     public void testNullDest() {
159         Bitmap dest = null;
160         mCopyHelper.request(mSurfaceRule.getSurface(), dest);
161     }
162 
163     @Test(expected = IllegalArgumentException.class)
testRecycledDest()164     public void testRecycledDest() {
165         Bitmap dest = Bitmap.createBitmap(5, 5, Config.ARGB_8888);
166         dest.recycle();
167         mCopyHelper.request(mSurfaceRule.getSurface(), dest);
168     }
169 
170     @Test
testNoSourceData()171     public void testNoSourceData() {
172         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
173         int result = mCopyHelper.request(mSurfaceRule.getSurface(), dest);
174         assertEquals(PixelCopy.ERROR_SOURCE_NO_DATA, result);
175     }
176 
177     @Test(expected = IllegalArgumentException.class)
testEmptySourceRectSurface()178     public void testEmptySourceRectSurface() {
179         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
180         mCopyHelper.request(mSurfaceRule.getSurface(), new Rect(), dest);
181     }
182 
183     @Test(expected = IllegalArgumentException.class)
testEmptySourceRectWindow()184     public void testEmptySourceRectWindow() {
185         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
186         mCopyHelper.request(mock(Window.class), new Rect(), dest);
187     }
188 
189     @Test(expected = IllegalArgumentException.class)
testInvalidSourceRectSurface()190     public void testInvalidSourceRectSurface() {
191         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
192         mCopyHelper.request(mSurfaceRule.getSurface(), new Rect(10, 10, 0, 0), dest);
193     }
194 
195     @Test(expected = IllegalArgumentException.class)
testInvalidSourceRectWindow()196     public void testInvalidSourceRectWindow() {
197         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
198         mCopyHelper.request(mock(Window.class), new Rect(10, 10, 0, 0), dest);
199     }
200 
201     @Test(expected = IllegalArgumentException.class)
testNoDecorView()202     public void testNoDecorView() {
203         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
204         Window mockWindow = mock(Window.class);
205         mCopyHelper.request(mockWindow, dest);
206     }
207 
208     @Test(expected = IllegalArgumentException.class)
testNoViewRoot()209     public void testNoViewRoot() {
210         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
211         Window mockWindow = mock(Window.class);
212         View view = new View(mInstrumentation.getTargetContext());
213         when(mockWindow.peekDecorView()).thenReturn(view);
214         mCopyHelper.request(mockWindow, dest);
215     }
216 
217     @Test
testRequestGetters()218     public void testRequestGetters() {
219         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
220         Bitmap dest = Bitmap.createBitmap(5, 5, Config.ARGB_8888);
221         Rect source = new Rect(3, 3, 40, 50);
222         PixelCopy.Request request = PixelCopy.Request.Builder.ofWindow(activity.getWindow())
223                 .setSourceRect(source)
224                 .setDestinationBitmap(dest)
225                 .build();
226         assertEquals(dest, request.getDestinationBitmap());
227         assertEquals(source, request.getSourceRect());
228     }
229 
waitForGlProducerActivity()230     private PixelCopyGLProducerCtsActivity waitForGlProducerActivity() {
231         PixelCopyGLProducerCtsActivity activity =
232                 mGLSurfaceViewActivityRule.launchActivity(null);
233         activity.waitForReady();
234         return activity;
235     }
236 
237     @Test
testGlProducerFullsize()238     public void testGlProducerFullsize() {
239         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
240         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
241         int result = mCopyHelper.request(activity.getView(), bitmap);
242         assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
243         assertEquals(100, bitmap.getWidth());
244         assertEquals(100, bitmap.getHeight());
245         assertEquals(Config.ARGB_8888, bitmap.getConfig());
246         assertBitmapQuadColor(bitmap,
247                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
248     }
249 
250     @Test
testGlProducerAutoSize()251     public void testGlProducerAutoSize() {
252         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
253         PixelCopy.Result result = copySurface(activity.getView());
254         assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result.getStatus());
255         Bitmap bitmap = result.getBitmap();
256         assertEquals(100, bitmap.getWidth());
257         assertEquals(100, bitmap.getHeight());
258         assertEquals(Config.ARGB_8888, bitmap.getConfig());
259         assertBitmapQuadColor(bitmap,
260                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
261     }
262 
263     @Test
testGlProducerCropTopLeft()264     public void testGlProducerCropTopLeft() {
265         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
266         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
267         int result = mCopyHelper.request(activity.getView(), new Rect(0, 0, 50, 50), bitmap);
268         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
269         assertBitmapQuadColor(bitmap,
270                 Color.RED, Color.RED, Color.RED, Color.RED);
271     }
272 
273     @Test
testGlProducerCropCenter()274     public void testGlProducerCropCenter() {
275         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
276         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
277         int result = mCopyHelper.request(activity.getView(), new Rect(25, 25, 75, 75), bitmap);
278         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
279         assertBitmapQuadColor(bitmap,
280                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
281     }
282 
283     @Test
testGlProducerCropBottomHalf()284     public void testGlProducerCropBottomHalf() {
285         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
286         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
287         int result = mCopyHelper.request(activity.getView(), new Rect(0, 50, 100, 100), bitmap);
288         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
289         assertBitmapQuadColor(bitmap,
290                 Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
291     }
292 
293     @Test
testGlProducerCropClamping()294     public void testGlProducerCropClamping() {
295         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
296         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
297         int result = mCopyHelper.request(activity.getView(), new Rect(50, -50, 150, 50), bitmap);
298         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
299         assertBitmapQuadColor(bitmap,
300                 Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN);
301     }
302 
303     @Test
testGlProducerScaling()304     public void testGlProducerScaling() {
305         // Since we only sample mid-pixel of each qudrant, filtering
306         // quality isn't tested
307         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
308         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
309         int result = mCopyHelper.request(activity.getView(), bitmap);
310         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
311         // Make sure nothing messed with the bitmap
312         assertEquals(20, bitmap.getWidth());
313         assertEquals(20, bitmap.getHeight());
314         assertEquals(Config.ARGB_8888, bitmap.getConfig());
315         assertBitmapQuadColor(bitmap,
316                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
317     }
318 
319     @Test
testReuseBitmap()320     public void testReuseBitmap() {
321         // Since we only sample mid-pixel of each qudrant, filtering
322         // quality isn't tested
323         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
324         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
325         int result = mCopyHelper.request(activity.getView(), bitmap);
326         assertEquals(result, PixelCopy.SUCCESS);
327         // Make sure nothing messed with the bitmap
328         assertEquals(20, bitmap.getWidth());
329         assertEquals(20, bitmap.getHeight());
330         assertEquals(Config.ARGB_8888, bitmap.getConfig());
331         assertBitmapQuadColor(bitmap,
332                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
333         bitmap.eraseColor(Color.MAGENTA);
334         int generationId = bitmap.getGenerationId();
335         result = mCopyHelper.request(activity.getView(), bitmap);
336         assertEquals(result, PixelCopy.SUCCESS);
337         // Make sure nothing messed with the bitmap
338         assertEquals(20, bitmap.getWidth());
339         assertEquals(20, bitmap.getHeight());
340         assertEquals(Config.ARGB_8888, bitmap.getConfig());
341         assertBitmapQuadColor(bitmap,
342                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
343         assertNotEquals(generationId, bitmap.getGenerationId());
344     }
345 
waitForWindowProducerActivity()346     private PixelCopyViewProducerActivity waitForWindowProducerActivity() {
347         PixelCopyViewProducerActivity activity = mFullscreenActivityRule.launch(
348                         PixelCopyViewProducerActivity.class);
349         activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
350         return activity;
351     }
352 
makeWindowRect( PixelCopyViewProducerActivity activity, int left, int top, int right, int bottom)353     private Rect makeWindowRect(
354             PixelCopyViewProducerActivity activity, int left, int top, int right, int bottom) {
355         Rect r = new Rect(left, top, right, bottom);
356         activity.normalizedToSurface(r);
357         return r;
358     }
359 
360     @Test
testViewProducer()361     public void testViewProducer() {
362         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
363         do {
364             final Rect src = makeWindowRect(activity, 0, 0, 100, 100);
365             final Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(),
366                     Config.ARGB_8888);
367             int result = copyWindow(activity.getContentView(), request -> {
368                 request.setDestinationBitmap(bitmap);
369                 request.setSourceRect(src);
370             }).getStatus();
371             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
372             assertEquals(Config.ARGB_8888, bitmap.getConfig());
373             assertBitmapQuadColor(bitmap,
374                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
375             assertBitmapEdgeColor(bitmap, Color.YELLOW);
376         } while (activity.rotate());
377     }
378 
379     @Test
testWindowProducerAutoSize()380     public void testWindowProducerAutoSize() {
381         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
382         Window window = activity.getWindow();
383         do {
384             PixelCopy.Result result = copyWindow(window);
385             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS,
386                     result.getStatus());
387             final Bitmap bitmap = result.getBitmap();
388             assertEquals(Config.ARGB_8888, bitmap.getConfig());
389             final View decorView = window.getDecorView();
390             assertTrue(bitmap.getWidth() >= decorView.getWidth());
391             assertTrue(bitmap.getHeight() >= decorView.getHeight());
392             // We can't directly assert qualities of the bitmap because the View's location
393             // is going to be affected by padding/insets.
394         } while (activity.rotate());
395     }
396 
397     @Test
testViewProducerAutoSizeWithSrc()398     public void testViewProducerAutoSizeWithSrc() {
399         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
400         do {
401             final Rect src = makeWindowRect(activity, 0, 0, 100, 100);
402             PixelCopy.Result result = copyWindow(activity.getContentView(), request -> {
403                 request.setSourceRect(src);
404             });
405             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result.getStatus());
406             final Bitmap bitmap = result.getBitmap();
407             assertEquals(Config.ARGB_8888, bitmap.getConfig());
408             assertEquals(src.width(), bitmap.getWidth());
409             assertEquals(src.height(), bitmap.getHeight());
410             assertBitmapQuadColor(bitmap,
411                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
412             assertBitmapEdgeColor(bitmap, Color.YELLOW);
413         } while (activity.rotate());
414     }
415 
416     @Test
testWindowProducerCropTopLeft()417     public void testWindowProducerCropTopLeft() {
418         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
419         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
420         do {
421             int result = mCopyHelper.request(
422                     activity.getWindow(), makeWindowRect(activity, 0, 0, 50, 50), bitmap);
423             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
424             assertBitmapQuadColor(bitmap,
425                     Color.RED, Color.RED, Color.RED, Color.RED);
426         } while (activity.rotate());
427     }
428 
429     @Test
testWindowProducerCropCenter()430     public void testWindowProducerCropCenter() {
431         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
432         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
433         do {
434             int result = mCopyHelper.request(
435                     activity.getWindow(), makeWindowRect(activity, 25, 25, 75, 75), bitmap);
436             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
437             assertBitmapQuadColor(bitmap,
438                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
439         } while (activity.rotate());
440     }
441 
442     @Test
testWindowProducerCropBottomHalf()443     public void testWindowProducerCropBottomHalf() {
444         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
445         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
446         do {
447             int result = mCopyHelper.request(
448                     activity.getWindow(), makeWindowRect(activity, 0, 50, 100, 100), bitmap);
449             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
450             assertBitmapQuadColor(bitmap,
451                     Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
452         } while (activity.rotate());
453     }
454 
455     @Test
testWindowProducerScaling()456     public void testWindowProducerScaling() {
457         // Since we only sample mid-pixel of each qudrant, filtering
458         // quality isn't tested
459         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
460         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
461         do {
462             int result = mCopyHelper.request(activity.getWindow(), bitmap);
463             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
464             // Make sure nothing messed with the bitmap
465             assertEquals(20, bitmap.getWidth());
466             assertEquals(20, bitmap.getHeight());
467             assertEquals(Config.ARGB_8888, bitmap.getConfig());
468             assertBitmapQuadColor(bitmap,
469                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
470         } while (activity.rotate());
471     }
472 
473     @Test
testWindowProducerCopyToRGBA16F()474     public void testWindowProducerCopyToRGBA16F() {
475         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
476         do {
477             Rect src = makeWindowRect(activity, 0, 0, 100, 100);
478             Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16);
479             int result = mCopyHelper.request(activity.getWindow(), src, bitmap);
480             // On OpenGL ES 2.0 devices a copy to RGBA_F16 can fail because there's
481             // not support for float textures
482             if (result != PixelCopy.ERROR_DESTINATION_INVALID) {
483                 assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
484                 assertEquals(Config.RGBA_F16, bitmap.getConfig());
485                 assertBitmapQuadColor(bitmap,
486                         Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
487                 assertBitmapEdgeColor(bitmap, Color.YELLOW);
488             }
489         } while (activity.rotate());
490     }
491 
492     @Test
testWindowProducer()493     public void testWindowProducer() {
494         Bitmap bitmap;
495         PixelCopyViewProducerActivity activity = waitForWindowProducerActivity();
496         Window window = activity.getWindow();
497         do {
498             Rect src = makeWindowRect(activity, 0, 0, 100, 100);
499             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
500             int result = mCopyHelper.request(window, src, bitmap);
501             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
502             assertEquals(Config.ARGB_8888, bitmap.getConfig());
503             assertBitmapQuadColor(bitmap,
504                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
505             assertBitmapEdgeColor(bitmap, Color.YELLOW);
506         } while (activity.rotate());
507     }
508 
waitForWideGamutWindowProducerActivity()509     private PixelCopyWideGamutViewProducerActivity waitForWideGamutWindowProducerActivity() {
510         PixelCopyWideGamutViewProducerActivity activity = mFullscreenActivityRule.launch(
511                         PixelCopyWideGamutViewProducerActivity.class);
512         activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
513         return activity;
514     }
515 
makeWideGamutWindowRect( PixelCopyWideGamutViewProducerActivity activity, int left, int top, int right, int bottom)516     private Rect makeWideGamutWindowRect(
517             PixelCopyWideGamutViewProducerActivity activity,
518             int left, int top, int right, int bottom) {
519         Rect r = new Rect(left, top, right, bottom);
520         activity.offsetForContent(r);
521         return r;
522     }
523 
524     @Test
testWideGamutWindowProducerCopyToRGBA8888()525     public void testWideGamutWindowProducerCopyToRGBA8888() {
526         PixelCopyWideGamutViewProducerActivity activity = waitForWideGamutWindowProducerActivity();
527         assertEquals(
528                 ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT,
529                 activity.getWindow().getAttributes().getColorMode()
530         );
531 
532         // Early out if the device does not support wide color gamut rendering
533         if (!activity.getWindow().isWideColorGamut()) {
534             return;
535         }
536 
537         do {
538             Rect src = makeWideGamutWindowRect(activity, 0, 0, 128, 128);
539             Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
540             int result = mCopyHelper.request(activity.getWindow(), src, bitmap);
541 
542             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
543             assertEquals(Config.ARGB_8888, bitmap.getConfig());
544 
545             assertEquals("Top left", Color.RED, bitmap.getPixel(32, 32));
546             assertEquals("Top right", Color.GREEN, bitmap.getPixel(96, 32));
547             assertEquals("Bottom left", Color.BLUE, bitmap.getPixel(32, 96));
548             assertEquals("Bottom right", Color.YELLOW, bitmap.getPixel(96, 96));
549         } while (activity.rotate());
550     }
551 
552     @Test
testWideGamutWindowProducerCopyToRGBA16F()553     public void testWideGamutWindowProducerCopyToRGBA16F() {
554         PixelCopyWideGamutViewProducerActivity activity = waitForWideGamutWindowProducerActivity();
555         assertEquals(
556                 ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT,
557                 activity.getWindow().getAttributes().getColorMode()
558         );
559 
560         // Early out if the device does not support wide color gamut rendering
561         if (!activity.getWindow().isWideColorGamut()) {
562             return;
563         }
564 
565         final WindowManager windowManager = (WindowManager) activity.getSystemService(
566                 Context.WINDOW_SERVICE);
567         final ColorSpace colorSpace = windowManager.getDefaultDisplay()
568                 .getPreferredWideGamutColorSpace();
569         final ColorSpace.Connector proPhotoToDisplayWideColorSpace = ColorSpace.connect(
570                 ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB), colorSpace);
571         final ColorSpace.Connector displayWideColorSpaceToExtendedSrgb = ColorSpace.connect(
572                 colorSpace, ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
573 
574         final float[] intermediateRed =
575                 proPhotoToDisplayWideColorSpace.transform(1.0f, 0.0f, 0.0f);
576         final float[] intermediateGreen = proPhotoToDisplayWideColorSpace
577                 .transform(0.0f, 1.0f, 0.0f);
578         final float[] intermediateBlue = proPhotoToDisplayWideColorSpace
579                 .transform(0.0f, 0.0f, 1.0f);
580         final float[] intermediateYellow = proPhotoToDisplayWideColorSpace
581                 .transform(1.0f, 1.0f, 0.0f);
582 
583         final float[] expectedRed =
584                 displayWideColorSpaceToExtendedSrgb.transform(intermediateRed);
585         final float[] expectedGreen = displayWideColorSpaceToExtendedSrgb
586                 .transform(intermediateGreen);
587         final float[] expectedBlue = displayWideColorSpaceToExtendedSrgb
588                 .transform(intermediateBlue);
589         final float[] expectedYellow = displayWideColorSpaceToExtendedSrgb
590                 .transform(intermediateYellow);
591 
592         do {
593             Rect src = makeWideGamutWindowRect(activity, 0, 0, 128, 128);
594             Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16,
595                     true, ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
596             int result = mCopyHelper.request(activity.getWindow(), src, bitmap);
597 
598             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
599             assertEquals(Config.RGBA_F16, bitmap.getConfig());
600 
601             ByteBuffer dst = ByteBuffer.allocateDirect(bitmap.getAllocationByteCount());
602             bitmap.copyPixelsToBuffer(dst);
603             dst.rewind();
604             dst.order(ByteOrder.LITTLE_ENDIAN);
605 
606             // ProPhoto RGB red in scRGB-nl
607             assertEqualsRgba16f("Top left", bitmap, 32, 32, dst, expectedRed[0],
608                     expectedRed[1], expectedRed[2], 1.0f);
609             // ProPhoto RGB green in scRGB-nl
610             assertEqualsRgba16f("Top right", bitmap, 96, 32, dst,
611                     expectedGreen[0], expectedGreen[1], expectedGreen[2], 1.0f);
612             // ProPhoto RGB blue in scRGB-nl
613             assertEqualsRgba16f("Bottom left",  bitmap, 32, 96, dst,
614                     expectedBlue[0], expectedBlue[1], expectedBlue[2], 1.0f);
615             // ProPhoto RGB yellow in scRGB-nl
616             assertEqualsRgba16f("Bottom right", bitmap, 96, 96, dst,
617                     expectedYellow[0], expectedYellow[1], expectedYellow[2], 1.0f);
618         } while (activity.rotate());
619     }
620 
waitForDialogProducerActivity()621     private PixelCopyViewProducerDialogActivity waitForDialogProducerActivity() {
622         PixelCopyViewProducerDialogActivity activity = mFullscreenActivityRule.launch(
623                 PixelCopyViewProducerDialogActivity.class);
624         activity.waitForFirstDrawCompleted(10, TimeUnit.SECONDS);
625         return activity;
626     }
627 
makeDialogRect( PixelCopyViewProducerDialogActivity activity, int left, int top, int right, int bottom)628     private Rect makeDialogRect(
629             PixelCopyViewProducerDialogActivity activity,
630             int left, int top, int right, int bottom) {
631         Rect r = new Rect(left, top, right, bottom);
632         activity.normalizedToSurface(r);
633         return r;
634     }
635 
636     @Test
testDialogProducer()637     public void testDialogProducer() {
638         PixelCopyViewProducerDialogActivity activity = waitForDialogProducerActivity();
639         do {
640             Rect src = makeDialogRect(activity, 0, 0, 100, 100);
641             Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
642             int result = mCopyHelper.request(activity.getWindow(), src, bitmap);
643             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
644             assertEquals(Config.ARGB_8888, bitmap.getConfig());
645             assertBitmapQuadColor(bitmap,
646                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
647             assertBitmapEdgeColor(bitmap, Color.YELLOW);
648         } while (activity.rotate());
649     }
650 
651     @Test
testDialogProducerCropTopLeft()652     public void testDialogProducerCropTopLeft() {
653         PixelCopyViewProducerDialogActivity activity = waitForDialogProducerActivity();
654         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
655         do {
656             int result = mCopyHelper.request(
657                     activity.getWindow(), makeDialogRect(activity, 0, 0, 50, 50), bitmap);
658             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
659             assertBitmapQuadColor(bitmap,
660                     Color.RED, Color.RED, Color.RED, Color.RED);
661         } while (activity.rotate());
662     }
663 
664     @Test
testDialogProducerCropCenter()665     public void testDialogProducerCropCenter() {
666         PixelCopyViewProducerDialogActivity activity = waitForDialogProducerActivity();
667         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
668         do {
669             int result = mCopyHelper.request(
670                     activity.getWindow(), makeDialogRect(activity, 25, 25, 75, 75), bitmap);
671             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
672             assertBitmapQuadColor(bitmap,
673                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
674         } while (activity.rotate());
675     }
676 
677     @Test
testDialogProducerCropBottomHalf()678     public void testDialogProducerCropBottomHalf() {
679         PixelCopyViewProducerDialogActivity activity = waitForDialogProducerActivity();
680         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
681         do {
682             int result = mCopyHelper.request(
683                     activity.getWindow(), makeDialogRect(activity, 0, 50, 100, 100), bitmap);
684             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
685             assertBitmapQuadColor(bitmap,
686                     Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
687         } while (activity.rotate());
688     }
689 
690     @Test
testDialogProducerScaling()691     public void testDialogProducerScaling() {
692         // Since we only sample mid-pixel of each qudrant, filtering
693         // quality isn't tested
694         PixelCopyViewProducerDialogActivity activity = waitForDialogProducerActivity();
695         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
696         do {
697             int result = mCopyHelper.request(activity.getWindow(), bitmap);
698             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
699             // Make sure nothing messed with the bitmap
700             assertEquals(20, bitmap.getWidth());
701             assertEquals(20, bitmap.getHeight());
702             assertEquals(Config.ARGB_8888, bitmap.getConfig());
703             assertBitmapQuadColor(bitmap,
704                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
705         } while (activity.rotate());
706     }
707 
708     @Test
testDialogProducerCopyToRGBA16F()709     public void testDialogProducerCopyToRGBA16F() {
710         PixelCopyViewProducerDialogActivity activity = waitForDialogProducerActivity();
711         do {
712             Rect src = makeDialogRect(activity, 0, 0, 100, 100);
713             Bitmap bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.RGBA_F16);
714             int result = mCopyHelper.request(activity.getWindow(), src, bitmap);
715             // On OpenGL ES 2.0 devices a copy to RGBA_F16 can fail because there's
716             // not support for float textures
717             if (result != PixelCopy.ERROR_DESTINATION_INVALID) {
718                 assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
719                 assertEquals(Config.RGBA_F16, bitmap.getConfig());
720                 assertBitmapQuadColor(bitmap,
721                         Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
722                 assertBitmapEdgeColor(bitmap, Color.YELLOW);
723             }
724         } while (activity.rotate());
725     }
726 
assertEqualsRgba16f(String message, Bitmap bitmap, int x, int y, ByteBuffer dst, float r, float g, float b, float a)727     private static void assertEqualsRgba16f(String message, Bitmap bitmap, int x, int y,
728             ByteBuffer dst, float r, float g, float b, float a) {
729         int index = y * bitmap.getRowBytes() + (x << 3);
730         short cR = dst.getShort(index);
731         short cG = dst.getShort(index + 2);
732         short cB = dst.getShort(index + 4);
733         short cA = dst.getShort(index + 6);
734 
735         assertEquals(message, r, Half.toFloat(cR), 0.01);
736         assertEquals(message, g, Half.toFloat(cG), 0.01);
737         assertEquals(message, b, Half.toFloat(cB), 0.01);
738         assertEquals(message, a, Half.toFloat(cA), 0.01);
739     }
740 
runGcAndFinalizersSync()741     private static void runGcAndFinalizersSync() {
742         Runtime.getRuntime().gc();
743         Runtime.getRuntime().runFinalization();
744 
745         final CountDownLatch fence = new CountDownLatch(1);
746         new Object() {
747             @Override
748             protected void finalize() throws Throwable {
749                 try {
750                     fence.countDown();
751                 } finally {
752                     super.finalize();
753                 }
754             }
755         };
756         try {
757             do {
758                 Runtime.getRuntime().gc();
759                 Runtime.getRuntime().runFinalization();
760             } while (!fence.await(100, TimeUnit.MILLISECONDS));
761         } catch (InterruptedException ex) {
762             throw new RuntimeException(ex);
763         }
764     }
765 
766     private static File sProcSelfFd = new File("/proc/self/fd");
getFdCount()767     private static int getFdCount() {
768         return sProcSelfFd.listFiles().length;
769     }
770 
assertNotLeaking(int iteration, Debug.MemoryInfo start, Debug.MemoryInfo end)771     private static void assertNotLeaking(int iteration,
772             Debug.MemoryInfo start, Debug.MemoryInfo end) {
773         Debug.getMemoryInfo(end);
774         assertNotEquals(0, start.getTotalPss());
775         assertNotEquals(0, end.getTotalPss());
776         if (end.getTotalPss() - start.getTotalPss() > 5000 /* kB */) {
777             runGcAndFinalizersSync();
778             Debug.getMemoryInfo(end);
779             if (end.getTotalPss() - start.getTotalPss() > 7000 /* kB */) {
780                 // Guarded by if so we don't continually generate garbage for the
781                 // assertion string.
782                 assertEquals("Memory leaked, iteration=" + iteration,
783                         start.getTotalPss(), end.getTotalPss(),
784                         7000 /* kb */);
785             }
786         }
787     }
788 
runNotLeakingTest(Runnable test)789     private static void runNotLeakingTest(Runnable test) {
790         Debug.MemoryInfo meminfoStart = new Debug.MemoryInfo();
791         Debug.MemoryInfo meminfoEnd = new Debug.MemoryInfo();
792         int fdCount = -1;
793         // Do a warmup to reach steady-state memory usage
794         for (int i = 0; i < 50; i++) {
795             test.run();
796         }
797         runGcAndFinalizersSync();
798         Debug.getMemoryInfo(meminfoStart);
799         fdCount = getFdCount();
800         // Now run the test
801         for (int i = 0; i < 2000; i++) {
802             if (i % 100 == 5) {
803                 assertNotLeaking(i, meminfoStart, meminfoEnd);
804                 final int curFdCount = getFdCount();
805                 if (curFdCount - fdCount > 10) {
806                     Assert.fail(String.format("FDs leaked. Expected=%d, current=%d, iteration=%d",
807                             fdCount, curFdCount, i));
808                 }
809             }
810             test.run();
811         }
812         assertNotLeaking(2000, meminfoStart, meminfoEnd);
813         final int curFdCount = getFdCount();
814         if (curFdCount - fdCount > 10) {
815             Assert.fail(String.format("FDs leaked. Expected=%d, current=%d", fdCount, curFdCount));
816         }
817     }
818 
819     @Test
820     @LargeTest
testNotLeaking()821     public void testNotLeaking() {
822         PixelCopyGLProducerCtsActivity activity =
823                 mGLSurfaceViewActivityRule.launchActivity(null);
824         activity.waitForReady();
825 
826         // Test a fullsize copy
827         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
828 
829         runNotLeakingTest(() -> {
830             int result = mCopyHelper.request(activity.getView(), bitmap);
831             assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
832             // Make sure nothing messed with the bitmap
833             assertEquals(100, bitmap.getWidth());
834             assertEquals(100, bitmap.getHeight());
835             assertEquals(Config.ARGB_8888, bitmap.getConfig());
836             assertBitmapQuadColor(bitmap,
837                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
838         });
839     }
840 
841     @Test
testVideoProducer()842     public void testVideoProducer() throws InterruptedException {
843         PixelCopyVideoSourceActivity activity =
844                 mVideoSourceActivityRule.launchActivity(null);
845 
846         Thread.sleep(2000);
847 
848         if (!activity.canPlayVideo()) {
849             Log.i(TAG, "Skipping testVideoProducer, video codec isn't supported");
850             return;
851         }
852         // This returns when the video has been prepared and playback has
853         // been started, it doesn't necessarily means a frame has actually been
854         // produced. There sadly isn't a callback for that.
855         // So we'll try for up to 900ms after this event to acquire a frame, otherwise
856         // it's considered a timeout.
857         activity.waitForPlaying();
858         assertTrue("Failed to start video playback", activity.canPlayVideo());
859         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
860         int copyResult = PixelCopy.ERROR_SOURCE_NO_DATA;
861         for (int i = 0; i < 30; i++) {
862             copyResult = mCopyHelper.request(activity.getVideoView(), bitmap);
863             if (copyResult != PixelCopy.ERROR_SOURCE_NO_DATA) {
864                 break;
865             }
866             Thread.sleep(30);
867         }
868         assertEquals(PixelCopy.SUCCESS, copyResult);
869         // A large threshold is used because decoder accuracy is covered in the
870         // media CTS tests, so we are mainly interested in verifying that rotation
871         // and YUV->RGB conversion were handled properly.
872         assertBitmapQuadColor(bitmap, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, 30);
873 
874         // Test that cropping works.
875         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(0, 0, 50, 50), bitmap);
876         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
877         assertBitmapQuadColor(bitmap,
878                 Color.RED, Color.RED, Color.RED, Color.RED, 30);
879 
880         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(50, 0, 100, 50), bitmap);
881         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
882         assertBitmapQuadColor(bitmap,
883                 Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN, 30);
884 
885         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(0, 50, 50, 100), bitmap);
886         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
887         assertBitmapQuadColor(bitmap,
888                 Color.BLUE, Color.BLUE, Color.BLUE, Color.BLUE, 30);
889 
890         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(50, 50, 100, 100), bitmap);
891         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
892         assertBitmapQuadColor(bitmap,
893                 Color.BLACK, Color.BLACK, Color.BLACK, Color.BLACK, 30);
894 
895 
896         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(25, 25, 75, 75), bitmap);
897         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
898         assertBitmapQuadColor(bitmap,
899                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, 30);
900 
901         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(0, 50, 100, 100), bitmap);
902         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
903         assertBitmapQuadColor(bitmap,
904                 Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK, 30);
905 
906         // Test that clamping works
907         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(50, -50, 150, 50), bitmap);
908         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
909         assertBitmapQuadColor(bitmap,
910                 Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN, 30);
911     }
912 
913     @Test
testBufferQueueCrop()914     public void testBufferQueueCrop() throws InterruptedException {
915         ImageReader reader = ImageReader.newInstance(100, 100, PixelFormat.RGBA_8888, 1,
916                 HardwareBuffer.USAGE_CPU_WRITE_OFTEN | HardwareBuffer.USAGE_CPU_READ_OFTEN
917                         | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
918         ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
919         Image image = writer.dequeueInputImage();
920         Image.Plane plane = image.getPlanes()[0];
921         Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4,
922                 image.getHeight(), Bitmap.Config.ARGB_8888);
923         Canvas canvas = new Canvas(bitmap);
924         Paint paint = new Paint();
925         paint.setAntiAlias(false);
926         canvas.drawColor(Color.MAGENTA);
927         canvas.translate(20f, 70f);
928         paint.setColor(Color.RED);
929         canvas.drawRect(0f, 0f, 10f, 10f, paint);
930         paint.setColor(Color.GREEN);
931         canvas.drawRect(10f, 0f, 20f, 10f, paint);
932         paint.setColor(Color.BLUE);
933         canvas.drawRect(0f, 10f, 10f, 20f, paint);
934         paint.setColor(Color.BLACK);
935         canvas.drawRect(10f, 10f, 20f, 20f, paint);
936         bitmap.copyPixelsToBuffer(plane.getBuffer());
937         image.setCropRect(new Rect(20, 70, 40, 90));
938         writer.queueInputImage(image);
939 
940         // implicit size
941         Bitmap result = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
942         int status = mCopyHelper.request(reader.getSurface(), result);
943         assertEquals("Copy request failed", PixelCopy.SUCCESS, status);
944         assertBitmapQuadColor(result, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
945 
946         // specified size
947         result = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
948         status = mCopyHelper.request(reader.getSurface(), new Rect(0, 0, 20, 20), result);
949         assertEquals("Copy request failed", PixelCopy.SUCCESS, status);
950         assertBitmapQuadColor(result, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
951     }
952 
953     @Test
testAutoSize()954     public void testAutoSize() throws InterruptedException {
955         ImageReader reader = ImageReader.newInstance(100, 100, PixelFormat.RGBA_8888, 1,
956                 HardwareBuffer.USAGE_CPU_WRITE_OFTEN | HardwareBuffer.USAGE_CPU_READ_OFTEN
957                         | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
958         ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
959         Image image = writer.dequeueInputImage();
960         Image.Plane plane = image.getPlanes()[0];
961         Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4,
962                 image.getHeight(), Bitmap.Config.ARGB_8888);
963         Canvas canvas = new Canvas(bitmap);
964         Paint paint = new Paint();
965         paint.setAntiAlias(false);
966         paint.setColor(Color.RED);
967         canvas.drawRect(0f, 0f, 50f, 50f, paint);
968         paint.setColor(Color.GREEN);
969         canvas.drawRect(50f, 0f, 100f, 50f, paint);
970         paint.setColor(Color.BLUE);
971         canvas.drawRect(0f, 50f, 50f, 100f, paint);
972         paint.setColor(Color.BLACK);
973         canvas.drawRect(50f, 50f, 100f, 100f, paint);
974         bitmap.copyPixelsToBuffer(plane.getBuffer());
975         writer.queueInputImage(image);
976 
977         PixelCopy.Result copyResult = copySurface(reader.getSurface());
978         assertEquals("Copy request failed", PixelCopy.SUCCESS, copyResult.getStatus());
979         Bitmap result = copyResult.getBitmap();
980         assertEquals(100, result.getWidth());
981         assertEquals(100, result.getHeight());
982         assertBitmapQuadColor(result, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
983     }
984 
985     @Test
testAutoSizeWithCrop()986     public void testAutoSizeWithCrop() throws InterruptedException {
987         ImageReader reader = ImageReader.newInstance(100, 100, PixelFormat.RGBA_8888, 1,
988                 HardwareBuffer.USAGE_CPU_WRITE_OFTEN | HardwareBuffer.USAGE_CPU_READ_OFTEN
989                         | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
990         ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
991         Image image = writer.dequeueInputImage();
992         Image.Plane plane = image.getPlanes()[0];
993         Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4,
994                 image.getHeight(), Bitmap.Config.ARGB_8888);
995         Canvas canvas = new Canvas(bitmap);
996         Paint paint = new Paint();
997         paint.setAntiAlias(false);
998         canvas.drawColor(Color.MAGENTA);
999         canvas.translate(20f, 70f);
1000         paint.setColor(Color.RED);
1001         canvas.drawRect(0f, 0f, 10f, 10f, paint);
1002         paint.setColor(Color.GREEN);
1003         canvas.drawRect(10f, 0f, 20f, 10f, paint);
1004         paint.setColor(Color.BLUE);
1005         canvas.drawRect(0f, 10f, 10f, 20f, paint);
1006         paint.setColor(Color.BLACK);
1007         canvas.drawRect(10f, 10f, 20f, 20f, paint);
1008         bitmap.copyPixelsToBuffer(plane.getBuffer());
1009         image.setCropRect(new Rect(20, 70, 40, 90));
1010         writer.queueInputImage(image);
1011 
1012         PixelCopy.Result copyResult = copySurface(reader.getSurface());
1013         assertEquals("Copy request failed", PixelCopy.SUCCESS, copyResult.getStatus());
1014         Bitmap result = copyResult.getBitmap();
1015         assertEquals(20, result.getWidth());
1016         assertEquals(20, result.getHeight());
1017         assertBitmapQuadColor(result, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
1018     }
1019 
1020     @Test
testAutoSizeWithSrcRect()1021     public void testAutoSizeWithSrcRect() throws InterruptedException {
1022         ImageReader reader = ImageReader.newInstance(100, 100, PixelFormat.RGBA_8888, 1,
1023                 HardwareBuffer.USAGE_CPU_WRITE_OFTEN | HardwareBuffer.USAGE_CPU_READ_OFTEN
1024                         | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
1025         ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
1026         Image image = writer.dequeueInputImage();
1027         Image.Plane plane = image.getPlanes()[0];
1028         Bitmap bitmap = Bitmap.createBitmap(plane.getRowStride() / 4,
1029                 image.getHeight(), Bitmap.Config.ARGB_8888);
1030         Canvas canvas = new Canvas(bitmap);
1031         Paint paint = new Paint();
1032         paint.setAntiAlias(false);
1033         canvas.drawColor(Color.MAGENTA);
1034         canvas.translate(20f, 70f);
1035         paint.setColor(Color.RED);
1036         canvas.drawRect(0f, 0f, 10f, 10f, paint);
1037         paint.setColor(Color.GREEN);
1038         canvas.drawRect(10f, 0f, 20f, 10f, paint);
1039         paint.setColor(Color.BLUE);
1040         canvas.drawRect(0f, 10f, 10f, 20f, paint);
1041         paint.setColor(Color.BLACK);
1042         canvas.drawRect(10f, 10f, 20f, 20f, paint);
1043         bitmap.copyPixelsToBuffer(plane.getBuffer());
1044         writer.queueInputImage(image);
1045 
1046         PixelCopy.Result copyResult = copySurface(reader.getSurface(),
1047                 request -> request.setSourceRect(new Rect(20, 70, 40, 90)));
1048         assertEquals("Copy request failed", PixelCopy.SUCCESS, copyResult.getStatus());
1049         Bitmap result = copyResult.getBitmap();
1050         assertEquals(20, result.getWidth());
1051         assertEquals(20, result.getHeight());
1052         assertBitmapQuadColor(result, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
1053     }
1054 
getPixelFloatPos(Bitmap bitmap, float xpos, float ypos)1055     private static int getPixelFloatPos(Bitmap bitmap, float xpos, float ypos) {
1056         return bitmap.getPixel((int) (bitmap.getWidth() * xpos), (int) (bitmap.getHeight() * ypos));
1057     }
1058 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight)1059     private void assertBitmapQuadColor(Bitmap bitmap,
1060             int topLeft, int topRight, int bottomLeft, int bottomRight) {
1061         assertBitmapQuadColor(mTestName.getMethodName(), "PixelCopyTest", bitmap,
1062                 topLeft, topRight, bottomLeft, bottomRight);
1063     }
1064 
assertBitmapQuadColor(String testName, String className, Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight)1065     public static void assertBitmapQuadColor(String testName, String className, Bitmap bitmap,
1066                 int topLeft, int topRight, int bottomLeft, int bottomRight) {
1067         try {
1068             // Just quickly sample 4 pixels in the various regions.
1069             assertEquals("Top left " + Integer.toHexString(topLeft) + ", actual= "
1070                             + Integer.toHexString(getPixelFloatPos(bitmap, .25f, .25f)),
1071                     topLeft, getPixelFloatPos(bitmap, .25f, .25f));
1072             assertEquals("Top right", topRight, getPixelFloatPos(bitmap, .75f, .25f));
1073             assertEquals("Bottom left", bottomLeft, getPixelFloatPos(bitmap, .25f, .75f));
1074             assertEquals("Bottom right", bottomRight, getPixelFloatPos(bitmap, .75f, .75f));
1075 
1076             // and some closer to the center point, to ensure that our quadrants are even
1077             float below = .45f;
1078             float above = .55f;
1079             assertEquals("Top left II " + Integer.toHexString(topLeft) + ", actual= "
1080                             + Integer.toHexString(getPixelFloatPos(bitmap, below, below)),
1081                     topLeft, getPixelFloatPos(bitmap, below, below));
1082             assertEquals("Top right II", topRight, getPixelFloatPos(bitmap, above, below));
1083             assertEquals("Bottom left II", bottomLeft, getPixelFloatPos(bitmap, below, above));
1084             assertEquals("Bottom right II", bottomRight, getPixelFloatPos(bitmap, above, above));
1085         } catch (AssertionError err) {
1086             BitmapDumper.dumpBitmap(bitmap, testName, className);
1087             throw err;
1088         }
1089     }
1090 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight, int threshold)1091     private void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
1092             int bottomLeft, int bottomRight, int threshold) {
1093         Function<Float, Integer> getX = (Float x) -> (int) (bitmap.getWidth() * x);
1094         Function<Float, Integer> getY = (Float y) -> (int) (bitmap.getHeight() * y);
1095 
1096         // Just quickly sample 4 pixels in the various regions.
1097         assertBitmapColor("Top left", bitmap, topLeft,
1098                 getX.apply(.25f), getY.apply(.25f), threshold);
1099         assertBitmapColor("Top right", bitmap, topRight,
1100                 getX.apply(.75f), getY.apply(.25f), threshold);
1101         assertBitmapColor("Bottom left", bitmap, bottomLeft,
1102                 getX.apply(.25f), getY.apply(.75f), threshold);
1103         assertBitmapColor("Bottom right", bitmap, bottomRight,
1104                 getX.apply(.75f), getY.apply(.75f), threshold);
1105 
1106         float below = .4f;
1107         float above = .6f;
1108         assertBitmapColor("Top left II", bitmap, topLeft,
1109                 getX.apply(below), getY.apply(below), threshold);
1110         assertBitmapColor("Top right II", bitmap, topRight,
1111                 getX.apply(above), getY.apply(below), threshold);
1112         assertBitmapColor("Bottom left II", bitmap, bottomLeft,
1113                 getX.apply(below), getY.apply(above), threshold);
1114         assertBitmapColor("Bottom right II", bitmap, bottomRight,
1115                 getX.apply(above), getY.apply(above), threshold);
1116     }
1117 
assertBitmapEdgeColor(Bitmap bitmap, int edgeColor)1118     private void assertBitmapEdgeColor(Bitmap bitmap, int edgeColor) {
1119         // Just quickly sample a few pixels on the edge and assert
1120         // they are edge color, then assert that just inside the edge is a different color
1121         assertBitmapColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 1);
1122         assertBitmapNotColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 2);
1123 
1124         assertBitmapColor("Left edge", bitmap, edgeColor, 1, bitmap.getHeight() / 2);
1125         assertBitmapNotColor("Left edge", bitmap, edgeColor, 2, bitmap.getHeight() / 2);
1126 
1127         assertBitmapColor("Bottom edge", bitmap, edgeColor,
1128                 bitmap.getWidth() / 2, bitmap.getHeight() - 2);
1129         assertBitmapNotColor("Bottom edge", bitmap, edgeColor,
1130                 bitmap.getWidth() / 2, bitmap.getHeight() - 3);
1131 
1132         assertBitmapColor("Right edge", bitmap, edgeColor,
1133                 bitmap.getWidth() - 2, bitmap.getHeight() / 2);
1134         assertBitmapNotColor("Right edge", bitmap, edgeColor,
1135                 bitmap.getWidth() - 3, bitmap.getHeight() / 2);
1136     }
1137 
pixelsAreSame(int ideal, int given, int threshold)1138     private static boolean pixelsAreSame(int ideal, int given, int threshold) {
1139         int error = Math.abs(Color.red(ideal) - Color.red(given));
1140         error += Math.abs(Color.green(ideal) - Color.green(given));
1141         error += Math.abs(Color.blue(ideal) - Color.blue(given));
1142         return (error < threshold);
1143     }
1144 
fail(Bitmap bitmap, String message)1145     private void fail(Bitmap bitmap, String message) {
1146         BitmapDumper.dumpBitmap(bitmap, mTestName.getMethodName(), "PixelCopyTest");
1147         Assert.fail(message);
1148     }
1149 
assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y)1150     private void assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y) {
1151         assertBitmapColor(debug, bitmap, color,  x, y, 10);
1152     }
1153 
assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y, int threshold)1154     private void assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y,
1155             int threshold) {
1156         int pixel = bitmap.getPixel(x, y);
1157         if (!pixelsAreSame(color, pixel, threshold)) {
1158             fail(bitmap, debug + "; expected=" + Integer.toHexString(color) + ", actual="
1159                     + Integer.toHexString(pixel));
1160         }
1161     }
1162 
assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y)1163     private void assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y) {
1164         int pixel = bitmap.getPixel(x, y);
1165         if (pixelsAreSame(color, pixel, 10)) {
1166             fail(bitmap, debug + "; actual=" + Integer.toHexString(pixel)
1167                     + " shouldn't have matched " + Integer.toHexString(color));
1168         }
1169     }
1170 
1171     private static class SurfaceTextureRule implements TestRule {
1172         private SurfaceTexture mSurfaceTexture = null;
1173         private Surface mSurface = null;
1174 
createIfNecessary()1175         private void createIfNecessary() {
1176             mSurfaceTexture = new SurfaceTexture(false);
1177             mSurface = new Surface(mSurfaceTexture);
1178         }
1179 
getSurface()1180         public Surface getSurface() {
1181             createIfNecessary();
1182             return mSurface;
1183         }
1184 
1185         @Override
apply(Statement base, Description description)1186         public Statement apply(Statement base, Description description) {
1187             return new CreateSurfaceTextureStatement(base);
1188         }
1189 
1190         private class CreateSurfaceTextureStatement extends Statement {
1191 
1192             private final Statement mBase;
1193 
CreateSurfaceTextureStatement(Statement base)1194             public CreateSurfaceTextureStatement(Statement base) {
1195                 mBase = base;
1196             }
1197 
1198             @Override
evaluate()1199             public void evaluate() throws Throwable {
1200                 try {
1201                     mBase.evaluate();
1202                 } finally {
1203                     try {
1204                         if (mSurface != null) mSurface.release();
1205                     } catch (Throwable t) {}
1206                     try {
1207                         if (mSurfaceTexture != null) mSurfaceTexture.release();
1208                     } catch (Throwable t) {}
1209                 }
1210             }
1211         }
1212     }
1213 }
1214