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

Commit ad646cf4 authored by Avichal Rakesh's avatar Avichal Rakesh
Browse files

camera: make exception handling more explicit in camera2 classes

Camera2 classes used CameraManager's `throwAsPublicFunction` as a
catchall to wrap and re-throw binder exceptions to the applications.
This is functionally correct, however it left some code paths
ambiguous whether it would terminate the method or not.
Consequently there were a few anti-pattern in the code,
specifically:
1. Unreachable statements from compiler not realizing that calls
   to `throwAsPublicException` are terminal and no handling is needed
   after.
2. Methods catching Throwables only to pass it to
   `throwAsPublicFunction` which would finally re-throw it. Catching
   generic Throwables is only one step away from difficult to debug
   bugs and may hide larger issues. It is recommended to only catch
   exceptions intended to be handled.

As more classes are getting added which rely on `throwAsPublicFunction`
to wrap relevant exceptions into application facing exceptions,
this CL moves the logic into a separate helper class, and breaks the
method up to accept specific Exceptions rather than a generic
`Throwable`.

It also updates all usages of `throwAsPublicException` to ensure
that callers are only handling the exceptions they expect to
handle, and explicitly marks the call with `throw` to ensure that
the compiler knows where execution will terminate preventing false
error messages about missing return statements.

Bug: 320741775
Test: No functional change.
      atest CtsCameraTestCases:CameraManagerTest passes
      atest CtsCameraTestCases:CameraDeviceTest passes
Change-Id: Ic2b765961688ab1cd5e931d8a6ffca8b583d1952
parent 59e8043a
Loading
Loading
Loading
Loading
+16 −70
Original line number Diff line number Diff line
@@ -45,10 +45,10 @@ import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.utils.CameraIdAndSessionConfiguration;
import android.hardware.camera2.utils.ConcurrentCameraIdCombination;
import android.hardware.camera2.utils.ExceptionUtils;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
@@ -643,7 +643,7 @@ public final class CameraManager {
            ServiceSpecificException sse = new ServiceSpecificException(
                    ICameraService.ERROR_DISCONNECTED,
                    "Camera service is currently unavailable");
            throwAsPublicException(sse);
            throw ExceptionUtils.throwAsPublicException(sse);
        }

        return multiResolutionStreamConfigurations;
@@ -736,7 +736,7 @@ public final class CameraManager {

                characteristics = new CameraCharacteristics(info);
            } catch (ServiceSpecificException e) {
                throwAsPublicException(e);
                throw ExceptionUtils.throwAsPublicException(e);
            } catch (RemoteException e) {
                // Camera service died - act as if the camera was disconnected
                throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
@@ -858,11 +858,11 @@ public final class CameraManager {
                            e.errorCode == ICameraService.ERROR_DISCONNECTED ||
                            e.errorCode == ICameraService.ERROR_CAMERA_IN_USE) {
                        // Per API docs, these failures call onError and throw
                        throwAsPublicException(e);
                        throw ExceptionUtils.throwAsPublicException(e);
                    }
                } else {
                    // Unexpected failure - rethrow
                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                }
            } catch (RemoteException e) {
                // Camera service died - act as if it's a CAMERA_DISCONNECTED case
@@ -870,7 +870,7 @@ public final class CameraManager {
                    ICameraService.ERROR_DISCONNECTED,
                    "Camera service is currently unavailable");
                deviceImpl.setRemoteFailure(sse);
                throwAsPublicException(sse);
                throw ExceptionUtils.throwAsPublicException(sse);
            }

            // TODO: factor out callback to be non-nested, then move setter to constructor
