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