• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 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.hardware.camera2.cts;
18 
19 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
20 
21 import android.graphics.BitmapFactory;
22 import android.graphics.ImageFormat;
23 import android.graphics.PointF;
24 import android.graphics.Rect;
25 import android.hardware.camera2.CameraAccessException;
26 import android.hardware.camera2.CameraCaptureSession;
27 import android.hardware.camera2.CameraDevice;
28 import android.hardware.camera2.CameraManager;
29 import android.hardware.camera2.CameraCharacteristics;
30 import android.hardware.camera2.CaptureFailure;
31 import android.hardware.camera2.CaptureRequest;
32 import android.hardware.camera2.CaptureResult;
33 import android.hardware.camera2.TotalCaptureResult;
34 import android.hardware.cts.helpers.CameraUtils;
35 import android.util.Size;
36 import android.hardware.camera2.params.MeteringRectangle;
37 import android.hardware.camera2.params.StreamConfigurationMap;
38 import android.media.Image;
39 import android.media.ImageReader;
40 import android.media.Image.Plane;
41 import android.os.Handler;
42 import android.util.Log;
43 import android.view.Surface;
44 
45 import com.android.ex.camera2.blocking.BlockingCameraManager;
46 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
47 import com.android.ex.camera2.blocking.BlockingSessionCallback;
48 import com.android.ex.camera2.blocking.BlockingStateCallback;
49 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
50 
51 import junit.framework.Assert;
52 
53 import org.mockito.Mockito;
54 
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.lang.reflect.Array;
58 import java.nio.ByteBuffer;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collections;
62 import java.util.Comparator;
63 import java.util.List;
64 import java.util.concurrent.LinkedBlockingQueue;
65 import java.util.concurrent.TimeUnit;
66 
67 /**
68  * A package private utility class for wrapping up the camera2 cts test common utility functions
69  */
70 public class CameraTestUtils extends Assert {
71     private static final String TAG = "CameraTestUtils";
72     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
73     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
74     public static final Size SIZE_BOUND_1080P = new Size(1920, 1088);
75     public static final Size SIZE_BOUND_2160P = new Size(3840, 2160);
76     // Only test the preview size that is no larger than 1080p.
77     public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P;
78     // Default timeouts for reaching various states
79     public static final int CAMERA_OPEN_TIMEOUT_MS = 3000;
80     public static final int CAMERA_CLOSE_TIMEOUT_MS = 3000;
81     public static final int CAMERA_IDLE_TIMEOUT_MS = 3000;
82     public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000;
83     public static final int CAMERA_BUSY_TIMEOUT_MS = 1000;
84     public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000;
85     public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 3000;
86     public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000;
87     public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000;
88 
89     public static final int SESSION_CONFIGURE_TIMEOUT_MS = 3000;
90     public static final int SESSION_CLOSE_TIMEOUT_MS = 3000;
91     public static final int SESSION_READY_TIMEOUT_MS = 3000;
92     public static final int SESSION_ACTIVE_TIMEOUT_MS = 1000;
93 
94     public static final int MAX_READER_IMAGES = 5;
95 
96     /**
97      * Create an {@link android.media.ImageReader} object and get the surface.
98      *
99      * @param size The size of this ImageReader to be created.
100      * @param format The format of this ImageReader to be created
101      * @param maxNumImages The max number of images that can be acquired simultaneously.
102      * @param listener The listener used by this ImageReader to notify callbacks.
103      * @param handler The handler to use for any listener callbacks.
104      */
makeImageReader(Size size, int format, int maxNumImages, ImageReader.OnImageAvailableListener listener, Handler handler)105     public static ImageReader makeImageReader(Size size, int format, int maxNumImages,
106             ImageReader.OnImageAvailableListener listener, Handler handler) {
107         ImageReader reader =  ImageReader.newInstance(size.getWidth(), size.getHeight(), format,
108                 maxNumImages);
109         reader.setOnImageAvailableListener(listener, handler);
110         if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size);
111         return reader;
112     }
113 
114     /**
115      * Close pending images and clean up an {@link android.media.ImageReader} object.
116      * @param reader an {@link android.media.ImageReader} to close.
117      */
closeImageReader(ImageReader reader)118     public static void closeImageReader(ImageReader reader) {
119         if (reader != null) {
120             reader.close();
121         }
122     }
123 
124     /**
125      * Dummy listener that release the image immediately once it is available.
126      *
127      * <p>
128      * It can be used for the case where we don't care the image data at all.
129      * </p>
130      */
131     public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
132         @Override
onImageAvailable(ImageReader reader)133         public void onImageAvailable(ImageReader reader) {
134             Image image = null;
135             try {
136                 image = reader.acquireNextImage();
137             } finally {
138                 if (image != null) {
139                     image.close();
140                 }
141             }
142         }
143     }
144 
145     /**
146      * Image listener that release the image immediately after validating the image
147      */
148     public static class ImageVerifierListener implements ImageReader.OnImageAvailableListener {
149         private Size mSize;
150         private int mFormat;
151 
ImageVerifierListener(Size sz, int format)152         public ImageVerifierListener(Size sz, int format) {
153             mSize = sz;
154             mFormat = format;
155         }
156 
157         @Override
onImageAvailable(ImageReader reader)158         public void onImageAvailable(ImageReader reader) {
159             Image image = null;
160             try {
161                 image = reader.acquireNextImage();
162             } finally {
163                 if (image != null) {
164                     validateImage(image, mSize.getWidth(), mSize.getHeight(), mFormat, null);
165                     image.close();
166                 }
167             }
168         }
169     }
170 
171     public static class SimpleImageReaderListener
172             implements ImageReader.OnImageAvailableListener {
173         private final LinkedBlockingQueue<Image> mQueue =
174                 new LinkedBlockingQueue<Image>();
175 
176         @Override
onImageAvailable(ImageReader reader)177         public void onImageAvailable(ImageReader reader) {
178             try {
179                 mQueue.put(reader.acquireNextImage());
180             } catch (InterruptedException e) {
181                 throw new UnsupportedOperationException(
182                         "Can't handle InterruptedException in onImageAvailable");
183             }
184         }
185 
186         /**
187          * Get an image from the image reader.
188          *
189          * @param timeout Timeout value for the wait.
190          * @return The image from the image reader.
191          */
getImage(long timeout)192         public Image getImage(long timeout) throws InterruptedException {
193             Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
194             assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
195             return image;
196         }
197     }
198 
199     public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback {
200         private final LinkedBlockingQueue<CaptureResult> mQueue =
201                 new LinkedBlockingQueue<CaptureResult>();
202 
203         @Override
onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber)204         public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
205                 long timestamp, long frameNumber)
206         {
207         }
208 
209         @Override
onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result)210         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
211                 TotalCaptureResult result) {
212             try {
213                 mQueue.put(result);
214             } catch (InterruptedException e) {
215                 throw new UnsupportedOperationException(
216                         "Can't handle InterruptedException in onCaptureCompleted");
217             }
218         }
219 
220         @Override
onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)221         public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
222                 CaptureFailure failure) {
223         }
224 
225         @Override
onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber)226         public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId,
227                 long frameNumber) {
228         }
229 
getCaptureResult(long timeout)230         public CaptureResult getCaptureResult(long timeout) {
231             try {
232                 CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
233                 assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
234                 return result;
235             } catch (InterruptedException e) {
236                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
237             }
238         }
239 
240         /**
241          * Get the {@link #CaptureResult capture result} for a given
242          * {@link #CaptureRequest capture request}.
243          *
244          * @param myRequest The {@link #CaptureRequest capture request} whose
245          *            corresponding {@link #CaptureResult capture result} was
246          *            being waited for
247          * @param numResultsWait Number of frames to wait for the capture result
248          *            before timeout.
249          * @throws TimeoutRuntimeException If more than numResultsWait results are
250          *            seen before the result matching myRequest arrives, or each
251          *            individual wait for result times out after
252          *            {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
253          */
getCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)254         public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest,
255                 int numResultsWait) {
256             if (numResultsWait < 0) {
257                 throw new IllegalArgumentException("numResultsWait must be no less than 0");
258             }
259 
260             CaptureResult result;
261             int i = 0;
262             do {
263                 result = getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
264                 if (result.getRequest().equals(myRequest)) {
265                     return result;
266                 }
267             } while (i++ < numResultsWait);
268 
269             throw new TimeoutRuntimeException("Unable to get the expected capture result after "
270                     + "waiting for " + numResultsWait + " results");
271         }
272 
hasMoreResults()273         public boolean hasMoreResults()
274         {
275             return mQueue.isEmpty();
276         }
277     }
278 
279     /**
280      * Block until the camera is opened.
281      *
282      * <p>Don't use this to test #onDisconnected/#onError since this will throw
283      * an AssertionError if it fails to open the camera device.</p>
284      *
285      * @return CameraDevice opened camera device
286      *
287      * @throws IllegalArgumentException
288      *            If the handler is null, or if the handler's looper is current.
289      * @throws CameraAccessException
290      *            If open fails immediately.
291      * @throws BlockingOpenException
292      *            If open fails after blocking for some amount of time.
293      * @throws TimeoutRuntimeException
294      *            If opening times out. Typically unrecoverable.
295      */
openCamera(CameraManager manager, String cameraId, CameraDevice.StateCallback listener, Handler handler)296     public static CameraDevice openCamera(CameraManager manager, String cameraId,
297             CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException,
298             BlockingOpenException {
299 
300         /**
301          * Although camera2 API allows 'null' Handler (it will just use the current
302          * thread's Looper), this is not what we want for CTS.
303          *
304          * In CTS the default looper is used only to process events in between test runs,
305          * so anything sent there would not be executed inside a test and the test would fail.
306          *
307          * In this case, BlockingCameraManager#openCamera performs the check for us.
308          */
309         return (new BlockingCameraManager(manager)).openCamera(cameraId, listener, handler);
310     }
311 
312 
313     /**
314      * Block until the camera is opened.
315      *
316      * <p>Don't use this to test #onDisconnected/#onError since this will throw
317      * an AssertionError if it fails to open the camera device.</p>
318      *
319      * @throws IllegalArgumentException
320      *            If the handler is null, or if the handler's looper is current.
321      * @throws CameraAccessException
322      *            If open fails immediately.
323      * @throws BlockingOpenException
324      *            If open fails after blocking for some amount of time.
325      * @throws TimeoutRuntimeException
326      *            If opening times out. Typically unrecoverable.
327      */
openCamera(CameraManager manager, String cameraId, Handler handler)328     public static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler)
329             throws CameraAccessException,
330             BlockingOpenException {
331         return openCamera(manager, cameraId, /*listener*/null, handler);
332     }
333 
334     /**
335      * Configure a new camera session with output surfaces.
336      *
337      * @param camera The CameraDevice to be configured.
338      * @param outputSurfaces The surface list that used for camera output.
339      * @param listener The callback CameraDevice will notify when capture results are available.
340      */
configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)341     public static CameraCaptureSession configureCameraSession(CameraDevice camera,
342             List<Surface> outputSurfaces,
343             CameraCaptureSession.StateCallback listener, Handler handler)
344             throws CameraAccessException {
345         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
346         camera.createCaptureSession(outputSurfaces, sessionListener, handler);
347 
348         return sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
349     }
350 
assertArrayNotEmpty(T arr, String message)351     public static <T> void assertArrayNotEmpty(T arr, String message) {
352         assertTrue(message, arr != null && Array.getLength(arr) > 0);
353     }
354 
355     /**
356      * Check if the format is a legal YUV format camera supported.
357      */
checkYuvFormat(int format)358     public static void checkYuvFormat(int format) {
359         if ((format != ImageFormat.YUV_420_888) &&
360                 (format != ImageFormat.NV21) &&
361                 (format != ImageFormat.YV12)) {
362             fail("Wrong formats: " + format);
363         }
364     }
365 
366     /**
367      * Check if image size and format match given size and format.
368      */
checkImage(Image image, int width, int height, int format)369     public static void checkImage(Image image, int width, int height, int format) {
370         assertNotNull("Input image is invalid", image);
371         assertEquals("Format doesn't match", format, image.getFormat());
372         assertEquals("Width doesn't match", width, image.getWidth());
373         assertEquals("Height doesn't match", height, image.getHeight());
374     }
375 
376     /**
377      * <p>Read data from all planes of an Image into a contiguous unpadded, unpacked
378      * 1-D linear byte array, such that it can be write into disk, or accessed by
379      * software conveniently. It supports YUV_420_888/NV21/YV12 and JPEG input
380      * Image format.</p>
381      *
382      * <p>For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
383      * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
384      * (xstride = width, ystride = height for chroma and luma components).</p>
385      *
386      * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p>
387      */
getDataFromImage(Image image)388     public static byte[] getDataFromImage(Image image) {
389         assertNotNull("Invalid image:", image);
390         int format = image.getFormat();
391         int width = image.getWidth();
392         int height = image.getHeight();
393         int rowStride, pixelStride;
394         byte[] data = null;
395 
396         // Read image data
397         Plane[] planes = image.getPlanes();
398         assertTrue("Fail to get image planes", planes != null && planes.length > 0);
399 
400         // Check image validity
401         checkAndroidImageFormat(image);
402 
403         ByteBuffer buffer = null;
404         // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
405         if (format == ImageFormat.JPEG) {
406             buffer = planes[0].getBuffer();
407             assertNotNull("Fail to get jpeg ByteBuffer", buffer);
408             data = new byte[buffer.remaining()];
409             buffer.get(data);
410             buffer.rewind();
411             return data;
412         }
413 
414         int offset = 0;
415         data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
416         int maxRowSize = planes[0].getRowStride();
417         for (int i = 0; i < planes.length; i++) {
418             if (maxRowSize < planes[i].getRowStride()) {
419                 maxRowSize = planes[i].getRowStride();
420             }
421         }
422         byte[] rowData = new byte[maxRowSize];
423         if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
424         for (int i = 0; i < planes.length; i++) {
425             buffer = planes[i].getBuffer();
426             assertNotNull("Fail to get bytebuffer from plane", buffer);
427             rowStride = planes[i].getRowStride();
428             pixelStride = planes[i].getPixelStride();
429             assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
430             if (VERBOSE) {
431                 Log.v(TAG, "pixelStride " + pixelStride);
432                 Log.v(TAG, "rowStride " + rowStride);
433                 Log.v(TAG, "width " + width);
434                 Log.v(TAG, "height " + height);
435             }
436             // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
437             int w = (i == 0) ? width : width / 2;
438             int h = (i == 0) ? height : height / 2;
439             assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
440             for (int row = 0; row < h; row++) {
441                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
442                 if (pixelStride == bytesPerPixel) {
443                     // Special case: optimized read of the entire row
444                     int length = w * bytesPerPixel;
445                     buffer.get(data, offset, length);
446                     // Advance buffer the remainder of the row stride
447                     buffer.position(buffer.position() + rowStride - length);
448                     offset += length;
449                 } else {
450                     // Generic case: should work for any pixelStride but slower.
451                     // Use intermediate buffer to avoid read byte-by-byte from
452                     // DirectByteBuffer, which is very bad for performance
453                     buffer.get(rowData, 0, rowStride);
454                     for (int col = 0; col < w; col++) {
455                         data[offset++] = rowData[col * pixelStride];
456                     }
457                 }
458             }
459             if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
460             buffer.rewind();
461         }
462         return data;
463     }
464 
465     /**
466      * <p>Check android image format validity for an image, only support below formats:</p>
467      *
468      * <p>YUV_420_888/NV21/YV12, can add more for future</p>
469      */
checkAndroidImageFormat(Image image)470     public static void checkAndroidImageFormat(Image image) {
471         int format = image.getFormat();
472         Plane[] planes = image.getPlanes();
473         switch (format) {
474             case ImageFormat.YUV_420_888:
475             case ImageFormat.NV21:
476             case ImageFormat.YV12:
477                 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
478                 break;
479             case ImageFormat.JPEG:
480             case ImageFormat.RAW_SENSOR:
481                 assertEquals("Jpeg Image should have one plane", 1, planes.length);
482                 break;
483             default:
484                 fail("Unsupported Image Format: " + format);
485         }
486     }
487 
dumpFile(String fileName, byte[] data)488     public static void dumpFile(String fileName, byte[] data) {
489         FileOutputStream outStream;
490         try {
491             Log.v(TAG, "output will be saved as " + fileName);
492             outStream = new FileOutputStream(fileName);
493         } catch (IOException ioe) {
494             throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
495         }
496 
497         try {
498             outStream.write(data);
499             outStream.close();
500         } catch (IOException ioe) {
501             throw new RuntimeException("failed writing data to file " + fileName, ioe);
502         }
503     }
504 
505     /**
506      * Get the available output sizes for the user-defined {@code format}.
507      *
508      * <p>Note that implementation-defined/hidden formats are not supported.</p>
509      */
getSupportedSizeForFormat(int format, String cameraId, CameraManager cameraManager)510     public static Size[] getSupportedSizeForFormat(int format, String cameraId,
511             CameraManager cameraManager) throws CameraAccessException {
512         CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
513         assertNotNull("Can't get camera characteristics!", properties);
514         if (VERBOSE) {
515             Log.v(TAG, "get camera characteristics for camera: " + cameraId);
516         }
517         StreamConfigurationMap configMap =
518                 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
519         Size[] availableSizes = configMap.getOutputSizes(format);
520         assertArrayNotEmpty(availableSizes, "availableSizes should not be empty");
521         if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
522         return availableSizes;
523     }
524 
525     /**
526      * Size comparator that compares the number of pixels it covers.
527      *
528      * <p>If two the areas of two sizes are same, compare the widths.</p>
529      */
530     public static class SizeComparator implements Comparator<Size> {
531         @Override
compare(Size lhs, Size rhs)532         public int compare(Size lhs, Size rhs) {
533             return CameraUtils
534                     .compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight());
535         }
536     }
537 
538     /**
539      * Get sorted size list in descending order. Remove the sizes larger than
540      * the bound. If the bound is null, don't do the size bound filtering.
541      */
getSupportedPreviewSizes(String cameraId, CameraManager cameraManager, Size bound)542     static public List<Size> getSupportedPreviewSizes(String cameraId,
543             CameraManager cameraManager, Size bound) throws CameraAccessException {
544         return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound);
545     }
546 
547     /**
548      * Get a sorted list of sizes from a given size list.
549      *
550      * <p>
551      * The size is compare by area it covers, if the areas are same, then
552      * compare the widths.
553      * </p>
554      *
555      * @param sizeList The input size list to be sorted
556      * @param ascending True if the order is ascending, otherwise descending order
557      * @return The ordered list of sizes
558      */
getAscendingOrderSizes(final List<Size> sizeList, boolean ascending)559     static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) {
560         if (sizeList == null) {
561             throw new IllegalArgumentException("sizeList shouldn't be null");
562         }
563 
564         Comparator<Size> comparator = new SizeComparator();
565         List<Size> sortedSizes = new ArrayList<Size>();
566         sortedSizes.addAll(sizeList);
567         Collections.sort(sortedSizes, comparator);
568         if (!ascending) {
569             Collections.reverse(sortedSizes);
570         }
571 
572         return sortedSizes;
573     }
574 
575     /**
576      * Get sorted (descending order) size list for given format. Remove the sizes larger than
577      * the bound. If the bound is null, don't do the size bound filtering.
578      */
getSortedSizesForFormat(String cameraId, CameraManager cameraManager, int format, Size bound)579     static private List<Size> getSortedSizesForFormat(String cameraId,
580             CameraManager cameraManager, int format, Size bound) throws CameraAccessException {
581         Comparator<Size> comparator = new SizeComparator();
582         Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager);
583         List<Size> sortedSizes = null;
584         if (bound != null) {
585             sortedSizes = new ArrayList<Size>(/*capacity*/1);
586             for (Size sz : sizes) {
587                 if (comparator.compare(sz, bound) <= 0) {
588                     sortedSizes.add(sz);
589                 }
590             }
591         } else {
592             sortedSizes = Arrays.asList(sizes);
593         }
594         assertTrue("Supported size list should have at least one element",
595                 sortedSizes.size() > 0);
596 
597         Collections.sort(sortedSizes, comparator);
598         // Make it in descending order.
599         Collections.reverse(sortedSizes);
600         return sortedSizes;
601     }
602 
603     /**
604      * Get supported video size list for a given camera device.
605      *
606      * <p>
607      * Filter out the sizes that are larger than the bound. If the bound is
608      * null, don't do the size bound filtering.
609      * </p>
610      */
getSupportedVideoSizes(String cameraId, CameraManager cameraManager, Size bound)611     static public List<Size> getSupportedVideoSizes(String cameraId,
612             CameraManager cameraManager, Size bound) throws CameraAccessException {
613         return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.YUV_420_888, bound);
614     }
615 
616     /**
617      * Get supported video size list (descending order) for a given camera device.
618      *
619      * <p>
620      * Filter out the sizes that are larger than the bound. If the bound is
621      * null, don't do the size bound filtering.
622      * </p>
623      */
getSupportedStillSizes(String cameraId, CameraManager cameraManager, Size bound)624     static public List<Size> getSupportedStillSizes(String cameraId,
625             CameraManager cameraManager, Size bound) throws CameraAccessException {
626         return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound);
627     }
628 
getMinPreviewSize(String cameraId, CameraManager cameraManager)629     static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager)
630             throws CameraAccessException {
631         List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null);
632         return sizes.get(sizes.size() - 1);
633     }
634 
635     /**
636      * Get max supported preview size for a camera device.
637      */
getMaxPreviewSize(String cameraId, CameraManager cameraManager)638     static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager)
639             throws CameraAccessException {
640         return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null);
641     }
642 
643     /**
644      * Get max preview size for a camera device in the supported sizes that are no larger
645      * than the bound.
646      */
getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)647     static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)
648             throws CameraAccessException {
649         List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound);
650         return sizes.get(0);
651     }
652 
653     /**
654      * Get the largest size by area.
655      *
656      * @param sizes an array of sizes, must have at least 1 element
657      *
658      * @return Largest Size
659      *
660      * @throws IllegalArgumentException if sizes was null or had 0 elements
661      */
getMaxSize(Size[] sizes)662     public static Size getMaxSize(Size[] sizes) {
663         if (sizes == null || sizes.length == 0) {
664             throw new IllegalArgumentException("sizes was empty");
665         }
666 
667         Size sz = sizes[0];
668         for (Size size : sizes) {
669             if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
670                 sz = size;
671             }
672         }
673 
674         return sz;
675     }
676 
677     /**
678      * Get object array from byte array.
679      *
680      * @param array Input byte array to be converted
681      * @return Byte object array converted from input byte array
682      */
toObject(byte[] array)683     public static Byte[] toObject(byte[] array) {
684         return convertPrimitiveArrayToObjectArray(array, Byte.class);
685     }
686 
687     /**
688      * Get object array from int array.
689      *
690      * @param array Input int array to be converted
691      * @return Integer object array converted from input int array
692      */
toObject(int[] array)693     public static Integer[] toObject(int[] array) {
694         return convertPrimitiveArrayToObjectArray(array, Integer.class);
695     }
696 
697     /**
698      * Get object array from float array.
699      *
700      * @param array Input float array to be converted
701      * @return Float object array converted from input float array
702      */
toObject(float[] array)703     public static Float[] toObject(float[] array) {
704         return convertPrimitiveArrayToObjectArray(array, Float.class);
705     }
706 
707     /**
708      * Get object array from double array.
709      *
710      * @param array Input double array to be converted
711      * @return Double object array converted from input double array
712      */
toObject(double[] array)713     public static Double[] toObject(double[] array) {
714         return convertPrimitiveArrayToObjectArray(array, Double.class);
715     }
716 
717     /**
718      * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]).
719      *
720      * @param array Input array object
721      * @param wrapperClass The boxed class it converts to
722      * @return Boxed version of primitive array
723      */
convertPrimitiveArrayToObjectArray(final Object array, final Class<T> wrapperClass)724     private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array,
725             final Class<T> wrapperClass) {
726         // getLength does the null check and isArray check already.
727         int arrayLength = Array.getLength(array);
728         if (arrayLength == 0) {
729             throw new IllegalArgumentException("Input array shouldn't be empty");
730         }
731 
732         @SuppressWarnings("unchecked")
733         final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength);
734         for (int i = 0; i < arrayLength; i++) {
735             Array.set(result, i, Array.get(array, i));
736         }
737         return result;
738     }
739 
740     /**
741      * Validate image based on format and size.
742      * <p>
743      * Only RAW_SENSOR, YUV420_888 and JPEG formats are supported. Calling this
744      * method with other formats will cause a UnsupportedOperationException.
745      * </p>
746      *
747      * @param image The image to be validated.
748      * @param width The image width.
749      * @param height The image height.
750      * @param format The image format.
751      * @param filePath The debug dump file path, null if don't want to dump to
752      *            file.
753      * @throws UnsupportedOperationException if calling with format other than
754      *             RAW_SENSOR, YUV420_888 or JPEG.
755      */
validateImage(Image image, int width, int height, int format, String filePath)756     public static void validateImage(Image image, int width, int height, int format,
757             String filePath) {
758         checkImage(image, width, height, format);
759 
760         /**
761          * TODO: validate timestamp:
762          * 1. capture result timestamp against the image timestamp (need
763          * consider frame drops)
764          * 2. timestamps should be monotonically increasing for different requests
765          */
766         if(VERBOSE) Log.v(TAG, "validating Image");
767         byte[] data = getDataFromImage(image);
768         assertTrue("Invalid image data", data != null && data.length > 0);
769 
770         switch (format) {
771             case ImageFormat.JPEG:
772                 validateJpegData(data, width, height, filePath);
773                 break;
774             case ImageFormat.YUV_420_888:
775                 validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
776                 break;
777             case ImageFormat.RAW_SENSOR:
778                 validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath);
779                 break;
780             default:
781                 throw new UnsupportedOperationException("Unsupported format for validation: "
782                         + format);
783         }
784     }
785 
786     /**
787      * Provide a mock for {@link CameraDevice.StateCallback}.
788      *
789      * <p>Only useful because mockito can't mock {@link CameraDevice.StateCallback} which is an
790      * abstract class.</p>
791      *
792      * <p>
793      * Use this instead of other classes when needing to verify interactions, since
794      * trying to spy on {@link BlockingStateCallback} (or others) will cause unnecessary extra
795      * interactions which will cause false test failures.
796      * </p>
797      *
798      */
799     public static class MockStateCallback extends CameraDevice.StateCallback {
800 
801         @Override
onOpened(CameraDevice camera)802         public void onOpened(CameraDevice camera) {
803         }
804 
805         @Override
onDisconnected(CameraDevice camera)806         public void onDisconnected(CameraDevice camera) {
807         }
808 
809         @Override
onError(CameraDevice camera, int error)810         public void onError(CameraDevice camera, int error) {
811         }
812 
MockStateCallback()813         private MockStateCallback() {}
814 
815         /**
816          * Create a Mockito-ready mocked StateCallback.
817          */
mock()818         public static MockStateCallback mock() {
819             return Mockito.spy(new MockStateCallback());
820         }
821     }
822 
validateJpegData(byte[] jpegData, int width, int height, String filePath)823     private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) {
824         BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
825         // DecodeBound mode: only parse the frame header to get width/height.
826         // it doesn't decode the pixel.
827         bmpOptions.inJustDecodeBounds = true;
828         BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions);
829         assertEquals(width, bmpOptions.outWidth);
830         assertEquals(height, bmpOptions.outHeight);
831 
832         // Pixel decoding mode: decode whole image. check if the image data
833         // is decodable here.
834         assertNotNull("Decoding jpeg failed",
835                 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length));
836         if (DEBUG && filePath != null) {
837             String fileName =
838                     filePath + "/" + width + "x" + height + ".jpeg";
839             dumpFile(fileName, jpegData);
840         }
841     }
842 
validateYuvData(byte[] yuvData, int width, int height, int format, long ts, String filePath)843     private static void validateYuvData(byte[] yuvData, int width, int height, int format,
844             long ts, String filePath) {
845         checkYuvFormat(format);
846         if (VERBOSE) Log.v(TAG, "Validating YUV data");
847         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
848         assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
849 
850         // TODO: Can add data validation for test pattern.
851 
852         if (DEBUG && filePath != null) {
853             String fileName =
854                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv";
855             dumpFile(fileName, yuvData);
856         }
857     }
858 
validateRaw16Data(byte[] rawData, int width, int height, int format, long ts, String filePath)859     private static void validateRaw16Data(byte[] rawData, int width, int height, int format,
860             long ts, String filePath) {
861         if (VERBOSE) Log.v(TAG, "Validating raw data");
862         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
863         assertEquals("Yuv data doesn't match", expectedSize, rawData.length);
864 
865         // TODO: Can add data validation for test pattern.
866 
867         if (DEBUG && filePath != null) {
868             String fileName =
869                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16";
870             dumpFile(fileName, rawData);
871         }
872 
873         return;
874     }
875 
getValueNotNull(CaptureResult result, CaptureResult.Key<T> key)876     public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
877         if (result == null) {
878             throw new IllegalArgumentException("Result must not be null");
879         }
880 
881         T value = result.get(key);
882         assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
883         return value;
884     }
885 
886     /**
887      * Get a crop region for a given zoom factor and center position.
888      * <p>
889      * The center position is normalized position in range of [0, 1.0], where
890      * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right
891      * corner. The center position could limit the effective minimal zoom
892      * factor, for example, if the center position is (0.75, 0.75), the
893      * effective minimal zoom position becomes 2.0. If the requested zoom factor
894      * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned.
895      * </p>
896      * <p>
897      * The aspect ratio of the crop region is maintained the same as the aspect
898      * ratio of active array.
899      * </p>
900      *
901      * @param zoomFactor The zoom factor to generate the crop region, it must be
902      *            >= 1.0
903      * @param center The normalized zoom center point that is in the range of [0, 1].
904      * @param maxZoom The max zoom factor supported by this device.
905      * @param activeArray The active array size of this device.
906      * @return crop region for the given normalized center and zoom factor.
907      */
getCropRegionForZoom(float zoomFactor, final PointF center, final float maxZoom, final Rect activeArray)908     public static Rect getCropRegionForZoom(float zoomFactor, final PointF center,
909             final float maxZoom, final Rect activeArray) {
910         if (zoomFactor < 1.0) {
911             throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0");
912         }
913         if (center.x > 1.0 || center.x < 0) {
914             throw new IllegalArgumentException("center.x " + center.x
915                     + " should be in range of [0, 1.0]");
916         }
917         if (center.y > 1.0 || center.y < 0) {
918             throw new IllegalArgumentException("center.y " + center.y
919                     + " should be in range of [0, 1.0]");
920         }
921         if (maxZoom < 1.0) {
922             throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0");
923         }
924         if (activeArray == null) {
925             throw new IllegalArgumentException("activeArray must not be null");
926         }
927 
928         float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x),
929                 Math.min(center.y, 1.0f - center.y));
930         float minEffectiveZoom =  0.5f / minCenterLength;
931         if (minEffectiveZoom > maxZoom) {
932             throw new IllegalArgumentException("Requested center " + center.toString() +
933                     " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max"
934                             + " zoom factor " + maxZoom);
935         }
936 
937         if (zoomFactor < minEffectiveZoom) {
938             Log.w(TAG, "Requested zoomFactor " + zoomFactor + " > minimal zoomable factor "
939                     + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom);
940             zoomFactor = minEffectiveZoom;
941         }
942 
943         int cropCenterX = (int)(activeArray.width() * center.x);
944         int cropCenterY = (int)(activeArray.height() * center.y);
945         int cropWidth = (int) (activeArray.width() / zoomFactor);
946         int cropHeight = (int) (activeArray.height() / zoomFactor);
947 
948         return new Rect(
949                 /*left*/cropCenterX - cropWidth / 2,
950                 /*top*/cropCenterY - cropHeight / 2,
951                 /*right*/ cropCenterX + cropWidth / 2 - 1,
952                 /*bottom*/cropCenterY + cropHeight / 2 - 1);
953     }
954 
955     /**
956      * Calculate output 3A region from the intersection of input 3A region and cropped region.
957      *
958      * @param requestRegions The input 3A regions
959      * @param cropRect The cropped region
960      * @return expected 3A regions output in capture result
961      */
getExpectedOutputRegion( MeteringRectangle[] requestRegions, Rect cropRect)962     public static MeteringRectangle[] getExpectedOutputRegion(
963             MeteringRectangle[] requestRegions, Rect cropRect){
964         MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length];
965         for (int i = 0; i < requestRegions.length; i++) {
966             Rect requestRect = requestRegions[i].getRect();
967             Rect resultRect = new Rect();
968             assertTrue("Input 3A region must intersect cropped region",
969                         resultRect.setIntersect(requestRect, cropRect));
970             resultRegions[i] = new MeteringRectangle(
971                     resultRect,
972                     requestRegions[i].getMeteringWeight());
973         }
974         return resultRegions;
975     }
976 }
977