Camera2: Fire all callbacks
- Allow configureOutputs to be called when device is
actively working, and document what happens then.
- At the managed level, trigger proper sequence of callbacks for
device state changes, for all the callbacks that are not yet
managed by the camera service.
- Restructure CameraManager.openDevice to have the device itself fire the
initial callbacks.
- Make CameraDevice.configureOutputs(null) work.
- Make CameraDeviec.configureOutputs(identical surfaces) work.
- Ensure proper checking for camera closed state.
Bug: 10360518
Change-Id: I9db348ee9c5ce4d3fe02fd34e779acc85cba68dc
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 463063c..c5d0999 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -55,8 +55,10 @@
private final Object mLock = new Object();
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
- private StateListener mDeviceListener;
- private Handler mDeviceHandler;
+ private final StateListener mDeviceListener;
+ private final Handler mDeviceHandler;
+
+ private boolean mIdle = true;
private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
new SparseArray<CaptureListenerHolder>();
@@ -67,8 +69,72 @@
private final String mCameraId;
- public CameraDevice(String cameraId) {
+ // Runnables for all state transitions, except error, which needs the
+ // error code argument
+
+ private final Runnable mCallOnOpened = new Runnable() {
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onOpened(CameraDevice.this);
+ }
+ }
+ };
+
+ private final Runnable mCallOnUnconfigured = new Runnable() {
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onUnconfigured(CameraDevice.this);
+ }
+ }
+ };
+
+ private final Runnable mCallOnActive = new Runnable() {
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onActive(CameraDevice.this);
+ }
+ }
+ };
+
+ private final Runnable mCallOnBusy = new Runnable() {
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onBusy(CameraDevice.this);
+ }
+ }
+ };
+
+ private final Runnable mCallOnClosed = new Runnable() {
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onClosed(CameraDevice.this);
+ }
+ }
+ };
+
+ private final Runnable mCallOnIdle = new Runnable() {
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onIdle(CameraDevice.this);
+ }
+ }
+ };
+
+ private final Runnable mCallOnDisconnected = new Runnable() {
+ public void run() {
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onDisconnected(CameraDevice.this);
+ }
+ }
+ };
+
+ public CameraDevice(String cameraId, StateListener listener, Handler handler) {
+ if (cameraId == null || listener == null || handler == null) {
+ throw new IllegalArgumentException("Null argument given");
+ }
mCameraId = cameraId;
+ mDeviceListener = listener;
+ mDeviceHandler = handler;
TAG = String.format("CameraDevice-%s-JV", mCameraId);
DEBUG = Log.isLoggable(TAG, Log.DEBUG);
}
@@ -79,7 +145,12 @@
public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
// TODO: Move from decorator to direct binder-mediated exceptions
- mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
+ synchronized(mLock) {
+ mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
+
+ mDeviceHandler.post(mCallOnOpened);
+ mDeviceHandler.post(mCallOnUnconfigured);
+ }
}
@Override
@@ -89,7 +160,13 @@
@Override
public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
+ // Treat a null input the same an empty list
+ if (outputs == null) {
+ outputs = new ArrayList<Surface>();
+ }
synchronized (mLock) {
+ checkIfCameraClosed();
+
HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete
@@ -105,9 +182,13 @@
}
}
- try {
- // TODO: mRemoteDevice.beginConfigure
+ mDeviceHandler.post(mCallOnBusy);
+ stopRepeating();
+ try {
+ mRemoteDevice.waitUntilIdle();
+
+ // TODO: mRemoteDevice.beginConfigure
// Delete all streams first (to free up HW resources)
for (Integer streamId : deleteList) {
mRemoteDevice.deleteStream(streamId);
@@ -126,7 +207,7 @@
} catch (CameraRuntimeException e) {
if (e.getReason() == CAMERA_IN_USE) {
throw new IllegalStateException("The camera is currently busy." +
- " You must call waitUntilIdle before trying to reconfigure.");
+ " You must wait until the previous operation completes.");
}
throw e.asChecked();
@@ -134,6 +215,12 @@
// impossible
return;
}
+
+ if (outputs.size() > 0) {
+ mDeviceHandler.post(mCallOnIdle);
+ } else {
+ mDeviceHandler.post(mCallOnUnconfigured);
+ }
}
}
@@ -141,6 +228,7 @@
public CaptureRequest.Builder createCaptureRequest(int templateType)
throws CameraAccessException {
synchronized (mLock) {
+ checkIfCameraClosed();
CameraMetadataNative templatedRequest = new CameraMetadataNative();
@@ -188,7 +276,7 @@
}
synchronized (mLock) {
-
+ checkIfCameraClosed();
int requestId;
try {
@@ -208,6 +296,11 @@
mRepeatingRequestIdStack.add(requestId);
}
+ if (mIdle) {
+ mDeviceHandler.post(mCallOnActive);
+ }
+ mIdle = false;
+
return requestId;
}
}
@@ -233,7 +326,7 @@
public void stopRepeating() throws CameraAccessException {
synchronized (mLock) {
-
+ checkIfCameraClosed();
while (!mRepeatingRequestIdStack.isEmpty()) {
int requestId = mRepeatingRequestIdStack.pop();
@@ -270,20 +363,11 @@
}
@Override
- public void setDeviceListener(StateListener listener, Handler handler) {
- synchronized (mLock) {
- if (listener != null) {
- handler = checkHandler(handler);
- }
-
- mDeviceListener = listener;
- mDeviceHandler = handler;
- }
- }
-
- @Override
public void flush() throws CameraAccessException {
synchronized (mLock) {
+ checkIfCameraClosed();
+
+ mDeviceHandler.post(mCallOnBusy);
try {
mRemoteDevice.flush();
} catch (CameraRuntimeException e) {
@@ -297,9 +381,6 @@
@Override
public void close() {
-
- // TODO: every method should throw IllegalStateException after close has been called
-
synchronized (mLock) {
try {
@@ -312,8 +393,11 @@
// impossible
}
- mRemoteDevice = null;
+ if (mRemoteDevice != null) {
+ mDeviceHandler.post(mCallOnClosed);
+ }
+ mRemoteDevice = null;
}
}
@@ -399,49 +483,44 @@
@Override
public void onCameraError(final int errorCode) {
- synchronized (mLock) {
- if (CameraDevice.this.mDeviceListener == null) return;
- final StateListener listener = CameraDevice.this.mDeviceListener;
- Runnable r = null;
+ Runnable r = null;
+ if (isClosed()) return;
+
+ synchronized(mLock) {
switch (errorCode) {
case ERROR_CAMERA_DISCONNECTED:
- r = new Runnable() {
- public void run() {
- listener.onDisconnected(CameraDevice.this);
- }
- };
+ r = mCallOnDisconnected;
break;
+ default:
+ Log.e(TAG, "Unknown error from camera device: " + errorCode);
+ // no break
case ERROR_CAMERA_DEVICE:
case ERROR_CAMERA_SERVICE:
r = new Runnable() {
public void run() {
- listener.onError(CameraDevice.this, errorCode);
+ if (!CameraDevice.this.isClosed()) {
+ mDeviceListener.onError(CameraDevice.this, errorCode);
+ }
}
};
break;
- default:
- Log.e(TAG, "Unknown error from camera device: " + errorCode);
}
- if (r != null) {
- CameraDevice.this.mDeviceHandler.post(r);
- }
+ CameraDevice.this.mDeviceHandler.post(r);
}
}
@Override
public void onCameraIdle() {
+ if (isClosed()) return;
+
if (DEBUG) {
Log.d(TAG, "Camera now idle");
}
synchronized (mLock) {
- if (CameraDevice.this.mDeviceListener == null) return;
- final StateListener listener = CameraDevice.this.mDeviceListener;
- Runnable r = new Runnable() {
- public void run() {
- listener.onIdle(CameraDevice.this);
- }
- };
- CameraDevice.this.mDeviceHandler.post(r);
+ if (!CameraDevice.this.mIdle) {
+ CameraDevice.this.mDeviceHandler.post(mCallOnIdle);
+ }
+ CameraDevice.this.mIdle = true;
}
}
@@ -461,14 +540,18 @@
return;
}
+ if (isClosed()) return;
+
// Dispatch capture start notice
holder.getHandler().post(
new Runnable() {
public void run() {
- holder.getListener().onCaptureStarted(
- CameraDevice.this,
- holder.getRequest(),
- timestamp);
+ if (!CameraDevice.this.isClosed()) {
+ holder.getListener().onCaptureStarted(
+ CameraDevice.this,
+ holder.getRequest(),
+ timestamp);
+ }
}
});
}
@@ -503,6 +586,8 @@
return;
}
+ if (isClosed()) return;
+
final CaptureRequest request = holder.getRequest();
final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
@@ -510,10 +595,12 @@
new Runnable() {
@Override
public void run() {
- holder.getListener().onCaptureCompleted(
- CameraDevice.this,
- request,
- resultAsCapture);
+ if (!CameraDevice.this.isClosed()){
+ holder.getListener().onCaptureCompleted(
+ CameraDevice.this,
+ request,
+ resultAsCapture);
+ }
}
});
}
@@ -541,4 +628,10 @@
throw new IllegalStateException("CameraDevice was already closed");
}
}
+
+ private boolean isClosed() {
+ synchronized(mLock) {
+ return (mRemoteDevice == null);
+ }
+ }
}