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