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