Camera2: Use device callbacks, and check for captureStarted callbacks

- Use BlockingStateListener
- CameraDeviceTest:
 - Wait on expected state transitions to verify state transitions
 - Add checks for shutter (CaptureStarted)
- Other tests use minimal waits to ensure correctness

Bug: 10360518
Change-Id: Ib177f4e633589b88e3df4a26aeae3d4b6a4f7a37
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 851ebf19..f68b10a 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -16,6 +16,10 @@
 
 package android.hardware.camera2.cts;
 
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
+import static org.mockito.Mockito.*;
+
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
@@ -33,8 +37,8 @@
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.ex.camera2.blocking.BlockingStateListener;
 import org.mockito.ArgumentMatcher;
-import static org.mockito.Mockito.*;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,9 +51,10 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private CameraManager mCameraManager;
-    private CameraDevice.StateListener mMockDeviceListener;
+    private BlockingStateListener mCameraListener;
     private CameraTestThread mLooperThread;
     private Handler mCallbackHandler;
+    private int mLatestState = STATE_UNINITIALIZED;
 
     /**
      * The error triggered flag starts out as false, and it will flip to true if any errors
@@ -62,7 +67,8 @@
     private CameraTestThread mDummyThread;
     private Surface mSurface;
 
-    private static final int CAPTURE_WAIT_TIMEOUT_MS = 1000;
+    private static final int CAMERA_CONFIGURE_TIMEOUT_MS = 2000;
+    private static final int CAPTURE_WAIT_TIMEOUT_MS = 2000;
     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.
@@ -92,7 +98,7 @@
          * Use spy object here since we want to use the SimpleDeviceListener callback
          * implementation (spy doesn't stub the functions unless we ask it to do so).
          */
-        mMockDeviceListener = spy(new SimpleDeviceListener());
+        mCameraListener = spy(new BlockingStateListener());
     }
 
     @Override
@@ -104,7 +110,14 @@
          * 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);
+        verify(mCameraListener, never())
+                .onError(
+                    any(CameraDevice.class),
+                    anyInt());
+        verify(mCameraListener, never())
+                .onDisconnected(
+                    any(CameraDevice.class));
+
         mCameraManager = (CameraManager)mContext.getSystemService(Context.CAMERA_SERVICE);
         assertNotNull("Can't connect to camera manager", mCameraManager);
         createDefaultSurface();
@@ -119,75 +132,6 @@
         super.tearDown();
     }
 
-    /**
-     * This class need to be public because spy need access it.
-     *
-     * <p><b>Warning</b>: {@link #onOpened} does nothing, so only use this with the
-     * {@link CameraTestUtils#openCamera}
-     * If using this with {@link CameraManager#openDevice} directly,
-     * remember to implement openCamera!</p>
-     */
-    public class SimpleDeviceListener extends CameraDevice.StateListener {
-        private final Object mIdleLock = new Object();
-        private boolean mIdle = false;
-
-        public SimpleDeviceListener() {
-        }
-
-        // Wait for idle to occur, with a timeout in milliseconds.
-        // A timeout of 0 means indefinite wait
-        public void waitForIdle(long timeout) {
-            synchronized(mIdleLock) {
-                if (!mIdle) {
-                    try {
-                        if (timeout > 0) {
-                            mIdleLock.wait(timeout);
-                        } else {
-                            mIdleLock.wait();
-                        }
-                    } catch (InterruptedException e) {
-                        // Probably fail the idle assert, but needs no other
-                        // action
-                    }
-                    assertTrue("Timeout waiting for camera device idle", mIdle);
-                }
-                mIdle = false;
-            }
-        }
-
-        // Clear idle flag
-        public void clearIdleFlag() {
-            synchronized(mIdleLock) {
-                mIdle = false;
-            }
-        }
-
-        @Override
-        public void onIdle(CameraDevice camera) {
-            synchronized(mIdleLock) {
-                mIdle = true;
-                mIdleLock.notifyAll();
-            }
-        }
-
-        @Override
-        public void onDisconnected(CameraDevice camera) {
-            // Not expecting disconnections
-            mErrorTriggered = true;
-        }
-
-        @Override
-        public void onError(CameraDevice camera, int error) {
-            mErrorTriggered = true;
-        }
-
-        @Override
-        public void onOpened(CameraDevice camera) {
-            // Do nothing. Handled by CameraTestUtils#openCamera
-            // TODO: If using this listener with CameraManager#openCamera, IMPLEMENT THIS.
-        }
-    }
-
     public void testCameraDeviceCreateCaptureBuilder() throws Exception {
         String[] ids = mCameraManager.getCameraIdList();
         for (int i = 0; i < ids.length; i++) {
@@ -226,7 +170,7 @@
             CameraDevice camera = null;
             try {
                 camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
-                        mMockDeviceListener, mCallbackHandler);
+                        mCameraListener, mCallbackHandler);
                 assertNotNull(
                         String.format("Failed to open camera device %s", ids[i]), camera);
 
@@ -235,7 +179,7 @@
                  * Also, wait some time to check if device doesn't run into error.
                  */
                 SystemClock.sleep(ERROR_LISTENER_WAIT_TIMEOUT_MS);
-                verify(mMockDeviceListener, never())
+                verify(mCameraListener, never())
                         .onError(
                                 any(CameraDevice.class),
                                 anyInt());
@@ -287,9 +231,10 @@
             CameraDevice camera = null;
             try {
                 camera = CameraTestUtils.openCamera(mCameraManager, ids[i],
-                        mMockDeviceListener, mCallbackHandler);
+                        mCameraListener, mCallbackHandler);
                 assertNotNull(
                         String.format("Failed to open camera device %s", ids[i]), camera);
+                waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
 
                 prepareCapture(camera);
 
@@ -320,7 +265,7 @@
                     // Test: burst of 5 shots of different template types
                     captureBurstShot(camera, ids[i], mTemplates, mTemplates.length, repeating);
                 }
