• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.hardware.camera2.cts;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.BitmapFactory;
21 import android.graphics.ImageFormat;
22 import android.graphics.PointF;
23 import android.graphics.Rect;
24 import android.graphics.SurfaceTexture;
25 import android.hardware.camera2.CameraAccessException;
26 import android.hardware.camera2.CameraCaptureSession;
27 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
28 import android.hardware.camera2.CameraDevice;
29 import android.hardware.camera2.CameraManager;
30 import android.hardware.camera2.CameraMetadata;
31 import android.hardware.camera2.CameraCharacteristics;
32 import android.hardware.camera2.CaptureFailure;
33 import android.hardware.camera2.CaptureRequest;
34 import android.hardware.camera2.CaptureResult;
35 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
36 import android.hardware.camera2.cts.helpers.StaticMetadata;
37 import android.hardware.camera2.params.InputConfiguration;
38 import android.hardware.camera2.TotalCaptureResult;
39 import android.hardware.cts.helpers.CameraUtils;
40 import android.hardware.camera2.params.MeteringRectangle;
41 import android.hardware.camera2.params.MandatoryStreamCombination;
42 import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
43 import android.hardware.camera2.params.OutputConfiguration;
44 import android.hardware.camera2.params.SessionConfiguration;
45 import android.hardware.camera2.params.StreamConfigurationMap;
46 import android.location.Location;
47 import android.location.LocationManager;
48 import android.media.ExifInterface;
49 import android.media.Image;
50 import android.media.ImageReader;
51 import android.media.ImageWriter;
52 import android.media.Image.Plane;
53 import android.os.Build;
54 import android.os.Handler;
55 import android.util.Log;
56 import android.util.Pair;
57 import android.util.Size;
58 import android.util.Range;
59 import android.view.Display;
60 import android.view.Surface;
61 import android.view.WindowManager;
62 
63 import com.android.ex.camera2.blocking.BlockingCameraManager;
64 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
65 import com.android.ex.camera2.blocking.BlockingSessionCallback;
66 import com.android.ex.camera2.blocking.BlockingStateCallback;
67 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
68 
69 import junit.framework.Assert;
70 
71 import org.mockito.Mockito;
72 
73 import java.io.FileOutputStream;
74 import java.io.IOException;
75 import java.lang.reflect.Array;
76 import java.nio.ByteBuffer;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Collections;
80 import java.util.Comparator;
81 import java.util.Date;
82 import java.util.HashMap;
83 import java.util.HashSet;
84 import java.util.List;
85 import java.util.Set;
86 import java.util.concurrent.atomic.AtomicLong;
87 import java.util.concurrent.Executor;
88 import java.util.concurrent.LinkedBlockingQueue;
89 import java.util.concurrent.Semaphore;
90 import java.util.concurrent.TimeUnit;
91 import java.text.ParseException;
92 import java.text.SimpleDateFormat;
93 
94 /**
95  * A package private utility class for wrapping up the camera2 cts test common utility functions
96  */
97 public class CameraTestUtils extends Assert {
98     private static final String TAG = "CameraTestUtils";
99     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
100     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
101     public static final Size SIZE_BOUND_1080P = new Size(1920, 1088);
102     public static final Size SIZE_BOUND_2160P = new Size(3840, 2160);
103     // Only test the preview size that is no larger than 1080p.
104     public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P;
105     // Default timeouts for reaching various states
106     public static final int CAMERA_OPEN_TIMEOUT_MS = 3000;
107     public static final int CAMERA_CLOSE_TIMEOUT_MS = 3000;
108     public static final int CAMERA_IDLE_TIMEOUT_MS = 3000;
109     public static final int CAMERA_ACTIVE_TIMEOUT_MS = 1000;
110     public static final int CAMERA_BUSY_TIMEOUT_MS = 1000;
111     public static final int CAMERA_UNCONFIGURED_TIMEOUT_MS = 1000;
112     public static final int CAMERA_CONFIGURE_TIMEOUT_MS = 3000;
113     public static final int CAPTURE_RESULT_TIMEOUT_MS = 3000;
114     public static final int CAPTURE_IMAGE_TIMEOUT_MS = 3000;
115 
116     public static final int SESSION_CONFIGURE_TIMEOUT_MS = 3000;
117     public static final int SESSION_CLOSE_TIMEOUT_MS = 3000;
118     public static final int SESSION_READY_TIMEOUT_MS = 5000;
119     public static final int SESSION_ACTIVE_TIMEOUT_MS = 1000;
120 
121     public static final int MAX_READER_IMAGES = 5;
122 
123     public static final String OFFLINE_CAMERA_ID = "offline_camera_id";
124 
125     private static final int EXIF_DATETIME_LENGTH = 19;
126     private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
127     private static final float EXIF_FOCAL_LENGTH_ERROR_MARGIN = 0.001f;
128     private static final float EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO = 0.05f;
129     private static final float EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC = 0.002f;
130     private static final float EXIF_APERTURE_ERROR_MARGIN = 0.001f;
131 
132     private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER);
133     private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
134     private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
135 
136     static {
137         sTestLocation0.setTime(1199145600000L);
138         sTestLocation0.setLatitude(37.736071);
139         sTestLocation0.setLongitude(-122.441983);
140         sTestLocation0.setAltitude(21.0);
141 
142         sTestLocation1.setTime(1199145601000L);
143         sTestLocation1.setLatitude(0.736071);
144         sTestLocation1.setLongitude(0.441983);
145         sTestLocation1.setAltitude(1.0);
146 
147         sTestLocation2.setTime(1199145602000L);
148         sTestLocation2.setLatitude(-89.736071);
149         sTestLocation2.setLongitude(-179.441983);
150         sTestLocation2.setAltitude(100000.0);
151     }
152 
153     // Exif test data vectors.
154     public static final ExifTestData[] EXIF_TEST_DATA = {
155             new ExifTestData(
156                     /*gpsLocation*/ sTestLocation0,
157                     /* orientation */90,
158                     /* jpgQuality */(byte) 80,
159                     /* thumbQuality */(byte) 75),
160             new ExifTestData(
161                     /*gpsLocation*/ sTestLocation1,
162                     /* orientation */180,
163                     /* jpgQuality */(byte) 90,
164                     /* thumbQuality */(byte) 85),
165             new ExifTestData(
166                     /*gpsLocation*/ sTestLocation2,
167                     /* orientation */270,
168                     /* jpgQuality */(byte) 100,
169                     /* thumbQuality */(byte) 100)
170     };
171 
172     /**
173      * Create an {@link android.media.ImageReader} object and get the surface.
174      *
175      * @param size The size of this ImageReader to be created.
176      * @param format The format of this ImageReader to be created
177      * @param maxNumImages The max number of images that can be acquired simultaneously.
178      * @param listener The listener used by this ImageReader to notify callbacks.
179      * @param handler The handler to use for any listener callbacks.
180      */
makeImageReader(Size size, int format, int maxNumImages, ImageReader.OnImageAvailableListener listener, Handler handler)181     public static ImageReader makeImageReader(Size size, int format, int maxNumImages,
182             ImageReader.OnImageAvailableListener listener, Handler handler) {
183         ImageReader reader;
184         reader = ImageReader.newInstance(size.getWidth(), size.getHeight(), format,
185                 maxNumImages);
186         reader.setOnImageAvailableListener(listener, handler);
187         if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size);
188         return reader;
189     }
190 
191     /**
192      * Create an ImageWriter and hook up the ImageListener.
193      *
194      * @param inputSurface The input surface of the ImageWriter.
195      * @param maxImages The max number of Images that can be dequeued simultaneously.
196      * @param listener The listener used by this ImageWriter to notify callbacks
197      * @param handler The handler to post listener callbacks.
198      * @return ImageWriter object created.
199      */
makeImageWriter( Surface inputSurface, int maxImages, ImageWriter.OnImageReleasedListener listener, Handler handler)200     public static ImageWriter makeImageWriter(
201             Surface inputSurface, int maxImages,
202             ImageWriter.OnImageReleasedListener listener, Handler handler) {
203         ImageWriter writer = ImageWriter.newInstance(inputSurface, maxImages);
204         writer.setOnImageReleasedListener(listener, handler);
205         return writer;
206     }
207 
setupConfigurationTargets(List<MandatoryStreamInformation> streamsInfo, List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets, List<ImageReader> yuvTargets, List<ImageReader> y8Targets, List<ImageReader> rawTargets, List<ImageReader> heicTargets, List<ImageReader> depth16Targets, List<OutputConfiguration> outputConfigs, int numBuffers, boolean substituteY8, boolean substituteHeic, String overridePhysicalCameraId, Handler handler)208     public static void setupConfigurationTargets(List<MandatoryStreamInformation> streamsInfo,
209             List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
210             List<ImageReader> yuvTargets, List<ImageReader> y8Targets,
211             List<ImageReader> rawTargets, List<ImageReader> heicTargets,
212             List<ImageReader> depth16Targets, List<OutputConfiguration> outputConfigs,
213             int numBuffers, boolean substituteY8, boolean substituteHeic,
214             String overridePhysicalCameraId, Handler handler) {
215 
216         ImageDropperListener imageDropperListener = new ImageDropperListener();
217 
218         for (MandatoryStreamInformation streamInfo : streamsInfo) {
219             if (streamInfo.isInput()) {
220                 continue;
221             }
222             int format = streamInfo.getFormat();
223             if (substituteY8 && (format == ImageFormat.YUV_420_888)) {
224                 format = ImageFormat.Y8;
225             } else if (substituteHeic && (format == ImageFormat.JPEG)) {
226                 format = ImageFormat.HEIC;
227             }
228             Surface newSurface;
229             Size[] availableSizes = new Size[streamInfo.getAvailableSizes().size()];
230             availableSizes = streamInfo.getAvailableSizes().toArray(availableSizes);
231             Size targetSize = CameraTestUtils.getMaxSize(availableSizes);
232 
233             switch (format) {
234                 case ImageFormat.PRIVATE: {
235                     SurfaceTexture target = new SurfaceTexture(/*random int*/1);
236                     target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
237                     OutputConfiguration config = new OutputConfiguration(new Surface(target));
238                     if (overridePhysicalCameraId != null) {
239                         config.setPhysicalCameraId(overridePhysicalCameraId);
240                     }
241                     outputConfigs.add(config);
242                     privTargets.add(target);
243                     break;
244                 }
245                 case ImageFormat.JPEG: {
246                     ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
247                             targetSize.getHeight(), format, numBuffers);
248                     target.setOnImageAvailableListener(imageDropperListener, handler);
249                     OutputConfiguration config = new OutputConfiguration(target.getSurface());
250                     if (overridePhysicalCameraId != null) {
251                         config.setPhysicalCameraId(overridePhysicalCameraId);
252                     }
253                     outputConfigs.add(config);
254                     jpegTargets.add(target);
255                     break;
256                 }
257                 case ImageFormat.YUV_420_888: {
258                     ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
259                             targetSize.getHeight(), format, numBuffers);
260                     target.setOnImageAvailableListener(imageDropperListener, handler);
261                     OutputConfiguration config = new OutputConfiguration(target.getSurface());
262                     if (overridePhysicalCameraId != null) {
263                         config.setPhysicalCameraId(overridePhysicalCameraId);
264                     }
265                     outputConfigs.add(config);
266                     yuvTargets.add(target);
267                     break;
268                 }
269                 case ImageFormat.Y8: {
270                     ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
271                             targetSize.getHeight(), format, numBuffers);
272                     target.setOnImageAvailableListener(imageDropperListener, handler);
273                     OutputConfiguration config = new OutputConfiguration(target.getSurface());
274                     if (overridePhysicalCameraId != null) {
275                         config.setPhysicalCameraId(overridePhysicalCameraId);
276                     }
277                     outputConfigs.add(config);
278                     y8Targets.add(target);
279                     break;
280                 }
281                 case ImageFormat.RAW_SENSOR: {
282                     // targetSize could be null in the logical camera case where only
283                     // physical camera supports RAW stream.
284                     if (targetSize != null) {
285                         ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
286                                 targetSize.getHeight(), format, numBuffers);
287                         target.setOnImageAvailableListener(imageDropperListener, handler);
288                         OutputConfiguration config =
289                                 new OutputConfiguration(target.getSurface());
290                         if (overridePhysicalCameraId != null) {
291                             config.setPhysicalCameraId(overridePhysicalCameraId);
292                         }
293                         outputConfigs.add(config);
294                         rawTargets.add(target);
295                     }
296                     break;
297                 }
298                 case ImageFormat.HEIC: {
299                     ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
300                             targetSize.getHeight(), format, numBuffers);
301                     target.setOnImageAvailableListener(imageDropperListener, handler);
302                     OutputConfiguration config = new OutputConfiguration(target.getSurface());
303                     if (overridePhysicalCameraId != null) {
304                         config.setPhysicalCameraId(overridePhysicalCameraId);
305                     }
306                     outputConfigs.add(config);
307                     heicTargets.add(target);
308                     break;
309                 }
310                 case ImageFormat.DEPTH16: {
311                     ImageReader target = ImageReader.newInstance(targetSize.getWidth(),
312                             targetSize.getHeight(), format, numBuffers);
313                     target.setOnImageAvailableListener(imageDropperListener, handler);
314                     OutputConfiguration config = new OutputConfiguration(target.getSurface());
315                     if (overridePhysicalCameraId != null) {
316                         config.setPhysicalCameraId(overridePhysicalCameraId);
317                     }
318                     outputConfigs.add(config);
319                     depth16Targets.add(target);
320                     break;
321                 }
322                 default:
323                     fail("Unknown output format " + format);
324             }
325         }
326     }
327 
328     /**
329      * Close pending images and clean up an {@link android.media.ImageReader} object.
330      * @param reader an {@link android.media.ImageReader} to close.
331      */
closeImageReader(ImageReader reader)332     public static void closeImageReader(ImageReader reader) {
333         if (reader != null) {
334             reader.close();
335         }
336     }
337 
338     /**
339      * Close the pending images then close current active {@link ImageReader} objects.
340      */
closeImageReaders(ImageReader[] readers)341     public static void closeImageReaders(ImageReader[] readers) {
342         if ((readers != null) && (readers.length > 0)) {
343             for (ImageReader reader : readers) {
344                 CameraTestUtils.closeImageReader(reader);
345             }
346         }
347     }
348 
349     /**
350      * Close pending images and clean up an {@link android.media.ImageWriter} object.
351      * @param writer an {@link android.media.ImageWriter} to close.
352      */
closeImageWriter(ImageWriter writer)353     public static void closeImageWriter(ImageWriter writer) {
354         if (writer != null) {
355             writer.close();
356         }
357     }
358 
359     /**
360      * Dummy listener that release the image immediately once it is available.
361      *
362      * <p>
363      * It can be used for the case where we don't care the image data at all.
364      * </p>
365      */
366     public static class ImageDropperListener implements ImageReader.OnImageAvailableListener {
367         @Override
onImageAvailable(ImageReader reader)368         public synchronized void onImageAvailable(ImageReader reader) {
369             Image image = null;
370             try {
371                 image = reader.acquireNextImage();
372             } finally {
373                 if (image != null) {
374                     image.close();
375                     mImagesDropped++;
376                 }
377             }
378         }
379 
getImageCount()380         public synchronized int getImageCount() {
381             return mImagesDropped;
382         }
383 
resetImageCount()384         public synchronized void resetImageCount() {
385             mImagesDropped = 0;
386         }
387 
388         private int mImagesDropped = 0;
389     }
390 
391     /**
392      * Image listener that release the image immediately after validating the image
393      */
394     public static class ImageVerifierListener implements ImageReader.OnImageAvailableListener {
395         private Size mSize;
396         private int mFormat;
397 
ImageVerifierListener(Size sz, int format)398         public ImageVerifierListener(Size sz, int format) {
399             mSize = sz;
400             mFormat = format;
401         }
402 
403         @Override
onImageAvailable(ImageReader reader)404         public void onImageAvailable(ImageReader reader) {
405             Image image = null;
406             try {
407                 image = reader.acquireNextImage();
408             } finally {
409                 if (image != null) {
410                     // Should only do some quick validity checks in callback, as the ImageReader
411                     // could be closed asynchronously, which will close all images acquired from
412                     // this ImageReader.
413                     checkImage(image, mSize.getWidth(), mSize.getHeight(), mFormat);
414                     checkAndroidImageFormat(image);
415                     image.close();
416                 }
417             }
418         }
419     }
420 
421     public static class SimpleImageReaderListener
422             implements ImageReader.OnImageAvailableListener {
423         private final LinkedBlockingQueue<Image> mQueue =
424                 new LinkedBlockingQueue<Image>();
425         // Indicate whether this listener will drop images or not,
426         // when the queued images reaches the reader maxImages
427         private final boolean mAsyncMode;
428         // maxImages held by the queue in async mode.
429         private final int mMaxImages;
430 
431         /**
432          * Create a synchronous SimpleImageReaderListener that queues the images
433          * automatically when they are available, no image will be dropped. If
434          * the caller doesn't call getImage(), the producer will eventually run
435          * into buffer starvation.
436          */
SimpleImageReaderListener()437         public SimpleImageReaderListener() {
438             mAsyncMode = false;
439             mMaxImages = 0;
440         }
441 
442         /**
443          * Create a synchronous/asynchronous SimpleImageReaderListener that
444          * queues the images automatically when they are available. For
445          * asynchronous listener, image will be dropped if the queued images
446          * reach to maxImages queued. If the caller doesn't call getImage(), the
447          * producer will not be blocked. For synchronous listener, no image will
448          * be dropped. If the caller doesn't call getImage(), the producer will
449          * eventually run into buffer starvation.
450          *
451          * @param asyncMode If the listener is operating at asynchronous mode.
452          * @param maxImages The max number of images held by this listener.
453          */
454         /**
455          *
456          * @param asyncMode
457          */
SimpleImageReaderListener(boolean asyncMode, int maxImages)458         public SimpleImageReaderListener(boolean asyncMode, int maxImages) {
459             mAsyncMode = asyncMode;
460             mMaxImages = maxImages;
461         }
462 
463         @Override
onImageAvailable(ImageReader reader)464         public void onImageAvailable(ImageReader reader) {
465             try {
466                 Image imge = reader.acquireNextImage();
467                 if (imge == null) {
468                     return;
469                 }
470                 mQueue.put(imge);
471                 if (mAsyncMode && mQueue.size() >= mMaxImages) {
472                     Image img = mQueue.poll();
473                     img.close();
474                 }
475             } catch (InterruptedException e) {
476                 throw new UnsupportedOperationException(
477                         "Can't handle InterruptedException in onImageAvailable");
478             }
479         }
480 
481         /**
482          * Get an image from the image reader.
483          *
484          * @param timeout Timeout value for the wait.
485          * @return The image from the image reader.
486          */
getImage(long timeout)487         public Image getImage(long timeout) throws InterruptedException {
488             Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
489             assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
490             return image;
491         }
492 
493         /**
494          * Drain the pending images held by this listener currently.
495          *
496          */
drain()497         public void drain() {
498             while (!mQueue.isEmpty()) {
499                 Image image = mQueue.poll();
500                 assertNotNull("Unable to get an image", image);
501                 image.close();
502             }
503         }
504     }
505 
506     public static class SimpleImageWriterListener implements ImageWriter.OnImageReleasedListener {
507         private final Semaphore mImageReleasedSema = new Semaphore(0);
508         private final ImageWriter mWriter;
509         @Override
onImageReleased(ImageWriter writer)510         public void onImageReleased(ImageWriter writer) {
511             if (writer != mWriter) {
512                 return;
513             }
514 
515             if (VERBOSE) {
516                 Log.v(TAG, "Input image is released");
517             }
518             mImageReleasedSema.release();
519         }
520 
SimpleImageWriterListener(ImageWriter writer)521         public SimpleImageWriterListener(ImageWriter writer) {
522             if (writer == null) {
523                 throw new IllegalArgumentException("writer cannot be null");
524             }
525             mWriter = writer;
526         }
527 
waitForImageReleased(long timeoutMs)528         public void waitForImageReleased(long timeoutMs) throws InterruptedException {
529             if (!mImageReleasedSema.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
530                 fail("wait for image available timed out after " + timeoutMs + "ms");
531             }
532         }
533     }
534 
535     public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback {
536         private final LinkedBlockingQueue<TotalCaptureResult> mQueue =
537                 new LinkedBlockingQueue<TotalCaptureResult>();
538         private final LinkedBlockingQueue<CaptureFailure> mFailureQueue =
539                 new LinkedBlockingQueue<>();
540         private final LinkedBlockingQueue<Integer> mAbortQueue =
541                 new LinkedBlockingQueue<>();
542         // Pair<CaptureRequest, Long> is a pair of capture request and timestamp.
543         private final LinkedBlockingQueue<Pair<CaptureRequest, Long>> mCaptureStartQueue =
544                 new LinkedBlockingQueue<>();
545         // Pair<Int, Long> is a pair of sequence id and frame number
546         private final LinkedBlockingQueue<Pair<Integer, Long>> mCaptureSequenceCompletedQueue =
547                 new LinkedBlockingQueue<>();
548 
549         private AtomicLong mNumFramesArrived = new AtomicLong(0);
550 
551         @Override
onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber)552         public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
553                 long timestamp, long frameNumber) {
554             try {
555                 mCaptureStartQueue.put(new Pair(request, timestamp));
556             } catch (InterruptedException e) {
557                 throw new UnsupportedOperationException(
558                         "Can't handle InterruptedException in onCaptureStarted");
559             }
560         }
561 
562         @Override
onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result)563         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
564                 TotalCaptureResult result) {
565             try {
566                 mNumFramesArrived.incrementAndGet();
567                 mQueue.put(result);
568             } catch (InterruptedException e) {
569                 throw new UnsupportedOperationException(
570                         "Can't handle InterruptedException in onCaptureCompleted");
571             }
572         }
573 
574         @Override
onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure)575         public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
576                 CaptureFailure failure) {
577             try {
578                 mFailureQueue.put(failure);
579             } catch (InterruptedException e) {
580                 throw new UnsupportedOperationException(
581                         "Can't handle InterruptedException in onCaptureFailed");
582             }
583         }
584 
585         @Override
onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId)586         public void onCaptureSequenceAborted(CameraCaptureSession session, int sequenceId) {
587             try {
588                 mAbortQueue.put(sequenceId);
589             } catch (InterruptedException e) {
590                 throw new UnsupportedOperationException(
591                         "Can't handle InterruptedException in onCaptureAborted");
592             }
593         }
594 
595         @Override
onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId, long frameNumber)596         public void onCaptureSequenceCompleted(CameraCaptureSession session, int sequenceId,
597                 long frameNumber) {
598             try {
599                 mCaptureSequenceCompletedQueue.put(new Pair(sequenceId, frameNumber));
600             } catch (InterruptedException e) {
601                 throw new UnsupportedOperationException(
602                         "Can't handle InterruptedException in onCaptureSequenceCompleted");
603             }
604         }
605 
getTotalNumFrames()606         public long getTotalNumFrames() {
607             return mNumFramesArrived.get();
608         }
609 
getCaptureResult(long timeout)610         public CaptureResult getCaptureResult(long timeout) {
611             return getTotalCaptureResult(timeout);
612         }
613 
getCaptureResult(long timeout, long timestamp)614         public TotalCaptureResult getCaptureResult(long timeout, long timestamp) {
615             try {
616                 long currentTs = -1L;
617                 TotalCaptureResult result;
618                 while (true) {
619                     result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
620                     if (result == null) {
621                         throw new RuntimeException(
622                                 "Wait for a capture result timed out in " + timeout + "ms");
623                     }
624                     currentTs = result.get(CaptureResult.SENSOR_TIMESTAMP);
625                     if (currentTs == timestamp) {
626                         return result;
627                     }
628                 }
629 
630             } catch (InterruptedException e) {
631                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
632             }
633         }
634 
getTotalCaptureResult(long timeout)635         public TotalCaptureResult getTotalCaptureResult(long timeout) {
636             try {
637                 TotalCaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
638                 assertNotNull("Wait for a capture result timed out in " + timeout + "ms", result);
639                 return result;
640             } catch (InterruptedException e) {
641                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
642             }
643         }
644 
645         /**
646          * Get the {@link #CaptureResult capture result} for a given
647          * {@link #CaptureRequest capture request}.
648          *
649          * @param myRequest The {@link #CaptureRequest capture request} whose
650          *            corresponding {@link #CaptureResult capture result} was
651          *            being waited for
652          * @param numResultsWait Number of frames to wait for the capture result
653          *            before timeout.
654          * @throws TimeoutRuntimeException If more than numResultsWait results are
655          *            seen before the result matching myRequest arrives, or each
656          *            individual wait for result times out after
657          *            {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
658          */
getCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)659         public CaptureResult getCaptureResultForRequest(CaptureRequest myRequest,
660                 int numResultsWait) {
661             return getTotalCaptureResultForRequest(myRequest, numResultsWait);
662         }
663 
664         /**
665          * Get the {@link #TotalCaptureResult total capture result} for a given
666          * {@link #CaptureRequest capture request}.
667          *
668          * @param myRequest The {@link #CaptureRequest capture request} whose
669          *            corresponding {@link #TotalCaptureResult capture result} was
670          *            being waited for
671          * @param numResultsWait Number of frames to wait for the capture result
672          *            before timeout.
673          * @throws TimeoutRuntimeException If more than numResultsWait results are
674          *            seen before the result matching myRequest arrives, or each
675          *            individual wait for result times out after
676          *            {@value #CAPTURE_RESULT_TIMEOUT_MS}ms.
677          */
getTotalCaptureResultForRequest(CaptureRequest myRequest, int numResultsWait)678         public TotalCaptureResult getTotalCaptureResultForRequest(CaptureRequest myRequest,
679                 int numResultsWait) {
680             ArrayList<CaptureRequest> captureRequests = new ArrayList<>(1);
681             captureRequests.add(myRequest);
682             return getTotalCaptureResultsForRequests(captureRequests, numResultsWait)[0];
683         }
684 
685         /**
686          * Get an array of {@link #TotalCaptureResult total capture results} for a given list of
687          * {@link #CaptureRequest capture requests}. This can be used when the order of results
688          * may not the same as the order of requests.
689          *
690          * @param captureRequests The list of {@link #CaptureRequest capture requests} whose
691          *            corresponding {@link #TotalCaptureResult capture results} are
692          *            being waited for.
693          * @param numResultsWait Number of frames to wait for the capture results
694          *            before timeout.
695          * @throws TimeoutRuntimeException If more than numResultsWait results are
696          *            seen before all the results matching captureRequests arrives.
697          */
getTotalCaptureResultsForRequests( List<CaptureRequest> captureRequests, int numResultsWait)698         public TotalCaptureResult[] getTotalCaptureResultsForRequests(
699                 List<CaptureRequest> captureRequests, int numResultsWait) {
700             if (numResultsWait < 0) {
701                 throw new IllegalArgumentException("numResultsWait must be no less than 0");
702             }
703             if (captureRequests == null || captureRequests.size() == 0) {
704                 throw new IllegalArgumentException("captureRequests must have at least 1 request.");
705             }
706 
707             // Create a request -> a list of result indices map that it will wait for.
708             HashMap<CaptureRequest, ArrayList<Integer>> remainingResultIndicesMap = new HashMap<>();
709             for (int i = 0; i < captureRequests.size(); i++) {
710                 CaptureRequest request = captureRequests.get(i);
711                 ArrayList<Integer> indices = remainingResultIndicesMap.get(request);
712                 if (indices == null) {
713                     indices = new ArrayList<>();
714                     remainingResultIndicesMap.put(request, indices);
715                 }
716                 indices.add(i);
717             }
718 
719             TotalCaptureResult[] results = new TotalCaptureResult[captureRequests.size()];
720             int i = 0;
721             do {
722                 TotalCaptureResult result = getTotalCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
723                 CaptureRequest request = result.getRequest();
724                 ArrayList<Integer> indices = remainingResultIndicesMap.get(request);
725                 if (indices != null) {
726                     results[indices.get(0)] = result;
727                     indices.remove(0);
728 
729                     // Remove the entry if all results for this request has been fulfilled.
730                     if (indices.isEmpty()) {
731                         remainingResultIndicesMap.remove(request);
732                     }
733                 }
734 
735                 if (remainingResultIndicesMap.isEmpty()) {
736                     return results;
737                 }
738             } while (i++ < numResultsWait);
739 
740             throw new TimeoutRuntimeException("Unable to get the expected capture result after "
741                     + "waiting for " + numResultsWait + " results");
742         }
743 
744         /**
745          * Get an array list of {@link #CaptureFailure capture failure} with maxNumFailures entries
746          * at most. If it times out before maxNumFailures failures are received, return the failures
747          * received so far.
748          *
749          * @param maxNumFailures The maximal number of failures to return. If it times out before
750          *                       the maximal number of failures are received, return the received
751          *                       failures so far.
752          * @throws UnsupportedOperationException If an error happens while waiting on the failure.
753          */
getCaptureFailures(long maxNumFailures)754         public ArrayList<CaptureFailure> getCaptureFailures(long maxNumFailures) {
755             ArrayList<CaptureFailure> failures = new ArrayList<>();
756             try {
757                 for (int i = 0; i < maxNumFailures; i++) {
758                     CaptureFailure failure = mFailureQueue.poll(CAPTURE_RESULT_TIMEOUT_MS,
759                             TimeUnit.MILLISECONDS);
760                     if (failure == null) {
761                         // If waiting on a failure times out, return the failures so far.
762                         break;
763                     }
764                     failures.add(failure);
765                 }
766             }  catch (InterruptedException e) {
767                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
768             }
769 
770             return failures;
771         }
772 
773         /**
774          * Get an array list of aborted capture sequence ids with maxNumAborts entries
775          * at most. If it times out before maxNumAborts are received, return the aborted sequences
776          * received so far.
777          *
778          * @param maxNumAborts The maximal number of aborted sequences to return. If it times out
779          *                     before the maximal number of aborts are received, return the received
780          *                     failed sequences so far.
781          * @throws UnsupportedOperationException If an error happens while waiting on the failed
782          *                                       sequences.
783          */
geAbortedSequences(long maxNumAborts)784         public ArrayList<Integer> geAbortedSequences(long maxNumAborts) {
785             ArrayList<Integer> abortList = new ArrayList<>();
786             try {
787                 for (int i = 0; i < maxNumAborts; i++) {
788                     Integer abortSequence = mAbortQueue.poll(CAPTURE_RESULT_TIMEOUT_MS,
789                             TimeUnit.MILLISECONDS);
790                     if (abortSequence == null) {
791                         break;
792                     }
793                     abortList.add(abortSequence);
794                 }
795             }  catch (InterruptedException e) {
796                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
797             }
798 
799             return abortList;
800         }
801 
802         /**
803          * Wait until the capture start of a request and expected timestamp arrives or it times
804          * out after a number of capture starts.
805          *
806          * @param request The request for the capture start to wait for.
807          * @param timestamp The timestamp for the capture start to wait for.
808          * @param numCaptureStartsWait The number of capture start events to wait for before timing
809          *                             out.
810          */
waitForCaptureStart(CaptureRequest request, Long timestamp, int numCaptureStartsWait)811         public void waitForCaptureStart(CaptureRequest request, Long timestamp,
812                 int numCaptureStartsWait) throws Exception {
813             Pair<CaptureRequest, Long> expectedShutter = new Pair<>(request, timestamp);
814 
815             int i = 0;
816             do {
817                 Pair<CaptureRequest, Long> shutter = mCaptureStartQueue.poll(
818                         CAPTURE_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
819 
820                 if (shutter == null) {
821                     throw new TimeoutRuntimeException("Unable to get any more capture start " +
822                             "event after waiting for " + CAPTURE_RESULT_TIMEOUT_MS + " ms.");
823                 } else if (expectedShutter.equals(shutter)) {
824                     return;
825                 }
826 
827             } while (i++ < numCaptureStartsWait);
828 
829             throw new TimeoutRuntimeException("Unable to get the expected capture start " +
830                     "event after waiting for " + numCaptureStartsWait + " capture starts");
831         }
832 
833         /**
834          * Wait until it receives capture sequence completed callback for a given squence ID.
835          *
836          * @param sequenceId The sequence ID of the capture sequence completed callback to wait for.
837          * @param timeoutMs Time to wait for each capture sequence complete callback before
838          *                  timing out.
839          */
getCaptureSequenceLastFrameNumber(int sequenceId, long timeoutMs)840         public long getCaptureSequenceLastFrameNumber(int sequenceId, long timeoutMs) {
841             try {
842                 while (true) {
843                     Pair<Integer, Long> completedSequence =
844                             mCaptureSequenceCompletedQueue.poll(timeoutMs, TimeUnit.MILLISECONDS);
845                     assertNotNull("Wait for a capture sequence completed timed out in " +
846                             timeoutMs + "ms", completedSequence);
847 
848                     if (completedSequence.first.equals(sequenceId)) {
849                         return completedSequence.second.longValue();
850                     }
851                 }
852             } catch (InterruptedException e) {
853                 throw new UnsupportedOperationException("Unhandled interrupted exception", e);
854             }
855         }
856 
hasMoreResults()857         public boolean hasMoreResults()
858         {
859             return !mQueue.isEmpty();
860         }
861 
hasMoreFailures()862         public boolean hasMoreFailures()
863         {
864             return !mFailureQueue.isEmpty();
865         }
866 
hasMoreAbortedSequences()867         public boolean hasMoreAbortedSequences()
868         {
869             return !mAbortQueue.isEmpty();
870         }
871 
drain()872         public void drain() {
873             mQueue.clear();
874             mNumFramesArrived.getAndSet(0);
875             mFailureQueue.clear();
876             mCaptureStartQueue.clear();
877             mAbortQueue.clear();
878         }
879     }
880 
hasCapability(CameraCharacteristics characteristics, int capability)881     public static boolean hasCapability(CameraCharacteristics characteristics, int capability) {
882         int [] capabilities =
883                 characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
884         for (int c : capabilities) {
885             if (c == capability) {
886                 return true;
887             }
888         }
889         return false;
890     }
891 
isSystemCamera(CameraManager manager, String cameraId)892     public static boolean isSystemCamera(CameraManager manager, String cameraId)
893             throws CameraAccessException {
894         CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
895         return hasCapability(characteristics,
896                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA);
897     }
898 
getCameraIdListForTesting(CameraManager manager, boolean getSystemCameras)899     public static String[] getCameraIdListForTesting(CameraManager manager,
900             boolean getSystemCameras)
901             throws CameraAccessException {
902         String [] ids = manager.getCameraIdListNoLazy();
903         List<String> idsForTesting = new ArrayList<String>();
904         for (String id : ids) {
905             boolean isSystemCamera = isSystemCamera(manager, id);
906             if (getSystemCameras == isSystemCamera) {
907                 idsForTesting.add(id);
908             }
909         }
910         return idsForTesting.toArray(new String[idsForTesting.size()]);
911     }
912 
getConcurrentCameraIds(CameraManager manager, boolean getSystemCameras)913     public static Set<Set<String>> getConcurrentCameraIds(CameraManager manager,
914             boolean getSystemCameras)
915             throws CameraAccessException {
916         Set<String> cameraIds = new HashSet<String>(Arrays.asList(getCameraIdListForTesting(manager, getSystemCameras)));
917         Set<Set<String>> combinations =  manager.getConcurrentCameraIds();
918         Set<Set<String>> correctComb = new HashSet<Set<String>>();
919         for (Set<String> comb : combinations) {
920             Set<String> filteredIds = new HashSet<String>();
921             for (String id : comb) {
922                 if (cameraIds.contains(id)) {
923                     filteredIds.add(id);
924                 }
925             }
926             if (filteredIds.isEmpty()) {
927                 continue;
928             }
929             correctComb.add(filteredIds);
930         }
931         return correctComb;
932     }
933 
934     /**
935      * Block until the camera is opened.
936      *
937      * <p>Don't use this to test #onDisconnected/#onError since this will throw
938      * an AssertionError if it fails to open the camera device.</p>
939      *
940      * @return CameraDevice opened camera device
941      *
942      * @throws IllegalArgumentException
943      *            If the handler is null, or if the handler's looper is current.
944      * @throws CameraAccessException
945      *            If open fails immediately.
946      * @throws BlockingOpenException
947      *            If open fails after blocking for some amount of time.
948      * @throws TimeoutRuntimeException
949      *            If opening times out. Typically unrecoverable.
950      */
openCamera(CameraManager manager, String cameraId, CameraDevice.StateCallback listener, Handler handler)951     public static CameraDevice openCamera(CameraManager manager, String cameraId,
952             CameraDevice.StateCallback listener, Handler handler) throws CameraAccessException,
953             BlockingOpenException {
954 
955         /**
956          * Although camera2 API allows 'null' Handler (it will just use the current
957          * thread's Looper), this is not what we want for CTS.
958          *
959          * In CTS the default looper is used only to process events in between test runs,
960          * so anything sent there would not be executed inside a test and the test would fail.
961          *
962          * In this case, BlockingCameraManager#openCamera performs the check for us.
963          */
964         return (new BlockingCameraManager(manager)).openCamera(cameraId, listener, handler);
965     }
966 
967 
968     /**
969      * Block until the camera is opened.
970      *
971      * <p>Don't use this to test #onDisconnected/#onError since this will throw
972      * an AssertionError if it fails to open the camera device.</p>
973      *
974      * @throws IllegalArgumentException
975      *            If the handler is null, or if the handler's looper is current.
976      * @throws CameraAccessException
977      *            If open fails immediately.
978      * @throws BlockingOpenException
979      *            If open fails after blocking for some amount of time.
980      * @throws TimeoutRuntimeException
981      *            If opening times out. Typically unrecoverable.
982      */
openCamera(CameraManager manager, String cameraId, Handler handler)983     public static CameraDevice openCamera(CameraManager manager, String cameraId, Handler handler)
984             throws CameraAccessException,
985             BlockingOpenException {
986         return openCamera(manager, cameraId, /*listener*/null, handler);
987     }
988 
989     /**
990      * Configure a new camera session with output surfaces and type.
991      *
992      * @param camera The CameraDevice to be configured.
993      * @param outputSurfaces The surface list that used for camera output.
994      * @param listener The callback CameraDevice will notify when capture results are available.
995      */
configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, boolean isHighSpeed, CameraCaptureSession.StateCallback listener, Handler handler)996     public static CameraCaptureSession configureCameraSession(CameraDevice camera,
997             List<Surface> outputSurfaces, boolean isHighSpeed,
998             CameraCaptureSession.StateCallback listener, Handler handler)
999             throws CameraAccessException {
1000         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
1001         if (isHighSpeed) {
1002             camera.createConstrainedHighSpeedCaptureSession(outputSurfaces,
1003                     sessionListener, handler);
1004         } else {
1005             camera.createCaptureSession(outputSurfaces, sessionListener, handler);
1006         }
1007         CameraCaptureSession session =
1008                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
1009         assertFalse("Camera session should not be a reprocessable session",
1010                 session.isReprocessable());
1011         String sessionType = isHighSpeed ? "High Speed" : "Normal";
1012         assertTrue("Capture session type must be " + sessionType,
1013                 isHighSpeed ==
1014                 CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(session.getClass()));
1015 
1016         return session;
1017     }
1018 
1019     /**
1020      * Build a new constrained camera session with output surfaces, type and recording session
1021      * parameters.
1022      *
1023      * @param camera The CameraDevice to be configured.
1024      * @param outputSurfaces The surface list that used for camera output.
1025      * @param listener The callback CameraDevice will notify when capture results are available.
1026      * @param initialRequest Initial request settings to use as session parameters.
1027      */
buildConstrainedCameraSession(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler, CaptureRequest initialRequest)1028     public static CameraCaptureSession buildConstrainedCameraSession(CameraDevice camera,
1029             List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener,
1030             Handler handler, CaptureRequest initialRequest) throws CameraAccessException {
1031         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
1032 
1033         List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
1034         for (Surface surface : outputSurfaces) {
1035             outConfigurations.add(new OutputConfiguration(surface));
1036         }
1037         SessionConfiguration sessionConfig = new SessionConfiguration(
1038                 SessionConfiguration.SESSION_HIGH_SPEED, outConfigurations,
1039                 new HandlerExecutor(handler), sessionListener);
1040         sessionConfig.setSessionParameters(initialRequest);
1041         camera.createCaptureSession(sessionConfig);
1042 
1043         CameraCaptureSession session =
1044                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
1045         assertFalse("Camera session should not be a reprocessable session",
1046                 session.isReprocessable());
1047         assertTrue("Capture session type must be High Speed",
1048                 CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(
1049                         session.getClass()));
1050 
1051         return session;
1052     }
1053 
1054     /**
1055      * Configure a new camera session with output configurations.
1056      *
1057      * @param camera The CameraDevice to be configured.
1058      * @param outputs The OutputConfiguration list that is used for camera output.
1059      * @param listener The callback CameraDevice will notify when capture results are available.
1060      */
configureCameraSessionWithConfig(CameraDevice camera, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)1061     public static CameraCaptureSession configureCameraSessionWithConfig(CameraDevice camera,
1062             List<OutputConfiguration> outputs,
1063             CameraCaptureSession.StateCallback listener, Handler handler)
1064             throws CameraAccessException {
1065         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
1066         camera.createCaptureSessionByOutputConfigurations(outputs, sessionListener, handler);
1067         CameraCaptureSession session =
1068                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
1069         assertFalse("Camera session should not be a reprocessable session",
1070                 session.isReprocessable());
1071         return session;
1072     }
1073 
1074     /**
1075      * Try configure a new camera session with output configurations.
1076      *
1077      * @param camera The CameraDevice to be configured.
1078      * @param outputs The OutputConfiguration list that is used for camera output.
1079      * @param listener The callback CameraDevice will notify when capture results are available.
1080      */
tryConfigureCameraSessionWithConfig(CameraDevice camera, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)1081     public static CameraCaptureSession tryConfigureCameraSessionWithConfig(CameraDevice camera,
1082             List<OutputConfiguration> outputs,
1083             CameraCaptureSession.StateCallback listener, Handler handler)
1084             throws CameraAccessException {
1085         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
1086         camera.createCaptureSessionByOutputConfigurations(outputs, sessionListener, handler);
1087 
1088         Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
1089                                    BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
1090         int state = sessionListener.getStateWaiter().waitForAnyOfStates(
1091                 Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
1092 
1093         CameraCaptureSession session = null;
1094         if (state == BlockingSessionCallback.SESSION_READY) {
1095             session = sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
1096             assertFalse("Camera session should not be a reprocessable session",
1097                     session.isReprocessable());
1098         }
1099         return session;
1100     }
1101 
1102     /**
1103      * Configure a new camera session with output surfaces and initial session parameters.
1104      *
1105      * @param camera The CameraDevice to be configured.
1106      * @param outputSurfaces The surface list that used for camera output.
1107      * @param listener The callback CameraDevice will notify when session is available.
1108      * @param handler The handler used to notify callbacks.
1109      * @param initialRequest Initial request settings to use as session parameters.
1110      */
configureCameraSessionWithParameters(CameraDevice camera, List<Surface> outputSurfaces, BlockingSessionCallback listener, Handler handler, CaptureRequest initialRequest)1111     public static CameraCaptureSession configureCameraSessionWithParameters(CameraDevice camera,
1112             List<Surface> outputSurfaces, BlockingSessionCallback listener,
1113             Handler handler, CaptureRequest initialRequest) throws CameraAccessException {
1114         List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
1115         for (Surface surface : outputSurfaces) {
1116             outConfigurations.add(new OutputConfiguration(surface));
1117         }
1118         SessionConfiguration sessionConfig = new SessionConfiguration(
1119                 SessionConfiguration.SESSION_REGULAR, outConfigurations,
1120                 new HandlerExecutor(handler), listener);
1121         sessionConfig.setSessionParameters(initialRequest);
1122         camera.createCaptureSession(sessionConfig);
1123 
1124         CameraCaptureSession session = listener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
1125         assertFalse("Camera session should not be a reprocessable session",
1126                 session.isReprocessable());
1127         assertFalse("Capture session type must be regular",
1128                 CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(
1129                         session.getClass()));
1130 
1131         return session;
1132     }
1133 
1134     /**
1135      * Configure a new camera session with output surfaces.
1136      *
1137      * @param camera The CameraDevice to be configured.
1138      * @param outputSurfaces The surface list that used for camera output.
1139      * @param listener The callback CameraDevice will notify when capture results are available.
1140      */
configureCameraSession(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)1141     public static CameraCaptureSession configureCameraSession(CameraDevice camera,
1142             List<Surface> outputSurfaces,
1143             CameraCaptureSession.StateCallback listener, Handler handler)
1144             throws CameraAccessException {
1145 
1146         return configureCameraSession(camera, outputSurfaces, /*isHighSpeed*/false,
1147                 listener, handler);
1148     }
1149 
configureReprocessableCameraSession(CameraDevice camera, InputConfiguration inputConfiguration, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)1150     public static CameraCaptureSession configureReprocessableCameraSession(CameraDevice camera,
1151             InputConfiguration inputConfiguration, List<Surface> outputSurfaces,
1152             CameraCaptureSession.StateCallback listener, Handler handler)
1153             throws CameraAccessException {
1154         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
1155         camera.createReprocessableCaptureSession(inputConfiguration, outputSurfaces,
1156                 sessionListener, handler);
1157 
1158         Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
1159                                    BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
1160         int state = sessionListener.getStateWaiter().waitForAnyOfStates(
1161                 Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
1162 
1163         assertTrue("Creating a reprocessable session failed.",
1164                 state == BlockingSessionCallback.SESSION_READY);
1165 
1166         CameraCaptureSession session =
1167                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
1168         assertTrue("Camera session should be a reprocessable session", session.isReprocessable());
1169 
1170         return session;
1171     }
1172 
1173     /**
1174      * Create a reprocessable camera session with input and output configurations.
1175      *
1176      * @param camera The CameraDevice to be configured.
1177      * @param inputConfiguration The input configuration used to create this session.
1178      * @param outputs The output configurations used to create this session.
1179      * @param listener The callback CameraDevice will notify when capture results are available.
1180      * @param handler The handler used to notify callbacks.
1181      * @return The session ready to use.
1182      * @throws CameraAccessException
1183      */
configureReprocCameraSessionWithConfig(CameraDevice camera, InputConfiguration inputConfiguration, List<OutputConfiguration> outputs, CameraCaptureSession.StateCallback listener, Handler handler)1184     public static CameraCaptureSession configureReprocCameraSessionWithConfig(CameraDevice camera,
1185             InputConfiguration inputConfiguration, List<OutputConfiguration> outputs,
1186             CameraCaptureSession.StateCallback listener, Handler handler)
1187             throws CameraAccessException {
1188         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
1189         camera.createReprocessableCaptureSessionByConfigurations(inputConfiguration, outputs,
1190                 sessionListener, handler);
1191 
1192         Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
1193                                    BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
1194         int state = sessionListener.getStateWaiter().waitForAnyOfStates(
1195                 Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
1196 
1197         assertTrue("Creating a reprocessable session failed.",
1198                 state == BlockingSessionCallback.SESSION_READY);
1199 
1200         CameraCaptureSession session =
1201                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
1202         assertTrue("Camera session should be a reprocessable session", session.isReprocessable());
1203 
1204         return session;
1205     }
1206 
assertArrayNotEmpty(T arr, String message)1207     public static <T> void assertArrayNotEmpty(T arr, String message) {
1208         assertTrue(message, arr != null && Array.getLength(arr) > 0);
1209     }
1210 
1211     /**
1212      * Check if the format is a legal YUV format camera supported.
1213      */
checkYuvFormat(int format)1214     public static void checkYuvFormat(int format) {
1215         if ((format != ImageFormat.YUV_420_888) &&
1216                 (format != ImageFormat.NV21) &&
1217                 (format != ImageFormat.YV12)) {
1218             fail("Wrong formats: " + format);
1219         }
1220     }
1221 
1222     /**
1223      * Check if image size and format match given size and format.
1224      */
checkImage(Image image, int width, int height, int format)1225     public static void checkImage(Image image, int width, int height, int format) {
1226         // Image reader will wrap YV12/NV21 image by YUV_420_888
1227         if (format == ImageFormat.NV21 || format == ImageFormat.YV12) {
1228             format = ImageFormat.YUV_420_888;
1229         }
1230         assertNotNull("Input image is invalid", image);
1231         assertEquals("Format doesn't match", format, image.getFormat());
1232         assertEquals("Width doesn't match", width, image.getWidth());
1233         assertEquals("Height doesn't match", height, image.getHeight());
1234     }
1235 
1236     /**
1237      * <p>Read data from all planes of an Image into a contiguous unpadded, unpacked
1238      * 1-D linear byte array, such that it can be write into disk, or accessed by
1239      * software conveniently. It supports YUV_420_888/NV21/YV12 and JPEG input
1240      * Image format.</p>
1241      *
1242      * <p>For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
1243      * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
1244      * (xstride = width, ystride = height for chroma and luma components).</p>
1245      *
1246      * <p>For JPEG, it returns a 1-D byte array contains a complete JPEG image.</p>
1247      */
getDataFromImage(Image image)1248     public static byte[] getDataFromImage(Image image) {
1249         assertNotNull("Invalid image:", image);
1250         int format = image.getFormat();
1251         int width = image.getWidth();
1252         int height = image.getHeight();
1253         int rowStride, pixelStride;
1254         byte[] data = null;
1255 
1256         // Read image data
1257         Plane[] planes = image.getPlanes();
1258         assertTrue("Fail to get image planes", planes != null && planes.length > 0);
1259 
1260         // Check image validity
1261         checkAndroidImageFormat(image);
1262 
1263         ByteBuffer buffer = null;
1264         // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
1265         // Same goes for DEPTH_POINT_CLOUD, RAW_PRIVATE, DEPTH_JPEG, and HEIC
1266         if (format == ImageFormat.JPEG || format == ImageFormat.DEPTH_POINT_CLOUD ||
1267                 format == ImageFormat.RAW_PRIVATE || format == ImageFormat.DEPTH_JPEG ||
1268                 format == ImageFormat.HEIC) {
1269             buffer = planes[0].getBuffer();
1270             assertNotNull("Fail to get jpeg/depth/heic ByteBuffer", buffer);
1271             data = new byte[buffer.remaining()];
1272             buffer.get(data);
1273             buffer.rewind();
1274             return data;
1275         }
1276 
1277         int offset = 0;
1278         data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
1279         int maxRowSize = planes[0].getRowStride();
1280         for (int i = 0; i < planes.length; i++) {
1281             if (maxRowSize < planes[i].getRowStride()) {
1282                 maxRowSize = planes[i].getRowStride();
1283             }
1284         }
1285         byte[] rowData = new byte[maxRowSize];
1286         if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
1287         for (int i = 0; i < planes.length; i++) {
1288             buffer = planes[i].getBuffer();
1289             assertNotNull("Fail to get bytebuffer from plane", buffer);
1290             rowStride = planes[i].getRowStride();
1291             pixelStride = planes[i].getPixelStride();
1292             assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
1293             if (VERBOSE) {
1294                 Log.v(TAG, "pixelStride " + pixelStride);
1295                 Log.v(TAG, "rowStride " + rowStride);
1296                 Log.v(TAG, "width " + width);
1297                 Log.v(TAG, "height " + height);
1298             }
1299             // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
1300             int w = (i == 0) ? width : width / 2;
1301             int h = (i == 0) ? height : height / 2;
1302             assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
1303             for (int row = 0; row < h; row++) {
1304                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
1305                 int length;
1306                 if (pixelStride == bytesPerPixel) {
1307                     // Special case: optimized read of the entire row
1308                     length = w * bytesPerPixel;
1309                     buffer.get(data, offset, length);
1310                     offset += length;
1311                 } else {
1312                     // Generic case: should work for any pixelStride but slower.
1313                     // Use intermediate buffer to avoid read byte-by-byte from
1314                     // DirectByteBuffer, which is very bad for performance
1315                     length = (w - 1) * pixelStride + bytesPerPixel;
1316                     buffer.get(rowData, 0, length);
1317                     for (int col = 0; col < w; col++) {
1318                         data[offset++] = rowData[col * pixelStride];
1319                     }
1320                 }
1321                 // Advance buffer the remainder of the row stride
1322                 if (row < h - 1) {
1323                     buffer.position(buffer.position() + rowStride - length);
1324                 }
1325             }
1326             if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
1327             buffer.rewind();
1328         }
1329         return data;
1330     }
1331 
1332     /**
1333      * <p>Check android image format validity for an image, only support below formats:</p>
1334      *
1335      * <p>YUV_420_888/NV21/YV12, can add more for future</p>
1336      */
checkAndroidImageFormat(Image image)1337     public static void checkAndroidImageFormat(Image image) {
1338         int format = image.getFormat();
1339         Plane[] planes = image.getPlanes();
1340         switch (format) {
1341             case ImageFormat.YUV_420_888:
1342             case ImageFormat.NV21:
1343             case ImageFormat.YV12:
1344                 assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
1345                 break;
1346             case ImageFormat.JPEG:
1347             case ImageFormat.RAW_SENSOR:
1348             case ImageFormat.RAW_PRIVATE:
1349             case ImageFormat.DEPTH16:
1350             case ImageFormat.DEPTH_POINT_CLOUD:
1351             case ImageFormat.DEPTH_JPEG:
1352             case ImageFormat.Y8:
1353             case ImageFormat.HEIC:
1354                 assertEquals("JPEG/RAW/depth/Y8 Images should have one plane", 1, planes.length);
1355                 break;
1356             default:
1357                 fail("Unsupported Image Format: " + format);
1358         }
1359     }
1360 
dumpFile(String fileName, Bitmap data)1361     public static void dumpFile(String fileName, Bitmap data) {
1362         FileOutputStream outStream;
1363         try {
1364             Log.v(TAG, "output will be saved as " + fileName);
1365             outStream = new FileOutputStream(fileName);
1366         } catch (IOException ioe) {
1367             throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
1368         }
1369 
1370         try {
1371             data.compress(Bitmap.CompressFormat.JPEG, /*quality*/90, outStream);
1372             outStream.close();
1373         } catch (IOException ioe) {
1374             throw new RuntimeException("failed writing data to file " + fileName, ioe);
1375         }
1376     }
1377 
dumpFile(String fileName, byte[] data)1378     public static void dumpFile(String fileName, byte[] data) {
1379         FileOutputStream outStream;
1380         try {
1381             Log.v(TAG, "output will be saved as " + fileName);
1382             outStream = new FileOutputStream(fileName);
1383         } catch (IOException ioe) {
1384             throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
1385         }
1386 
1387         try {
1388             outStream.write(data);
1389             outStream.close();
1390         } catch (IOException ioe) {
1391             throw new RuntimeException("failed writing data to file " + fileName, ioe);
1392         }
1393     }
1394 
1395     /**
1396      * Get the available output sizes for the user-defined {@code format}.
1397      *
1398      * <p>Note that implementation-defined/hidden formats are not supported.</p>
1399      */
getSupportedSizeForFormat(int format, String cameraId, CameraManager cameraManager)1400     public static Size[] getSupportedSizeForFormat(int format, String cameraId,
1401             CameraManager cameraManager) throws CameraAccessException {
1402         CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
1403         assertNotNull("Can't get camera characteristics!", properties);
1404         if (VERBOSE) {
1405             Log.v(TAG, "get camera characteristics for camera: " + cameraId);
1406         }
1407         StreamConfigurationMap configMap =
1408                 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1409         Size[] availableSizes = configMap.getOutputSizes(format);
1410         assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for format: "
1411                 + format);
1412         Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(format);
1413         if (highResAvailableSizes != null && highResAvailableSizes.length > 0) {
1414             Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length];
1415             System.arraycopy(availableSizes, 0, allSizes, 0,
1416                     availableSizes.length);
1417             System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length,
1418                     highResAvailableSizes.length);
1419             availableSizes = allSizes;
1420         }
1421         if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
1422         return availableSizes;
1423     }
1424 
1425     /**
1426      * Get the available output sizes for the given class.
1427      *
1428      */
getSupportedSizeForClass(Class klass, String cameraId, CameraManager cameraManager)1429     public static Size[] getSupportedSizeForClass(Class klass, String cameraId,
1430             CameraManager cameraManager) throws CameraAccessException {
1431         CameraCharacteristics properties = cameraManager.getCameraCharacteristics(cameraId);
1432         assertNotNull("Can't get camera characteristics!", properties);
1433         if (VERBOSE) {
1434             Log.v(TAG, "get camera characteristics for camera: " + cameraId);
1435         }
1436         StreamConfigurationMap configMap =
1437                 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1438         Size[] availableSizes = configMap.getOutputSizes(klass);
1439         assertArrayNotEmpty(availableSizes, "availableSizes should not be empty for class: "
1440                 + klass);
1441         Size[] highResAvailableSizes = configMap.getHighResolutionOutputSizes(ImageFormat.PRIVATE);
1442         if (highResAvailableSizes != null && highResAvailableSizes.length > 0) {
1443             Size[] allSizes = new Size[availableSizes.length + highResAvailableSizes.length];
1444             System.arraycopy(availableSizes, 0, allSizes, 0,
1445                     availableSizes.length);
1446             System.arraycopy(highResAvailableSizes, 0, allSizes, availableSizes.length,
1447                     highResAvailableSizes.length);
1448             availableSizes = allSizes;
1449         }
1450         if (VERBOSE) Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(availableSizes));
1451         return availableSizes;
1452     }
1453 
1454     /**
1455      * Size comparator that compares the number of pixels it covers.
1456      *
1457      * <p>If two the areas of two sizes are same, compare the widths.</p>
1458      */
1459     public static class SizeComparator implements Comparator<Size> {
1460         @Override
compare(Size lhs, Size rhs)1461         public int compare(Size lhs, Size rhs) {
1462             return CameraUtils
1463                     .compareSizes(lhs.getWidth(), lhs.getHeight(), rhs.getWidth(), rhs.getHeight());
1464         }
1465     }
1466 
1467     /**
1468      * Get sorted size list in descending order. Remove the sizes larger than
1469      * the bound. If the bound is null, don't do the size bound filtering.
1470      */
getSupportedPreviewSizes(String cameraId, CameraManager cameraManager, Size bound)1471     static public List<Size> getSupportedPreviewSizes(String cameraId,
1472             CameraManager cameraManager, Size bound) throws CameraAccessException {
1473 
1474         Size[] rawSizes = getSupportedSizeForClass(android.view.SurfaceHolder.class, cameraId,
1475                 cameraManager);
1476         assertArrayNotEmpty(rawSizes,
1477                 "Available sizes for SurfaceHolder class should not be empty");
1478         if (VERBOSE) {
1479             Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes));
1480         }
1481 
1482         if (bound == null) {
1483             return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false);
1484         }
1485 
1486         List<Size> sizes = new ArrayList<Size>();
1487         for (Size sz: rawSizes) {
1488             if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) {
1489                 sizes.add(sz);
1490             }
1491         }
1492         return getAscendingOrderSizes(sizes, /*ascending*/false);
1493     }
1494 
1495     /**
1496      * Get a sorted list of sizes from a given size list.
1497      *
1498      * <p>
1499      * The size is compare by area it covers, if the areas are same, then
1500      * compare the widths.
1501      * </p>
1502      *
1503      * @param sizeList The input size list to be sorted
1504      * @param ascending True if the order is ascending, otherwise descending order
1505      * @return The ordered list of sizes
1506      */
getAscendingOrderSizes(final List<Size> sizeList, boolean ascending)1507     static public List<Size> getAscendingOrderSizes(final List<Size> sizeList, boolean ascending) {
1508         if (sizeList == null) {
1509             throw new IllegalArgumentException("sizeList shouldn't be null");
1510         }
1511 
1512         Comparator<Size> comparator = new SizeComparator();
1513         List<Size> sortedSizes = new ArrayList<Size>();
1514         sortedSizes.addAll(sizeList);
1515         Collections.sort(sortedSizes, comparator);
1516         if (!ascending) {
1517             Collections.reverse(sortedSizes);
1518         }
1519 
1520         return sortedSizes;
1521     }
1522 
1523     /**
1524      * Get sorted (descending order) size list for given format. Remove the sizes larger than
1525      * the bound. If the bound is null, don't do the size bound filtering.
1526      */
getSortedSizesForFormat(String cameraId, CameraManager cameraManager, int format, Size bound)1527     static public List<Size> getSortedSizesForFormat(String cameraId,
1528             CameraManager cameraManager, int format, Size bound) throws CameraAccessException {
1529         Comparator<Size> comparator = new SizeComparator();
1530         Size[] sizes = getSupportedSizeForFormat(format, cameraId, cameraManager);
1531         List<Size> sortedSizes = null;
1532         if (bound != null) {
1533             sortedSizes = new ArrayList<Size>(/*capacity*/1);
1534             for (Size sz : sizes) {
1535                 if (comparator.compare(sz, bound) <= 0) {
1536                     sortedSizes.add(sz);
1537                 }
1538             }
1539         } else {
1540             sortedSizes = Arrays.asList(sizes);
1541         }
1542         assertTrue("Supported size list should have at least one element",
1543                 sortedSizes.size() > 0);
1544 
1545         Collections.sort(sortedSizes, comparator);
1546         // Make it in descending order.
1547         Collections.reverse(sortedSizes);
1548         return sortedSizes;
1549     }
1550 
1551     /**
1552      * Get supported video size list for a given camera device.
1553      *
1554      * <p>
1555      * Filter out the sizes that are larger than the bound. If the bound is
1556      * null, don't do the size bound filtering.
1557      * </p>
1558      */
getSupportedVideoSizes(String cameraId, CameraManager cameraManager, Size bound)1559     static public List<Size> getSupportedVideoSizes(String cameraId,
1560             CameraManager cameraManager, Size bound) throws CameraAccessException {
1561 
1562         Size[] rawSizes = getSupportedSizeForClass(android.media.MediaRecorder.class,
1563                 cameraId, cameraManager);
1564         assertArrayNotEmpty(rawSizes,
1565                 "Available sizes for MediaRecorder class should not be empty");
1566         if (VERBOSE) {
1567             Log.v(TAG, "Supported sizes are: " + Arrays.deepToString(rawSizes));
1568         }
1569 
1570         if (bound == null) {
1571             return getAscendingOrderSizes(Arrays.asList(rawSizes), /*ascending*/false);
1572         }
1573 
1574         List<Size> sizes = new ArrayList<Size>();
1575         for (Size sz: rawSizes) {
1576             if (sz.getWidth() <= bound.getWidth() && sz.getHeight() <= bound.getHeight()) {
1577                 sizes.add(sz);
1578             }
1579         }
1580         return getAscendingOrderSizes(sizes, /*ascending*/false);
1581     }
1582 
1583     /**
1584      * Get supported video size list (descending order) for a given camera device.
1585      *
1586      * <p>
1587      * Filter out the sizes that are larger than the bound. If the bound is
1588      * null, don't do the size bound filtering.
1589      * </p>
1590      */
getSupportedStillSizes(String cameraId, CameraManager cameraManager, Size bound)1591     static public List<Size> getSupportedStillSizes(String cameraId,
1592             CameraManager cameraManager, Size bound) throws CameraAccessException {
1593         return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.JPEG, bound);
1594     }
1595 
getSupportedHeicSizes(String cameraId, CameraManager cameraManager, Size bound)1596     static public List<Size> getSupportedHeicSizes(String cameraId,
1597             CameraManager cameraManager, Size bound) throws CameraAccessException {
1598         return getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.HEIC, bound);
1599     }
1600 
getMinPreviewSize(String cameraId, CameraManager cameraManager)1601     static public Size getMinPreviewSize(String cameraId, CameraManager cameraManager)
1602             throws CameraAccessException {
1603         List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, null);
1604         return sizes.get(sizes.size() - 1);
1605     }
1606 
1607     /**
1608      * Get max supported preview size for a camera device.
1609      */
getMaxPreviewSize(String cameraId, CameraManager cameraManager)1610     static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager)
1611             throws CameraAccessException {
1612         return getMaxPreviewSize(cameraId, cameraManager, /*bound*/null);
1613     }
1614 
1615     /**
1616      * Get max preview size for a camera device in the supported sizes that are no larger
1617      * than the bound.
1618      */
getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)1619     static public Size getMaxPreviewSize(String cameraId, CameraManager cameraManager, Size bound)
1620             throws CameraAccessException {
1621         List<Size> sizes = getSupportedPreviewSizes(cameraId, cameraManager, bound);
1622         return sizes.get(0);
1623     }
1624 
1625     /**
1626      * Get max depth size for a camera device.
1627      */
getMaxDepthSize(String cameraId, CameraManager cameraManager)1628     static public Size getMaxDepthSize(String cameraId, CameraManager cameraManager)
1629             throws CameraAccessException {
1630         List<Size> sizes = getSortedSizesForFormat(cameraId, cameraManager, ImageFormat.DEPTH16,
1631                 /*bound*/ null);
1632         return sizes.get(0);
1633     }
1634 
1635     /**
1636      * Get the largest size by area.
1637      *
1638      * @param sizes an array of sizes, must have at least 1 element
1639      *
1640      * @return Largest Size
1641      *
1642      * @throws IllegalArgumentException if sizes was null or had 0 elements
1643      */
getMaxSize(Size... sizes)1644     public static Size getMaxSize(Size... sizes) {
1645         if (sizes == null || sizes.length == 0) {
1646             throw new IllegalArgumentException("sizes was empty");
1647         }
1648 
1649         Size sz = sizes[0];
1650         for (Size size : sizes) {
1651             if (size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
1652                 sz = size;
1653             }
1654         }
1655 
1656         return sz;
1657     }
1658 
1659     /**
1660      * Get the largest size by area within (less than) bound
1661      *
1662      * @param sizes an array of sizes, must have at least 1 element
1663      *
1664      * @return Largest Size. Null if no such size exists within bound.
1665      *
1666      * @throws IllegalArgumentException if sizes was null or had 0 elements, or bound is invalid.
1667      */
getMaxSizeWithBound(Size[] sizes, int bound)1668     public static Size getMaxSizeWithBound(Size[] sizes, int bound) {
1669         if (sizes == null || sizes.length == 0) {
1670             throw new IllegalArgumentException("sizes was empty");
1671         }
1672         if (bound <= 0) {
1673             throw new IllegalArgumentException("bound is invalid");
1674         }
1675 
1676         Size sz = null;
1677         for (Size size : sizes) {
1678             if (size.getWidth() * size.getHeight() >= bound) {
1679                 continue;
1680             }
1681 
1682             if (sz == null ||
1683                     size.getWidth() * size.getHeight() > sz.getWidth() * sz.getHeight()) {
1684                 sz = size;
1685             }
1686         }
1687 
1688         return sz;
1689     }
1690 
1691     /**
1692      * Returns true if the given {@code array} contains the given element.
1693      *
1694      * @param array {@code array} to check for {@code elem}
1695      * @param elem {@code elem} to test for
1696      * @return {@code true} if the given element is contained
1697      */
contains(int[] array, int elem)1698     public static boolean contains(int[] array, int elem) {
1699         if (array == null) return false;
1700         for (int i = 0; i < array.length; i++) {
1701             if (elem == array[i]) return true;
1702         }
1703         return false;
1704     }
1705 
1706     /**
1707      * Get object array from byte array.
1708      *
1709      * @param array Input byte array to be converted
1710      * @return Byte object array converted from input byte array
1711      */
toObject(byte[] array)1712     public static Byte[] toObject(byte[] array) {
1713         return convertPrimitiveArrayToObjectArray(array, Byte.class);
1714     }
1715 
1716     /**
1717      * Get object array from int array.
1718      *
1719      * @param array Input int array to be converted
1720      * @return Integer object array converted from input int array
1721      */
toObject(int[] array)1722     public static Integer[] toObject(int[] array) {
1723         return convertPrimitiveArrayToObjectArray(array, Integer.class);
1724     }
1725 
1726     /**
1727      * Get object array from float array.
1728      *
1729      * @param array Input float array to be converted
1730      * @return Float object array converted from input float array
1731      */
toObject(float[] array)1732     public static Float[] toObject(float[] array) {
1733         return convertPrimitiveArrayToObjectArray(array, Float.class);
1734     }
1735 
1736     /**
1737      * Get object array from double array.
1738      *
1739      * @param array Input double array to be converted
1740      * @return Double object array converted from input double array
1741      */
toObject(double[] array)1742     public static Double[] toObject(double[] array) {
1743         return convertPrimitiveArrayToObjectArray(array, Double.class);
1744     }
1745 
1746     /**
1747      * Convert a primitive input array into its object array version (e.g. from int[] to Integer[]).
1748      *
1749      * @param array Input array object
1750      * @param wrapperClass The boxed class it converts to
1751      * @return Boxed version of primitive array
1752      */
convertPrimitiveArrayToObjectArray(final Object array, final Class<T> wrapperClass)1753     private static <T> T[] convertPrimitiveArrayToObjectArray(final Object array,
1754             final Class<T> wrapperClass) {
1755         // getLength does the null check and isArray check already.
1756         int arrayLength = Array.getLength(array);
1757         if (arrayLength == 0) {
1758             throw new IllegalArgumentException("Input array shouldn't be empty");
1759         }
1760 
1761         @SuppressWarnings("unchecked")
1762         final T[] result = (T[]) Array.newInstance(wrapperClass, arrayLength);
1763         for (int i = 0; i < arrayLength; i++) {
1764             Array.set(result, i, Array.get(array, i));
1765         }
1766         return result;
1767     }
1768 
1769     /**
1770      * Validate image based on format and size.
1771      *
1772      * @param image The image to be validated.
1773      * @param width The image width.
1774      * @param height The image height.
1775      * @param format The image format.
1776      * @param filePath The debug dump file path, null if don't want to dump to
1777      *            file.
1778      * @throws UnsupportedOperationException if calling with an unknown format
1779      */
validateImage(Image image, int width, int height, int format, String filePath)1780     public static void validateImage(Image image, int width, int height, int format,
1781             String filePath) {
1782         checkImage(image, width, height, format);
1783 
1784         /**
1785          * TODO: validate timestamp:
1786          * 1. capture result timestamp against the image timestamp (need
1787          * consider frame drops)
1788          * 2. timestamps should be monotonically increasing for different requests
1789          */
1790         if(VERBOSE) Log.v(TAG, "validating Image");
1791         byte[] data = getDataFromImage(image);
1792         assertTrue("Invalid image data", data != null && data.length > 0);
1793 
1794         switch (format) {
1795             // Clients must be able to process and handle depth jpeg images like any other
1796             // regular jpeg.
1797             case ImageFormat.DEPTH_JPEG:
1798             case ImageFormat.JPEG:
1799                 validateJpegData(data, width, height, filePath);
1800                 break;
1801             case ImageFormat.YUV_420_888:
1802             case ImageFormat.YV12:
1803                 validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
1804                 break;
1805             case ImageFormat.RAW_SENSOR:
1806                 validateRaw16Data(data, width, height, format, image.getTimestamp(), filePath);
1807                 break;
1808             case ImageFormat.DEPTH16:
1809                 validateDepth16Data(data, width, height, format, image.getTimestamp(), filePath);
1810                 break;
1811             case ImageFormat.DEPTH_POINT_CLOUD:
1812                 validateDepthPointCloudData(data, width, height, format, image.getTimestamp(), filePath);
1813                 break;
1814             case ImageFormat.RAW_PRIVATE:
1815                 validateRawPrivateData(data, width, height, image.getTimestamp(), filePath);
1816                 break;
1817             case ImageFormat.Y8:
1818                 validateY8Data(data, width, height, format, image.getTimestamp(), filePath);
1819                 break;
1820             case ImageFormat.HEIC:
1821                 validateHeicData(data, width, height, filePath);
1822                 break;
1823             default:
1824                 throw new UnsupportedOperationException("Unsupported format for validation: "
1825                         + format);
1826         }
1827     }
1828 
1829     public static class HandlerExecutor implements Executor {
1830         private final Handler mHandler;
1831 
HandlerExecutor(Handler handler)1832         public HandlerExecutor(Handler handler) {
1833             assertNotNull("handler must be valid", handler);
1834             mHandler = handler;
1835         }
1836 
1837         @Override
execute(Runnable runCmd)1838         public void execute(Runnable runCmd) {
1839             mHandler.post(runCmd);
1840         }
1841     }
1842 
1843     /**
1844      * Provide a mock for {@link CameraDevice.StateCallback}.
1845      *
1846      * <p>Only useful because mockito can't mock {@link CameraDevice.StateCallback} which is an
1847      * abstract class.</p>
1848      *
1849      * <p>
1850      * Use this instead of other classes when needing to verify interactions, since
1851      * trying to spy on {@link BlockingStateCallback} (or others) will cause unnecessary extra
1852      * interactions which will cause false test failures.
1853      * </p>
1854      *
1855      */
1856     public static class MockStateCallback extends CameraDevice.StateCallback {
1857 
1858         @Override
onOpened(CameraDevice camera)1859         public void onOpened(CameraDevice camera) {
1860         }
1861 
1862         @Override
onDisconnected(CameraDevice camera)1863         public void onDisconnected(CameraDevice camera) {
1864         }
1865 
1866         @Override
onError(CameraDevice camera, int error)1867         public void onError(CameraDevice camera, int error) {
1868         }
1869 
MockStateCallback()1870         private MockStateCallback() {}
1871 
1872         /**
1873          * Create a Mockito-ready mocked StateCallback.
1874          */
mock()1875         public static MockStateCallback mock() {
1876             return Mockito.spy(new MockStateCallback());
1877         }
1878     }
1879 
validateJpegData(byte[] jpegData, int width, int height, String filePath)1880     private static void validateJpegData(byte[] jpegData, int width, int height, String filePath) {
1881         BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
1882         // DecodeBound mode: only parse the frame header to get width/height.
1883         // it doesn't decode the pixel.
1884         bmpOptions.inJustDecodeBounds = true;
1885         BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, bmpOptions);
1886         assertEquals(width, bmpOptions.outWidth);
1887         assertEquals(height, bmpOptions.outHeight);
1888 
1889         // Pixel decoding mode: decode whole image. check if the image data
1890         // is decodable here.
1891         assertNotNull("Decoding jpeg failed",
1892                 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length));
1893         if (DEBUG && filePath != null) {
1894             String fileName =
1895                     filePath + "/" + width + "x" + height + ".jpeg";
1896             dumpFile(fileName, jpegData);
1897         }
1898     }
1899 
validateYuvData(byte[] yuvData, int width, int height, int format, long ts, String filePath)1900     private static void validateYuvData(byte[] yuvData, int width, int height, int format,
1901             long ts, String filePath) {
1902         checkYuvFormat(format);
1903         if (VERBOSE) Log.v(TAG, "Validating YUV data");
1904         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
1905         assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
1906 
1907         // TODO: Can add data validation for test pattern.
1908 
1909         if (DEBUG && filePath != null) {
1910             String fileName =
1911                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".yuv";
1912             dumpFile(fileName, yuvData);
1913         }
1914     }
1915 
validateRaw16Data(byte[] rawData, int width, int height, int format, long ts, String filePath)1916     private static void validateRaw16Data(byte[] rawData, int width, int height, int format,
1917             long ts, String filePath) {
1918         if (VERBOSE) Log.v(TAG, "Validating raw data");
1919         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
1920         assertEquals("Raw data doesn't match", expectedSize, rawData.length);
1921 
1922         // TODO: Can add data validation for test pattern.
1923 
1924         if (DEBUG && filePath != null) {
1925             String fileName =
1926                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".raw16";
1927             dumpFile(fileName, rawData);
1928         }
1929 
1930         return;
1931     }
1932 
validateY8Data(byte[] rawData, int width, int height, int format, long ts, String filePath)1933     private static void validateY8Data(byte[] rawData, int width, int height, int format,
1934             long ts, String filePath) {
1935         if (VERBOSE) Log.v(TAG, "Validating Y8 data");
1936         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
1937         assertEquals("Y8 data doesn't match", expectedSize, rawData.length);
1938 
1939         // TODO: Can add data validation for test pattern.
1940 
1941         if (DEBUG && filePath != null) {
1942             String fileName =
1943                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".y8";
1944             dumpFile(fileName, rawData);
1945         }
1946 
1947         return;
1948     }
1949 
validateRawPrivateData(byte[] rawData, int width, int height, long ts, String filePath)1950     private static void validateRawPrivateData(byte[] rawData, int width, int height,
1951             long ts, String filePath) {
1952         if (VERBOSE) Log.v(TAG, "Validating private raw data");
1953         // Expect each RAW pixel should occupy at least one byte and no more than 30 bytes
1954         int expectedSizeMin = width * height;
1955         int expectedSizeMax = width * height * 30;
1956 
1957         assertTrue("Opaque RAW size " + rawData.length + "out of normal bound [" +
1958                 expectedSizeMin + "," + expectedSizeMax + "]",
1959                 expectedSizeMin <= rawData.length && rawData.length <= expectedSizeMax);
1960 
1961         if (DEBUG && filePath != null) {
1962             String fileName =
1963                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".rawPriv";
1964             dumpFile(fileName, rawData);
1965         }
1966 
1967         return;
1968     }
1969 
validateDepth16Data(byte[] depthData, int width, int height, int format, long ts, String filePath)1970     private static void validateDepth16Data(byte[] depthData, int width, int height, int format,
1971             long ts, String filePath) {
1972 
1973         if (VERBOSE) Log.v(TAG, "Validating depth16 data");
1974         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
1975         assertEquals("Depth data doesn't match", expectedSize, depthData.length);
1976 
1977 
1978         if (DEBUG && filePath != null) {
1979             String fileName =
1980                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth16";
1981             dumpFile(fileName, depthData);
1982         }
1983 
1984         return;
1985 
1986     }
1987 
validateDepthPointCloudData(byte[] depthData, int width, int height, int format, long ts, String filePath)1988     private static void validateDepthPointCloudData(byte[] depthData, int width, int height, int format,
1989             long ts, String filePath) {
1990 
1991         if (VERBOSE) Log.v(TAG, "Validating depth point cloud data");
1992 
1993         // Can't validate size since it is variable
1994 
1995         if (DEBUG && filePath != null) {
1996             String fileName =
1997                     filePath + "/" + width + "x" + height + "_" + ts / 1e6 + ".depth_point_cloud";
1998             dumpFile(fileName, depthData);
1999         }
2000 
2001         return;
2002 
2003     }
2004 
validateHeicData(byte[] heicData, int width, int height, String filePath)2005     private static void validateHeicData(byte[] heicData, int width, int height, String filePath) {
2006         BitmapFactory.Options bmpOptions = new BitmapFactory.Options();
2007         // DecodeBound mode: only parse the frame header to get width/height.
2008         // it doesn't decode the pixel.
2009         bmpOptions.inJustDecodeBounds = true;
2010         BitmapFactory.decodeByteArray(heicData, 0, heicData.length, bmpOptions);
2011         assertEquals(width, bmpOptions.outWidth);
2012         assertEquals(height, bmpOptions.outHeight);
2013 
2014         // Pixel decoding mode: decode whole image. check if the image data
2015         // is decodable here.
2016         assertNotNull("Decoding heic failed",
2017                 BitmapFactory.decodeByteArray(heicData, 0, heicData.length));
2018         if (DEBUG && filePath != null) {
2019             String fileName =
2020                     filePath + "/" + width + "x" + height + ".heic";
2021             dumpFile(fileName, heicData);
2022         }
2023     }
2024 
getValueNotNull(CaptureResult result, CaptureResult.Key<T> key)2025     public static <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
2026         if (result == null) {
2027             throw new IllegalArgumentException("Result must not be null");
2028         }
2029 
2030         T value = result.get(key);
2031         assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
2032         return value;
2033     }
2034 
getValueNotNull(CameraCharacteristics characteristics, CameraCharacteristics.Key<T> key)2035     public static <T> T getValueNotNull(CameraCharacteristics characteristics,
2036             CameraCharacteristics.Key<T> key) {
2037         if (characteristics == null) {
2038             throw new IllegalArgumentException("Camera characteristics must not be null");
2039         }
2040 
2041         T value = characteristics.get(key);
2042         assertNotNull("Value of Key " + key.getName() + "shouldn't be null", value);
2043         return value;
2044     }
2045 
2046     /**
2047      * Get a crop region for a given zoom factor and center position.
2048      * <p>
2049      * The center position is normalized position in range of [0, 1.0], where
2050      * (0, 0) represents top left corner, (1.0. 1.0) represents bottom right
2051      * corner. The center position could limit the effective minimal zoom
2052      * factor, for example, if the center position is (0.75, 0.75), the
2053      * effective minimal zoom position becomes 2.0. If the requested zoom factor
2054      * is smaller than 2.0, a crop region with 2.0 zoom factor will be returned.
2055      * </p>
2056      * <p>
2057      * The aspect ratio of the crop region is maintained the same as the aspect
2058      * ratio of active array.
2059      * </p>
2060      *
2061      * @param zoomFactor The zoom factor to generate the crop region, it must be
2062      *            >= 1.0
2063      * @param center The normalized zoom center point that is in the range of [0, 1].
2064      * @param maxZoom The max zoom factor supported by this device.
2065      * @param activeArray The active array size of this device.
2066      * @return crop region for the given normalized center and zoom factor.
2067      */
getCropRegionForZoom(float zoomFactor, final PointF center, final float maxZoom, final Rect activeArray)2068     public static Rect getCropRegionForZoom(float zoomFactor, final PointF center,
2069             final float maxZoom, final Rect activeArray) {
2070         if (zoomFactor < 1.0) {
2071             throw new IllegalArgumentException("zoom factor " + zoomFactor + " should be >= 1.0");
2072         }
2073         if (center.x > 1.0 || center.x < 0) {
2074             throw new IllegalArgumentException("center.x " + center.x
2075                     + " should be in range of [0, 1.0]");
2076         }
2077         if (center.y > 1.0 || center.y < 0) {
2078             throw new IllegalArgumentException("center.y " + center.y
2079                     + " should be in range of [0, 1.0]");
2080         }
2081         if (maxZoom < 1.0) {
2082             throw new IllegalArgumentException("max zoom factor " + maxZoom + " should be >= 1.0");
2083         }
2084         if (activeArray == null) {
2085             throw new IllegalArgumentException("activeArray must not be null");
2086         }
2087 
2088         float minCenterLength = Math.min(Math.min(center.x, 1.0f - center.x),
2089                 Math.min(center.y, 1.0f - center.y));
2090         float minEffectiveZoom =  0.5f / minCenterLength;
2091         if (minEffectiveZoom > maxZoom) {
2092             throw new IllegalArgumentException("Requested center " + center.toString() +
2093                     " has minimal zoomable factor " + minEffectiveZoom + ", which exceeds max"
2094                             + " zoom factor " + maxZoom);
2095         }
2096 
2097         if (zoomFactor < minEffectiveZoom) {
2098             Log.w(TAG, "Requested zoomFactor " + zoomFactor + " < minimal zoomable factor "
2099                     + minEffectiveZoom + ". It will be overwritten by " + minEffectiveZoom);
2100             zoomFactor = minEffectiveZoom;
2101         }
2102 
2103         int cropCenterX = (int)(activeArray.width() * center.x);
2104         int cropCenterY = (int)(activeArray.height() * center.y);
2105         int cropWidth = (int) (activeArray.width() / zoomFactor);
2106         int cropHeight = (int) (activeArray.height() / zoomFactor);
2107 
2108         return new Rect(
2109                 /*left*/cropCenterX - cropWidth / 2,
2110                 /*top*/cropCenterY - cropHeight / 2,
2111                 /*right*/ cropCenterX + cropWidth / 2,
2112                 /*bottom*/cropCenterY + cropHeight / 2);
2113     }
2114 
2115     /**
2116      * Get AeAvailableTargetFpsRanges and sort them in descending order by max fps
2117      *
2118      * @param staticInfo camera static metadata
2119      * @return AeAvailableTargetFpsRanges in descending order by max fps
2120      */
getDescendingTargetFpsRanges(StaticMetadata staticInfo)2121     public static Range<Integer>[] getDescendingTargetFpsRanges(StaticMetadata staticInfo) {
2122         Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked();
2123         Arrays.sort(fpsRanges, new Comparator<Range<Integer>>() {
2124             public int compare(Range<Integer> r1, Range<Integer> r2) {
2125                 return r2.getUpper() - r1.getUpper();
2126             }
2127         });
2128         return fpsRanges;
2129     }
2130 
2131     /**
2132      * Get AeAvailableTargetFpsRanges with max fps not exceeding 30
2133      *
2134      * @param staticInfo camera static metadata
2135      * @return AeAvailableTargetFpsRanges with max fps not exceeding 30
2136      */
getTargetFpsRangesUpTo30(StaticMetadata staticInfo)2137     public static List<Range<Integer>> getTargetFpsRangesUpTo30(StaticMetadata staticInfo) {
2138         Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked();
2139         ArrayList<Range<Integer>> fpsRangesUpTo30 = new ArrayList<Range<Integer>>();
2140         for (Range<Integer> fpsRange : fpsRanges) {
2141             if (fpsRange.getUpper() <= 30) {
2142                 fpsRangesUpTo30.add(fpsRange);
2143             }
2144         }
2145         return fpsRangesUpTo30;
2146     }
2147 
2148     /**
2149      * Get AeAvailableTargetFpsRanges with max fps greater than 30
2150      *
2151      * @param staticInfo camera static metadata
2152      * @return AeAvailableTargetFpsRanges with max fps greater than 30
2153      */
getTargetFpsRangesGreaterThan30(StaticMetadata staticInfo)2154     public static List<Range<Integer>> getTargetFpsRangesGreaterThan30(StaticMetadata staticInfo) {
2155         Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked();
2156         ArrayList<Range<Integer>> fpsRangesGreaterThan30 = new ArrayList<Range<Integer>>();
2157         for (Range<Integer> fpsRange : fpsRanges) {
2158             if (fpsRange.getUpper() > 30) {
2159                 fpsRangesGreaterThan30.add(fpsRange);
2160             }
2161         }
2162         return fpsRangesGreaterThan30;
2163     }
2164 
2165     /**
2166      * Calculate output 3A region from the intersection of input 3A region and cropped region.
2167      *
2168      * @param requestRegions The input 3A regions
2169      * @param cropRect The cropped region
2170      * @return expected 3A regions output in capture result
2171      */
getExpectedOutputRegion( MeteringRectangle[] requestRegions, Rect cropRect)2172     public static MeteringRectangle[] getExpectedOutputRegion(
2173             MeteringRectangle[] requestRegions, Rect cropRect){
2174         MeteringRectangle[] resultRegions = new MeteringRectangle[requestRegions.length];
2175         for (int i = 0; i < requestRegions.length; i++) {
2176             Rect requestRect = requestRegions[i].getRect();
2177             Rect resultRect = new Rect();
2178             boolean intersect = resultRect.setIntersect(requestRect, cropRect);
2179             resultRegions[i] = new MeteringRectangle(
2180                     resultRect,
2181                     intersect ? requestRegions[i].getMeteringWeight() : 0);
2182         }
2183         return resultRegions;
2184     }
2185 
2186     /**
2187      * Copy source image data to destination image.
2188      *
2189      * @param src The source image to be copied from.
2190      * @param dst The destination image to be copied to.
2191      * @throws IllegalArgumentException If the source and destination images have
2192      *             different format, size, or one of the images is not copyable.
2193      */
imageCopy(Image src, Image dst)2194     public static void imageCopy(Image src, Image dst) {
2195         if (src == null || dst == null) {
2196             throw new IllegalArgumentException("Images should be non-null");
2197         }
2198         if (src.getFormat() != dst.getFormat()) {
2199             throw new IllegalArgumentException("Src and dst images should have the same format");
2200         }
2201         if (src.getFormat() == ImageFormat.PRIVATE ||
2202                 dst.getFormat() == ImageFormat.PRIVATE) {
2203             throw new IllegalArgumentException("PRIVATE format images are not copyable");
2204         }
2205 
2206         Size srcSize = new Size(src.getWidth(), src.getHeight());
2207         Size dstSize = new Size(dst.getWidth(), dst.getHeight());
2208         if (!srcSize.equals(dstSize)) {
2209             throw new IllegalArgumentException("source image size " + srcSize + " is different"
2210                     + " with " + "destination image size " + dstSize);
2211         }
2212 
2213         // TODO: check the owner of the dst image, it must be from ImageWriter, other source may
2214         // not be writable. Maybe we should add an isWritable() method in image class.
2215 
2216         Plane[] srcPlanes = src.getPlanes();
2217         Plane[] dstPlanes = dst.getPlanes();
2218         ByteBuffer srcBuffer = null;
2219         ByteBuffer dstBuffer = null;
2220         for (int i = 0; i < srcPlanes.length; i++) {
2221             srcBuffer = srcPlanes[i].getBuffer();
2222             dstBuffer = dstPlanes[i].getBuffer();
2223             int srcPos = srcBuffer.position();
2224             srcBuffer.rewind();
2225             dstBuffer.rewind();
2226             int srcRowStride = srcPlanes[i].getRowStride();
2227             int dstRowStride = dstPlanes[i].getRowStride();
2228             int srcPixStride = srcPlanes[i].getPixelStride();
2229             int dstPixStride = dstPlanes[i].getPixelStride();
2230 
2231             if (srcPixStride > 2 || dstPixStride > 2) {
2232                 throw new IllegalArgumentException("source pixel stride " + srcPixStride +
2233                         " with destination pixel stride " + dstPixStride +
2234                         " is not supported");
2235             }
2236 
2237             if (srcRowStride == dstRowStride && srcPixStride == dstPixStride) {
2238                 // Fast path, just copy the content in the byteBuffer all together.
2239                 dstBuffer.put(srcBuffer);
2240             } else {
2241                 Size effectivePlaneSize = getEffectivePlaneSizeForImage(src, i);
2242                 int srcRowByteCount = srcRowStride;
2243                 int dstRowByteCount = dstRowStride;
2244                 byte[] srcDataRow = new byte[srcRowByteCount];
2245 
2246                 if (srcPixStride == dstPixStride) {
2247                     // Row by row copy case
2248                     for (int row = 0; row < effectivePlaneSize.getHeight(); row++) {
2249                         if (row == effectivePlaneSize.getHeight() - 1) {
2250                             // Special case for interleaved planes: need handle the last row
2251                             // carefully to avoid memory corruption. Check if we have enough bytes
2252                             // to copy.
2253                             int remainingBytes = srcBuffer.remaining();
2254                             if (srcRowByteCount > remainingBytes) {
2255                                 srcRowByteCount = remainingBytes;
2256                             }
2257                         }
2258                         srcBuffer.get(srcDataRow, /*offset*/0, srcRowByteCount);
2259                         dstBuffer.put(srcDataRow, /*offset*/0,
2260                                 Math.min(srcRowByteCount, dstRowByteCount));
2261                     }
2262                 } else {
2263                     // Row by row per pixel copy case
2264                     byte[] dstDataRow = new byte[dstRowByteCount];
2265                     for (int row = 0; row < effectivePlaneSize.getHeight(); row++) {
2266                         if (row == effectivePlaneSize.getHeight() - 1) {
2267                             // Special case for interleaved planes: need handle the last row
2268                             // carefully to avoid memory corruption. Check if we have enough bytes
2269                             // to copy.
2270                             int remainingBytes = srcBuffer.remaining();
2271                             if (srcRowByteCount > remainingBytes) {
2272                                 srcRowByteCount = remainingBytes;
2273                             }
2274                             remainingBytes = dstBuffer.remaining();
2275                             if (dstRowByteCount > remainingBytes) {
2276                                 dstRowByteCount = remainingBytes;
2277                             }
2278                         }
2279                         srcBuffer.get(srcDataRow, /*offset*/0, srcRowByteCount);
2280                         int pos = dstBuffer.position();
2281                         dstBuffer.get(dstDataRow, /*offset*/0, dstRowByteCount);
2282                         dstBuffer.position(pos);
2283                         for (int x = 0; x < effectivePlaneSize.getWidth(); x++) {
2284                             dstDataRow[x * dstPixStride] = srcDataRow[x * srcPixStride];
2285                         }
2286                         dstBuffer.put(dstDataRow, /*offset*/0, dstRowByteCount);
2287                     }
2288                 }
2289             }
2290             srcBuffer.position(srcPos);
2291             dstBuffer.rewind();
2292         }
2293     }
2294 
getEffectivePlaneSizeForImage(Image image, int planeIdx)2295     private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) {
2296         switch (image.getFormat()) {
2297             case ImageFormat.YUV_420_888:
2298                 if (planeIdx == 0) {
2299                     return new Size(image.getWidth(), image.getHeight());
2300                 } else {
2301                     return new Size(image.getWidth() / 2, image.getHeight() / 2);
2302                 }
2303             case ImageFormat.JPEG:
2304             case ImageFormat.RAW_SENSOR:
2305             case ImageFormat.RAW10:
2306             case ImageFormat.RAW12:
2307             case ImageFormat.DEPTH16:
2308                 return new Size(image.getWidth(), image.getHeight());
2309             case ImageFormat.PRIVATE:
2310                 return new Size(0, 0);
2311             default:
2312                 throw new UnsupportedOperationException(
2313                         String.format("Invalid image format %d", image.getFormat()));
2314         }
2315     }
2316 
2317     /**
2318      * <p>
2319      * Checks whether the two images are strongly equal.
2320      * </p>
2321      * <p>
2322      * Two images are strongly equal if and only if the data, formats, sizes,
2323      * and timestamps are same. For {@link ImageFormat#PRIVATE PRIVATE} format
2324      * images, the image data is not not accessible thus the data comparison is
2325      * effectively skipped as the number of planes is zero.
2326      * </p>
2327      * <p>
2328      * Note that this method compares the pixel data even outside of the crop
2329      * region, which may not be necessary for general use case.
2330      * </p>
2331      *
2332      * @param lhsImg First image to be compared with.
2333      * @param rhsImg Second image to be compared with.
2334      * @return true if the two images are equal, false otherwise.
2335      * @throws IllegalArgumentException If either of image is null.
2336      */
isImageStronglyEqual(Image lhsImg, Image rhsImg)2337     public static boolean isImageStronglyEqual(Image lhsImg, Image rhsImg) {
2338         if (lhsImg == null || rhsImg == null) {
2339             throw new IllegalArgumentException("Images should be non-null");
2340         }
2341 
2342         if (lhsImg.getFormat() != rhsImg.getFormat()) {
2343             Log.i(TAG, "lhsImg format " + lhsImg.getFormat() + " is different with rhsImg format "
2344                     + rhsImg.getFormat());
2345             return false;
2346         }
2347 
2348         if (lhsImg.getWidth() != rhsImg.getWidth()) {
2349             Log.i(TAG, "lhsImg width " + lhsImg.getWidth() + " is different with rhsImg width "
2350                     + rhsImg.getWidth());
2351             return false;
2352         }
2353 
2354         if (lhsImg.getHeight() != rhsImg.getHeight()) {
2355             Log.i(TAG, "lhsImg height " + lhsImg.getHeight() + " is different with rhsImg height "
2356                     + rhsImg.getHeight());
2357             return false;
2358         }
2359 
2360         if (lhsImg.getTimestamp() != rhsImg.getTimestamp()) {
2361             Log.i(TAG, "lhsImg timestamp " + lhsImg.getTimestamp()
2362                     + " is different with rhsImg timestamp " + rhsImg.getTimestamp());
2363             return false;
2364         }
2365 
2366         if (!lhsImg.getCropRect().equals(rhsImg.getCropRect())) {
2367             Log.i(TAG, "lhsImg crop rect " + lhsImg.getCropRect()
2368                     + " is different with rhsImg crop rect " + rhsImg.getCropRect());
2369             return false;
2370         }
2371 
2372         // Compare data inside of the image.
2373         Plane[] lhsPlanes = lhsImg.getPlanes();
2374         Plane[] rhsPlanes = rhsImg.getPlanes();
2375         ByteBuffer lhsBuffer = null;
2376         ByteBuffer rhsBuffer = null;
2377         for (int i = 0; i < lhsPlanes.length; i++) {
2378             lhsBuffer = lhsPlanes[i].getBuffer();
2379             rhsBuffer = rhsPlanes[i].getBuffer();
2380             lhsBuffer.rewind();
2381             rhsBuffer.rewind();
2382             // Special case for YUV420_888 buffer with different layout
2383             if (lhsImg.getFormat() == ImageFormat.YUV_420_888 &&
2384                     (lhsPlanes[i].getPixelStride() != rhsPlanes[i].getPixelStride() ||
2385                      lhsPlanes[i].getRowStride() != rhsPlanes[i].getRowStride())) {
2386                 int width = getEffectivePlaneSizeForImage(lhsImg, i).getWidth();
2387                 int height = getEffectivePlaneSizeForImage(lhsImg, i).getHeight();
2388                 int rowSizeL = lhsPlanes[i].getRowStride();
2389                 int rowSizeR = rhsPlanes[i].getRowStride();
2390                 byte[] lhsRow = new byte[rowSizeL];
2391                 byte[] rhsRow = new byte[rowSizeR];
2392                 int pixStrideL = lhsPlanes[i].getPixelStride();
2393                 int pixStrideR = rhsPlanes[i].getPixelStride();
2394                 for (int r = 0; r < height; r++) {
2395                     if (r == height -1) {
2396                         rowSizeL = lhsBuffer.remaining();
2397                         rowSizeR = rhsBuffer.remaining();
2398                     }
2399                     lhsBuffer.get(lhsRow, /*offset*/0, rowSizeL);
2400                     rhsBuffer.get(rhsRow, /*offset*/0, rowSizeR);
2401                     for (int c = 0; c < width; c++) {
2402                         if (lhsRow[c * pixStrideL] != rhsRow[c * pixStrideR]) {
2403                             Log.i(TAG, String.format(
2404                                     "byte buffers for plane %d row %d col %d don't match.",
2405                                     i, r, c));
2406                             return false;
2407                         }
2408                     }
2409                 }
2410             } else {
2411                 // Compare entire buffer directly
2412                 if (!lhsBuffer.equals(rhsBuffer)) {
2413                     Log.i(TAG, "byte buffers for plane " +  i + " don't match.");
2414                     return false;
2415                 }
2416             }
2417         }
2418 
2419         return true;
2420     }
2421 
2422     /**
2423      * Set jpeg related keys in a capture request builder.
2424      *
2425      * @param builder The capture request builder to set the keys inl
2426      * @param exifData The exif data to set.
2427      * @param thumbnailSize The thumbnail size to set.
2428      * @param collector The camera error collector to collect errors.
2429      */
setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData, Size thumbnailSize, CameraErrorCollector collector)2430     public static void setJpegKeys(CaptureRequest.Builder builder, ExifTestData exifData,
2431             Size thumbnailSize, CameraErrorCollector collector) {
2432         builder.set(CaptureRequest.JPEG_THUMBNAIL_SIZE, thumbnailSize);
2433         builder.set(CaptureRequest.JPEG_GPS_LOCATION, exifData.gpsLocation);
2434         builder.set(CaptureRequest.JPEG_ORIENTATION, exifData.jpegOrientation);
2435         builder.set(CaptureRequest.JPEG_QUALITY, exifData.jpegQuality);
2436         builder.set(CaptureRequest.JPEG_THUMBNAIL_QUALITY,
2437                 exifData.thumbnailQuality);
2438 
2439         // Validate request set and get.
2440         collector.expectEquals("JPEG thumbnail size request set and get should match",
2441                 thumbnailSize, builder.get(CaptureRequest.JPEG_THUMBNAIL_SIZE));
2442         collector.expectTrue("GPS locations request set and get should match.",
2443                 areGpsFieldsEqual(exifData.gpsLocation,
2444                 builder.get(CaptureRequest.JPEG_GPS_LOCATION)));
2445         collector.expectEquals("JPEG orientation request set and get should match",
2446                 exifData.jpegOrientation,
2447                 builder.get(CaptureRequest.JPEG_ORIENTATION));
2448         collector.expectEquals("JPEG quality request set and get should match",
2449                 exifData.jpegQuality, builder.get(CaptureRequest.JPEG_QUALITY));
2450         collector.expectEquals("JPEG thumbnail quality request set and get should match",
2451                 exifData.thumbnailQuality,
2452                 builder.get(CaptureRequest.JPEG_THUMBNAIL_QUALITY));
2453     }
2454 
2455     /**
2456      * Simple validation of JPEG image size and format.
2457      * <p>
2458      * Only validate the image object basic correctness. It is fast, but doesn't actually
2459      * check the buffer data. Assert is used here as it make no sense to
2460      * continue the test if the jpeg image captured has some serious failures.
2461      * </p>
2462      *
2463      * @param image The captured JPEG/HEIC image
2464      * @param expectedSize Expected capture JEPG/HEIC size
2465      * @param format JPEG/HEIC image format
2466      */
basicValidateBlobImage(Image image, Size expectedSize, int format)2467     public static void basicValidateBlobImage(Image image, Size expectedSize, int format) {
2468         Size imageSz = new Size(image.getWidth(), image.getHeight());
2469         assertTrue(
2470                 String.format("Image size doesn't match (expected %s, actual %s) ",
2471                         expectedSize.toString(), imageSz.toString()), expectedSize.equals(imageSz));
2472         assertEquals("Image format should be " + ((format == ImageFormat.HEIC) ? "HEIC" : "JPEG"),
2473                 format, image.getFormat());
2474         assertNotNull("Image plane shouldn't be null", image.getPlanes());
2475         assertEquals("Image plane number should be 1", 1, image.getPlanes().length);
2476 
2477         // Jpeg/Heic decoding validate was done in ImageReaderTest,
2478         // no need to duplicate the test here.
2479     }
2480 
2481     /**
2482      * Verify the EXIF and JPEG related keys in a capture result are expected.
2483      * - Capture request get values are same as were set.
2484      * - capture result's exif data is the same as was set by
2485      *   the capture request.
2486      * - new tags in the result set by the camera service are
2487      *   present and semantically correct.
2488      *
2489      * @param image The output JPEG/HEIC image to verify.
2490      * @param captureResult The capture result to verify.
2491      * @param expectedSize The expected JPEG/HEIC size.
2492      * @param expectedThumbnailSize The expected thumbnail size.
2493      * @param expectedExifData The expected EXIF data
2494      * @param staticInfo The static metadata for the camera device.
2495      * @param blobFilename The filename to dump the jpeg/heic to.
2496      * @param collector The camera error collector to collect errors.
2497      * @param format JPEG/HEIC format
2498      */
verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize, Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo, CameraErrorCollector collector, String debugFileNameBase, int format)2499     public static void verifyJpegKeys(Image image, CaptureResult captureResult, Size expectedSize,
2500             Size expectedThumbnailSize, ExifTestData expectedExifData, StaticMetadata staticInfo,
2501             CameraErrorCollector collector, String debugFileNameBase, int format) throws Exception {
2502 
2503         basicValidateBlobImage(image, expectedSize, format);
2504 
2505         byte[] blobBuffer = getDataFromImage(image);
2506         // Have to dump into a file to be able to use ExifInterface
2507         String filePostfix = (format == ImageFormat.HEIC ? ".heic" : ".jpeg");
2508         String blobFilename = debugFileNameBase + "/verifyJpegKeys" + filePostfix;
2509         dumpFile(blobFilename, blobBuffer);
2510         ExifInterface exif = new ExifInterface(blobFilename);
2511 
2512         if (expectedThumbnailSize.equals(new Size(0,0))) {
2513             collector.expectTrue("Jpeg shouldn't have thumbnail when thumbnail size is (0, 0)",
2514                     !exif.hasThumbnail());
2515         } else {
2516             collector.expectTrue("Jpeg must have thumbnail for thumbnail size " +
2517                     expectedThumbnailSize, exif.hasThumbnail());
2518         }
2519 
2520         // Validate capture result vs. request
2521         Size resultThumbnailSize = captureResult.get(CaptureResult.JPEG_THUMBNAIL_SIZE);
2522         int orientationTested = expectedExifData.jpegOrientation;
2523         // Legacy shim always doesn't rotate thumbnail size
2524         if ((orientationTested == 90 || orientationTested == 270) &&
2525                 staticInfo.isHardwareLevelAtLeastLimited()) {
2526             int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
2527                     /*defaultValue*/-1);
2528             if (exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) {
2529                 // Device physically rotated image+thumbnail data
2530                 // Expect thumbnail size to be also rotated
2531                 resultThumbnailSize = new Size(resultThumbnailSize.getHeight(),
2532                         resultThumbnailSize.getWidth());
2533             }
2534         }
2535 
2536         collector.expectEquals("JPEG thumbnail size result and request should match",
2537                 expectedThumbnailSize, resultThumbnailSize);
2538         if (collector.expectKeyValueNotNull(captureResult, CaptureResult.JPEG_GPS_LOCATION) !=
2539                 null) {
2540             collector.expectTrue("GPS location result and request should match.",
2541                     areGpsFieldsEqual(expectedExifData.gpsLocation,
2542                     captureResult.get(CaptureResult.JPEG_GPS_LOCATION)));
2543         }
2544         collector.expectEquals("JPEG orientation result and request should match",
2545                 expectedExifData.jpegOrientation,
2546                 captureResult.get(CaptureResult.JPEG_ORIENTATION));
2547         collector.expectEquals("JPEG quality result and request should match",
2548                 expectedExifData.jpegQuality, captureResult.get(CaptureResult.JPEG_QUALITY));
2549         collector.expectEquals("JPEG thumbnail quality result and request should match",
2550                 expectedExifData.thumbnailQuality,
2551                 captureResult.get(CaptureResult.JPEG_THUMBNAIL_QUALITY));
2552 
2553         // Validate other exif tags for all non-legacy devices
2554         if (!staticInfo.isHardwareLevelLegacy()) {
2555             verifyJpegExifExtraTags(exif, expectedSize, captureResult, staticInfo, collector,
2556                     expectedExifData);
2557         }
2558     }
2559 
2560     /**
2561      * Get the degree of an EXIF orientation.
2562      */
getExifOrientationInDegree(int exifOrientation, CameraErrorCollector collector)2563     private static int getExifOrientationInDegree(int exifOrientation,
2564             CameraErrorCollector collector) {
2565         switch (exifOrientation) {
2566             case ExifInterface.ORIENTATION_NORMAL:
2567                 return 0;
2568             case ExifInterface.ORIENTATION_ROTATE_90:
2569                 return 90;
2570             case ExifInterface.ORIENTATION_ROTATE_180:
2571                 return 180;
2572             case ExifInterface.ORIENTATION_ROTATE_270:
2573                 return 270;
2574             default:
2575                 collector.addMessage("It is impossible to get non 0, 90, 180, 270 degress exif" +
2576                         "info based on the request orientation range");
2577                 return 0;
2578         }
2579     }
2580 
2581     /**
2582      * Validate and return the focal length.
2583      *
2584      * @param result Capture result to get the focal length
2585      * @return Focal length from capture result or -1 if focal length is not available.
2586      */
validateFocalLength(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)2587     private static float validateFocalLength(CaptureResult result, StaticMetadata staticInfo,
2588             CameraErrorCollector collector) {
2589         float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
2590         Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
2591         if (collector.expectTrue("Focal length is invalid",
2592                 resultFocalLength != null && resultFocalLength > 0)) {
2593             List<Float> focalLengthList =
2594                     Arrays.asList(CameraTestUtils.toObject(focalLengths));
2595             collector.expectTrue("Focal length should be one of the available focal length",
2596                     focalLengthList.contains(resultFocalLength));
2597             return resultFocalLength;
2598         }
2599         return -1;
2600     }
2601 
2602     /**
2603      * Validate and return the aperture.
2604      *
2605      * @param result Capture result to get the aperture
2606      * @return Aperture from capture result or -1 if aperture is not available.
2607      */
validateAperture(CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector)2608     private static float validateAperture(CaptureResult result, StaticMetadata staticInfo,
2609             CameraErrorCollector collector) {
2610         float[] apertures = staticInfo.getAvailableAperturesChecked();
2611         Float resultAperture = result.get(CaptureResult.LENS_APERTURE);
2612         if (collector.expectTrue("Capture result aperture is invalid",
2613                 resultAperture != null && resultAperture > 0)) {
2614             List<Float> apertureList =
2615                     Arrays.asList(CameraTestUtils.toObject(apertures));
2616             collector.expectTrue("Aperture should be one of the available apertures",
2617                     apertureList.contains(resultAperture));
2618             return resultAperture;
2619         }
2620         return -1;
2621     }
2622 
2623     /**
2624      * Return the closest value in an array of floats.
2625      */
getClosestValueInArray(float[] values, float target)2626     private static float getClosestValueInArray(float[] values, float target) {
2627         int minIdx = 0;
2628         float minDistance = Math.abs(values[0] - target);
2629         for(int i = 0; i < values.length; i++) {
2630             float distance = Math.abs(values[i] - target);
2631             if (minDistance > distance) {
2632                 minDistance = distance;
2633                 minIdx = i;
2634             }
2635         }
2636 
2637         return values[minIdx];
2638     }
2639 
2640     /**
2641      * Return if two Location's GPS field are the same.
2642      */
areGpsFieldsEqual(Location a, Location b)2643     private static boolean areGpsFieldsEqual(Location a, Location b) {
2644         if (a == null || b == null) {
2645             return false;
2646         }
2647 
2648         return a.getTime() == b.getTime() && a.getLatitude() == b.getLatitude() &&
2649                 a.getLongitude() == b.getLongitude() && a.getAltitude() == b.getAltitude() &&
2650                 a.getProvider() == b.getProvider();
2651     }
2652 
2653     /**
2654      * Verify extra tags in JPEG EXIF
2655      */
verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize, CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector, ExifTestData expectedExifData)2656     private static void verifyJpegExifExtraTags(ExifInterface exif, Size jpegSize,
2657             CaptureResult result, StaticMetadata staticInfo, CameraErrorCollector collector,
2658             ExifTestData expectedExifData)
2659             throws ParseException {
2660         /**
2661          * TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH and TAG_ORIENTATION.
2662          * Orientation and exif width/height need to be tested carefully, two cases:
2663          *
2664          * 1. Device rotate the image buffer physically, then exif width/height may not match
2665          * the requested still capture size, we need swap them to check.
2666          *
2667          * 2. Device use the exif tag to record the image orientation, it doesn't rotate
2668          * the jpeg image buffer itself. In this case, the exif width/height should always match
2669          * the requested still capture size, and the exif orientation should always match the
2670          * requested orientation.
2671          *
2672          */
2673         int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, /*defaultValue*/0);
2674         int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, /*defaultValue*/0);
2675         Size exifSize = new Size(exifWidth, exifHeight);
2676         // Orientation could be missing, which is ok, default to 0.
2677         int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
2678                 /*defaultValue*/-1);
2679         // Get requested orientation from result, because they should be same.
2680         if (collector.expectKeyValueNotNull(result, CaptureResult.JPEG_ORIENTATION) != null) {
2681             int requestedOrientation = result.get(CaptureResult.JPEG_ORIENTATION);
2682             final int ORIENTATION_MIN = ExifInterface.ORIENTATION_UNDEFINED;
2683             final int ORIENTATION_MAX = ExifInterface.ORIENTATION_ROTATE_270;
2684             boolean orientationValid = collector.expectTrue(String.format(
2685                     "Exif orientation must be in range of [%d, %d]",
2686                     ORIENTATION_MIN, ORIENTATION_MAX),
2687                     exifOrientation >= ORIENTATION_MIN && exifOrientation <= ORIENTATION_MAX);
2688             if (orientationValid) {
2689                 /**
2690                  * Device captured image doesn't respect the requested orientation,
2691                  * which means it rotates the image buffer physically. Then we
2692                  * should swap the exif width/height accordingly to compare.
2693                  */
2694                 boolean deviceRotatedImage = exifOrientation == ExifInterface.ORIENTATION_UNDEFINED;
2695 
2696                 if (deviceRotatedImage) {
2697                     // Case 1.
2698                     boolean needSwap = (requestedOrientation % 180 == 90);
2699                     if (needSwap) {
2700                         exifSize = new Size(exifHeight, exifWidth);
2701                     }
2702                 } else {
2703                     // Case 2.
2704                     collector.expectEquals("Exif orientaiton should match requested orientation",
2705                             requestedOrientation, getExifOrientationInDegree(exifOrientation,
2706                             collector));
2707                 }
2708             }
2709         }
2710 
2711         /**
2712          * Ideally, need check exifSize == jpegSize == actual buffer size. But
2713          * jpegSize == jpeg decode bounds size(from jpeg jpeg frame
2714          * header, not exif) was validated in ImageReaderTest, no need to
2715          * validate again here.
2716          */
2717         collector.expectEquals("Exif size should match jpeg capture size", jpegSize, exifSize);
2718 
2719         // TAG_DATETIME, it should be local time
2720         long currentTimeInMs = System.currentTimeMillis();
2721         long currentTimeInSecond = currentTimeInMs / 1000;
2722         Date date = new Date(currentTimeInMs);
2723         String localDatetime = new SimpleDateFormat("yyyy:MM:dd HH:").format(date);
2724         String dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
2725         if (collector.expectTrue("Exif TAG_DATETIME shouldn't be null", dateTime != null)) {
2726             collector.expectTrue("Exif TAG_DATETIME is wrong",
2727                     dateTime.length() == EXIF_DATETIME_LENGTH);
2728             long exifTimeInSecond =
2729                     new SimpleDateFormat("yyyy:MM:dd HH:mm:ss").parse(dateTime).getTime() / 1000;
2730             long delta = currentTimeInSecond - exifTimeInSecond;
2731             collector.expectTrue("Capture time deviates too much from the current time",
2732                     Math.abs(delta) < EXIF_DATETIME_ERROR_MARGIN_SEC);
2733             // It should be local time.
2734             collector.expectTrue("Exif date time should be local time",
2735                     dateTime.startsWith(localDatetime));
2736         }
2737 
2738         boolean isExternalCamera = staticInfo.isExternalCamera();
2739         if (!isExternalCamera) {
2740             // TAG_FOCAL_LENGTH.
2741             float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked();
2742             float exifFocalLength = (float)exif.getAttributeDouble(
2743                         ExifInterface.TAG_FOCAL_LENGTH, -1);
2744             collector.expectEquals("Focal length should match",
2745                     getClosestValueInArray(focalLengths, exifFocalLength),
2746                     exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN);
2747             // More checks for focal length.
2748             collector.expectEquals("Exif focal length should match capture result",
2749                     validateFocalLength(result, staticInfo, collector),
2750                     exifFocalLength, EXIF_FOCAL_LENGTH_ERROR_MARGIN);
2751 
2752             // TAG_EXPOSURE_TIME
2753             // ExifInterface API gives exposure time value in the form of float instead of rational
2754             String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
2755             collector.expectNotNull("Exif TAG_EXPOSURE_TIME shouldn't be null", exposureTime);
2756             if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_EXPOSURE_TIME)) {
2757                 if (exposureTime != null) {
2758                     double exposureTimeValue = Double.parseDouble(exposureTime);
2759                     long expTimeResult = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
2760                     double expected = expTimeResult / 1e9;
2761                     double tolerance = expected * EXIF_EXPOSURE_TIME_ERROR_MARGIN_RATIO;
2762                     tolerance = Math.max(tolerance, EXIF_EXPOSURE_TIME_MIN_ERROR_MARGIN_SEC);
2763                     collector.expectEquals("Exif exposure time doesn't match", expected,
2764                             exposureTimeValue, tolerance);
2765                 }
2766             }
2767 
2768             // TAG_APERTURE
2769             // ExifInterface API gives aperture value in the form of float instead of rational
2770             String exifAperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
2771             collector.expectNotNull("Exif TAG_APERTURE shouldn't be null", exifAperture);
2772             if (staticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)) {
2773                 float[] apertures = staticInfo.getAvailableAperturesChecked();
2774                 if (exifAperture != null) {
2775                     float apertureValue = Float.parseFloat(exifAperture);
2776                     collector.expectEquals("Aperture value should match",
2777                             getClosestValueInArray(apertures, apertureValue),
2778                             apertureValue, EXIF_APERTURE_ERROR_MARGIN);
2779                     // More checks for aperture.
2780                     collector.expectEquals("Exif aperture length should match capture result",
2781                             validateAperture(result, staticInfo, collector),
2782                             apertureValue, EXIF_APERTURE_ERROR_MARGIN);
2783                 }
2784             }
2785 
2786             // TAG_MAKE
2787             String make = exif.getAttribute(ExifInterface.TAG_MAKE);
2788             collector.expectEquals("Exif TAG_MAKE is incorrect", Build.MANUFACTURER, make);
2789 
2790             // TAG_MODEL
2791             String model = exif.getAttribute(ExifInterface.TAG_MODEL);
2792             collector.expectEquals("Exif TAG_MODEL is incorrect", Build.MODEL, model);
2793 
2794 
2795             // TAG_ISO
2796             int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, /*defaultValue*/-1);
2797             if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY) ||
2798                     staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) {
2799                 int expectedIso = 100;
2800                 if (staticInfo.areKeysAvailable(CaptureResult.SENSOR_SENSITIVITY)) {
2801                     expectedIso = result.get(CaptureResult.SENSOR_SENSITIVITY);
2802                 }
2803                 if (staticInfo.areKeysAvailable(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST)) {
2804                     expectedIso = expectedIso *
2805                             result.get(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
2806                 } else {
2807                     expectedIso *= 100;
2808                 }
2809                 collector.expectInRange("Exif TAG_ISO is incorrect", iso,
2810                         expectedIso/100, (expectedIso+50)/100);
2811             }
2812         } else {
2813             // External camera specific checks
2814             // TAG_MAKE
2815             String make = exif.getAttribute(ExifInterface.TAG_MAKE);
2816             collector.expectNotNull("Exif TAG_MAKE is null", make);
2817 
2818             // TAG_MODEL
2819             String model = exif.getAttribute(ExifInterface.TAG_MODEL);
2820             collector.expectNotNull("Exif TAG_MODEL is nuill", model);
2821         }
2822 
2823 
2824         /**
2825          * TAG_FLASH. TODO: For full devices, can check a lot more info
2826          * (http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html#Flash)
2827          */
2828         String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
2829         collector.expectNotNull("Exif TAG_FLASH shouldn't be null", flash);
2830 
2831         /**
2832          * TAG_WHITE_BALANCE. TODO: For full devices, with the DNG tags, we
2833          * should be able to cross-check android.sensor.referenceIlluminant.
2834          */
2835         String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
2836         collector.expectNotNull("Exif TAG_WHITE_BALANCE shouldn't be null", whiteBalance);
2837 
2838         // TAG_DATETIME_DIGITIZED (a.k.a Create time for digital cameras).
2839         String digitizedTime = exif.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED);
2840         collector.expectNotNull("Exif TAG_DATETIME_DIGITIZED shouldn't be null", digitizedTime);
2841         if (digitizedTime != null) {
2842             String expectedDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME);
2843             collector.expectNotNull("Exif TAG_DATETIME shouldn't be null", expectedDateTime);
2844             if (expectedDateTime != null) {
2845                 collector.expectEquals("dataTime should match digitizedTime",
2846                         expectedDateTime, digitizedTime);
2847             }
2848         }
2849 
2850         /**
2851          * TAG_SUBSEC_TIME. Since the sub second tag strings are truncated to at
2852          * most 9 digits in ExifInterface implementation, use getAttributeInt to
2853          * sanitize it. When the default value -1 is returned, it means that
2854          * this exif tag either doesn't exist or is a non-numerical invalid
2855          * string. Same rule applies to the rest of sub second tags.
2856          */
2857         int subSecTime = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME, /*defaultValue*/-1);
2858         collector.expectTrue("Exif TAG_SUBSEC_TIME value is null or invalid!", subSecTime >= 0);
2859 
2860         // TAG_SUBSEC_TIME_ORIG
2861         int subSecTimeOrig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_ORIG,
2862                 /*defaultValue*/-1);
2863         collector.expectTrue("Exif TAG_SUBSEC_TIME_ORIG value is null or invalid!",
2864                 subSecTimeOrig >= 0);
2865 
2866         // TAG_SUBSEC_TIME_DIG
2867         int subSecTimeDig = exif.getAttributeInt(ExifInterface.TAG_SUBSEC_TIME_DIG,
2868                 /*defaultValue*/-1);
2869         collector.expectTrue(
2870                 "Exif TAG_SUBSEC_TIME_DIG value is null or invalid!", subSecTimeDig >= 0);
2871 
2872         /**
2873          * TAG_GPS_DATESTAMP & TAG_GPS_TIMESTAMP.
2874          * The GPS timestamp information should be in seconds UTC time.
2875          */
2876         String gpsDatestamp = exif.getAttribute(ExifInterface.TAG_GPS_DATESTAMP);
2877         collector.expectNotNull("Exif TAG_GPS_DATESTAMP shouldn't be null", gpsDatestamp);
2878         String gpsTimestamp = exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP);
2879         collector.expectNotNull("Exif TAG_GPS_TIMESTAMP shouldn't be null", gpsTimestamp);
2880 
2881         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy:MM:dd hh:mm:ss z");
2882         String gpsExifTimeString = gpsDatestamp + " " + gpsTimestamp + " UTC";
2883         Date gpsDateTime = dateFormat.parse(gpsExifTimeString);
2884         Date expected = new Date(expectedExifData.gpsLocation.getTime());
2885         collector.expectEquals("Jpeg EXIF GPS time should match", expected, gpsDateTime);
2886     }
2887 
2888 
2889     /**
2890      * Immutable class wrapping the exif test data.
2891      */
2892     public static class ExifTestData {
2893         public final Location gpsLocation;
2894         public final int jpegOrientation;
2895         public final byte jpegQuality;
2896         public final byte thumbnailQuality;
2897 
ExifTestData(Location location, int orientation, byte jpgQuality, byte thumbQuality)2898         public ExifTestData(Location location, int orientation,
2899                 byte jpgQuality, byte thumbQuality) {
2900             gpsLocation = location;
2901             jpegOrientation = orientation;
2902             jpegQuality = jpgQuality;
2903             thumbnailQuality = thumbQuality;
2904         }
2905     }
2906 
getPreviewSizeBound(WindowManager windowManager, Size bound)2907     public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) {
2908         Display display = windowManager.getDefaultDisplay();
2909 
2910         int width = display.getWidth();
2911         int height = display.getHeight();
2912 
2913         if (height > width) {
2914             height = width;
2915             width = display.getHeight();
2916         }
2917 
2918         if (bound.getWidth() <= width &&
2919             bound.getHeight() <= height)
2920             return bound;
2921         else
2922             return new Size(width, height);
2923     }
2924 
2925     /**
2926      * Check if a particular stream configuration is supported by configuring it
2927      * to the device.
2928      */
isStreamConfigurationSupported(CameraDevice camera, List<Surface> outputSurfaces, CameraCaptureSession.StateCallback listener, Handler handler)2929     public static boolean isStreamConfigurationSupported(CameraDevice camera,
2930             List<Surface> outputSurfaces,
2931             CameraCaptureSession.StateCallback listener, Handler handler) {
2932         try {
2933             configureCameraSession(camera, outputSurfaces, listener, handler);
2934             return true;
2935         } catch (Exception e) {
2936             Log.i(TAG, "This stream configuration is not supported due to " + e.getMessage());
2937             return false;
2938         }
2939     }
2940 
2941     public final static class SessionConfigSupport {
2942         public final boolean error;
2943         public final boolean callSupported;
2944         public final boolean configSupported;
2945 
SessionConfigSupport(boolean error, boolean callSupported, boolean configSupported)2946         public SessionConfigSupport(boolean error,
2947                 boolean callSupported, boolean configSupported) {
2948             this.error = error;
2949             this.callSupported = callSupported;
2950             this.configSupported = configSupported;
2951         }
2952     }
2953 
2954     /**
2955      * Query whether a particular stream combination is supported.
2956      */
checkSessionConfigurationWithSurfaces(CameraDevice camera, Handler handler, List<Surface> outputSurfaces, InputConfiguration inputConfig, int operatingMode, boolean defaultSupport, String msg)2957     public static void checkSessionConfigurationWithSurfaces(CameraDevice camera,
2958             Handler handler, List<Surface> outputSurfaces, InputConfiguration inputConfig,
2959             int operatingMode, boolean defaultSupport, String msg) {
2960         List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
2961         for (Surface surface : outputSurfaces) {
2962             outConfigurations.add(new OutputConfiguration(surface));
2963         }
2964 
2965         checkSessionConfigurationSupported(camera, handler, outConfigurations,
2966                 inputConfig, operatingMode, defaultSupport, msg);
2967     }
2968 
checkSessionConfigurationSupported(CameraDevice camera, Handler handler, List<OutputConfiguration> outputConfigs, InputConfiguration inputConfig, int operatingMode, boolean defaultSupport, String msg)2969     public static void checkSessionConfigurationSupported(CameraDevice camera,
2970             Handler handler, List<OutputConfiguration> outputConfigs,
2971             InputConfiguration inputConfig, int operatingMode, boolean defaultSupport,
2972             String msg) {
2973         SessionConfigSupport sessionConfigSupported =
2974                 isSessionConfigSupported(camera, handler, outputConfigs, inputConfig,
2975                 operatingMode, defaultSupport);
2976 
2977         assertTrue(msg, !sessionConfigSupported.error && sessionConfigSupported.configSupported);
2978     }
2979 
2980     /**
2981      * Query whether a particular stream combination is supported.
2982      */
isSessionConfigSupported(CameraDevice camera, Handler handler, List<OutputConfiguration> outputConfigs, InputConfiguration inputConfig, int operatingMode, boolean defaultSupport)2983     public static SessionConfigSupport isSessionConfigSupported(CameraDevice camera,
2984             Handler handler, List<OutputConfiguration> outputConfigs,
2985             InputConfiguration inputConfig, int operatingMode, boolean defaultSupport) {
2986         boolean ret;
2987         BlockingSessionCallback sessionListener = new BlockingSessionCallback();
2988 
2989         SessionConfiguration sessionConfig = new SessionConfiguration(operatingMode, outputConfigs,
2990                 new HandlerExecutor(handler), sessionListener);
2991         if (inputConfig != null) {
2992             sessionConfig.setInputConfiguration(inputConfig);
2993         }
2994 
2995         try {
2996             ret = camera.isSessionConfigurationSupported(sessionConfig);
2997         } catch (UnsupportedOperationException e) {
2998             // Camera doesn't support session configuration query
2999             return new SessionConfigSupport(false/*error*/,
3000                     false/*callSupported*/, defaultSupport/*configSupported*/);
3001         } catch (IllegalArgumentException e) {
3002             return new SessionConfigSupport(true/*error*/,
3003                     false/*callSupported*/, false/*configSupported*/);
3004         } catch (android.hardware.camera2.CameraAccessException e) {
3005             return new SessionConfigSupport(true/*error*/,
3006                     false/*callSupported*/, false/*configSupported*/);
3007         }
3008 
3009         return new SessionConfigSupport(false/*error*/,
3010                 true/*callSupported*/, ret/*configSupported*/);
3011     }
3012 
3013     /**
3014      * Wait for numResultWait frames
3015      *
3016      * @param resultListener The capture listener to get capture result back.
3017      * @param numResultsWait Number of frame to wait
3018      * @param timeout Wait timeout in ms.
3019      *
3020      * @return the last result, or {@code null} if there was none
3021      */
waitForNumResults(SimpleCaptureCallback resultListener, int numResultsWait, int timeout)3022     public static CaptureResult waitForNumResults(SimpleCaptureCallback resultListener,
3023             int numResultsWait, int timeout) {
3024         if (numResultsWait < 0 || resultListener == null) {
3025             throw new IllegalArgumentException(
3026                     "Input must be positive number and listener must be non-null");
3027         }
3028 
3029         CaptureResult result = null;
3030         for (int i = 0; i < numResultsWait; i++) {
3031             result = resultListener.getCaptureResult(timeout);
3032         }
3033 
3034         return result;
3035     }
3036 
3037     /**
3038      * Wait for any expected result key values available in a certain number of results.
3039      *
3040      * <p>
3041      * Check the result immediately if numFramesWait is 0.
3042      * </p>
3043      *
3044      * @param listener The capture listener to get capture result.
3045      * @param resultKey The capture result key associated with the result value.
3046      * @param expectedValues The list of result value need to be waited for,
3047      * return immediately if the list is empty.
3048      * @param numResultsWait Number of frame to wait before times out.
3049      * @param timeout result wait time out in ms.
3050      * @throws TimeoutRuntimeException If more than numResultsWait results are.
3051      * seen before the result matching myRequest arrives, or each individual wait
3052      * for result times out after 'timeout' ms.
3053      */
waitForAnyResultValue(SimpleCaptureCallback listener, CaptureResult.Key<T> resultKey, List<T> expectedValues, int numResultsWait, int timeout)3054     public static <T> void waitForAnyResultValue(SimpleCaptureCallback listener,
3055             CaptureResult.Key<T> resultKey, List<T> expectedValues, int numResultsWait,
3056             int timeout) {
3057         if (numResultsWait < 0 || listener == null || expectedValues == null) {
3058             throw new IllegalArgumentException(
3059                     "Input must be non-negative number and listener/expectedValues "
3060                     + "must be non-null");
3061         }
3062 
3063         int i = 0;
3064         CaptureResult result;
3065         do {
3066             result = listener.getCaptureResult(timeout);
3067             T value = result.get(resultKey);
3068             for ( T expectedValue : expectedValues) {
3069                 if (VERBOSE) {
3070                     Log.v(TAG, "Current result value for key " + resultKey.getName() + " is: "
3071                             + value.toString());
3072                 }
3073                 if (value.equals(expectedValue)) {
3074                     return;
3075                 }
3076             }
3077         } while (i++ < numResultsWait);
3078 
3079         throw new TimeoutRuntimeException(
3080                 "Unable to get the expected result value " + expectedValues + " for key " +
3081                         resultKey.getName() + " after waiting for " + numResultsWait + " results");
3082     }
3083 
3084     /**
3085      * Wait for expected result key value available in a certain number of results.
3086      *
3087      * <p>
3088      * Check the result immediately if numFramesWait is 0.
3089      * </p>
3090      *
3091      * @param listener The capture listener to get capture result
3092      * @param resultKey The capture result key associated with the result value
3093      * @param expectedValue The result value need to be waited for
3094      * @param numResultsWait Number of frame to wait before times out
3095      * @param timeout Wait time out.
3096      * @throws TimeoutRuntimeException If more than numResultsWait results are
3097      * seen before the result matching myRequest arrives, or each individual wait
3098      * for result times out after 'timeout' ms.
3099      */
waitForResultValue(SimpleCaptureCallback listener, CaptureResult.Key<T> resultKey, T expectedValue, int numResultsWait, int timeout)3100     public static <T> void waitForResultValue(SimpleCaptureCallback listener,
3101             CaptureResult.Key<T> resultKey, T expectedValue, int numResultsWait, int timeout) {
3102         List<T> expectedValues = new ArrayList<T>();
3103         expectedValues.add(expectedValue);
3104         waitForAnyResultValue(listener, resultKey, expectedValues, numResultsWait, timeout);
3105     }
3106 
3107     /**
3108      * Wait for AE to be stabilized before capture: CONVERGED or FLASH_REQUIRED.
3109      *
3110      * <p>Waits for {@code android.sync.maxLatency} number of results first, to make sure
3111      * that the result is synchronized (or {@code numResultWaitForUnknownLatency} if the latency
3112      * is unknown.</p>
3113      *
3114      * <p>This is a no-op for {@code LEGACY} devices since they don't report
3115      * the {@code aeState} result.</p>
3116      *
3117      * @param resultListener The capture listener to get capture result back.
3118      * @param numResultWaitForUnknownLatency Number of frame to wait if camera device latency is
3119      *                                       unknown.
3120      * @param staticInfo corresponding camera device static metadata.
3121      * @param settingsTimeout wait timeout for settings application in ms.
3122      * @param resultTimeout wait timeout for result in ms.
3123      * @param numResultsWait Number of frame to wait before times out.
3124      */
waitForAeStable(SimpleCaptureCallback resultListener, int numResultWaitForUnknownLatency, StaticMetadata staticInfo, int settingsTimeout, int numResultWait)3125     public static void waitForAeStable(SimpleCaptureCallback resultListener,
3126             int numResultWaitForUnknownLatency, StaticMetadata staticInfo,
3127             int settingsTimeout, int numResultWait) {
3128         waitForSettingsApplied(resultListener, numResultWaitForUnknownLatency, staticInfo,
3129                 settingsTimeout);
3130 
3131         if (!staticInfo.isHardwareLevelAtLeastLimited()) {
3132             // No-op for metadata
3133             return;
3134         }
3135         List<Integer> expectedAeStates = new ArrayList<Integer>();
3136         expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_CONVERGED));
3137         expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED));
3138         waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, expectedAeStates,
3139                 numResultWait, settingsTimeout);
3140     }
3141 
3142     /**
3143      * Wait for enough results for settings to be applied
3144      *
3145      * @param resultListener The capture listener to get capture result back.
3146      * @param numResultWaitForUnknownLatency Number of frame to wait if camera device latency is
3147      *                                       unknown.
3148      * @param staticInfo corresponding camera device static metadata.
3149      * @param timeout wait timeout in ms.
3150      */
waitForSettingsApplied(SimpleCaptureCallback resultListener, int numResultWaitForUnknownLatency, StaticMetadata staticInfo, int timeout)3151     public static void waitForSettingsApplied(SimpleCaptureCallback resultListener,
3152             int numResultWaitForUnknownLatency, StaticMetadata staticInfo, int timeout) {
3153         int maxLatency = staticInfo.getSyncMaxLatency();
3154         if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
3155             maxLatency = numResultWaitForUnknownLatency;
3156         }
3157         // Wait for settings to take effect
3158         waitForNumResults(resultListener, maxLatency, timeout);
3159     }
3160 
getSuitableFpsRangeForDuration(String cameraId, long frameDuration, StaticMetadata staticInfo)3161     public static Range<Integer> getSuitableFpsRangeForDuration(String cameraId,
3162             long frameDuration, StaticMetadata staticInfo) {
3163         // Add 0.05 here so Fps like 29.99 evaluated to 30
3164         int minBurstFps = (int) Math.floor(1e9 / frameDuration + 0.05f);
3165         boolean foundConstantMaxYUVRange = false;
3166         boolean foundYUVStreamingRange = false;
3167         boolean isExternalCamera = staticInfo.isExternalCamera();
3168         boolean isNIR = staticInfo.isNIRColorFilter();
3169 
3170         // Find suitable target FPS range - as high as possible that covers the max YUV rate
3171         // Also verify that there's a good preview rate as well
3172         List<Range<Integer> > fpsRanges = Arrays.asList(
3173                 staticInfo.getAeAvailableTargetFpsRangesChecked());
3174         Range<Integer> targetRange = null;
3175         for (Range<Integer> fpsRange : fpsRanges) {
3176             if (fpsRange.getLower() == minBurstFps && fpsRange.getUpper() == minBurstFps) {
3177                 foundConstantMaxYUVRange = true;
3178                 targetRange = fpsRange;
3179             } else if (isExternalCamera && fpsRange.getUpper() == minBurstFps) {
3180                 targetRange = fpsRange;
3181             }
3182             if (fpsRange.getLower() <= 15 && fpsRange.getUpper() == minBurstFps) {
3183                 foundYUVStreamingRange = true;
3184             }
3185 
3186         }
3187 
3188         if (!isExternalCamera) {
3189             assertTrue(String.format("Cam %s: Target FPS range of (%d, %d) must be supported",
3190                     cameraId, minBurstFps, minBurstFps), foundConstantMaxYUVRange);
3191         }
3192 
3193         if (!isNIR) {
3194             assertTrue(String.format(
3195                     "Cam %s: Target FPS range of (x, %d) where x <= 15 must be supported",
3196                     cameraId, minBurstFps), foundYUVStreamingRange);
3197         }
3198         return targetRange;
3199     }
3200 }
3201