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      */
makeImageReader(Size size, int format, int maxNumImages, ImageReader.OnImageAvailableListener listener, Handler handler)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      */
makeImageWriter( Surface inputSurface, int maxImages, ImageWriter.OnImageReleasedListener listener, Handler handler)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      */
closeImageReader(ImageReader reader)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      */
closeImageReaders(ImageReader[] readers)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      */
closeImageWriter(ImageWriter writer)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
onImageAvailable(ImageReader reader)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 
getImageCount()253         public synchronized int getImageCount() {
254             return mImagesDropped;
255         }
256 
resetImageCount()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 
ImageVerifierListener(Size sz, int format)271         public ImageVerifierListener(Size sz, int format) {
272             mSize = sz;
273             mFormat = format;
274         }
275 
276         @Override
onImageAvailable(ImageReader reader)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 validity checks 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          */
SimpleImageReaderListener()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          */
SimpleImageReaderListener(boolean asyncMode, int maxImages)331         public SimpleImageReaderListener(boolean asyncMode, int maxImages) {
332             mAsyncMode = asyncMode;
333             mMaxImages = maxImages;
334         }
335 
336         @Override
onImageAvailable(ImageReader reader)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          */
getImage(long timeout)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          */
drain()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
onImageReleased(ImageWriter writer)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 
SimpleImageWriterListener(ImageWriter writer)394         public SimpleImageWriterListener(ImageWriter writer) {
395             if (writer == null) {
396                 throw new IllegalArgumentException("writer cannot be null");
397             }
398             mWriter = writer;
399         }
400 
waitForImageReleased(long timeoutMs)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
onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber)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
onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result)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
onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)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
onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber)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 
getTotalNumFrames()467         public long getTotalNumFrames() {
468             return mNumFramesArrived.get();
469         }
470 
getCaptureResult(long timeout)471         public CaptureResult getCaptureResult(long timeout) {
472             return getTotalCaptureResult(timeout);
473         }
474 
getCaptureResult(long timeout, long timestamp)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 
getTotalCaptureResult(long timeout)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          */
getCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)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          */
getTotalCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)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          */
getTotalCaptureResultsForRequests( List<CaptureRequest> captureRequests, int numResultsWait)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          */
getCaptureFailures(long maxNumFailures)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          */
waitForCaptureStart(CaptureRequest request, Long timestamp, int numCaptureStartsWait)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          */
getCaptureSequenceLastFrameNumber(int sequenceId, long timeoutMs)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 
hasMoreResults()689         public boolean hasMoreResults()
690         {
691             return !mQueue.isEmpty();
692         }
693 
hasMoreFailures()694         public boolean hasMoreFailures()
695         {
696             return !mFailureQueue.isEmpty();
697         }
698 
drain()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      */
openCamera(CameraManager manager, String cameraId, CameraDevice.StateCallback listener, Handler handler)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      */
openCamera(CameraManager manager, String cameraId, Handler handler)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      */
configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, boolean isHighSpeed, CameraCaptureSession.StateCallback listener, Handler handler)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      */
buildConstrainedCameraSession(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler, CaptureRequest initialRequest)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      */
configureCameraSessionWithConfig(CameraDevice camera, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)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      */
tryConfigureCameraSessionWithConfig(CameraDevice camera, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)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      */
configureCameraSessionWithParameters(CameraDevice camera, List<Surface> outputSurfaces, BlockingSessionCallback listener, Handler handler, CaptureRequest initialRequest)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      */
configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)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 
configureReprocessableCameraSession(CameraDevice camera, InputConfiguration inputConfiguration, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)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      */
configureReprocCameraSessionWithConfig(CameraDevice camera, InputConfiguration inputConfiguration, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)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 
assertArrayNotEmpty(T arr, String message)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      */
checkYuvFormat(int format)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      */
checkImage(Image image, int width, int height, int format)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      */
getDataFromImage(Image image)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      */
checkAndroidImageFormat(Image image)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 
dumpFile(String fileName, Bitmap data)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 
dumpFile(String fileName, byte[] data)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      */
getSupportedSizeForFormat(int format, String cameraId, CameraManager cameraManager)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      */
getSupportedSizeForClass(Class klass, String cameraId, CameraManager cameraManager)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
compare(Size lhs, Size rhs)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      */
getSupportedPreviewSizes(String cameraId, CameraManager cameraManager, Size bound)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      */
getAscendingOrderSizes(final List<Size> sizeList, boolean ascending)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      */
getSortedSizesForFormat(String cameraId, CameraManager cameraManager, int format, Size bound)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      */
getSupportedVideoSizes(String cameraId, CameraManager cameraManager, Size bound)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      */
getSupportedStillSizes(String cameraId, CameraManager cameraManager, Size bound)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 
getSupportedHeicSizes(String cameraId, CameraManager cameraManager, Size bound)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 
getMinPreviewSize(String cameraId, CameraManager cameraManager)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      */
getMaxPreviewSize(String cameraId, CameraManager cameraManager)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      */
getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)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      */
getMaxDepthSize(String cameraId, CameraManager cameraManager)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      */
getMaxSize(Size... sizes)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      */
getMaxSizeWithBound(Size[] sizes, int bound)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      */
contains(int[] array, int elem)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      */
toObject(byte[] array)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      */
toObject(int[] array)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      */
toObject(float[] array)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      */
toObject(double[] array)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      */
convertPrimitiveArrayToObjectArray(final Object array, final Class<T> wrapperClass)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      */
validateImage(Image image, int width, int height, int format, String filePath)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 
HandlerExecutor(Handler handler)1605         public HandlerExecutor(Handler handler) {
1606             assertNotNull("handler must be valid", handler);
1607             mHandler = handler;
1608         }
1609 
1610         @Override
execute(Runnable runCmd)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
onOpened(CameraDevice camera)1632         public void onOpened(CameraDevice camera) {
1633         }
1634 
1635         @Override
onDisconnected(CameraDevice camera)1636         public void onDisconnected(CameraDevice camera) {
1637         }
1638 
1639         @Override
onError(CameraDevice camera, int error)1640         public void onError(CameraDevice camera, int error) {
1641         }
1642 
MockStateCallback()1643         private MockStateCallback() {}
1644 
1645         /**
1646          * Create a Mockito-ready mocked StateCallback.
1647          */
mock()1648         public static MockStateCallback mock() {
1649             return Mockito.spy(new MockStateCallback());
1650         }
1651     }
1652 
validateJpegData(byte[] jpegData, int width, int height, String filePath)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 
validateYuvData(byte[] yuvData, int width, int height, int format, long ts, String filePath)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 
validateRaw16Data(byte[] rawData, int width, int height, int format, long ts, String filePath)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 
validateY8Data(byte[] rawData, int width, int height, int format, long ts, String filePath)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 
validateRawPrivateData(byte[] rawData, int width, int height, long ts, String filePath)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 
validateDepth16Data(byte[] depthData, int width, int height, int format, long ts, String filePath)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 
validateDepthPointCloudData(byte[] depthData, int width, int height, int format, long ts, String filePath)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 
validateHeicData(byte[] heicData, int width, int height, String filePath)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 
getValueNotNull(CaptureResult result, CaptureResult.Key<T> key)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 
getValueNotNull(CameraCharacteristics characteristics, CameraCharacteristics.Key<T> key)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      */
getCropRegionForZoom(float zoomFactor, final PointF center, final float maxZoom, final Rect activeArray)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      */
getDescendingTargetFpsRanges(StaticMetadata staticInfo)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      */
getTargetFpsRangesUpTo30(StaticMetadata staticInfo)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      */
getTargetFpsRangesGreaterThan30(StaticMetadata staticInfo)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      */
getExpectedOutputRegion( MeteringRectangle[] requestRegions, Rect cropRect)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      */
imageCopy(Image src, Image dst)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 
getEffectivePlaneSizeForImage(Image image, int planeIdx)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      */
isImageStronglyEqual(Image lhsImg, Image rhsImg)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      */
setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData, Size thumbnailSize, CameraErrorCollector collector)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 basic correctness. 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      */
basicValidateBlobImage(Image image, Size expectedSize, int format)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      */
verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize, Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo, CameraErrorCollector collector, String debugFileNameBase, int format)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      */
getExifOrientationInDegree(int exifOrientation, CameraErrorCollector collector)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      */
validateFocalLength(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)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      */
validateAperture(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)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      */
getClosestValueInArray(float[] values, float target)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      */
areGpsFieldsEqual(Location a, Location b)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      */
verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize, CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector, ExifTestData expectedExifData)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 
ExifTestData(Location location, int orientation, byte jpgQuality, byte thumbQuality)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 
getPreviewSizeBound(WindowManager windowManager, Size bound)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      */
isStreamConfigurationSupported(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)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 
SessionConfigSupport(boolean error, boolean callSupported, boolean configSupported)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      */
checkSessionConfigurationWithSurfaces(CameraDevice camera, Handler handler, List<Surface> outputSurfaces, InputConfiguration inputConfig, int operatingMode, boolean defaultSupport, String msg)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 
checkSessionConfigurationSupported(CameraDevice camera, Handler handler, List<OutputConfiguration> outputConfigs, InputConfiguration inputConfig, int operatingMode, boolean defaultSupport, String msg)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      */
isSessionConfigSupported(CameraDevice camera, Handler handler, List<OutputConfiguration> outputConfigs, InputConfiguration inputConfig, int operatingMode, boolean defaultSupport)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      */
waitForNumResults(SimpleCaptureCallback resultListener, int numResultsWait, int timeout)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      */
waitForAnyResultValue(SimpleCaptureCallback listener, CaptureResult.Key<T> resultKey, List<T> expectedValues, int numResultsWait, int timeout)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      */
waitForResultValue(SimpleCaptureCallback listener, CaptureResult.Key<T> resultKey, T expectedValue, int numResultsWait, int timeout)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      */
waitForAeStable(SimpleCaptureCallback resultListener, int numResultWaitForUnknownLatency, StaticMetadata staticInfo, int settingsTimeout, int numResultWait)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      */
waitForSettingsApplied(SimpleCaptureCallback resultListener, int numResultWaitForUnknownLatency, StaticMetadata staticInfo, int timeout)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 
getSuitableFpsRangeForDuration(String cameraId, long frameDuration, StaticMetadata staticInfo)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