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