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