Camera2: Invoke onError callbacks for failure to open
When the initial attempt to connect to the remote camera device fails,
fire the onError callback as documented, instead of throwing an
exception from open().
Also ensure the correct exception is sent when methods are called
while in the error state, and make sure onClosed() is called correctly
if closing the device after an initial startup error.
Bug: 14413756
Bug: 14413363
Change-Id: I0822261dad52bcd428a0c4556202f00032499990
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index e21fb1f..9046b13 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -226,7 +226,7 @@
synchronized (mLock) {
- ICameraDeviceUser cameraUser;
+ ICameraDeviceUser cameraUser = null;
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
new android.hardware.camera2.impl.CameraDeviceImpl(
@@ -248,8 +248,23 @@
// Use legacy camera implementation for HAL1 devices
Log.i(TAG, "Using legacy camera HAL.");
cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+ } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
+ e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
+ e.getReason() == CameraAccessException.CAMERA_DISABLED ||
+ e.getReason() == CameraAccessException.CAMERA_DISCONNECTED ||
+ e.getReason() == CameraAccessException.CAMERA_ERROR) {
+ // Received one of the known connection errors
+ // The remote camera device cannot be connected to, so
+ // set the local camera to the startup error state
+ deviceImpl.setRemoteFailure(e);
+
+ if (e.getReason() == CameraAccessException.CAMERA_DISABLED ||
+ e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) {
+ // Per API docs, these failures call onError and throw
+ throw e;
+ }
} else {
- // Rethrow otherwise
+ // Unexpected failure - rethrow
throw e;
}
}
@@ -299,7 +314,7 @@
*
* <p>If opening the camera device fails, then the device listener's
* {@link CameraDevice.StateListener#onError onError} method will be called, and subsequent
- * calls on the camera device will throw an {@link IllegalStateException}.</p>
+ * calls on the camera device will throw a {@link CameraAccessException}.</p>
*
* @param cameraId
* The unique identifier of the camera device to open
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 81bd2fd..9795082 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -64,6 +64,7 @@
private volatile StateListener mSessionStateListener;
private final Handler mDeviceHandler;
+ private boolean mInError = false;
private boolean mIdle = true;
/** map request IDs to listener/request data */
@@ -211,6 +212,9 @@
public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
// TODO: Move from decorator to direct binder-mediated exceptions
synchronized(mLock) {
+ // If setRemoteFailure already called, do nothing
+ if (mInError) return;
+
mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
mDeviceHandler.post(mCallOnOpened);
@@ -218,6 +222,52 @@
}
}
+ /**
+ * Call to indicate failed connection to a remote camera device.
+ *
+ * <p>This places the camera device in the error state and informs the listener.
+ * Use in place of setRemoteDevice() when startup fails.</p>
+ */
+ public void setRemoteFailure(final CameraRuntimeException failure) {
+ int failureCode = StateListener.ERROR_CAMERA_DEVICE;
+ boolean failureIsError = true;
+
+ switch (failure.getReason()) {
+ case CameraAccessException.CAMERA_IN_USE:
+ failureCode = StateListener.ERROR_CAMERA_IN_USE;
+ break;
+ case CameraAccessException.MAX_CAMERAS_IN_USE:
+ failureCode = StateListener.ERROR_MAX_CAMERAS_IN_USE;
+ break;
+ case CameraAccessException.CAMERA_DISABLED:
+ failureCode = StateListener.ERROR_CAMERA_DISABLED;
+ break;
+ case CameraAccessException.CAMERA_DISCONNECTED:
+ failureIsError = false;
+ break;
+ case CameraAccessException.CAMERA_ERROR:
+ failureCode = StateListener.ERROR_CAMERA_DEVICE;
+ break;
+ default:
+ Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason());
+ break;
+ }
+ final int code = failureCode;
+ final boolean isError = failureIsError;
+ synchronized (mLock) {
+ mInError = true;
+ mDeviceHandler.post(new Runnable() {
+ public void run() {
+ if (isError) {
+ mDeviceListener.onError(CameraDeviceImpl.this, code);
+ } else {
+ mDeviceListener.onDisconnected(CameraDeviceImpl.this);
+ }
+ }
+ });
+ }
+ }
+
@Override
public String getId() {
return mCameraId;
@@ -230,7 +280,7 @@
outputs = new ArrayList<Surface>();
}
synchronized (mLock) {
- checkIfCameraClosed();
+ checkIfCameraClosedOrInError();
HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete
@@ -298,7 +348,7 @@
Log.d(TAG, "createCaptureSession");
}
- checkIfCameraClosed();
+ checkIfCameraClosedOrInError();
// TODO: we must be in UNCONFIGURED mode to begin with, or using another session
@@ -336,7 +386,7 @@
public CaptureRequest.Builder createCaptureRequest(int templateType)
throws CameraAccessException {
synchronized (mLock) {
- checkIfCameraClosed();
+ checkIfCameraClosedOrInError();
CameraMetadataNative templatedRequest = new CameraMetadataNative();
@@ -456,7 +506,7 @@
}
synchronized (mLock) {
- checkIfCameraClosed();
+ checkIfCameraClosedOrInError();
int requestId;
if (repeating) {
@@ -528,7 +578,7 @@
public void stopRepeating() throws CameraAccessException {
synchronized (mLock) {
- checkIfCameraClosed();
+ checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
int requestId = mRepeatingRequestId;
@@ -559,7 +609,7 @@
private void waitUntilIdle() throws CameraAccessException {
synchronized (mLock) {
- checkIfCameraClosed();
+ checkIfCameraClosedOrInError();
if (mRepeatingRequestId != REQUEST_ID_NONE) {
throw new IllegalStateException("Active repeating request ongoing");
}
@@ -580,7 +630,7 @@
@Override
public void flush() throws CameraAccessException {
synchronized (mLock) {
- checkIfCameraClosed();
+ checkIfCameraClosedOrInError();
mDeviceHandler.post(mCallOnBusy);
try {
@@ -614,11 +664,15 @@
// impossible
}
- if (mRemoteDevice != null) {
+ // Only want to fire the onClosed callback once;
+ // either a normal close where the remote device is valid
+ // or a close after a startup error (no remote device but in error state)
+ if (mRemoteDevice != null || mInError) {
mDeviceHandler.post(mCallOnClosed);
}
mRemoteDevice = null;
+ mInError = false;
}
}
@@ -835,6 +889,7 @@
if (isClosed()) return;
synchronized(mLock) {
+ mInError = true;
switch (errorCode) {
case ERROR_CAMERA_DISCONNECTED:
r = mCallOnDisconnected;
@@ -1032,7 +1087,11 @@
return handler;
}
- private void checkIfCameraClosed() {
+ private void checkIfCameraClosedOrInError() throws CameraAccessException {
+ if (mInError) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
+ "The camera device has encountered a serious error");
+ }
if (mRemoteDevice == null) {
throw new IllegalStateException("CameraDevice was already closed");
}