Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7fcb3578 authored by Eino-Ville Talvala's avatar Eino-Ville Talvala
Browse files

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
parent 648a309d
Loading
Loading
Loading
Loading
+18 −3
Original line number Diff line number Diff line
@@ -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 {
     *
     * <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
+68 −9
Original line number Diff line number Diff line
@@ -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.
     *
     * <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 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
            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 @@ 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");
        }