-                verify(mMockDeviceListener, never())
+                verify(mCameraListener, never())
                         .onError(
                                 any(CameraDevice.class),
                                 anyInt());
@@ -339,6 +284,9 @@
             int template,
             boolean repeating) throws Exception {
 
+        assertEquals("Bad initial state for preparing to capture",
+                mLatestState, STATE_IDLE);
+
         CaptureRequest.Builder requestBuilder = camera.createCaptureRequest(template);
         assertNotNull("Failed to create capture request", requestBuilder);
         requestBuilder.addTarget(mSurface);
@@ -356,14 +304,15 @@
             camera.setRepeatingRequest(requestBuilder.build(), mockCaptureListener,
                     mCallbackHandler);
         }
+        waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
 
         int expectedCaptureResultCount = repeating ? REPEATING_CAPTURE_EXPECTED_RESULT_COUNT : 1;
         verifyCaptureResults(camera, mockCaptureListener, expectedCaptureResultCount);
 
         if (repeating) {
             camera.stopRepeating();
-            camera.waitUntilIdle();
         }
+        waitForState(STATE_IDLE, CAMERA_CONFIGURE_TIMEOUT_MS);
     }
 
     private void captureBurstShot(
@@ -373,6 +322,9 @@
             int len,
             boolean repeating) throws Exception {
 
+        assertEquals("Bad initial state for preparing to capture",
+                mLatestState, STATE_IDLE);
+
         assertTrue("Invalid args to capture function", len <= templates.length);
         List<CaptureRequest> requests = new ArrayList<CaptureRequest>();
         for (int i = 0; i < len; i++) {
@@ -394,6 +346,8 @@
         else {
             camera.setRepeatingBurst(requests, mockCaptureListener, mCallbackHandler);
         }
+        waitForState(STATE_ACTIVE, CAMERA_CONFIGURE_TIMEOUT_MS);
+
         int expectedResultCount = len;
         if (repeating) {
             expectedResultCount *= REPEATING_CAPTURE_EXPECTED_RESULT_COUNT;
@@ -403,14 +357,20 @@
 
         if (repeating) {
             camera.stopRepeating();
-            camera.waitUntilIdle();
         }
+        waitForState(STATE_IDLE, CAMERA_CONFIGURE_TIMEOUT_MS);
     }
 
+    // Precondition: Device must be in known IDLE/UNCONFIGURED state (has been waited for)
     private void prepareCapture(CameraDevice camera) throws Exception {
+        assertTrue("Bad initial state for preparing to capture",
+                mLatestState == STATE_IDLE || mLatestState == STATE_UNCONFIGURED);
+
         List<Surface> outputSurfaces = new ArrayList<Surface>(1);
         outputSurfaces.add(mSurface);
         camera.configureOutputs(outputSurfaces);
+        waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+        waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
     }
 
     /**
@@ -445,6 +405,11 @@
         mReader.setOnImageAvailableListener(listener, mDummyThread.start());
     }
 
+    private void waitForState(int state, long timeout) {
+        mCameraListener.waitForState(state, timeout);
+        mLatestState = state;
+    }
+
     private void verifyCaptureResults(
             CameraDevice camera,
             CameraDevice.CaptureListener mockListener,
@@ -462,5 +427,14 @@
                                 eq(camera),
                                 argThat(new IsCameraMetadataNotEmpty<CaptureRequest>()),
                                 isA(CaptureFailure.class));
+        // Should receive expected number of capture shutter calls
+        verify(mockListener,
+                atLeast(expectResultCount))
+                        .onCaptureStarted(
+                               eq(camera),
+                               isA(CaptureRequest.class),
+                               anyLong());
+
     }
+
 }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index f3a4fa7..a2d965c 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -46,28 +46,11 @@
     private static final String TAG = "CameraTestUtils";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    /**
-     * Provide a default implementation of CameraDevice.StateListener that does nothing.
-     */
-    public abstract static class DeviceStateListener extends CameraDevice.StateListener {
-        public DeviceStateListener() {
-        }
-
-        @Override
-        public void onOpened(CameraDevice camera) {
-            // Do nothing. It should be handled by #openCamera
-        }
-
-        @Override
-        public void onDisconnected(CameraDevice camera) {
-            // Do nothing
-        }
-
-        @Override
-        public void onError(CameraDevice camera, int error) {
-            // Do nothing
-        }
-    }
+    // Default timeouts for reaching various states
+    public static final int CAMERA_OPEN_TIMEOUT_MS = 500;
+    public static final int CAMERA_IDLE_TIMEOUT_MS = 2000;
+    public static final int CAMERA_ACTIVE_TIMEOUT_MS = 500;
+    public static final int CAMERA_BUSY_TIMEOUT_MS = 500;
 
     /**
      * Block until the camera is opened.
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
index 1dccb7b..b117f4b 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateListener.*;
 
 import android.content.Context;
 import android.graphics.BitmapFactory;
@@ -36,6 +37,7 @@
 import android.view.Surface;
 
 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateListener;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -65,6 +67,7 @@
 
     private CameraManager mCameraManager;
     private CameraDevice mCamera;
+    private BlockingStateListener mCameraListener;
     private String[] mCameraIds;
     private ImageReader mReader = null;
     private Handler mHandler = null;
@@ -84,6 +87,7 @@
         mCameraIds = mCameraManager.getCameraIdList();
         mLooperThread = new CameraTestThread();
         mHandler = mLooperThread.start();
+        mCameraListener = new BlockingStateListener();
     }
 
     @Override
@@ -221,6 +225,8 @@
         assertNotNull("Fail to get surface from ImageReader", surface);
         outputSurfaces.add(surface);
         mCamera.configureOutputs(outputSurfaces);
+        mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+        mCameraListener.waitForState(STATE_IDLE, CAMERA_IDLE_TIMEOUT_MS);
 
         CaptureRequest.Builder captureBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -235,6 +241,7 @@
         // TODO: Add more format here, and wrap each one as a function.
         Image img;
         int captureCount = NUM_FRAME_VERIFIED;
+
         // Only verify single image for still capture
         if (format == ImageFormat.JPEG) {
             captureCount = 1;
@@ -263,8 +270,12 @@
     }
 
     private void stopCapture() throws CameraAccessException {
-        mCamera.stopRepeating();
-        mCamera.waitUntilIdle();
+        if (VERBOSE) Log.v(TAG, "Stopping capture and waiting for idle");
+        // Stop repeat, wait for captures to complete, and disconnect from surfaces
+        mCamera.configureOutputs(/*outputs*/ null);
+        mCameraListener.waitForState(STATE_BUSY, CAMERA_BUSY_TIMEOUT_MS);
+        mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_IDLE_TIMEOUT_MS);
+        // Camera has disconnected, clear out the reader
         mReader.close();
         mReader = null;
         mListener = null;
@@ -275,7 +286,8 @@
             throw new IllegalStateException("Already have open camera device");
         }
         try {
-            mCamera = openCamera(mCameraManager, cameraId, mHandler);
+            mCamera = CameraTestUtils.openCamera(
+                mCameraManager, cameraId, mCameraListener, mHandler);
         } catch (CameraAccessException e) {
             mCamera = null;
             fail("Fail to open camera, " + Log.getStackTraceString(e));
@@ -283,6 +295,7 @@
             mCamera = null;
             fail("Fail to open camera, " + Log.getStackTraceString(e));
         }
+        mCameraListener.waitForState(STATE_UNCONFIGURED, CAMERA_OPEN_TIMEOUT_MS);
     }
 
     private void closeDevice(String cameraId) {