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