Home | History | Annotate | Download | only in helpers
      1 /*
      2  * Copyright 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.mediaframeworktest.helpers;
     18 
     19 import com.android.ex.camera2.blocking.BlockingCameraManager;
     20 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
     21 import com.android.ex.camera2.blocking.BlockingSessionCallback;
     22 import com.android.ex.camera2.blocking.BlockingStateCallback;
     23 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
     24 
     25 import junit.framework.Assert;
     26 
     27 import org.mockito.Mockito;
     28 
     29 import android.graphics.Bitmap;
     30 import android.graphics.BitmapFactory;
     31 import android.graphics.ImageFormat;
     32 import android.graphics.PointF;
     33 import android.graphics.Rect;
     34 import android.hardware.camera2.CameraAccessException;
     35 import android.hardware.camera2.CameraCaptureSession;
     36 import android.hardware.camera2.CameraCharacteristics;
     37 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
     38 import android.hardware.camera2.CameraDevice;
     39 import android.hardware.camera2.CameraManager;
     40 import android.hardware.camera2.CaptureFailure;
     41 import android.hardware.camera2.CaptureRequest;
     42 import android.hardware.camera2.CaptureResult;
     43 import android.hardware.camera2.TotalCaptureResult;
     44 import android.hardware.camera2.params.InputConfiguration;
     45 import android.hardware.camera2.params.MeteringRectangle;
     46 import android.hardware.camera2.params.StreamConfigurationMap;
     47 import android.location.Location;
     48 import android.location.LocationManager;
     49 import android.media.ExifInterface;
     50 import android.media.Image;
     51 import android.media.Image.Plane;
     52 import android.media.ImageReader;
     53 import android.media.ImageWriter;
     54 import android.os.Build;
     55 import android.os.Environment;
     56 import android.os.Handler;
     57 import android.util.Log;
     58 import android.util.Pair;
     59 import android.util.Size;
     60 import android.view.Display;
     61 import android.view.Surface;
     62 import android.view.WindowManager;
     63 
     64 import java.io.FileOutputStream;
     65 import java.io.IOException;
     66 import java.lang.reflect.Array;
     67 import java.nio.ByteBuffer;
     68 import java.text.ParseException;
     69 import java.text.SimpleDateFormat;
     70 import java.util.ArrayList;
     71 import java.util.Arrays;
     72 import java.util.Collections;
     73 import java.util.Comparator;
     74 import java.util.Date;
     75 import java.util.HashMap;
     76 import java.util.List;
     77 import java.util.concurrent.LinkedBlockingQueue;
     78 import java.util.concurrent.Semaphore;
     79 import java.util.concurrent.TimeUnit;
     80 import java.util.concurrent.atomic.AtomicLong;
     81 
     82 /**
     83  * A package private utility class for wrapping up the camera2 framework test common utility
     84  * functions
     85  */
     86 /**
     87  * (non-Javadoc)
     88  * @see android.hardware.camera2.cts.CameraTestUtils
     89  */
     90 public class CameraTestUtils extends Assert {
     91     private static final String TAG = "CameraTestUtils";
     92     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     93     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     94     public static final Size SIZE_BOUND_1080P = new Size(1920, 1088);
     95     public static final Size SIZE_BOUND_2160P = new Size(3840, 2160);
     96     // Only test the preview size that is no larger than 1080p.
     97     public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P;
     98     // Default timeouts for reaching various states
     99     public static final int CAMERA_OPEN_TIMEOUT_MS = 3000;
    100     public static final int CAMERA_CLOSE_TIMEOUT_MS = 3000;
    101     public static final int CAMERA_IDLE_TIMEOUT_MS = 3000;
    102     public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000;
    103     public static final int CAMERA_BUSY_TIMEOUT_MS = 1000;
    104     public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000;
    105     public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 3000;
    106     public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000;
    107     public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000;
    108 
    109     public static final int SESSION_CONFIGURE_TIMEOUT_MS = 3000;
    110     public static final int SESSION_CLOSE_TIMEOUT_MS = 3000;
    111     public static final int SESSION_READY_TIMEOUT_MS = 3000;
    112     public static final int SESSION_ACTIVE_TIMEOUT_MS = 1000;
    113 
    114     public static final int MAX_READER_IMAGES = 5;
    115 
    116     private static final int EXIF_DATETIME_LENGTH = 19;
    117     private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
    118     private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f;
    119     private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO = 0.05f;
    120     private static final float EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC = 0.002f;
    121     private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f;
    122 
    123     private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER);
    124     private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
    125     private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
    126 
    127     protected static final String DEBUG_FILE_NAME_BASE =
    128             Environment.getExternalStorageDirectory().getPath();
    129 
    130     static {
    131         sTestLocation0.setTime(1199145600L);
    132         sTestLocation0.setLatitude(37.736071);
    133         sTestLocation0.setLongitude(-122.441983);
    134         sTestLocation0.setAltitude(21.0);
    135 
    136         sTestLocation1.setTime(1199145601L);
    137         sTestLocation1.setLatitude(0.736071);
    138         sTestLocation1.setLongitude(0.441983);
    139         sTestLocation1.setAltitude(1.0);
    140 
    141         sTestLocation2.setTime(1199145602L);
    142         sTestLocation2.setLatitude(-89.736071);
    143         sTestLocation2.setLongitude(-179.441983);
    144         sTestLocation2.setAltitude(100000.0);
    145     }
    146 
    147     // Exif test data vectors.
    148     public static final ExifTestData[] EXIF_TEST_DATA = {
    149             new ExifTestData(
    150                     /*gpsLocation*/ sTestLocation0,
    151                     /* orientation */90,
    152                     /* jpgQuality */(byte) 80,
    153                     /* thumbQuality */(byte) 75),
    154             new ExifTestData(
    155                     /*gpsLocation*/ sTestLocation1,
    156                     /* orientation */180,
    157                     /* jpgQuality */(byte) 90,
    158                     /* thumbQuality */(byte) 85),
    159             new ExifTestData(
    160                     /*gpsLocation*/ sTestLocation2,
    161                     /* orientation */270,
    162                     /* jpgQuality */(byte) 100,
    163                     /* thumbQuality */(byte) 100)
    164     };
    165 
    166     /**
    167      * Create an {@link ImageReader} object and get the surface.
    168      *
    169      * @param size The size of this ImageReader to be created.
    170      * @param format The format of this ImageReader to be created
    171      * @param maxNumImages The max number of images that can be acquired simultaneously.
    172      * @param listener The listener used by this ImageReader to notify callbacks.
    173      * @param handler The handler to use for any listener callbacks.
    174      */
    175     public static ImageReader makeImageReader(Size size, int format, int maxNumImages,
    176             ImageReader.OnImageAvailableListener listener, Handler handler) {
    177         ImageReader reader;
    178         reader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format,
    179                 maxNumImages);
    180         reader.setOnImageAvailableListener(listener, handler);
    181         if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size);
    182         return reader;
    183     }
    184 
    185     /**
    186      * Create an ImageWriter and hook up the ImageListener.
    187      *
    188      * @param inputSurface The input surface of the ImageWriter.
    189      * @param maxImages The max number of Images that can be dequeued simultaneously.
    190      * @param listener The listener used by this ImageWriter to notify callbacks
    191      * @param handler The handler to post listener callbacks.
    192      * @return ImageWriter object created.
    193      */
    194     public static ImageWriter makeImageWriter(
    195             Surface inputSurface, int maxImages,
    196             ImageWriter.OnImageReleasedListener listener, Handler handler) {
    197         ImageWriter writer = ImageWriter.newInstance(inputSurface, maxImages);
    198         writer.setOnImageReleasedListener(listener, handler);
    199         return writer;
    200     }
    201 
    202     /**
    203      * Close pending images and clean up an {@link ImageReader} object.
    204      * @param reader an {@link ImageReader} to close.
    205      */
    206     public static void closeImageReader(ImageReader reader) {
    207         if (reader != null) {
    208             reader.close();
    209         }
    210     }
    211 
    212     /**
    213      * Close pending images and clean up an {@link ImageWriter} object.
    214      * @param writer an {@link ImageWriter} to close.
    215      */
    216     public static void closeImageWriter(ImageWriter writer) {
    217         if (writer != null) {
    218             writer.close();
    219         }
    220     }
    221 
    222     /**
    223      * Dummy listener that release the image immediately once it is available.
    224      *
    225      * <p>
    226      * It can be used for the case where we don't care the image data at all.
    227      * </p>
    228      */
    229     public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
    230         @Override
    231         public void onImageAvailable(ImageReader reader) {
    232             Image image = null;
    233             try {
    234                 image = reader.acquireNextImage();
    235             } finally {
    236                 if (image != null) {
    237                     image.close();
    238                 }
    239             }
    240         }
    241     }
    242 
    243     /**
    244      * Image listener that release the image immediately after validating the image
    245      */
    246     public static class ImageVerifierListener implements ImageReader.OnImageAvailableListener {
    247         private Size mSize;
    248         private int mFormat;
    249 
    250         public ImageVerifierListener(Size sz, int format) {
    251             mSize = sz;
    252             mFormat = format;
    253         }
    254 
    255         @Override
    256         public void onImageAvailable(ImageReader reader) {
    257             Image image = null;
    258             try {
    259                 image = reader.acquireNextImage();
    260             } finally {
    261                 if (image != null) {
    262                     validateImage(image, mSize.getWidth(), mSize.getHeight(), mFormat, null);
    263                     image.close();
    264                 }
    265             }
    266         }
    267     }
    268 
    269     public static class SimpleImageReaderListener
    270             implements ImageReader.OnImageAvailableListener {
    271         private final LinkedBlockingQueue<Image> mQueue =
    272                 new LinkedBlockingQueue<Image>();
    273         // Indicate whether this listener will drop images or not,
    274         // when the queued images reaches the reader maxImages
    275         private final boolean mAsyncMode;
    276         // maxImages held by the queue in async mode.
    277         private final int mMaxImages;
    278 
    279         /**
    280          * Create a synchronous SimpleImageReaderListener that queues the images
    281          * automatically when they are available, no image will be dropped. If
    282          * the caller doesn't call getImage(), the producer will eventually run
    283          * into buffer starvation.
    284          */
    285         public SimpleImageReaderListener() {
    286             mAsyncMode = false;
    287             mMaxImages = 0;
    288         }
    289 
    290         /**
    291          * Create a synchronous/asynchronous SimpleImageReaderListener that
    292          * queues the images automatically when they are available. For
    293          * asynchronous listener, image will be dropped if the queued images
    294          * reach to maxImages queued. If the caller doesn't call getImage(), the
    295          * producer will not be blocked. For synchronous listener, no image will
    296          * be dropped. If the caller doesn't call getImage(), the producer will
    297          * eventually run into buffer starvation.
    298          *
    299          * @param asyncMode If the listener is operating at asynchronous mode.
    300          * @param maxImages The max number of images held by this listener.
    301          */
    302         /**
    303          *
    304          * @param asyncMode
    305          */
    306         public SimpleImageReaderListener(boolean asyncMode, int maxImages) {
    307             mAsyncMode = asyncMode;
    308             mMaxImages = maxImages;
    309         }
    310 
    311         @Override
    312         public void onImageAvailable(ImageReader reader) {
    313             try {
    314                 mQueue.put(reader.acquireNextImage());
    315                 if (mAsyncMode && mQueue.size() >= mMaxImages) {
    316                     Image img = mQueue.poll();
    317                     img.close();
    318                 }
    319             } catch (InterruptedException e) {
    320                 throw new UnsupportedOperationException(
    321                         "Can't handle InterruptedException in onImageAvailable");
    322             }
    323         }
    324 
    325         /**
    326          * Get an image from the image reader.
    327          *
    328          * @param timeout Timeout value for the wait.
    329          * @return The image from the image reader.
    330          */
    331         public Image getImage(long timeout) throws InterruptedException {
    332             Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
    333             assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
    334             return image;
    335         }
    336 
    337         /**
    338          * Drain the pending images held by this listener currently.
    339          *
    340          */
    341         public void drain() {
    342             while (!mQueue.isEmpty()) {
    343                 Image image = mQueue.poll();
    344                 assertNotNull("Unable to get an image", image);
    345                 image.close();
    346             }
    347         }
    348     }
    349 
    350     public static class SimpleImageWriterListener implements ImageWriter.OnImageReleasedListener {
    351         private final Semaphore mImageReleasedSema = new Semaphore(0);
    352         private final ImageWriter mWriter;
    353         @Override
    354         public void onImageReleased(ImageWriter writer) {
    355             if (writer != mWriter) {
    356                 return;
    357             }
    358 
    359             if (VERBOSE) {
    360                 Log.v(TAG, "Input image is released");
    361             }
    362             mImageReleasedSema.release();
    363         }
    364 
    365         public SimpleImageWriterListener(ImageWriter writer) {
    366             if (writer == null) {
    367                 throw new IllegalArgumentException("writer cannot be null");
    368             }
    369             mWriter = writer;
    370         }
    371 
    372         public void waitForImageReleased(long timeoutMs) throws InterruptedException {
    373             if (!mImageReleasedSema.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
    374                 fail("wait for image available timed out after " + timeoutMs + "ms");
    375             }
    376         }
    377     }
    378 
    379     public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback {
    380         private final LinkedBlockingQueue<TotalCaptureResult> mQueue =
    381                 new LinkedBlockingQueue<TotalCaptureResult>();
    382         private final LinkedBlockingQueue<CaptureFailure> mFailureQueue =
    383                 new LinkedBlockingQueue<>();
    384         // Pair<CaptureRequest, Long> is a pair of capture request and timestamp.
    385         private final LinkedBlockingQueue<Pair<CaptureRequest, Long>> mCaptureStartQueue =
    386                 new LinkedBlockingQueue<>();
    387 
    388         private AtomicLong mNumFramesArrived = new AtomicLong(0);
    389 
    390         @Override
    391         public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
    392                 long timestamp, long frameNumber) {
    393             try {
    394                 mCaptureStartQueue.put(new Pair(request, timestamp));
    395             } catch (InterruptedException e) {
    396                 throw new UnsupportedOperationException(
    397                         "Can't handle InterruptedException in onCaptureStarted");
    398             }
    399         }
    400 
    401         @Override
    402         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
    403                 TotalCaptureResult result) {
    404             try {
    405                 mNumFramesArrived.incrementAndGet();
    406                 mQueue.put(result);
    407             } catch (InterruptedException e) {
    408                 throw new UnsupportedOperationException(
    409                         "Can't handle InterruptedException in onCaptureCompleted");
    410             }
    411         }
    412 
    413         @Override
    414         public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
    415                 CaptureFailure failure) {
    416             try {
    417                 mFailureQueue.put(failure);
    418             } catch (InterruptedException e) {
    419                 throw new UnsupportedOperationException(
    420                         "Can't handle InterruptedException in onCaptureFailed");
    421             }
    422         }
    423 
    424         @Override
    425         public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId,
    426                 long frameNumber) {
    427         }
    428 
    429         public long getTotalNumFrames() {
    430             return mNumFramesArrived.get();
    431         }
    432 
    433         public CaptureResult getCaptureResult(long timeout) {
    434             return getTotalCaptureResult(timeout);
    435         }
    436 
    437         public TotalCaptureResult getCaptureResult(long timeout, long timestamp) {
    438             try {
    439                 long currentTs = -1L;
    440                 TotalCaptureResult result;
    441                 while (true) {
    442                     result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
    443                     if (result == null) {
    444                         throw new RuntimeException(
    445                                 "Wait for a capture result timed out in " + timeout + "ms");
    446                     }
    447                     currentTs = result.get(CaptureResult.SENSOR_TIMESTAMP);
    448                     if (currentTs == timestamp) {
    449                         return result;
    450                     }
    451                 }
    452 
    453             } catch (InterruptedException e) {
    454                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
    455             }
    456         }
    457 
    458         public TotalCaptureResult getTotalCaptureResult(long timeout) {
    459             try {
    460                 TotalCaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
    461                 assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
    462                 return result;
    463             } catch (InterruptedException e) {
    464                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
    465             }
    466         }
    467 
    468         /**
    469          * Get the {@link #CaptureResult capture result} for a given
    470          * {@link #CaptureRequest capture request}.
    471          *
    472          * @param myRequest The {@link #CaptureRequest capture request} whose
    473          *            corresponding {@link #CaptureResult capture result} was
    474          *            being waited for
    475          * @param numResultsWait Number of frames to wait for the capture result
    476          *            before timeout.
    477          * @throws TimeoutRuntimeException If more than numResultsWait results are
    478          *            seen before the result matching myRequest arrives, or each
    479          *            individual wait for result times out after
    480          *            {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
    481          */
    482         public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest,
    483                 int numResultsWait) {
    484             return getTotalCaptureResultForRequest(myRequest, numResultsWait);
    485         }
    486 
    487         /**
    488          * Get the {@link #TotalCaptureResult total capture result} for a given
    489          * {@link #CaptureRequest capture request}.
    490          *
    491          * @param myRequest The {@link #CaptureRequest capture request} whose
    492          *            corresponding {@link #TotalCaptureResult capture result} was
    493          *            being waited for
    494          * @param numResultsWait Number of frames to wait for the capture result
    495          *            before timeout.
    496          * @throws TimeoutRuntimeException If more than numResultsWait results are
    497          *            seen before the result matching myRequest arrives, or each
    498          *            individual wait for result times out after
    499          *            {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
    500          */
    501         public TotalCaptureResult getTotalCaptureResultForRequest(CaptureRequest myRequest,
    502                 int numResultsWait) {
    503             ArrayList<CaptureRequest> captureRequests = new ArrayList<>(1);
    504             captureRequests.add(myRequest);
    505             return getTotalCaptureResultsForRequests(captureRequests, numResultsWait)[0];
    506         }
    507 
    508         /**
    509          * Get an array of {@link #TotalCaptureResult total capture results} for a given list of
    510          * {@link #CaptureRequest capture requests}. This can be used when the order of results
    511          * may not the same as the order of requests.
    512          *
    513          * @param captureRequests The list of {@link #CaptureRequest capture requests} whose
    514          *            corresponding {@link #TotalCaptureResult capture results} are
    515          *            being waited for.
    516          * @param numResultsWait Number of frames to wait for the capture results
    517          *            before timeout.
    518          * @throws TimeoutRuntimeException If more than numResultsWait results are
    519          *            seen before all the results matching captureRequests arrives.
    520          */
    521         public TotalCaptureResult[] getTotalCaptureResultsForRequests(
    522                 List<CaptureRequest> captureRequests, int numResultsWait) {
    523             if (numResultsWait < 0) {
    524                 throw new IllegalArgumentException("numResultsWait must be no less than 0");
    525             }
    526             if (captureRequests == null || captureRequests.size() == 0) {
    527                 throw new IllegalArgumentException("captureRequests must have at least 1 request.");
    528             }
    529 
    530             // Create a request -> a list of result indices map that it will wait for.
    531             HashMap<CaptureRequest, ArrayList<Integer>> remainingResultIndicesMap = new HashMap<>();
    532             for (int i = 0; i < captureRequests.size(); i++) {
    533                 CaptureRequest request = captureRequests.get(i);
    534                 ArrayList<Integer> indices = remainingResultIndicesMap.get(request);
    535                 if (indices == null) {
    536                     indices = new ArrayList<>();
    537                     remainingResultIndicesMap.put(request, indices);
    538                 }
    539                 indices.add(i);
    540             }
    541 
    542             TotalCaptureResult[] results = new TotalCaptureResult[captureRequests.size()];
    543             int i = 0;
    544             do {
    545                 TotalCaptureResult result = getTotalCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
    546                 CaptureRequest request = result.getRequest();
    547                 ArrayList<Integer> indices = remainingResultIndicesMap.get(request);
    548                 if (indices != null) {
    549                     results[indices.get(0)] = result;
    550                     indices.remove(0);
    551 
    552                     // Remove the entry if all results for this request has been fulfilled.
    553                     if (indices.isEmpty()) {
    554                         remainingResultIndicesMap.remove(request);
    555                     }
    556                 }
    557 
    558                 if (remainingResultIndicesMap.isEmpty()) {
    559                     return results;
    560                 }
    561             } while (i++ < numResultsWait);
    562 
    563             throw new TimeoutRuntimeException("Unable to get the expected capture result after "
    564                     + "waiting for " + numResultsWait + " results");
    565         }
    566 
    567         /**
    568          * Get an array list of {@link #CaptureFailure capture failure} with maxNumFailures entries
    569          * at most. If it times out before maxNumFailures failures are received, return the failures
    570          * received so far.
    571          *
    572          * @param maxNumFailures The maximal number of failures to return. If it times out before
    573          *                       the maximal number of failures are received, return the received
    574          *                       failures so far.
    575          * @throws UnsupportedOperationException If an error happens while waiting on the failure.
    576          */
    577         public ArrayList<CaptureFailure> getCaptureFailures(long maxNumFailures) {
    578             ArrayList<CaptureFailure> failures = new ArrayList<>();
    579             try {
    580                 for (int i = 0; i < maxNumFailures; i++) {
    581                     CaptureFailure failure = mFailureQueue.poll(CAPTURE_RESULT_TIMEOUT_MS,
    582                             TimeUnit.MILLISECONDS);
    583                     if (failure == null) {
    584                         // If waiting on a failure times out, return the failures so far.
    585                         break;
    586                     }
    587                     failures.add(failure);
    588                 }
    589             }  catch (InterruptedException e) {
    590                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
    591             }
    592 
    593             return failures;
    594         }
    595 
    596         /**
    597          * Wait until the capture start of a request and expected timestamp arrives or it times
    598          * out after a number of capture starts.
    599          *
    600          * @param request The request for the capture start to wait for.
    601          * @param timestamp The timestamp for the capture start to wait for.
    602          * @param numCaptureStartsWait The number of capture start events to wait for before timing
    603          *                             out.
    604          */
    605         public void waitForCaptureStart(CaptureRequest request, Long timestamp,
    606                 int numCaptureStartsWait) throws Exception {
    607             Pair<CaptureRequest, Long> expectedShutter = new Pair<>(request, timestamp);
    608 
    609             int i = 0;
    610             do {
    611                 Pair<CaptureRequest, Long> shutter = mCaptureStartQueue.poll(
    612                         CAPTURE_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
    613 
    614                 if (shutter == null) {
    615                     throw new TimeoutRuntimeException("Unable to get any more capture start " +
    616                             "event after waiting for " + CAPTURE_RESULT_TIMEOUT_MS + " ms.");
    617                 } else if (expectedShutter.equals(shutter)) {
    618                     return;
    619                 }
    620 
    621             } while (i++ < numCaptureStartsWait);
    622 
    623             throw new TimeoutRuntimeException("Unable to get the expected capture start " +
    624                     "event after waiting for " + numCaptureStartsWait + " capture starts");
    625         }
    626 
    627         public boolean hasMoreResults()
    628         {
    629             return mQueue.isEmpty();
    630         }
    631 
    632         public void drain() {
    633             mQueue.clear();
    634             mNumFramesArrived.getAndSet(0);
    635             mFailureQueue.clear();
    636             mCaptureStartQueue.clear();
    637         }
    638     }
    639 
    640     /**
    641      * Block until the camera is opened.
    642      *
    643      * <p>Don't use this to test #onDisconnected/#onError since this will throw
    644      * an AssertionError if it fails to open the camera device.</p>
    645      *
    646      * @return CameraDevice opened camera device
    647      *
    648      * @throws IllegalArgumentException
    649      *            If the handler is null, or if the handler's looper is current.
    650      * @throws CameraAccessException
    651      *            If open fails immediately.
    652      * @throws BlockingOpenException
    653      *            If open fails after blocking for some amount of time.
    654      * @throws TimeoutRuntimeException
    655      *            If opening times out. Typically unrecoverable.
    656      */
    657     public static CameraDevice openCamera(CameraManager manager, String cameraId,
    658             CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException,
    659             BlockingOpenException {
    660 
    661         /**
    662          * Although camera2 API allows 'null' Handler (it will just use the current
    663          * thread's Looper), this is not what we want for CTS.
    664          *
    665          * In Camera framework test the default looper is used only to process events
    666          * in between test runs,
    667          * so anything sent there would not be executed inside a test and the test would fail.
    668          *
    669          * In this case, BlockingCameraManager#openCamera performs the check for us.
    670          */
    671         return (new BlockingCameraManager(manager)).openCamera(cameraId, listener, handler);
    672     }
    673 
    674 
    675     /**
    676      * Block until the camera is opened.
    677      *
    678      * <p>Don't use this to test #onDisconnected/#onError since this will throw
    679      * an AssertionError if it fails to open the camera device.</p>
    680      *
    681      * @throws IllegalArgumentException
    682      *            If the handler is null, or if the handler's looper is current.
    683      * @throws CameraAccessException
    684      *            If open fails immediately.
    685      * @throws BlockingOpenException
    686      *            If open fails after blocking for some amount of time.
    687      * @throws TimeoutRuntimeException
    688      *            If opening times out. Typically unrecoverable.
    689      */
    690     public static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler)
    691             throws CameraAccessException,
    692             BlockingOpenException {
    693         return openCamera(manager, cameraId, /*listener*/null, handler);
    694     }
    695 
    696     /**
    697      * Configure a new camera session with output surfaces and type.
    698      *
    699      * @param camera The CameraDevice to be configured.
    700      * @param outputSurfaces The surface list that used for camera output.
    701      * @param listener The callback CameraDevice will notify when capture results are available.
    702      */
    703     public static CameraCaptureSession configureCameraSession(CameraDevice camera,
    704             List<Surface> outputSurfaces, boolean isHighSpeed,
    705             CameraCaptureSession.StateCallback listener, Handler handler)
    706             throws CameraAccessException {
    707         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
    708         if (isHighSpeed) {
    709             camera.createConstrainedHighSpeedCaptureSession(outputSurfaces,
    710                     sessionListener, handler);
    711         } else {
    712             camera.createCaptureSession(outputSurfaces, sessionListener, handler);
    713         }
    714         CameraCaptureSession session =
    715                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
    716         assertFalse("Camera session should not be a reprocessable session",
    717                 session.isReprocessable());
    718         String sessionType = isHighSpeed ? "High Speed" : "Normal";
    719         assertTrue("Capture session type must be " + sessionType,
    720                 isHighSpeed ==
    721                 CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(session.getClass()));
    722 
    723         return session;
    724     }
    725 
    726     /**
    727      * Configure a new camera session with output surfaces.
    728      *
    729      * @param camera The CameraDevice to be configured.
    730      * @param outputSurfaces The surface list that used for camera output.
    731      * @param listener The callback CameraDevice will notify when capture results are available.
    732      */
    733     public static CameraCaptureSession configureCameraSession(CameraDevice camera,
    734             List<Surface> outputSurfaces,
    735             CameraCaptureSession.StateCallback listener, Handler handler)
    736             throws CameraAccessException {
    737 
    738         return configureCameraSession(camera, outputSurfaces, /*isHighSpeed*/false,
    739                 listener, handler);
    740     }
    741 
    742     public static CameraCaptureSession configureReprocessableCameraSession(CameraDevice camera,
    743             InputConfiguration inputConfiguration, List<Surface> outputSurfaces,
    744             CameraCaptureSession.StateCallback listener, Handler handler)
    745             throws CameraAccessException {
    746         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
    747         camera.createReprocessableCaptureSession(inputConfiguration, outputSurfaces,
    748                 sessionListener, handler);
    749 
    750         Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
    751                                    BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
    752         int state = sessionListener.getStateWaiter().waitForAnyOfStates(
    753                 Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
    754 
    755         assertTrue("Creating a reprocessable session failed.",
    756                 state == BlockingSessionCallback.SESSION_READY);
    757 
    758         CameraCaptureSession session =
    759                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
    760         assertTrue("Camera session should be a reprocessable session", session.isReprocessable());
    761 
    762         return session;
    763     }
    764 
    765     public static <T> void assertArrayNotEmpty(T arr, String message) {
    766         assertTrue(message, arr != null && Array.getLength(arr) > 0);
    767     }
    768 
    769     /**
    770      * Check if the format is a legal YUV format camera supported.
    771      */
    772     public static void checkYuvFormat(int format) {
    773         if ((format != ImageFormat.YUV_420_888) &&
    774                 (format != ImageFormat.NV21) &&
    775                 (format != ImageFormat.YV12)) {
    776             fail("Wrong formats: " + format);
    777         }
    778     }
    779 
    780     /**
    781      * Check if image size and format match given size and format.
    782      */
    783     public static void checkImage(Image image, int width, int height, int format) {
    784         // Image reader will wrap YV12/NV21 image by YUV_420_888
    785         if (format == ImageFormat.NV21 || format == ImageFormat.YV12) {
    786             format = ImageFormat.YUV_420_888;
    787         }
    788         assertNotNull("Input image is invalid", image);
    789         assertEquals("Format doesn't match", format, image.getFormat());
    790         assertEquals("Width doesn't match", width, image.getWidth());
    791         assertEquals("Height doesn't match", height, image.getHeight());
    792     }
    793 
    794     /**
    795      * <p>Read data from all planes of an Image into a contiguous unpadded, unpacked
    796      * 1-D linear byte array, such that it can be write into disk, or accessed by
    797      * software conveniently. It supports YUV_420_888/NV21/YV12 and JPEG input
    798      * Image format.</p>
    799      *
    800      * <p>For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
    801      * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
    802      * (xstride = width, ystride = height for chroma and luma components).</p>
    803      *
    804      * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p>
    805      */
    806     public static byte[] getDataFromImage(Image image) {
    807         assertNotNull("Invalid image:", image);
    808         int format = image.getFormat();
    809         int width = image.getWidth();
    810         int height = image.getHeight();
    811         int rowStride, pixelStride;
    812         byte[] data = null;
    813 
    814         // Read image data
    815         Plane[] planes = image.getPlanes();
    816         assertTrue("Fail to get image planes", planes != null && planes.length > 0);
    817 
    818         // Check image validity
    819         checkAndroidImageFormat(image);
    820 
    821         ByteBuffer buffer = null;
    822         // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
    823         // Same goes for DEPTH_POINT_CLOUD
    824         if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD ||
    825                 format == ImageFormat.RAW_PRIVATE) {
    826             buffer = planes[0].getBuffer();
    827             assertNotNull("Fail to get jpeg or depth ByteBuffer", buffer);
    828             data = new byte[buffer.remaining()];
    829             buffer.get(data);
    830             buffer.rewind();
    831             return data;
    832         }
    833 
    834         int offset = 0;
    835         data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
    836         int maxRowSize = planes[0].getRowStride();
    837         for (int i = 0; i < planes.length; i++) {
    838             if (maxRowSize < planes[i].getRowStride()) {
    839                 maxRowSize = planes[i].getRowStride();
    840             }
    841         }
    842         byte[] rowData = new byte[maxRowSize];
    843         if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
    844         for (int i = 0; i < planes.length; i++) {
    845             buffer = planes[i].getBuffer();
    846             assertNotNull("Fail to get bytebuffer from plane", buffer);
    847             rowStride = planes[i].getRowStride();
    848             pixelStride = planes[i].getPixelStride();
    849             assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
    850             if (VERBOSE) {
    851                 Log.v(TAG, "pixelStride " + pixelStride);
    852                 Log.v(TAG, "rowStride " + rowStride);
    853                 Log.v(TAG, "width " + width);
    854                 Log.v(TAG, "height " + height);
    855             }
    856             // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
    857             int w = (i == 0) ? width : width / 2;
    858             int h = (i == 0) ? height : height / 2;
    859             assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
    860             for (int row = 0; row < h; row++) {
    861                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
    862                 int length;
    863                 if (pixelStride == bytesPerPixel) {
    864                     // Special case: optimized read of the entire row
    865                     length = w * bytesPerPixel;
    866                     buffer.get(data, offset, length);
    867                     offset += length;
    868                 } else {
    869                     // Generic case: should work for any pixelStride but slower.
    870                     // Use intermediate buffer to avoid read byte-by-byte from
    871                     // DirectByteBuffer, which is very bad for performance
    872                     length = (w - 1) * pixelStride + bytesPerPixel;
    873                     buffer.get(rowData, 0, length);
    874                     for (int col = 0; col < w; col++) {
    875                         data[offset++] = rowData[col * pixelStride];
    876                     }
    877                 }
    878                 // Advance buffer the remainder of the row stride
    879                 if (row < h - 1) {
    880                     buffer.position(buffer.position() + rowStride - length);
    881                 }
    882             }
    883             if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
    884             buffer.rewind();
    885         }
    886         return data;
    887     }
    888 
    889     /**
    890      * <p>Check android image format validity for an image, only support below formats:</p>
    891      *
    892      * <p>YUV_420_888/NV21/YV12, can add more for future</p>
    893      */
    894     public static void checkAndroidImageFormat(Image image) {
    895         int format = image.getFormat();
    896         Plane[] planes = image.getPlanes();
    897         switch (format) {
    898             case ImageFormat.YUV_420_888:
    899             case ImageFormat.NV21:
    900             case ImageFormat.YV12:
    901                 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
    902                 break;
    903             case ImageFormat.JPEG:
    904             case ImageFormat.RAW_SENSOR:
    905             case ImageFormat.RAW_PRIVATE:
    906             case ImageFormat.DEPTH16:
    907             case ImageFormat.DEPTH_POINT_CLOUD:
    908                 assertEquals("JPEG/RAW/depth Images should have one plane", 1, planes.length);
    909                 break;
    910             default:
    911                 fail("Unsupported Image Format: " + format);
    912         }
    913     }
    914 
    915     public static void dumpFile(String fileName, Bitmap data) {
    916         FileOutputStream outStream;
    917         try {
    918             Log.v(TAG, "output will be saved as " + fileName);
    919             outStream = new FileOutputStream(fileName);
    920         } catch (IOException ioe) {
    921             throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
    922         }
    923 
    924         try {
    925             data.compress(Bitmap.CompressFormat.JPEG, /*quality*/90, outStream);
    926             outStream.close();
    927         } catch (IOException ioe) {
    928             throw new RuntimeException("failed writing data to file " + fileName, ioe);
    929         }
    930     }
    931 
    932     public static void dumpFile(String fileName, byte[] data) {
    933         FileOutputStream outStream;
    934         try {
    935             Log.v(TAG, "output will be saved as " + fileName);
    936             outStream = new FileOutputStream(fileName);
    937         } catch (IOException ioe) {
    938             throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
    939         }
    940 
    941         try {
    942             outStream.write(data);
    943             outStream.close();
    944         } catch (IOException ioe) {
    945             throw new RuntimeException("failed writing data to file " + fileName, ioe);
    946         }
    947     }
    948 
    949     /**
    950      * Get the available output sizes for the user-defined {@code format}.
    951      *
    952      * <p>Note that implementation-defined/hidden formats are not supported.</p>
    953      */
    954     public static Size[] getSupportedSizeForFormat(int format, String cameraId,
    955             CameraManager cameraManager) throws CameraAccessException {
    956         CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
    957         assertNotNull("Can't get camera characteristics!", properties);
    958         if (VERBOSE) {
    959             Log.v(TAG, "get camera characteristics for camera: " + cameraId);
    960         }
    961         StreamConfigurationMap configMap =
    962                 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    963         Size[] availableSizes = configMap.getOutputSizes(format);
    964         assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for format: "
    965                 + format);
    966         Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(format);
    967         if (highResAvailableSizes != null && highResAvailableSizes.length > 0) {
    968             Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length];
    969             System.arraycopy(availableSizes, 0, allSizes, 0,
    970                     availableSizes.length);
    971             System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length,
    972                     highResAvailableSizes.length);
    973             availableSizes = allSizes;
    974         }
    975         if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
    976         return availableSizes;
    977     }
    978 
    979     /**
    980      * Get the available output sizes for the given class.
    981      *
    982      */
    983     public static Size[] getSupportedSizeForClass(Class klass, String cameraId,
    984             CameraManager cameraManager) throws CameraAccessException {
    985         CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
    986         assertNotNull("Can't get camera characteristics!", properties);
    987         if (VERBOSE) {
    988             Log.v(TAG, "get camera characteristics for camera: " + cameraId);
    989         }
    990         StreamConfigurationMap configMap =
    991                 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    992         Size[] availableSizes = configMap.getOutputSizes(klass);
    993         assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for class: "
    994                 + klass);
    995         Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(ImageFormat.PRIVATE);
    996         if (highResAvailableSizes != null && highResAvailableSizes.length > 0) {
    997             Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length];
    998             System.arraycopy(availableSizes, 0, allSizes, 0,
    999                     availableSizes.length);
   1000             System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length,
   1001                     highResAvailableSizes.length);
   1002             availableSizes = allSizes;
   1003         }
   1004         if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
   1005         return availableSizes;
   1006     }
   1007 
   1008     /**
   1009      * Size comparator that compares the number of pixels it covers.
   1010      *
   1011      * <p>If two the areas of two sizes are same, compare the widths.</p>
   1012      */
   1013     public static class SizeComparator implements Comparator<Size> {
   1014         @Override
   1015         public int compare(Size lhs, Size rhs) {
   1016             return CameraUtils
   1017                     .compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight());
   1018         }
   1019     }
   1020 
   1021     /**
   1022      * Get sorted size list in descending order. Remove the sizes larger than
   1023      * the bound. If the bound is null, don't do the size bound filtering.
   1024      */
   1025     static public List<Size> getSupportedPreviewSizes(String cameraId,
   1026             CameraManager cameraManager, Size bound) throws CameraAccessException {
   1027 
   1028         Size[] rawSizes = getSupportedSizeForClass(android.view.SurfaceHolder.class, cameraId,
   1029                 cameraManager);
   1030         assertArrayNotEmpty(rawSizes,
   1031                 "Available sizes for SurfaceHolder class should not be empty");
   1032         if (VERBOSE) {
   1033             Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes));
   1034         }
   1035 
   1036         if (bound == null) {
   1037             return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false);
   1038         }
   1039 
   1040         List<Size> sizes = new ArrayList<Size>();
   1041         for (Size sz: rawSizes) {
   1042             if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) {
   1043                 sizes.add(sz);
   1044             }
   1045         }
   1046         return getAscendingOrderSizes(sizes, /*ascending*/false);
   1047     }
   1048 
   1049     /**
   1050      * Get a sorted list of sizes from a given size list.
   1051      *
   1052      * <p>
   1053      * The size is compare by area it covers, if the areas are same, then
   1054      * compare the widths.
   1055      * </p>
   1056      *
   1057      * @param sizeList The input size list to be sorted
   1058      * @param ascending True if the order is ascending, otherwise descending order
   1059      * @return The ordered list of sizes
   1060      */
   1061     static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) {
   1062         if (sizeList == null) {
   1063             throw new IllegalArgumentException("sizeList shouldn't be null");
   1064         }
   1065 
   1066         Comparator<Size> comparator = new SizeComparator();
   1067         List<Size> sortedSizes = new ArrayList<Size>();
   1068         sortedSizes.addAll(sizeList);
   1069         Collections.sort(sortedSizes, comparator);
   1070         if (!ascending) {
   1071             Collections.reverse(sortedSizes);
   1072         }
   1073 
   1074         return sortedSizes;
   1075     }
   1076 
   1077     /**
   1078      * Get sorted (descending order) size list for given format. Remove the sizes larger than
   1079      * the bound. If the bound is null, don't do the size bound filtering.
   1080      */
   1081     static public List<Size> getSortedSizesForFormat(String cameraId,
   1082             CameraManager cameraManager, int format, Size bound) throws CameraAccessException {
   1083         Comparator<Size> comparator = new SizeComparator();
   1084         Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager);
   1085         List<Size> sortedSizes = null;
   1086         if (bound != null) {
   1087             sortedSizes = new ArrayList<Size>(/*capacity*/1);
   1088             for (Size sz : sizes) {
   1089                 if (comparator.compare(sz, bound) <= 0) {
   1090                     sortedSizes.add(sz);
   1091                 }
   1092             }
   1093         } else {
   1094             sortedSizes = Arrays.asList(sizes);
   1095         }
   1096         assertTrue("Supported size list should have at least one element",
   1097                 sortedSizes.size() > 0);
   1098 
   1099         Collections.sort(sortedSizes, comparator);
   1100         // Make it in descending order.
   1101         Collections.reverse(sortedSizes);
   1102         return sortedSizes;
   1103     }
   1104 
   1105     /**
   1106      * Get supported video size list for a given camera device.
   1107      *
   1108      * <p>
   1109      * Filter out the sizes that are larger than the bound. If the bound is
   1110      * null, don't do the size bound filtering.
   1111      * </p>
   1112      */
   1113     static public List<Size> getSupportedVideoSizes(String cameraId,
   1114             CameraManager cameraManager, Size bound) throws CameraAccessException {
   1115 
   1116         Size[] rawSizes = getSupportedSizeForClass(android.media.MediaRecorder.class,
   1117                 cameraId, cameraManager);
   1118         assertArrayNotEmpty(rawSizes,
   1119                 "Available sizes for MediaRecorder class should not be empty");
   1120         if (VERBOSE) {
   1121             Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes));
   1122         }
   1123 
   1124         if (bound == null) {
   1125             return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false);
   1126         }
   1127 
   1128         List<Size> sizes = new ArrayList<Size>();
   1129         for (Size sz: rawSizes) {
   1130             if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) {
   1131                 sizes.add(sz);
   1132             }
   1133         }
   1134         return getAscendingOrderSizes(sizes, /*ascending*/false);
   1135     }
   1136 
   1137     /**
   1138      * Get supported video size list (descending order) for a given camera device.
   1139      *
   1140      * <p>
   1141      * Filter out the sizes that are larger than the bound. If the bound is
   1142      * null, don't do the size bound filtering.
   1143      * </p>
   1144      */
   1145     static public List<Size> getSupportedStillSizes(String cameraId,
   1146             CameraManager cameraManager, Size bound) throws CameraAccessException {
   1147         return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound);
   1148     }
   1149 
   1150     static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager)
   1151             throws CameraAccessException {
   1152         List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null);
   1153         return sizes.get(sizes.size() - 1);
   1154     }
   1155 
   1156     /**
   1157      * Get max supported preview size for a camera device.
   1158      */
   1159     static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager)
   1160             throws CameraAccessException {
   1161         return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null);
   1162     }
   1163 
   1164     /**
   1165      * Get max preview size for a camera device in the supported sizes that are no larger
   1166      * than the bound.
   1167      */
   1168     static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)
   1169             throws CameraAccessException {
   1170         List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound);
   1171         return sizes.get(0);
   1172     }
   1173 
   1174     /**
   1175      * Get max depth size for a camera device.
   1176      */
   1177     static public Size getMaxDepthSize(String cameraId, CameraManager cameraManager)
   1178             throws CameraAccessException {
   1179         List<Size> sizes = getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.DEPTH16,
   1180                 /*bound*/ null);
   1181         return sizes.get(0);
   1182     }
   1183 
   1184     /**
   1185      * Get the largest size by area.
   1186      *
   1187      * @param sizes an array of sizes, must have at least 1 element
   1188      *
   1189      * @return Largest Size
   1190      *
   1191      * @throws IllegalArgumentException if sizes was null or had 0 elements
   1192      */
   1193     public static Size getMaxSize(Size... sizes) {
   1194         if (sizes == null || sizes.length == 0) {
   1195             throw new IllegalArgumentException("sizes was empty");
   1196         }
   1197 
   1198         Size sz = sizes[0];
   1199         for (Size size : sizes) {
   1200             if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
   1201                 sz = size;
   1202             }
   1203         }
   1204 
   1205         return sz;
   1206     }
   1207 
   1208     /**
   1209      * Returns true if the given {@code array} contains the given element.
   1210      *
   1211      * @param array {@code array} to check for {@code elem}
   1212      * @param elem {@code elem} to test for
   1213      * @return {@code true} if the given element is contained
   1214      */
   1215     public static boolean contains(int[] array, int elem) {
   1216         if (array == null) return false;
   1217         for (int i = 0; i < array.length; i++) {
   1218             if (elem == array[i]) return true;
   1219         }
   1220         return false;
   1221     }
   1222 
   1223     /**
   1224      * Get object array from byte array.
   1225      *
   1226      * @param array Input byte array to be converted
   1227      * @return Byte object array converted from input byte array
   1228      */
   1229     public static Byte[] toObject(byte[] array) {
   1230         return convertPrimitiveArrayToObjectArray(array, Byte.class);
   1231     }
   1232 
   1233     /**
   1234      * Get object array from int array.
   1235      *
   1236      * @param array Input int array to be converted
   1237      * @return Integer object array converted from input int array
   1238      */
   1239     public static Integer[] toObject(int[] array) {
   1240         return convertPrimitiveArrayToObjectArray(array, Integer.class);
   1241     }
   1242 
   1243     /**
   1244      * Get object array from float array.
   1245      *
   1246      * @param array Input float array to be converted
   1247      * @return Float object array converted from input float array
   1248      */
   1249     public static Float[] toObject(float[] array) {
   1250         return convertPrimitiveArrayToObjectArray(array, Float.class);
   1251     }
   1252 
   1253     /**
   1254      * Get object array from double array.
   1255      *
   1256      * @param array Input double array to be converted
   1257      * @return Double object array converted from input double array
   1258      */
   1259     public static Double[] toObject(double[] array) {
   1260         return convertPrimitiveArrayToObjectArray(array, Double.class);
   1261     }
   1262 
   1263     /**
   1264      * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]).
   1265      *
   1266      * @param array Input array object
   1267      * @param wrapperClass The boxed class it converts to
   1268      * @return Boxed version of primitive array
   1269      */
   1270     private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array,
   1271             final Class<T> wrapperClass) {
   1272         // getLength does the null check and isArray check already.
   1273         int arrayLength = Array.getLength(array);
   1274         if (arrayLength == 0) {
   1275             throw new IllegalArgumentException("Input array shouldn't be empty");
   1276         }
   1277 
   1278         @SuppressWarnings("unchecked")
   1279         final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength);
   1280         for (int i = 0; i < arrayLength; i++) {
   1281             Array.set(result, i, Array.get(array, i));
   1282         }
   1283         return result;
   1284     }
   1285 
   1286     /**
   1287      * Validate image based on format and size.
   1288      *
   1289      * @param image The image to be validated.
   1290      * @param width The image width.
   1291      * @param height The image height.
   1292      * @param format The image format.
   1293      * @param filePath The debug dump file path, null if don't want to dump to
   1294      *            file.
   1295      * @throws UnsupportedOperationException if calling with an unknown format
   1296      */
   1297     public static void validateImage(Image image, int width, int height, int format,
   1298             String filePath) {
   1299         checkImage(image, width, height, format);
   1300 
   1301         /**
   1302          * TODO: validate timestamp:
   1303          * 1. capture result timestamp against the image timestamp (need
   1304          * consider frame drops)
   1305          * 2. timestamps should be monotonically increasing for different requests
   1306          */
   1307         if(VERBOSE) Log.v(TAG, "validating Image");
   1308         byte[] data = getDataFromImage(image);
   1309         assertTrue("Invalid image data", data != null && data.length > 0);
   1310 
   1311         switch (format) {
   1312             case ImageFormat.JPEG:
   1313                 validateJpegData(data, width, height, filePath);
   1314                 break;
   1315             case ImageFormat.YUV_420_888:
   1316             case ImageFormat.YV12:
   1317                 validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
   1318                 break;
   1319             case ImageFormat.RAW_SENSOR:
   1320                 validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath);
   1321                 break;
   1322             case ImageFormat.DEPTH16:
   1323                 validateDepth16Data(data, width, height, format, image.getTimestamp(), filePath);
   1324                 break;
   1325             case ImageFormat.DEPTH_POINT_CLOUD:
   1326                 validateDepthPointCloudData(data, width, height, format, image.getTimestamp(), filePath);
   1327                 break;
   1328             case ImageFormat.RAW_PRIVATE:
   1329                 validateRawPrivateData(data, width, height, image.getTimestamp(), filePath);
   1330                 break;
   1331             default:
   1332                 throw new UnsupportedOperationException("Unsupported format for validation: "
   1333                         + format);
   1334         }
   1335     }
   1336 
   1337     /**
   1338      * Provide a mock for {@link CameraDevice.StateCallback}.
   1339      *
   1340      * <p>Only useful because mockito can't mock {@link CameraDevice.StateCallback} which is an
   1341      * abstract class.</p>
   1342      *
   1343      * <p>
   1344      * Use this instead of other classes when needing to verify interactions, since
   1345      * trying to spy on {@link BlockingStateCallback} (or others) will cause unnecessary extra
   1346      * interactions which will cause false test failures.
   1347      * </p>
   1348      *
   1349      */
   1350     public static class MockStateCallback extends CameraDevice.StateCallback {
   1351 
   1352         @Override
   1353         public void onOpened(CameraDevice camera) {
   1354         }
   1355 
   1356         @Override
   1357         public void onDisconnected(CameraDevice camera) {
   1358         }
   1359 
   1360         @Override
   1361         public void onError(CameraDevice camera, int error) {
   1362         }
   1363 
   1364         private MockStateCallback() {}
   1365 
   1366         /**
   1367          * Create a Mockito-ready mocked StateCallback.
   1368          */
   1369         public static MockStateCallback mock() {
   1370             return Mockito.spy(new MockStateCallback());
   1371         }
   1372     }
   1373 
   1374     private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) {
   1375         BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
   1376         // DecodeBound mode: only parse the frame header to get width/height.
   1377         // it doesn't decode the pixel.
   1378         bmpOptions.inJustDecodeBounds = true;
   1379         BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions);
   1380         assertEquals(width, bmpOptions.outWidth);
   1381         assertEquals(height, bmpOptions.outHeight);
   1382 
   1383         // Pixel decoding mode: decode whole image. check if the image data
   1384         // is decodable here.
   1385         assertNotNull("Decoding jpeg failed",
   1386                 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length));
   1387         if (DEBUG && filePath != null) {
   1388             String fileName =
   1389                     filePath + "/" + width + "x" + height + ".jpeg";
   1390             dumpFile(fileName, jpegData);
   1391         }
   1392     }
   1393 
   1394     private static void validateYuvData(byte[] yuvData, int width, int height, int format,
   1395             long ts, String filePath) {
   1396         checkYuvFormat(format);
   1397         if (VERBOSE) Log.v(TAG, "Validating YUV data");
   1398         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
   1399         assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
   1400 
   1401         // TODO: Can add data validation for test pattern.
   1402 
   1403         if (DEBUG && filePath != null) {
   1404             String fileName =
   1405                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv";
   1406             dumpFile(fileName, yuvData);
   1407         }
   1408     }
   1409 
   1410     private static void validateRaw16Data(byte[] rawData, int width, int height, int format,
   1411             long ts, String filePath) {
   1412         if (VERBOSE) Log.v(TAG, "Validating raw data");
   1413         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
   1414         assertEquals("Raw data doesn't match", expectedSize, rawData.length);
   1415 
   1416         // TODO: Can add data validation for test pattern.
   1417 
   1418         if (DEBUG && filePath != null) {
   1419             String fileName =
   1420                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16";
   1421             dumpFile(fileName, rawData);
   1422         }
   1423 
   1424         return;
   1425     }
   1426 
   1427     private static void validateRawPrivateData(byte[] rawData, int width, int height,
   1428             long ts, String filePath) {
   1429         if (VERBOSE) Log.v(TAG, "Validating private raw data");
   1430         // Expect each RAW pixel should occupy at least one byte and no more than 2.5 bytes
   1431         int expectedSizeMin = width * height;
   1432         int expectedSizeMax = width * height * 5 / 2;
   1433 
   1434         assertTrue("Opaque RAW size " + rawData.length + "out of normal bound [" +
   1435                 expectedSizeMin + "," + expectedSizeMax + "]",
   1436                 expectedSizeMin <= rawData.length && rawData.length <= expectedSizeMax);
   1437 
   1438         if (DEBUG && filePath != null) {
   1439             String fileName =
   1440                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".rawPriv";
   1441             dumpFile(fileName, rawData);
   1442         }
   1443 
   1444         return;
   1445     }
   1446 
   1447     private static void validateDepth16Data(byte[] depthData, int width, int height, int format,
   1448             long ts, String filePath) {
   1449 
   1450         if (VERBOSE) Log.v(TAG, "Validating depth16 data");
   1451         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
   1452         assertEquals("Depth data doesn't match", expectedSize, depthData.length);
   1453 
   1454 
   1455         if (DEBUG && filePath != null) {
   1456             String fileName =
   1457                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth16";
   1458             dumpFile(fileName, depthData);
   1459         }
   1460 
   1461         return;
   1462 
   1463     }
   1464 
   1465     private static void validateDepthPointCloudData(byte[] depthData, int width, int height, int format,
   1466             long ts, String filePath) {
   1467 
   1468         if (VERBOSE) Log.v(TAG, "Validating depth point cloud data");
   1469 
   1470         // Can't validate size since it is variable
   1471 
   1472         if (DEBUG && filePath != null) {
   1473             String fileName =
   1474                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth_point_cloud";
   1475             dumpFile(fileName, depthData);
   1476         }
   1477 
   1478         return;
   1479 
   1480     }
   1481 
   1482     public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
   1483         if (result == null) {
   1484             throw new IllegalArgumentException("Result must not be null");
   1485         }
   1486 
   1487         T value = result.get(key);
   1488         assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
   1489         return value;
   1490     }
   1491 
   1492     public static <T> T getValueNotNull(CameraCharacteristics characteristics,
   1493             CameraCharacteristics.Key<T> key) {
   1494         if (characteristics == null) {
   1495             throw new IllegalArgumentException("Camera characteristics must not be null");
   1496         }
   1497 
   1498         T value = characteristics.get(key);
   1499         assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
   1500         return value;
   1501     }
   1502 
   1503     /**
   1504      * Get a crop region for a given zoom factor and center position.
   1505      * <p>
   1506      * The center position is normalized position in range of [0, 1.0], where
   1507      * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right
   1508      * corner. The center position could limit the effective minimal zoom
   1509      * factor, for example, if the center position is (0.75, 0.75), the
   1510      * effective minimal zoom position becomes 2.0. If the requested zoom factor
   1511      * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned.
   1512      * </p>
   1513      * <p>
   1514      * The aspect ratio of the crop region is maintained the same as the aspect
   1515      * ratio of active array.
   1516      * </p>
   1517      *
   1518      * @param zoomFactor The zoom factor to generate the crop region, it must be
   1519      *            >= 1.0
   1520      * @param center The normalized zoom center point that is in the range of [0, 1].
   1521      * @param maxZoom The max zoom factor supported by this device.
   1522      * @param activeArray The active array size of this device.
   1523      * @return crop region for the given normalized center and zoom factor.
   1524      */
   1525     public static Rect getCropRegionForZoom(float zoomFactor, final PointF center,
   1526             final float maxZoom, final Rect activeArray) {
   1527         if (zoomFactor < 1.0) {
   1528             throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0");
   1529         }
   1530         if (center.x > 1.0 || center.x < 0) {
   1531             throw new IllegalArgumentException("center.x " + center.x
   1532                     + " should be in range of [0, 1.0]");
   1533         }
   1534         if (center.y > 1.0 || center.y < 0) {
   1535             throw new IllegalArgumentException("center.y " + center.y
   1536                     + " should be in range of [0, 1.0]");
   1537         }
   1538         if (maxZoom < 1.0) {
   1539             throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0");
   1540         }
   1541         if (activeArray == null) {
   1542             throw new IllegalArgumentException("activeArray must not be null");
   1543         }
   1544 
   1545         float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x),
   1546                 Math.min(center.y, 1.0f - center.y));
   1547         float minEffectiveZoom =  0.5f / minCenterLength;
   1548         if (minEffectiveZoom > maxZoom) {
   1549             throw new IllegalArgumentException("Requested center " + center.toString() +
   1550                     " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max"
   1551                             + " zoom factor " + maxZoom);
   1552         }
   1553 
   1554         if (zoomFactor < minEffectiveZoom) {
   1555             Log.w(TAG, "Requested zoomFactor " + zoomFactor + " > minimal zoomable factor "
   1556                     + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom);
   1557             zoomFactor = minEffectiveZoom;
   1558         }
   1559 
   1560         int cropCenterX = (int)(activeArray.width() * center.x);
   1561         int cropCenterY = (int)(activeArray.height() * center.y);
   1562         int cropWidth = (int) (activeArray.width() / zoomFactor);
   1563         int cropHeight = (int) (activeArray.height() / zoomFactor);
   1564 
   1565         return new Rect(
   1566                 /*left*/cropCenterX - cropWidth / 2,
   1567                 /*top*/cropCenterY - cropHeight / 2,
   1568                 /*right*/ cropCenterX + cropWidth / 2 - 1,
   1569                 /*bottom*/cropCenterY + cropHeight / 2 - 1);
   1570     }
   1571 
   1572     /**
   1573      * Calculate output 3A region from the intersection of input 3A region and cropped region.
   1574      *
   1575      * @param requestRegions The input 3A regions
   1576      * @param cropRect The cropped region
   1577      * @return expected 3A regions output in capture result
   1578      */
   1579     public static MeteringRectangle[] getExpectedOutputRegion(
   1580             MeteringRectangle[] requestRegions, Rect cropRect){
   1581         MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length];
   1582         for (int i = 0; i < requestRegions.length; i++) {
   1583             Rect requestRect = requestRegions[i].getRect();
   1584             Rect resultRect = new Rect();
   1585             assertTrue("Input 3A region must intersect cropped region",
   1586                         resultRect.setIntersect(requestRect, cropRect));
   1587             resultRegions[i] = new MeteringRectangle(
   1588                     resultRect,
   1589                     requestRegions[i].getMeteringWeight());
   1590         }
   1591         return resultRegions;
   1592     }
   1593 
   1594     /**
   1595      * Copy source image data to destination image.
   1596      *
   1597      * @param src The source image to be copied from.
   1598      * @param dst The destination image to be copied to.
   1599      * @throws IllegalArgumentException If the source and destination images have
   1600      *             different format, or one of the images is not copyable.
   1601      */
   1602     public static void imageCopy(Image src, Image dst) {
   1603         if (src == null || dst == null) {
   1604             throw new IllegalArgumentException("Images should be non-null");
   1605         }
   1606         if (src.getFormat() != dst.getFormat()) {
   1607             throw new IllegalArgumentException("Src and dst images should have the same format");
   1608         }
   1609         if (src.getFormat() == ImageFormat.PRIVATE ||
   1610                 dst.getFormat() == ImageFormat.PRIVATE) {
   1611             throw new IllegalArgumentException("PRIVATE format images are not copyable");
   1612         }
   1613 
   1614         // TODO: check the owner of the dst image, it must be from ImageWriter, other source may
   1615         // not be writable. Maybe we should add an isWritable() method in image class.
   1616 
   1617         Plane[] srcPlanes = src.getPlanes();
   1618         Plane[] dstPlanes = dst.getPlanes();
   1619         ByteBuffer srcBuffer = null;
   1620         ByteBuffer dstBuffer = null;
   1621         for (int i = 0; i < srcPlanes.length; i++) {
   1622             srcBuffer = srcPlanes[i].getBuffer();
   1623             int srcPos = srcBuffer.position();
   1624             srcBuffer.rewind();
   1625             dstBuffer = dstPlanes[i].getBuffer();
   1626             dstBuffer.rewind();
   1627             dstBuffer.put(srcBuffer);
   1628             srcBuffer.position(srcPos);
   1629             dstBuffer.rewind();
   1630         }
   1631     }
   1632 
   1633     /**
   1634      * <p>
   1635      * Checks whether the two images are strongly equal.
   1636      * </p>
   1637      * <p>
   1638      * Two images are strongly equal if and only if the data, formats, sizes,
   1639      * and timestamps are same. For {@link ImageFormat#PRIVATE PRIVATE} format
   1640      * images, the image data is not not accessible thus the data comparison is
   1641      * effectively skipped as the number of planes is zero.
   1642      * </p>
   1643      * <p>
   1644      * Note that this method compares the pixel data even outside of the crop
   1645      * region, which may not be necessary for general use case.
   1646      * </p>
   1647      *
   1648      * @param lhsImg First image to be compared with.
   1649      * @param rhsImg Second image to be compared with.
   1650      * @return true if the two images are equal, false otherwise.
   1651      * @throws IllegalArgumentException If either of image is null.
   1652      */
   1653     public static boolean isImageStronglyEqual(Image lhsImg, Image rhsImg) {
   1654         if (lhsImg == null || rhsImg == null) {
   1655             throw new IllegalArgumentException("Images should be non-null");
   1656         }
   1657 
   1658         if (lhsImg.getFormat() != rhsImg.getFormat()) {
   1659             Log.i(TAG, "lhsImg format " + lhsImg.getFormat() + " is different with rhsImg format "
   1660                     + rhsImg.getFormat());
   1661             return false;
   1662         }
   1663 
   1664         if (lhsImg.getWidth() != rhsImg.getWidth()) {
   1665             Log.i(TAG, "lhsImg width " + lhsImg.getWidth() + " is different with rhsImg width "
   1666                     + rhsImg.getWidth());
   1667             return false;
   1668         }
   1669 
   1670         if (lhsImg.getHeight() != rhsImg.getHeight()) {
   1671             Log.i(TAG, "lhsImg height " + lhsImg.getHeight() + " is different with rhsImg height "
   1672                     + rhsImg.getHeight());
   1673             return false;
   1674         }
   1675 
   1676         if (lhsImg.getTimestamp() != rhsImg.getTimestamp()) {
   1677             Log.i(TAG, "lhsImg timestamp " + lhsImg.getTimestamp()
   1678                     + " is different with rhsImg timestamp " + rhsImg.getTimestamp());
   1679             return false;
   1680         }
   1681 
   1682         if (!lhsImg.getCropRect().equals(rhsImg.getCropRect())) {
   1683             Log.i(TAG, "lhsImg crop rect " + lhsImg.getCropRect()
   1684                     + " is different with rhsImg crop rect " + rhsImg.getCropRect());
   1685             return false;
   1686         }
   1687 
   1688         // Compare data inside of the image.
   1689         Plane[] lhsPlanes = lhsImg.getPlanes();
   1690         Plane[] rhsPlanes = rhsImg.getPlanes();
   1691         ByteBuffer lhsBuffer = null;
   1692         ByteBuffer rhsBuffer = null;
   1693         for (int i = 0; i < lhsPlanes.length; i++) {
   1694             lhsBuffer = lhsPlanes[i].getBuffer();
   1695             rhsBuffer = rhsPlanes[i].getBuffer();
   1696             if (!lhsBuffer.equals(rhsBuffer)) {
   1697                 Log.i(TAG, "byte buffers for plane " +  i + " don't matach.");
   1698                 return false;
   1699             }
   1700         }
   1701 
   1702         return true;
   1703     }
   1704 
   1705     /**
   1706      * Set jpeg related keys in a capture request builder.
   1707      *
   1708      * @param builder The capture request builder to set the keys inl
   1709      * @param exifData The exif data to set.
   1710      * @param thumbnailSize The thumbnail size to set.
   1711      * @param collector The camera error collector to collect errors.
   1712      */
   1713     public static void setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData,
   1714             Size thumbnailSize, CameraErrorCollector collector) {
   1715         builder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, thumbnailSize);
   1716         builder.set(CaptureRequest.JPEG_GPS_LOCATION, exifData.gpsLocation);
   1717         builder.set(CaptureRequest.JPEG_ORIENTATION, exifData.jpegOrientation);
   1718         builder.set(CaptureRequest.JPEG_QUALITY, exifData.jpegQuality);
   1719         builder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY,
   1720                 exifData.thumbnailQuality);
   1721 
   1722         // Validate request set and get.
   1723         collector.expectEquals("JPEG thumbnail size request set and get should match",
   1724                 thumbnailSize, builder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE));
   1725         collector.expectTrue("GPS locations request set and get should match.",
   1726                 areGpsFieldsEqual(exifData.gpsLocation,
   1727                 builder.get(CaptureRequest.JPEG_GPS_LOCATION)));
   1728         collector.expectEquals("JPEG orientation request set and get should match",
   1729                 exifData.jpegOrientation,
   1730                 builder.get(CaptureRequest.JPEG_ORIENTATION));
   1731         collector.expectEquals("JPEG quality request set and get should match",
   1732                 exifData.jpegQuality, builder.get(CaptureRequest.JPEG_QUALITY));
   1733         collector.expectEquals("JPEG thumbnail quality request set and get should match",
   1734                 exifData.thumbnailQuality,
   1735                 builder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY));
   1736     }
   1737 
   1738     /**
   1739      * Simple validation of JPEG image size and format.
   1740      * <p>
   1741      * Only validate the image object sanity. It is fast, but doesn't actually
   1742      * check the buffer data. Assert is used here as it make no sense to
   1743      * continue the test if the jpeg image captured has some serious failures.
   1744      * </p>
   1745      *
   1746      * @param image The captured jpeg image
   1747      * @param expectedSize Expected capture jpeg size
   1748      */
   1749     public static void basicValidateJpegImage(Image image, Size expectedSize) {
   1750         Size imageSz = new Size(image.getWidth(), image.getHeight());
   1751         assertTrue(
   1752                 String.format("Image size doesn't match (expected %s, actual %s) ",
   1753                         expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz));
   1754         assertEquals("Image format should be JPEG", ImageFormat.JPEG, image.getFormat());
   1755         assertNotNull("Image plane shouldn't be null", image.getPlanes());
   1756         assertEquals("Image plane number should be 1", 1, image.getPlanes().length);
   1757 
   1758         // Jpeg decoding validate was done in ImageReaderTest, no need to duplicate the test here.
   1759     }
   1760 
   1761     /**
   1762      * Verify the JPEG EXIF and JPEG related keys in a capture result are expected.
   1763      * - Capture request get values are same as were set.
   1764      * - capture result's exif data is the same as was set by
   1765      *   the capture request.
   1766      * - new tags in the result set by the camera service are
   1767      *   present and semantically correct.
   1768      *
   1769      * @param image The output JPEG image to verify.
   1770      * @param captureResult The capture result to verify.
   1771      * @param expectedSize The expected JPEG size.
   1772      * @param expectedThumbnailSize The expected thumbnail size.
   1773      * @param expectedExifData The expected EXIF data
   1774      * @param staticInfo The static metadata for the camera device.
   1775      * @param jpegFilename The filename to dump the jpeg to.
   1776      * @param collector The camera error collector to collect errors.
   1777      */
   1778     public static void verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize,
   1779             Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo,
   1780             CameraErrorCollector collector) throws Exception {
   1781 
   1782         basicValidateJpegImage(image, expectedSize);
   1783 
   1784         byte[] jpegBuffer = getDataFromImage(image);
   1785         // Have to dump into a file to be able to use ExifInterface
   1786         String jpegFilename = DEBUG_FILE_NAME_BASE + "/verifyJpegKeys.jpeg";
   1787         dumpFile(jpegFilename, jpegBuffer);
   1788         ExifInterface exif = new ExifInterface(jpegFilename);
   1789 
   1790         if (expectedThumbnailSize.equals(new Size(0,0))) {
   1791             collector.expectTrue("Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)",
   1792                     !exif.hasThumbnail());
   1793         } else {
   1794             collector.expectTrue("Jpeg must have thumbnail for thumbnail size " +
   1795                     expectedThumbnailSize, exif.hasThumbnail());
   1796         }
   1797 
   1798         // Validate capture result vs. request
   1799         Size resultThumbnailSize = captureResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE);
   1800         int orientationTested = expectedExifData.jpegOrientation;
   1801         // Legacy shim always doesn't rotate thumbnail size
   1802         if ((orientationTested == 90 || orientationTested == 270) &&
   1803                 staticInfo.isHardwareLevelLimitedOrBetter()) {
   1804             int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
   1805                     /*defaultValue*/-1);
   1806             if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) {
   1807                 // Device physically rotated image+thumbnail data
   1808                 // Expect thumbnail size to be also rotated
   1809                 resultThumbnailSize = new Size(resultThumbnailSize.getHeight(),
   1810                         resultThumbnailSize.getWidth());
   1811             }
   1812         }
   1813 
   1814         collector.expectEquals("JPEG thumbnail size result and request should match",
   1815                 expectedThumbnailSize, resultThumbnailSize);
   1816         if (collector.expectKeyValueNotNull(captureResult, CaptureResult.JPEG_GPS_LOCATION) !=
   1817                 null) {
   1818             collector.expectTrue("GPS location result and request should match.",
   1819                     areGpsFieldsEqual(expectedExifData.gpsLocation,
   1820                     captureResult.get(CaptureResult.JPEG_GPS_LOCATION)));
   1821         }
   1822         collector.expectEquals("JPEG orientation result and request should match",
   1823                 expectedExifData.jpegOrientation,
   1824                 captureResult.get(CaptureResult.JPEG_ORIENTATION));
   1825         collector.expectEquals("JPEG quality result and request should match",
   1826                 expectedExifData.jpegQuality, captureResult.get(CaptureResult.JPEG_QUALITY));
   1827         collector.expectEquals("JPEG thumbnail quality result and request should match",
   1828                 expectedExifData.thumbnailQuality,
   1829                 captureResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY));
   1830 
   1831         // Validate other exif tags for all non-legacy devices
   1832         if (!staticInfo.isHardwareLevelLegacy()) {
   1833             verifyJpegExifExtraTags(exif, expectedSize, captureResult, staticInfo, collector);
   1834         }
   1835     }
   1836 
   1837     /**
   1838      * Get the degree of an EXIF orientation.
   1839      */
   1840     private static int getExifOrientationInDegree(int exifOrientation,
   1841             CameraErrorCollector collector) {
   1842         switch (exifOrientation) {
   1843             case ExifInterface.ORIENTATION_NORMAL:
   1844                 return 0;
   1845             case ExifInterface.ORIENTATION_ROTATE_90:
   1846                 return 90;
   1847             case ExifInterface.ORIENTATION_ROTATE_180:
   1848                 return 180;
   1849             case ExifInterface.ORIENTATION_ROTATE_270:
   1850                 return 270;
   1851             default:
   1852                 collector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" +
   1853                         "info based on the request orientation range");
   1854                 return 0;
   1855         }
   1856     }
   1857 
   1858     /**
   1859      * Validate and return the focal length.
   1860      *
   1861      * @param result Capture result to get the focal length
   1862      * @return Focal length from capture result or -1 if focal length is not available.
   1863      */
   1864     private static float validateFocalLength(CaptureResult result, StaticMetadata staticInfo,
   1865             CameraErrorCollector collector) {
   1866         float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
   1867         Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
   1868         if (collector.expectTrue("Focal length is invalid",
   1869                 resultFocalLength != null && resultFocalLength > 0)) {
   1870             List<Float> focalLengthList =
   1871                     Arrays.asList(CameraTestUtils.toObject(focalLengths));
   1872             collector.expectTrue("Focal length should be one of the available focal length",
   1873                     focalLengthList.contains(resultFocalLength));
   1874             return resultFocalLength;
   1875         }
   1876         return -1;
   1877     }
   1878 
   1879     /**
   1880      * Validate and return the aperture.
   1881      *
   1882      * @param result Capture result to get the aperture
   1883      * @return Aperture from capture result or -1 if aperture is not available.
   1884      */
   1885     private static float validateAperture(CaptureResult result, StaticMetadata staticInfo,
   1886             CameraErrorCollector collector) {
   1887         float[] apertures = staticInfo.getAvailableAperturesChecked();
   1888         Float resultAperture = result.get(CaptureResult.LENS_APERTURE);
   1889         if (collector.expectTrue("Capture result aperture is invalid",
   1890                 resultAperture != null && resultAperture > 0)) {
   1891             List<Float> apertureList =
   1892                     Arrays.asList(CameraTestUtils.toObject(apertures));
   1893             collector.expectTrue("Aperture should be one of the available apertures",
   1894                     apertureList.contains(resultAperture));
   1895             return resultAperture;
   1896         }
   1897         return -1;
   1898     }
   1899 
   1900     /**
   1901      * Return the closest value in an array of floats.
   1902      */
   1903     private static float getClosestValueInArray(float[] values, float target) {
   1904         int minIdx = 0;
   1905         float minDistance = Math.abs(values[0] - target);
   1906         for(int i = 0; i < values.length; i++) {
   1907             float distance = Math.abs(values[i] - target);
   1908             if (minDistance > distance) {
   1909                 minDistance = distance;
   1910                 minIdx = i;
   1911             }
   1912         }
   1913 
   1914         return values[minIdx];
   1915     }
   1916 
   1917     /**
   1918      * Return if two Location's GPS field are the same.
   1919      */
   1920     private static boolean areGpsFieldsEqual(Location a, Location b) {
   1921         if (a == null || b == null) {
   1922             return false;
   1923         }
   1924 
   1925         return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() &&
   1926                 a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() &&
   1927                 a.getProvider() == b.getProvider();
   1928     }
   1929 
   1930     /**
   1931      * Verify extra tags in JPEG EXIF
   1932      */
   1933     private static void verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize,
   1934             CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)
   1935             throws ParseException {
   1936         /**
   1937          * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
   1938          * Orientation and exif width/height need to be tested carefully, two cases:
   1939          *
   1940          * 1. Device rotate the image buffer physically, then exif width/height may not match
   1941          * the requested still capture size, we need swap them to check.
   1942          *
   1943          * 2. Device use the exif tag to record the image orientation, it doesn't rotate
   1944          * the jpeg image buffer itself. In this case, the exif width/height should always match
   1945          * the requested still capture size, and the exif orientation should always match the
   1946          * requested orientation.
   1947          *
   1948          */
   1949         int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
   1950         int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
   1951         Size exifSize = new Size(exifWidth, exifHeight);
   1952         // Orientation could be missing, which is ok, default to 0.
   1953         int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
   1954                 /*defaultValue*/-1);
   1955         // Get requested orientation from result, because they should be same.
   1956         if (collector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) {
   1957             int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION);
   1958             final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
   1959             final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
   1960             boolean orientationValid = collector.expectTrue(String.format(
   1961                     "Exif orientation must be in range of [%d, %d]",
   1962                     ORIENTATION_MIN, ORIENTATION_MAX),
   1963                     exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
   1964             if (orientationValid) {
   1965                 /**
   1966                  * Device captured image doesn't respect the requested orientation,
   1967                  * which means it rotates the image buffer physically. Then we
   1968                  * should swap the exif width/height accordingly to compare.
   1969                  */
   1970                 boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
   1971 
   1972                 if (deviceRotatedImage) {
   1973                     // Case 1.
   1974                     boolean needSwap = (requestedOrientation % 180 == 90);
   1975                     if (needSwap) {
   1976                         exifSize = new Size(exifHeight, exifWidth);
   1977                     }
   1978                 } else {
   1979                     // Case 2.
   1980                     collector.expectEquals("Exif orientaiton should match requested orientation",
   1981                             requestedOrientation, getExifOrientationInDegree(exifOrientation,
   1982                             collector));
   1983                 }
   1984             }
   1985         }
   1986 
   1987         /**
   1988          * Ideally, need check exifSize == jpegSize == actual buffer size. But
   1989          * jpegSize == jpeg decode bounds size(from jpeg jpeg frame
   1990          * header, not exif) was validated in ImageReaderTest, no need to
   1991          * validate again here.
   1992          */
   1993         collector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
   1994 
   1995         // TAG_DATETIME, it should be local time
   1996         long currentTimeInMs = System.currentTimeMillis();
   1997         long currentTimeInSecond = currentTimeInMs / 1000;
   1998         Date date = new Date(currentTimeInMs);
   1999         String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date);
   2000         String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
   2001         if (collector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) {
   2002             collector.expectTrue("Exif TAG_DATETIME is wrong",
   2003                     dateTime.length() == EXIF_DATETIME_LENGTH);
   2004             long exifTimeInSecond =
   2005                     new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000;
   2006             long delta = currentTimeInSecond - exifTimeInSecond;
   2007             collector.expectTrue("Capture time deviates too much from the current time",
   2008                     Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC);
   2009             // It should be local time.
   2010             collector.expectTrue("Exif date time should be local time",
   2011                     dateTime.startsWith(localDatetime));
   2012         }
   2013 
   2014         // TAG_FOCAL_LENGTH.
   2015         float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
   2016         float exifFocalLength = (float)exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, -1);
   2017         collector.expectEquals("Focal length should match",
   2018                 getClosestValueInArray(focalLengths, exifFocalLength),
   2019                 exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN);
   2020         // More checks for focal length.
   2021         collector.expectEquals("Exif focal length should match capture result",
   2022                 validateFocalLength(result, staticInfo, collector), exifFocalLength);
   2023 
   2024         // TAG_EXPOSURE_TIME
   2025         // ExifInterface API gives exposure time value in the form of float instead of rational
   2026         String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
   2027         collector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime);
   2028         if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_EXPOSURE_TIME)) {
   2029             if (exposureTime != null) {
   2030                 double exposureTimeValue = Double.parseDouble(exposureTime);
   2031                 long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
   2032                 double expected = expTimeResult / 1e9;
   2033                 double tolerance = expected * EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO;
   2034                 tolerance = Math.max(tolerance, EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC);
   2035                 collector.expectEquals("Exif exposure time doesn't match", expected,
   2036                         exposureTimeValue, tolerance);
   2037             }
   2038         }
   2039 
   2040         // TAG_APERTURE
   2041         // ExifInterface API gives aperture value in the form of float instead of rational
   2042         String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
   2043         collector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture);
   2044         if (staticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) {
   2045             float[] apertures = staticInfo.getAvailableAperturesChecked();
   2046             if (exifAperture != null) {
   2047                 float apertureValue = Float.parseFloat(exifAperture);
   2048                 collector.expectEquals("Aperture value should match",
   2049                         getClosestValueInArray(apertures, apertureValue),
   2050                         apertureValue, EXIF_APERTURE_ERROR_MARGIN);
   2051                 // More checks for aperture.
   2052                 collector.expectEquals("Exif aperture length should match capture result",
   2053                         validateAperture(result, staticInfo, collector), apertureValue);
   2054             }
   2055         }
   2056 
   2057         /**
   2058          * TAG_FLASH. TODO: For full devices, can check a lot more info
   2059          * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash)
   2060          */
   2061         String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
   2062         collector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash);
   2063 
   2064         /**
   2065          * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we
   2066          * should be able to cross-check android.sensor.referenceIlluminant.
   2067          */
   2068         String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
   2069         collector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance);
   2070 
   2071         // TAG_MAKE
   2072         String make = exif.getAttribute(ExifInterface.TAG_MAKE);
   2073         collector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make);
   2074 
   2075         // TAG_MODEL
   2076         String model = exif.getAttribute(ExifInterface.TAG_MODEL);
   2077         collector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model);
   2078 
   2079 
   2080         // TAG_ISO
   2081         int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
   2082         if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
   2083             int expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
   2084             collector.expectEquals("Exif TAG_ISO is incorrect", expectedIso, iso);
   2085         }
   2086 
   2087         // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras).
   2088         String digitizedTime = exif.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED);
   2089         collector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime);
   2090         if (digitizedTime != null) {
   2091             String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
   2092             collector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime);
   2093             if (expectedDateTime != null) {
   2094                 collector.expectEquals("dataTime should match digitizedTime",
   2095                         expectedDateTime, digitizedTime);
   2096             }
   2097         }
   2098 
   2099         /**
   2100          * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at
   2101          * most 9 digits in ExifInterface implementation, use getAttributeInt to
   2102          * sanitize it. When the default value -1 is returned, it means that
   2103          * this exif tag either doesn't exist or is a non-numerical invalid
   2104          * string. Same rule applies to the rest of sub second tags.
   2105          */
   2106         int subSecTime = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME, /*defaultValue*/-1);
   2107         collector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime > 0);
   2108 
   2109         // TAG_SUBSEC_TIME_ORIG
   2110         int subSecTimeOrig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_ORIG,
   2111                 /*defaultValue*/-1);
   2112         collector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!",
   2113                 subSecTimeOrig > 0);
   2114 
   2115         // TAG_SUBSEC_TIME_DIG
   2116         int subSecTimeDig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_DIG,
   2117                 /*defaultValue*/-1);
   2118         collector.expectTrue(
   2119                 "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig > 0);
   2120     }
   2121 
   2122 
   2123     /**
   2124      * Immutable class wrapping the exif test data.
   2125      */
   2126     public static class ExifTestData {
   2127         public final Location gpsLocation;
   2128         public final int jpegOrientation;
   2129         public final byte jpegQuality;
   2130         public final byte thumbnailQuality;
   2131 
   2132         public ExifTestData(Location location, int orientation,
   2133                 byte jpgQuality, byte thumbQuality) {
   2134             gpsLocation = location;
   2135             jpegOrientation = orientation;
   2136             jpegQuality = jpgQuality;
   2137             thumbnailQuality = thumbQuality;
   2138         }
   2139     }
   2140 
   2141     public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) {
   2142         Display display = windowManager.getDefaultDisplay();
   2143 
   2144         int width = display.getWidth();
   2145         int height = display.getHeight();
   2146 
   2147         if (height > width) {
   2148             height = width;
   2149             width = display.getHeight();
   2150         }
   2151 
   2152         if (bound.getWidth() <= width &&
   2153             bound.getHeight() <= height)
   2154             return bound;
   2155         else
   2156             return new Size(width, height);
   2157     }
   2158 }
   2159