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