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