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