From 7fcb357811d4dc1f44624e30ad924e9e580d4cbf Mon Sep 17 00:00:00 2001 From: Eino-Ville Talvala Date: Tue, 3 Jun 2014 18:30:27 -0700 Subject: [PATCH] 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 --- .../hardware/camera2/CameraManager.java | 21 ++++- .../camera2/impl/CameraDeviceImpl.java | 77 ++++++++++++++++--- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index e21fb1fff95bf..9046b136e3c13 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -226,7 +226,7 @@ public final class CameraManager { synchronized (mLock) { - ICameraDeviceUser cameraUser; + ICameraDeviceUser cameraUser = null; android.hardware.camera2.impl.CameraDeviceImpl deviceImpl = new android.hardware.camera2.impl.CameraDeviceImpl( @@ -248,8 +248,23 @@ public final class CameraManager { // 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 @@ public final class CameraManager { * *

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}.

+ * calls on the camera device will throw a {@link CameraAccessException}.

* * @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 81bd2fd448754..97950827b6882 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -64,6 +64,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { 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 class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { 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 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { } } + /** + * Call to indicate failed connection to a remote camera device. + * + *

This places the camera device in the error state and informs the listener. + * Use in place of setRemoteDevice() when startup fails.

+ */ + 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 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { outputs = new ArrayList(); } synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); HashSet addSet = new HashSet(outputs); // Streams to create List deleteList = new ArrayList(); // Streams to delete @@ -298,7 +348,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { 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 class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); CameraMetadataNative templatedRequest = new CameraMetadataNative(); @@ -456,7 +506,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { } synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); int requestId; if (repeating) { @@ -528,7 +578,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { public void stopRepeating() throws CameraAccessException { synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); if (mRepeatingRequestId != REQUEST_ID_NONE) { int requestId = mRepeatingRequestId; @@ -559,7 +609,7 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { 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 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { @Override public void flush() throws CameraAccessException { synchronized (mLock) { - checkIfCameraClosed(); + checkIfCameraClosedOrInError(); mDeviceHandler.post(mCallOnBusy); try { @@ -614,11 +664,15 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { // 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 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { if (isClosed()) return; synchronized(mLock) { + mInError = true; switch (errorCode) { case ERROR_CAMERA_DISCONNECTED: r = mCallOnDisconnected; @@ -1032,7 +1087,11 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { 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"); }