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