• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.assertNotNull;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.when;
25 
26 import android.app.Instrumentation;
27 import android.graphics.Bitmap;
28 import android.graphics.Bitmap.Config;
29 import android.graphics.Color;
30 import android.graphics.Rect;
31 import android.graphics.SurfaceTexture;
32 import android.os.Debug;
33 import android.os.Debug.MemoryInfo;
34 import android.support.test.InstrumentationRegistry;
35 import android.support.test.filters.LargeTest;
36 import android.support.test.filters.MediumTest;
37 import android.support.test.rule.ActivityTestRule;
38 import android.support.test.runner.AndroidJUnit4;
39 import android.util.Log;
40 import android.view.PixelCopy;
41 import android.view.Surface;
42 import android.view.View;
43 import android.view.Window;
44 
45 import com.android.compatibility.common.util.SynchronousPixelCopy;
46 
47 import org.junit.Before;
48 import org.junit.Rule;
49 import org.junit.Test;
50 import org.junit.rules.TestRule;
51 import org.junit.runner.Description;
52 import org.junit.runner.RunWith;
53 import org.junit.runners.model.Statement;
54 
55 import java.util.concurrent.CountDownLatch;
56 import java.util.concurrent.TimeUnit;
57 
58 @MediumTest
59 @RunWith(AndroidJUnit4.class)
60 public class PixelCopyTest {
61     private static final String TAG = "PixelCopyTests";
62 
63     @Rule
64     public ActivityTestRule<PixelCopyGLProducerCtsActivity> mGLSurfaceViewActivityRule =
65             new ActivityTestRule<>(PixelCopyGLProducerCtsActivity.class, false, false);
66 
67     @Rule
68     public ActivityTestRule<PixelCopyVideoSourceActivity> mVideoSourceActivityRule =
69             new ActivityTestRule<>(PixelCopyVideoSourceActivity.class, false, false);
70 
71     @Rule
72     public ActivityTestRule<PixelCopyViewProducerActivity> mWindowSourceActivityRule =
73             new ActivityTestRule<>(PixelCopyViewProducerActivity.class, false, false);
74 
75     @Rule
76     public SurfaceTextureRule mSurfaceRule = new SurfaceTextureRule();
77 
78     private Instrumentation mInstrumentation;
79     private SynchronousPixelCopy mCopyHelper;
80 
81     @Before
setup()82     public void setup() {
83         mInstrumentation = InstrumentationRegistry.getInstrumentation();
84         assertNotNull(mInstrumentation);
85         mCopyHelper = new SynchronousPixelCopy();
86     }
87 
88     @Test(expected = IllegalArgumentException.class)
testNullDest()89     public void testNullDest() {
90         Bitmap dest = null;
91         mCopyHelper.request(mSurfaceRule.getSurface(), dest);
92     }
93 
94     @Test(expected = IllegalArgumentException.class)
testRecycledDest()95     public void testRecycledDest() {
96         Bitmap dest = Bitmap.createBitmap(5, 5, Config.ARGB_8888);
97         dest.recycle();
98         mCopyHelper.request(mSurfaceRule.getSurface(), dest);
99     }
100 
101     @Test
testNoSourceData()102     public void testNoSourceData() {
103         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
104         int result = mCopyHelper.request(mSurfaceRule.getSurface(), dest);
105         assertEquals(PixelCopy.ERROR_SOURCE_NO_DATA, result);
106     }
107 
108     @Test(expected = IllegalArgumentException.class)
testEmptySourceRectSurface()109     public void testEmptySourceRectSurface() {
110         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
111         mCopyHelper.request(mSurfaceRule.getSurface(), new Rect(), dest);
112     }
113 
114     @Test(expected = IllegalArgumentException.class)
testEmptySourceRectWindow()115     public void testEmptySourceRectWindow() {
116         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
117         mCopyHelper.request(mock(Window.class), new Rect(), dest);
118     }
119 
120     @Test(expected = IllegalArgumentException.class)
testInvalidSourceRectSurface()121     public void testInvalidSourceRectSurface() {
122         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
123         mCopyHelper.request(mSurfaceRule.getSurface(), new Rect(10, 10, 0, 0), dest);
124     }
125 
126     @Test(expected = IllegalArgumentException.class)
testInvalidSourceRectWindow()127     public void testInvalidSourceRectWindow() {
128         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
129         mCopyHelper.request(mock(Window.class), new Rect(10, 10, 0, 0), dest);
130     }
131 
132     @Test(expected = IllegalArgumentException.class)
testNoDecorView()133     public void testNoDecorView() {
134         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
135         Window mockWindow = mock(Window.class);
136         mCopyHelper.request(mockWindow, dest);
137     }
138 
139     @Test(expected = IllegalArgumentException.class)
testNoViewRoot()140     public void testNoViewRoot() {
141         Bitmap dest = Bitmap.createBitmap(5, 5, Bitmap.Config.ARGB_8888);
142         Window mockWindow = mock(Window.class);
143         View view = new View(mInstrumentation.getTargetContext());
144         when(mockWindow.peekDecorView()).thenReturn(view);
145         mCopyHelper.request(mockWindow, dest);
146     }
147 
waitForGlProducerActivity()148     private PixelCopyGLProducerCtsActivity waitForGlProducerActivity() {
149         CountDownLatch swapFence = new CountDownLatch(2);
150 
151         PixelCopyGLProducerCtsActivity activity =
152                 mGLSurfaceViewActivityRule.launchActivity(null);
153         activity.setSwapFence(swapFence);
154 
155         try {
156             while (!swapFence.await(5, TimeUnit.MILLISECONDS)) {
157                 activity.getView().requestRender();
158             }
159         } catch (InterruptedException ex) {
160             fail("Interrupted, error=" + ex.getMessage());
161         }
162         return activity;
163     }
164 
165     @Test
testGlProducerFullsize()166     public void testGlProducerFullsize() {
167         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
168         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
169         int result = mCopyHelper.request(activity.getView(), bitmap);
170         assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
171         assertEquals(100, bitmap.getWidth());
172         assertEquals(100, bitmap.getHeight());
173         assertEquals(Config.ARGB_8888, bitmap.getConfig());
174         assertBitmapQuadColor(bitmap,
175                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
176     }
177 
178     @Test
testGlProducerCropTopLeft()179     public void testGlProducerCropTopLeft() {
180         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
181         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
182         int result = mCopyHelper.request(activity.getView(), new Rect(0, 0, 50, 50), bitmap);
183         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
184         assertBitmapQuadColor(bitmap,
185                 Color.RED, Color.RED, Color.RED, Color.RED);
186     }
187 
188     @Test
testGlProducerCropCenter()189     public void testGlProducerCropCenter() {
190         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
191         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
192         int result = mCopyHelper.request(activity.getView(), new Rect(25, 25, 75, 75), bitmap);
193         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
194         assertBitmapQuadColor(bitmap,
195                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
196     }
197 
198     @Test
testGlProducerCropBottomHalf()199     public void testGlProducerCropBottomHalf() {
200         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
201         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
202         int result = mCopyHelper.request(activity.getView(), new Rect(0, 50, 100, 100), bitmap);
203         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
204         assertBitmapQuadColor(bitmap,
205                 Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
206     }
207 
208     @Test
testGlProducerCropClamping()209     public void testGlProducerCropClamping() {
210         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
211         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
212         int result = mCopyHelper.request(activity.getView(), new Rect(50, -50, 150, 50), bitmap);
213         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
214         assertBitmapQuadColor(bitmap,
215                 Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN);
216     }
217 
218     @Test
testGlProducerScaling()219     public void testGlProducerScaling() {
220         // Since we only sample mid-pixel of each qudrant, filtering
221         // quality isn't tested
222         PixelCopyGLProducerCtsActivity activity = waitForGlProducerActivity();
223         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
224         int result = mCopyHelper.request(activity.getView(), bitmap);
225         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
226         // Make sure nothing messed with the bitmap
227         assertEquals(20, bitmap.getWidth());
228         assertEquals(20, bitmap.getHeight());
229         assertEquals(Config.ARGB_8888, bitmap.getConfig());
230         assertBitmapQuadColor(bitmap,
231                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
232     }
233 
waitForWindowProducerActivity()234     private Window waitForWindowProducerActivity() {
235         PixelCopyViewProducerActivity activity =
236                 mWindowSourceActivityRule.launchActivity(null);
237         activity.waitForFirstDrawCompleted(3, TimeUnit.SECONDS);
238         return activity.getWindow();
239     }
240 
makeWindowRect(int left, int top, int right, int bottom)241     private Rect makeWindowRect(int left, int top, int right, int bottom) {
242         Rect r = new Rect(left, top, right, bottom);
243         mWindowSourceActivityRule.getActivity().normalizedToSurface(r);
244         return r;
245     }
246 
247     @Test
testWindowProducer()248     public void testWindowProducer() {
249         Bitmap bitmap;
250         Window window = waitForWindowProducerActivity();
251         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
252         do {
253             Rect src = makeWindowRect(0, 0, 100, 100);
254             bitmap = Bitmap.createBitmap(src.width(), src.height(), Config.ARGB_8888);
255             int result = mCopyHelper.request(window, src, bitmap);
256             assertEquals("Fullsize copy request failed", PixelCopy.SUCCESS, result);
257             assertEquals(Config.ARGB_8888, bitmap.getConfig());
258             assertBitmapQuadColor(bitmap,
259                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
260             assertBitmapEdgeColor(bitmap, Color.YELLOW);
261         } while (activity.rotate());
262     }
263 
264     @Test
testWindowProducerCropTopLeft()265     public void testWindowProducerCropTopLeft() {
266         Window window = waitForWindowProducerActivity();
267         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
268         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
269         do {
270             int result = mCopyHelper.request(window, makeWindowRect(0, 0, 50, 50), bitmap);
271             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
272             assertBitmapQuadColor(bitmap,
273                     Color.RED, Color.RED, Color.RED, Color.RED);
274         } while (activity.rotate());
275     }
276 
277     @Test
testWindowProducerCropCenter()278     public void testWindowProducerCropCenter() {
279         Window window = waitForWindowProducerActivity();
280         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
281         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
282         do {
283             int result = mCopyHelper.request(window, makeWindowRect(25, 25, 75, 75), bitmap);
284             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
285             assertBitmapQuadColor(bitmap,
286                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
287         } while (activity.rotate());
288     }
289 
290     @Test
testWindowProducerCropBottomHalf()291     public void testWindowProducerCropBottomHalf() {
292         Window window = waitForWindowProducerActivity();
293         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
294         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
295         do {
296             int result = mCopyHelper.request(window, makeWindowRect(0, 50, 100, 100), bitmap);
297             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
298             assertBitmapQuadColor(bitmap,
299                     Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK);
300         } while (activity.rotate());
301     }
302 
303     @Test
testWindowProducerScaling()304     public void testWindowProducerScaling() {
305         // Since we only sample mid-pixel of each qudrant, filtering
306         // quality isn't tested
307         Window window = waitForWindowProducerActivity();
308         Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
309         PixelCopyViewProducerActivity activity = mWindowSourceActivityRule.getActivity();
310         do {
311             int result = mCopyHelper.request(window, bitmap);
312             assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, result);
313             // Make sure nothing messed with the bitmap
314             assertEquals(20, bitmap.getWidth());
315             assertEquals(20, bitmap.getHeight());
316             assertEquals(Config.ARGB_8888, bitmap.getConfig());
317             assertBitmapQuadColor(bitmap,
318                     Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
319         } while (activity.rotate());
320     }
321 
runGcAndFinalizersSync()322     private void runGcAndFinalizersSync() {
323         final CountDownLatch fence = new CountDownLatch(1);
324         new Object() {
325             @Override
326             protected void finalize() throws Throwable {
327                 try {
328                     fence.countDown();
329                 } finally {
330                     super.finalize();
331                 }
332             }
333         };
334         try {
335             do {
336                 Runtime.getRuntime().gc();
337                 Runtime.getRuntime().runFinalization();
338             } while (!fence.await(100, TimeUnit.MILLISECONDS));
339         } catch (InterruptedException ex) {
340             throw new RuntimeException(ex);
341         }
342         Runtime.getRuntime().gc();
343     }
344 
assertNotLeaking(int iteration, MemoryInfo start, MemoryInfo end)345     private void assertNotLeaking(int iteration, MemoryInfo start, MemoryInfo end) {
346         Debug.getMemoryInfo(end);
347         if (end.getTotalPss() - start.getTotalPss() > 2000 /* kB */) {
348             runGcAndFinalizersSync();
349             Debug.getMemoryInfo(end);
350             if (end.getTotalPss() - start.getTotalPss() > 2000 /* kB */) {
351                 // Guarded by if so we don't continually generate garbage for the
352                 // assertion string.
353                 assertEquals("Memory leaked, iteration=" + iteration,
354                         start.getTotalPss(), end.getTotalPss(),
355                         2000 /* kb */);
356             }
357         }
358     }
359 
360     @Test
361     @LargeTest
testNotLeaking()362     public void testNotLeaking() {
363         try {
364             CountDownLatch swapFence = new CountDownLatch(2);
365 
366             PixelCopyGLProducerCtsActivity activity =
367                     mGLSurfaceViewActivityRule.launchActivity(null);
368             activity.setSwapFence(swapFence);
369 
370             while (!swapFence.await(5, TimeUnit.MILLISECONDS)) {
371                 activity.getView().requestRender();
372             }
373 
374             // Test a fullsize copy
375             Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
376 
377             MemoryInfo meminfoStart = new MemoryInfo();
378             MemoryInfo meminfoEnd = new MemoryInfo();
379 
380             for (int i = 0; i < 1000; i++) {
381                 if (i == 2) {
382                     // Not really the "start" but by having done a couple
383                     // we've fully initialized any state that may be required,
384                     // so memory usage should be stable now
385                     runGcAndFinalizersSync();
386                     Debug.getMemoryInfo(meminfoStart);
387                 }
388                 if (i % 100 == 5) {
389                     assertNotLeaking(i, meminfoStart, meminfoEnd);
390                 }
391                 int result = mCopyHelper.request(activity.getView(), bitmap);
392                 assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
393                 // Make sure nothing messed with the bitmap
394                 assertEquals(100, bitmap.getWidth());
395                 assertEquals(100, bitmap.getHeight());
396                 assertEquals(Config.ARGB_8888, bitmap.getConfig());
397                 assertBitmapQuadColor(bitmap,
398                         Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
399             }
400 
401             assertNotLeaking(1000, meminfoStart, meminfoEnd);
402 
403         } catch (InterruptedException e) {
404             fail("Interrupted, error=" + e.getMessage());
405         }
406     }
407 
408     @Test
testVideoProducer()409     public void testVideoProducer() throws InterruptedException {
410         PixelCopyVideoSourceActivity activity =
411                 mVideoSourceActivityRule.launchActivity(null);
412         if (!activity.canPlayVideo()) {
413             Log.i(TAG, "Skipping testVideoProducer, video codec isn't supported");
414             return;
415         }
416         // This returns when the video has been prepared and playback has
417         // been started, it doesn't necessarily means a frame has actually been
418         // produced. There sadly isn't a callback for that.
419         // So we'll try for up to 900ms after this event to acquire a frame, otherwise
420         // it's considered a timeout.
421         activity.waitForPlaying();
422         assertTrue("Failed to start video playback", activity.canPlayVideo());
423         Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
424         int copyResult = PixelCopy.ERROR_SOURCE_NO_DATA;
425         for (int i = 0; i < 30; i++) {
426             copyResult = mCopyHelper.request(activity.getVideoView(), bitmap);
427             if (copyResult != PixelCopy.ERROR_SOURCE_NO_DATA) {
428                 break;
429             }
430             Thread.sleep(30);
431         }
432         assertEquals(PixelCopy.SUCCESS, copyResult);
433         // A large threshold is used because decoder accuracy is covered in the
434         // media CTS tests, so we are mainly interested in verifying that rotation
435         // and YUV->RGB conversion were handled properly.
436         assertBitmapQuadColor(bitmap, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, 30);
437 
438         // Test that cropping works.
439         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(0, 0, 50, 50), bitmap);
440         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
441         assertBitmapQuadColor(bitmap,
442                 Color.RED, Color.RED, Color.RED, Color.RED, 30);
443 
444         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(25, 25, 75, 75), bitmap);
445         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
446         assertBitmapQuadColor(bitmap,
447                 Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, 30);
448 
449         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(0, 50, 100, 100), bitmap);
450         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
451         assertBitmapQuadColor(bitmap,
452                 Color.BLUE, Color.BLACK, Color.BLUE, Color.BLACK, 30);
453 
454         // Test that clamping works
455         copyResult = mCopyHelper.request(activity.getVideoView(), new Rect(50, -50, 150, 50), bitmap);
456         assertEquals("Scaled copy request failed", PixelCopy.SUCCESS, copyResult);
457         assertBitmapQuadColor(bitmap,
458                 Color.GREEN, Color.GREEN, Color.GREEN, Color.GREEN, 30);
459     }
460 
getPixelFloatPos(Bitmap bitmap, float xpos, float ypos)461     private static int getPixelFloatPos(Bitmap bitmap, float xpos, float ypos) {
462         return bitmap.getPixel((int) (bitmap.getWidth() * xpos), (int) (bitmap.getHeight() * ypos));
463     }
464 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight)465     private void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
466                 int bottomLeft, int bottomRight) {
467         // Just quickly sample 4 pixels in the various regions.
468         assertEquals("Top left " + Integer.toHexString(topLeft) + ", actual= "
469                 + Integer.toHexString(getPixelFloatPos(bitmap, .25f, .25f)),
470                 topLeft, getPixelFloatPos(bitmap, .25f, .25f));
471         assertEquals("Top right", topRight, getPixelFloatPos(bitmap, .75f, .25f));
472         assertEquals("Bottom left", bottomLeft, getPixelFloatPos(bitmap, .25f, .75f));
473         assertEquals("Bottom right", bottomRight, getPixelFloatPos(bitmap, .75f, .75f));
474     }
475 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight, int threshold)476     private void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
477             int bottomLeft, int bottomRight, int threshold) {
478         // Just quickly sample 4 pixels in the various regions.
479         assertTrue("Top left", pixelsAreSame(topLeft, getPixelFloatPos(bitmap, .25f, .25f),
480                 threshold));
481         assertTrue("Top right", pixelsAreSame(topRight, getPixelFloatPos(bitmap, .75f, .25f),
482                 threshold));
483         assertTrue("Bottom left", pixelsAreSame(bottomLeft, getPixelFloatPos(bitmap, .25f, .75f),
484                 threshold));
485         assertTrue("Bottom right", pixelsAreSame(bottomRight, getPixelFloatPos(bitmap, .75f, .75f),
486                 threshold));
487     }
488 
assertBitmapEdgeColor(Bitmap bitmap, int edgeColor)489     private void assertBitmapEdgeColor(Bitmap bitmap, int edgeColor) {
490         // Just quickly sample a few pixels on the edge and assert
491         // they are edge color, then assert that just inside the edge is a different color
492         assertBitmapColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 0);
493         assertBitmapNotColor("Top edge", bitmap, edgeColor, bitmap.getWidth() / 2, 1);
494 
495         assertBitmapColor("Left edge", bitmap, edgeColor, 0, bitmap.getHeight() / 2);
496         assertBitmapNotColor("Left edge", bitmap, edgeColor, 1, bitmap.getHeight() / 2);
497 
498         assertBitmapColor("Bottom edge", bitmap, edgeColor,
499                 bitmap.getWidth() / 2, bitmap.getHeight() - 1);
500         assertBitmapNotColor("Bottom edge", bitmap, edgeColor,
501                 bitmap.getWidth() / 2, bitmap.getHeight() - 2);
502 
503         assertBitmapColor("Right edge", bitmap, edgeColor,
504                 bitmap.getWidth() - 1, bitmap.getHeight() / 2);
505         assertBitmapNotColor("Right edge", bitmap, edgeColor,
506                 bitmap.getWidth() - 2, bitmap.getHeight() / 2);
507     }
508 
pixelsAreSame(int ideal, int given, int threshold)509     private boolean pixelsAreSame(int ideal, int given, int threshold) {
510         int error = Math.abs(Color.red(ideal) - Color.red(given));
511         error += Math.abs(Color.green(ideal) - Color.green(given));
512         error += Math.abs(Color.blue(ideal) - Color.blue(given));
513         return (error < threshold);
514     }
515 
assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y)516     private void assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y) {
517         int pixel = bitmap.getPixel(x, y);
518         if (!pixelsAreSame(color, pixel, 10)) {
519             fail(debug + "; expected=" + Integer.toHexString(color) + ", actual="
520                     + Integer.toHexString(pixel));
521         }
522     }
523 
assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y)524     private void assertBitmapNotColor(String debug, Bitmap bitmap, int color, int x, int y) {
525         int pixel = bitmap.getPixel(x, y);
526         if (pixelsAreSame(color, pixel, 10)) {
527             fail(debug + "; actual=" + Integer.toHexString(pixel)
528                     + " shouldn't have matched " + Integer.toHexString(color));
529         }
530     }
531 
532     private static class SurfaceTextureRule implements TestRule {
533         private SurfaceTexture mSurfaceTexture = null;
534         private Surface mSurface = null;
535 
createIfNecessary()536         private void createIfNecessary() {
537             mSurfaceTexture = new SurfaceTexture(false);
538             mSurface = new Surface(mSurfaceTexture);
539         }
540 
getSurface()541         public Surface getSurface() {
542             createIfNecessary();
543             return mSurface;
544         }
545 
546         @Override
apply(Statement base, Description description)547         public Statement apply(Statement base, Description description) {
548             return new CreateSurfaceTextureStatement(base);
549         }
550 
551         private class CreateSurfaceTextureStatement extends Statement {
552 
553             private final Statement mBase;
554 
CreateSurfaceTextureStatement(Statement base)555             public CreateSurfaceTextureStatement(Statement base) {
556                 mBase = base;
557             }
558 
559             @Override
evaluate()560             public void evaluate() throws Throwable {
561                 try {
562                     mBase.evaluate();
563                 } finally {
564                     try {
565                         if (mSurface != null) mSurface.release();
566                     } catch (Throwable t) {}
567                     try {
568                         if (mSurfaceTexture != null) mSurfaceTexture.release();
569                     } catch (Throwable t) {}
570                 }
571             }
572         }
573     }
574 }
575