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) {