Home | History | Annotate | Download | only in cts
      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      */
    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      */
    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
    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 
    152         public ImageVerifierListener(Size sz, int format) {
    153             mSize = sz;
    154             mFormat = format;
    155         }
    156 
    157         @Override
    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
    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          */
    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
    204         public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
    205                 long timestamp, long frameNumber)
    206         {
    207         }
    208 
    209         @Override
    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
    221         public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
    222                 CaptureFailure failure) {
    223         }
    224 
    225         @Override
    226         public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId,
    227                 long frameNumber) {
    228         }
    229 
    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          */
    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 
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    802         public void onOpened(CameraDevice camera) {
    803         }
    804 
    805         @Override
    806         public void onDisconnected(CameraDevice camera) {
    807         }
    808 
    809         @Override
    810         public void onError(CameraDevice camera, int error) {
    811         }
    812 
    813         private MockStateCallback() {}
    814 
    815         /**
    816          * Create a Mockito-ready mocked StateCallback.
    817          */
    818         public static MockStateCallback mock() {
    819             return Mockito.spy(new MockStateCallback());
    820         }
    821     }
    822 
    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 
    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 
    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 
    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      */
    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      */
    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