Camera2: CTS test for CameraDevice class.

Bug: 9802344

Change-Id: I34df48f9b7b07307ae87d0406fbbf27b2874360b
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index 76088a0..77bd2fb 100644
--- a/tests/tests/hardware/Android.mk
+++ b/tests/tests/hardware/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner mockito-target
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
new file mode 100644
index 0000000..8509491
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraProperties;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import org.mockito.ArgumentMatcher;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>Basic test for CameraDevice APIs.</p>
+ */
+public class CameraDeviceTest extends AndroidTestCase {
+    private static final String TAG = "CameraDeviceTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private CameraManager mCameraManager;
+    private CameraDevice.ErrorListener mMockErrorListener;
+
+    /**
+     * The error triggered flag starts out as false, and it will flip to true if any errors
+     * are ever caught; it won't be reset to false after that happens. This is due to the
+     * fact that when multiple tests are run back to back (as they are here), it's hard
+     * to associate the asynchronous error with the test that caused it (so we won't even try).
+     */
+    private boolean mErrorTriggered = false;
+    private ImageReader mReader;
+    private CameraTestThread mDummyThread;
+    private Surface mSurface;
+
+    private static final int CAPTURE_WAIT_TIMEOUT_MS = 1000;
+    private static final int ERROR_LISTENER_WAIT_TIMEOUT_MS = 1000;
+    private static final int REPEATING_CAPTURE_EXPECTED_RESULT_COUNT = 5;
+    // VGA size capture is required by CDD.
+    private static final int DEFAULT_CAPTURE_WIDTH = 640;
+    private static final int DEFAULT_CAPTURE_HEIGHT = 480;
+    private static final int MAX_NUM_IMAGES = 5;
+
+    private static int[] mTemplates = new int[] {
+            CameraDevice.TEMPLATE_MANUAL,
+            CameraDevice.TEMPLATE_PREVIEW,
+            CameraDevice.TEMPLATE_RECORD,
+            CameraDevice.TEMPLATE_STILL_CAPTURE,
+            CameraDevice.TEMPLATE_VIDEO_SNAPSHOT
+    };
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+        /**
+         * Workaround for mockito and JB-MR2 incompatibility
+         *
+         * Avoid java.lang.IllegalArgumentException: dexcache == null
+         * https://code.google.com/p/dexmaker/issues/detail?id=2
+         */
+        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
+        /**
+         * Create errorlistener in context scope, to catch asynchronous device error.
+         * Use spy object here since we want to use the SimpleErrorListener callback
+         * implementation (spy doesn't stub the functions unless we ask it to do so).
+         */
+        mMockErrorListener = spy(new SimpleErrorListener());
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        /**
+         * Due to the asynchronous nature of camera device error callback, we
+         * have to make sure device doesn't run into error state before. If so,
+         * fail the rest of the tests. This is especially needed when error
+         * callback is fired too late.
+         */
+        assertFalse("Camera Device runs into error state", mErrorTriggered);
+        mCameraManager = (CameraManager)mContext.getSystemService(Context.CAMERA_SERVICE);
+        assertNotNull("Can't connect to camera manager", mCameraManager);
+        createDefaultSurface();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDummyThread.close();
+        mReader.close();
+        super.tearDown();
+    }
+
+    /**
+     * This class need to be public because spy need access it.
+     */
+    public class SimpleErrorListener implements CameraDevice.ErrorListener {
+        @Override
+        public void onCameraDeviceError(CameraDevice camera, int error) {
+            mErrorTriggered = true;
+        }
+    }
+
+    public void testCameraDeviceCreateCaptureRequest() throws Exception {
+        String[] ids = mCameraManager.getDeviceIdList();
+        for (int i = 0; i < ids.length; i++) {
+            CameraDevice camera = null;
+            try {
+                camera = mCameraManager.openCamera(ids[i]);
+                assertNotNull(
+                        String.format("Failed to open camera device ID: %s", ids[i]), camera);
+
+                /**
+                 * Test: that each template type is supported, and that its required fields are
+                 * present.
+                 */
+                for (int j = 0; j < mTemplates.length; j++) {
+                    CaptureRequest capReq = camera.createCaptureRequest(mTemplates[j]);
+                    assertNotNull("Failed to create capture request", capReq);
+                    assertNotNull("Missing field: SENSOR_EXPOSURE_TIME",
+                            capReq.get(CaptureRequest.SENSOR_EXPOSURE_TIME));
+                    assertNotNull("Missing field: SENSOR_SENSITIVITY",
+                            capReq.get(CaptureRequest.SENSOR_SENSITIVITY));
+
+                    // TODO: Add more tests to check more fields.
+                }
+            }
+            finally {
+                if (camera != null) {
+                    camera.close();
+                }
+            }
+        }
+    }
+
+    public void testCameraDeviceGetProperties() throws Exception {
+        String[] ids = mCameraManager.getDeviceIdList();
+        for (int i = 0; i < ids.length; i++) {
+            CameraDevice camera = null;
+            try {
+                camera = mCameraManager.openCamera(ids[i]);
+                assertNotNull(
+                        String.format("Failed to open camera device %s", ids[i]), camera);
+
+                /**
+                 * Test: that the properties can be queried for this device.
+                 */
+                CameraProperties props = camera.getProperties();
+                assertNotNull("Failed to get camera properties", props);
+
+                // TODO: Add more tests to check more fields.
+            }
+            finally {
+                if (camera != null) {
+                    camera.close();
+                }
+            }
+        }
+    }
+
+    public void testCameraDeviceSetErrorListener() throws Exception {
+        String[] ids = mCameraManager.getDeviceIdList();
+        for (int i = 0; i < ids.length; i++) {
+            CameraDevice camera = null;
+            try {
+                camera = mCameraManager.openCamera(ids[i]);
+                assertNotNull(
+                        String.format("Failed to open camera device %s", ids[i]), camera);
+
+                /**
+                 * Test: that the error listener can be set without problems.
+                 * Also, wait some time to check if device doesn't run into error.
+                 */
+                camera.setErrorListener(mMockErrorListener);
+                SystemClock.sleep(ERROR_LISTENER_WAIT_TIMEOUT_MS);
+                verify(mMockErrorListener, never())
+                        .onCameraDeviceError(
+                                any(CameraDevice.class),
+                                anyInt());
+            }
+            finally {
+                if (camera != null) {
+                    camera.close();
+                }
+            }
+        }
+    }
+
+    public void testCameraDeviceCapture() throws Exception {
+        runCaptureTest(false, false);
+    }
+
+    public void testCameraDeviceCaptureBurst() throws Exception {
+        runCaptureTest(true, false);
+    }
+
+    public void testCameraDeviceRepeatingRequest() throws Exception {
+        runCaptureTest(false, true);
+    }
+
+    public void testCameraDeviceRepeatingBurst() throws Exception {
+        runCaptureTest(true, true);
+    }
+
+    private class IsCameraMetadataNotEmpty<T extends CameraMetadata>
+            extends ArgumentMatcher<T> {
+        public boolean matches(Object obj) {
+            /**
+             * Do the simple verification here. Only verify the timestamp for now.
+             * TODO: verify more required capture result metadata fields.
+             */
+            CameraMetadata result = (CameraMetadata) obj;
+            Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
+            if (timeStamp != null && timeStamp.longValue() > 0L) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private void runCaptureTest(boolean burst, boolean repeating) throws Exception {
+        String[] ids = mCameraManager.getDeviceIdList();
+        for (int i = 0; i < ids.length; i++) {
+            CameraDevice camera = null;
+            try {
+                camera = mCameraManager.openCamera(ids[i]);
+                assertNotNull(
+                        String.format("Failed to open camera device %s", ids[i]), camera);
+                camera.setErrorListener(mMockErrorListener);
+
+                prepareCapture(camera);
+
+                if (!burst) {
+                    // Test: that a single capture of each template type succeeds.
+                    for (int j = 0; j < mTemplates.length; j++) {
+                        captureSingleShot(camera, ids[i], mTemplates[j], repeating);
+                    }
+                }
+                else {
+                    // Test: burst of zero shots
+                    captureBurstShot(camera, ids[i], mTemplates, 0, repeating);
+
+                    // Test: burst of one shot
+                    captureBurstShot(camera, ids[i], mTemplates, 1, repeating);
+
+                    int[] templates = new int[] {
+                            CameraDevice.TEMPLATE_STILL_CAPTURE,
+                            CameraDevice.TEMPLATE_STILL_CAPTURE,
+                            CameraDevice.TEMPLATE_STILL_CAPTURE,
+                            CameraDevice.TEMPLATE_STILL_CAPTURE,
+                            CameraDevice.TEMPLATE_STILL_CAPTURE
+                            };
+
+                    // Test: burst of 5 shots of the same template type
+                    captureBurstShot(camera, ids[i], templates, templates.length, repeating);
+
+                    // Test: burst of 5 shots of different template types
+                    captureBurstShot(camera, ids[i], mTemplates, mTemplates.length, repeating);
+                }
+                verify(mMockErrorListener, never())
+                        .onCameraDeviceError(
+                                any(CameraDevice.class),
+                                anyInt());
+            }
+            finally {
+                if (camera != null) {
+                    camera.close();
+                }
+            }
+        }
+    }
+
+    private void captureSingleShot(
+            CameraDevice camera,
+            String id,
+            int template,
+            boolean repeating) throws Exception {
+
+        CaptureRequest request = camera.createCaptureRequest(template);
+        assertNotNull("Failed to create capture request", request);
+        request.addTarget(mSurface);
+        CameraDevice.CaptureListener mockCaptureListener =
+                mock(CameraDevice.CaptureListener.class);
+
+        if (VERBOSE) {
+            Log.v(TAG, String.format("Capturing shot for device %s, template %d",
+                    id, template));
+        }
+        if (!repeating) {
+            camera.capture(request, mockCaptureListener);
+        }
+        else {
+            camera.setRepeatingRequest(request, mockCaptureListener);
+        }
+
+        int expectedCaptureResultCount = repeating ? REPEATING_CAPTURE_EXPECTED_RESULT_COUNT : 1;
+        verifyCaptureResults(mockCaptureListener, expectedCaptureResultCount);
+
+        if (repeating) {
+            camera.stopRepeating();
+            camera.waitUntilIdle();
+        }
+    }
+
+    private void captureBurstShot(
+            CameraDevice camera,
+            String id,
+            int[] templates,
+            int len,
+            boolean repeating) throws Exception {
+
+        assertTrue("Invalid args to capture function", len <= templates.length);
+        List<CaptureRequest> requests = new ArrayList<CaptureRequest>();
+        for (int i = 0; i < len; i++) {
+            CaptureRequest request = camera.createCaptureRequest(templates[i]);
+            assertNotNull("Failed to create capture request", request);
+            request.addTarget(mSurface);
+            requests.add(request);
+        }
+        CameraDevice.CaptureListener mockCaptureListener =
+                mock(CameraDevice.CaptureListener.class);
+
+        if (VERBOSE) {
+            Log.v(TAG, String.format("Capturing burst shot for device %s", id));
+        }
+
+        if (!repeating) {
+            camera.captureBurst(requests, mockCaptureListener);
+        }
+        else {
+            camera.setRepeatingBurst(requests, mockCaptureListener);
+        }
+        int expectedResultCount = len;
+        if (repeating) {
+            expectedResultCount *= REPEATING_CAPTURE_EXPECTED_RESULT_COUNT;
+        }
+
+        verifyCaptureResults(mockCaptureListener, expectedResultCount);
+
+        if (repeating) {
+            camera.stopRepeating();
+            camera.waitUntilIdle();
+        }
+    }
+
+    private void prepareCapture(CameraDevice camera) throws Exception {
+        List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+        outputSurfaces.add(mSurface);
+        camera.configureOutputs(outputSurfaces);
+    }
+
+    /**
+     * Dummy listener that release the image immediately once it is available.
+     * It can be used for the case where we don't care the image data at all.
+     * TODO: move it to the CameraTestUtil class.
+     */
+    private class ImageDropperListener implements ImageReader.OnImageAvailableListener {
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            Image image = reader.getNextImage();
+            image.close();
+        }
+    }
+
+    private void createDefaultSurface() throws Exception {
+        ImageReader mReader =
+                new ImageReader(DEFAULT_CAPTURE_WIDTH,
+                        DEFAULT_CAPTURE_HEIGHT,
+                        ImageFormat.YUV_420_888,
+                        MAX_NUM_IMAGES);
+        mSurface = mReader.getSurface();
+        // Create dummy image listener since we don't care the image data in this test.
+        ImageReader.OnImageAvailableListener listener = new ImageDropperListener();
+        CameraTestThread mDummyThread = new CameraTestThread();
+        mReader.setImageAvailableListener(listener, mDummyThread.start());
+    }
+
+    private void verifyCaptureResults(
+            CameraDevice.CaptureListener mockListener,
+            int expectResultCount) {
+        // Should receive expected number of capture results.
+        verify(mockListener,
+                timeout(CAPTURE_WAIT_TIMEOUT_MS).atLeast(expectResultCount))
+                        .onCaptureComplete(
+                                any(CameraDevice.class),
+                                argThat(new IsCameraMetadataNotEmpty<CaptureRequest>()),
+                                argThat(new IsCameraMetadataNotEmpty<CaptureResult>()));
+        // Should not receive any capture failed callbacks.
+        verify(mockListener, never())
+                        .onCaptureFailed(
+                                any(CameraDevice.class),
+                                argThat(new IsCameraMetadataNotEmpty<CaptureRequest>()));
+    }
+}