@@ -1704,56 +1704,6 @@ public final class CameraManager {
        }
    }

    /**
     * Convert ServiceSpecificExceptions and Binder RemoteExceptions from camera binder interfaces
     * into the correct public exceptions.
     *
     * @hide
     */
    public static void throwAsPublicException(Throwable t) throws CameraAccessException {
        if (t instanceof ServiceSpecificException) {
            ServiceSpecificException e = (ServiceSpecificException) t;
            int reason = CameraAccessException.CAMERA_ERROR;
            switch(e.errorCode) {
                case ICameraService.ERROR_DISCONNECTED:
                    reason = CameraAccessException.CAMERA_DISCONNECTED;
                    break;
                case ICameraService.ERROR_DISABLED:
                    reason = CameraAccessException.CAMERA_DISABLED;
                    break;
                case ICameraService.ERROR_CAMERA_IN_USE:
                    reason = CameraAccessException.CAMERA_IN_USE;
                    break;
                case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
                    reason = CameraAccessException.MAX_CAMERAS_IN_USE;
                    break;
                case ICameraService.ERROR_DEPRECATED_HAL:
                    reason = CameraAccessException.CAMERA_DEPRECATED_HAL;
                    break;
                case ICameraService.ERROR_ILLEGAL_ARGUMENT:
                case ICameraService.ERROR_ALREADY_EXISTS:
                    throw new IllegalArgumentException(e.getMessage(), e);
                case ICameraService.ERROR_PERMISSION_DENIED:
                    throw new SecurityException(e.getMessage(), e);
                case ICameraService.ERROR_TIMED_OUT:
                case ICameraService.ERROR_INVALID_OPERATION:
                default:
                    reason = CameraAccessException.CAMERA_ERROR;
            }
            throw new CameraAccessException(reason, e.getMessage(), e);
        } else if (t instanceof DeadObjectException) {
            throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                    "Camera service has died unexpectedly",
                    t);
        } else if (t instanceof RemoteException) {
            throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
                    " which should never happen.", t);
        } else if (t instanceof RuntimeException) {
            RuntimeException e = (RuntimeException) t;
            throw e;
        }
    }

    /**
     * Queries the camera service if a cameraId is a hidden physical camera that belongs to a
     * logical camera device.
@@ -1829,13 +1779,13 @@ public final class CameraManager {
                        internalCamId, externalCamId, cameraInjectionCallback);
                injectionSessionImpl.setRemoteInjectionSession(injectionSession);
            } catch (ServiceSpecificException e) {
                throwAsPublicException(e);
                throw ExceptionUtils.throwAsPublicException(e);
            } catch (RemoteException e) {
                // Camera service died - act as if it's a CAMERA_DISCONNECTED case
                ServiceSpecificException sse = new ServiceSpecificException(
                        ICameraService.ERROR_DISCONNECTED,
                        "Camera service is currently unavailable");
                throwAsPublicException(sse);
                throw ExceptionUtils.throwAsPublicException(sse);
            }
        }
    }
@@ -2124,7 +2074,7 @@ public final class CameraManager {
                    cameraService.remapCameraIds(cameraIdRemapping);
                    mActiveCameraIdRemapping = cameraIdRemapping;
                } catch (ServiceSpecificException e) {
                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                    throw new CameraAccessException(
                            CameraAccessException.CAMERA_DISCONNECTED,
@@ -2148,7 +2098,7 @@ public final class CameraManager {
                try {
                    cameraService.injectSessionParams(cameraId, sessionParams.getNativeMetadata());
                } catch (ServiceSpecificException e) {
                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                    throw new CameraAccessException(
                            CameraAccessException.CAMERA_DISCONNECTED,
@@ -2391,15 +2341,13 @@ public final class CameraManager {
                    return mCameraService.isConcurrentSessionConfigurationSupported(
                            cameraIdsAndConfigs, targetSdkVersion);
                } catch (ServiceSpecificException e) {
                   throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                  // Camera service died - act as if the camera was disconnected
                  throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                          "Camera service is currently unavailable", e);
                }
            }

            return false;
        }

        public boolean isSessionConfigurationWithParametersSupported(
@@ -2411,15 +2359,13 @@ public final class CameraManager {
                    return mCameraService.isSessionConfigurationWithParametersSupported(
                            cameraId, sessionConfiguration);
                } catch (ServiceSpecificException e) {
                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                    // Camera service died - act as if the camera was disconnected
                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                          "Camera service is currently unavailable", e);
                }
            }

            return false;
        }

      /**
@@ -2462,7 +2408,7 @@ public final class CameraManager {
                try {
                    cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
                } catch(ServiceSpecificException e) {
                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                            "Camera service is currently unavailable");
@@ -2488,7 +2434,7 @@ public final class CameraManager {
                    cameraService.turnOnTorchWithStrengthLevel(cameraId, torchStrength,
                            mTorchClientBinder);
                } catch(ServiceSpecificException e) {
                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                            "Camera service is currently unavailable.");
@@ -2512,7 +2458,7 @@ public final class CameraManager {
                try {
                    torchStrength = cameraService.getTorchStrengthLevel(cameraId);
                } catch(ServiceSpecificException e) {
                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                            "Camera service is currently unavailable.");
@@ -2551,7 +2497,7 @@ public final class CameraManager {
                        throw new UnsupportedOperationException(e.getMessage());
                    }

                    throwAsPublicException(e);
                    throw ExceptionUtils.throwAsPublicException(e);
                } catch (RemoteException e) {
                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                            "Camera service is currently unavailable.");
+91 −73
Original line number Diff line number Diff line
@@ -18,14 +18,13 @@ package android.hardware.camera2.impl;

import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.ExceptionUtils;
import android.hardware.camera2.utils.SubmitInfo;
import android.os.IBinder;
import android.os.RemoteException;
@@ -69,9 +68,10 @@ public class ICameraDeviceUserWrapper {
            throws CameraAccessException  {
        try {
            return mRemoteDevice.submitRequest(request, streaming);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -79,27 +79,30 @@ public class ICameraDeviceUserWrapper {
            throws CameraAccessException {
        try {
            return mRemoteDevice.submitRequestList(requestList, streaming);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public long cancelRequest(int requestId) throws CameraAccessException {
        try {
            return mRemoteDevice.cancelRequest(requestId);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public void beginConfigure() throws CameraAccessException {
        try {
            mRemoteDevice.beginConfigure();
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -108,18 +111,20 @@ public class ICameraDeviceUserWrapper {
        try {
            return mRemoteDevice.endConfigure(operatingMode, (sessionParams == null) ?
                    new CameraMetadataNative() : sessionParams, startTimeMs);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public void deleteStream(int streamId) throws CameraAccessException {
        try {
            mRemoteDevice.deleteStream(streamId);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -127,9 +132,10 @@ public class ICameraDeviceUserWrapper {
            throws CameraAccessException {
        try {
            return mRemoteDevice.createStream(outputConfiguration);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -137,45 +143,50 @@ public class ICameraDeviceUserWrapper {
            throws CameraAccessException {
        try {
            return mRemoteDevice.createInputStream(width, height, format, isMultiResolution);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public Surface getInputSurface() throws CameraAccessException {
        try {
            return mRemoteDevice.getInputSurface();
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public CameraMetadataNative createDefaultRequest(int templateId) throws CameraAccessException {
        try {
            return mRemoteDevice.createDefaultRequest(templateId);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public CameraMetadataNative getCameraInfo() throws CameraAccessException {
        try {
            return mRemoteDevice.getCameraInfo();
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public void waitUntilIdle() throws CameraAccessException {
        try {
            mRemoteDevice.waitUntilIdle();
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -191,10 +202,9 @@ public class ICameraDeviceUserWrapper {
                throw new IllegalArgumentException("Invalid session configuration");
            }

            throw e;
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -213,46 +223,49 @@ public class ICameraDeviceUserWrapper {
                throw new IllegalArgumentException("Invalid session configuration");
            }

            throw e;
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public long flush() throws CameraAccessException {
        try {
            return mRemoteDevice.flush();
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public void prepare(int streamId) throws CameraAccessException {
        try {
            mRemoteDevice.prepare(streamId);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public void tearDown(int streamId) throws CameraAccessException {
        try {
            mRemoteDevice.tearDown(streamId);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public void prepare2(int maxCount, int streamId) throws CameraAccessException {
        try {
            mRemoteDevice.prepare2(maxCount, streamId);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -260,9 +273,10 @@ public class ICameraDeviceUserWrapper {
            throws CameraAccessException {
        try {
            mRemoteDevice.updateOutputConfiguration(streamId, config);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -270,9 +284,10 @@ public class ICameraDeviceUserWrapper {
            int[] offlineOutputIds) throws CameraAccessException {
        try {
            return mRemoteDevice.switchToOffline(cbs, offlineOutputIds);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

@@ -280,27 +295,30 @@ public class ICameraDeviceUserWrapper {
            throws CameraAccessException {
        try {
            mRemoteDevice.finalizeOutputConfigurations(streamId, deferredConfig);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public void setCameraAudioRestriction(int mode) throws CameraAccessException {
        try {
            mRemoteDevice.setCameraAudioRestriction(mode);
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

    public int getGlobalAudioRestriction() throws CameraAccessException {
        try {
            return mRemoteDevice.getGlobalAudioRestriction();
        } catch (Throwable t) {
            CameraManager.throwAsPublicException(t);
            throw new UnsupportedOperationException("Unexpected exception", t);
        } catch (ServiceSpecificException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        } catch (RemoteException e) {
            throw ExceptionUtils.throwAsPublicException(e);
        }
    }

+110 −0

File added.

Preview size limit exceeded, changes collapsed.