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