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