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