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