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

Commit 9b35b801 authored by Eino-Ville Talvala's avatar Eino-Ville Talvala Committed by Android (Google) Code Review
Browse files

Merge "CameraManager: Separate service listener into a singleton" into lmp-mr1-dev

parents 563e61f5 4c9c7a58
Loading
Loading
Loading
Loading
+191 −144
Original line number Diff line number Diff line
@@ -54,29 +54,17 @@ public final class CameraManager {
    private static final String TAG = "CameraManager";
    private final boolean DEBUG;

    /**
     * This should match the ICameraService definition
     */
    private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
    private static final int USE_CALLING_UID = -1;

    @SuppressWarnings("unused")
    private static final int API_VERSION_1 = 1;
    private static final int API_VERSION_2 = 2;

    // Access only through getCameraServiceLocked to deal with binder death
    private ICameraService mCameraService;

    private ArrayList<String> mDeviceIdList;

    private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
            new ArrayMap<AvailabilityCallback, Handler>();

    private final Context mContext;
    private final Object mLock = new Object();

    private final CameraServiceListener mServiceListener = new CameraServiceListener();

    /**
     * @hide
     */
@@ -84,8 +72,6 @@ public final class CameraManager {
        DEBUG = Log.isLoggable(TAG, Log.DEBUG);
        synchronized(mLock) {
            mContext = context;

            connectCameraServiceLocked();
        }
    }

@@ -116,6 +102,12 @@ public final class CameraManager {
     * <p>The first time a callback is registered, it is immediately called
     * with the availability status of all currently known camera devices.</p>
     *
     * <p>Since this callback will be registered with the camera service, remember to unregister it
     * once it is no longer needed; otherwise the callback will continue to receive events
     * indefinitely and it may prevent other resources from being released. Specifically, the
     * callbacks will be invoked independently of the general activity lifecycle and independently
     * of the state of individual CameraManager instances.</p>
     *
     * @param callback the new callback to send camera availability notices to
     * @param handler The handler on which the callback should be invoked, or
     * {@code null} to use the current thread's {@link android.os.Looper looper}.
@@ -130,13 +122,7 @@ public final class CameraManager {
            handler = new Handler(looper);
        }

        synchronized (mLock) {
            Handler oldHandler = mCallbackMap.put(callback, handler);
            // For new callbacks, provide initial availability information
            if (oldHandler == null) {
                mServiceListener.updateCallbackLocked(callback, handler);
            }
        }
        CameraManagerGlobal.get().registerAvailabilityCallback(callback, handler);
    }

    /**
@@ -148,9 +134,7 @@ public final class CameraManager {
     * @param callback The callback to remove from the notification list
     */
    public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
        synchronized (mLock) {
            mCallbackMap.remove(callback);
        }
        CameraManagerGlobal.get().unregisterAvailabilityCallback(callback);
    }

    /**
@@ -187,7 +171,7 @@ public final class CameraManager {
             * otherwise get them from the legacy shim instead.
             */

            ICameraService cameraService = getCameraServiceLocked();
            ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
            if (cameraService == null) {
                throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                        "Camera service is currently unavailable");
@@ -268,7 +252,7 @@ public final class CameraManager {
                try {
                    if (supportsCamera2ApiLocked(cameraId)) {
                        // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
                        ICameraService cameraService = getCameraServiceLocked();
                        ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
                        if (cameraService == null) {
                            throw new CameraRuntimeException(
                                CameraAccessException.CAMERA_DISCONNECTED,
@@ -443,13 +427,6 @@ public final class CameraManager {
        }
    }

    /**
     * Temporary for migrating to Callback naming
     * @hide
     */
    public static abstract class AvailabilityListener extends AvailabilityCallback {
    }

    /**
     * Return or create the list of currently connected camera devices.
     *
@@ -458,7 +435,7 @@ public final class CameraManager {
    private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
        if (mDeviceIdList == null) {
            int numCameras = 0;
            ICameraService cameraService = getCameraServiceLocked();
            ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
            ArrayList<String> deviceIdList = new ArrayList<>();

            // If no camera service, then no devices
@@ -515,18 +492,6 @@ public final class CameraManager {
        return mDeviceIdList;
    }

    private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
        int problem = e.getReason();
        switch (problem) {
            case CameraAccessException.CAMERA_DISCONNECTED:
                String errorMsg = CameraAccessException.getDefaultMessage(problem);
                Log.w(TAG, msg + ": " + errorMsg);
                break;
            default:
                throw new IllegalStateException(msg, e.asChecked());
        }
    }

    /**
     * Queries the camera service if it supports the camera2 api directly, or needs a shim.
     *
@@ -556,7 +521,7 @@ public final class CameraManager {
         * Anything else is an unexpected error we don't want to recover from.
         */
        try {
            ICameraService cameraService = getCameraServiceLocked();
            ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
            // If no camera service, no support
            if (cameraService == null) return false;

@@ -577,6 +542,85 @@ public final class CameraManager {
        return false;
    }

    /**
     * A per-process global camera manager instance, to retain a connection to the camera service,
     * and to distribute camera availability notices to API-registered callbacks
     */
    private static final class CameraManagerGlobal extends ICameraServiceListener.Stub
            implements IBinder.DeathRecipient {

        private static final String TAG = "CameraManagerGlobal";
        private final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

        // Singleton instance
        private static final CameraManagerGlobal gCameraManager =
            new CameraManagerGlobal();

        /**
         * This must match the ICameraService definition
         */
        private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";

        // Keep up-to-date with ICameraServiceListener.h

        // Device physically unplugged
        public static final int STATUS_NOT_PRESENT = 0;
        // Device physically has been plugged in
        // and the camera can be used exclusively
        public static final int STATUS_PRESENT = 1;
        // Device physically has been plugged in
        // but it will not be connect-able until enumeration is complete
        public static final int STATUS_ENUMERATING = 2;
        // Camera is in use by another app and cannot be used exclusively
        public static final int STATUS_NOT_AVAILABLE = 0x80000000;

        // End enums shared with ICameraServiceListener.h

        // Camera ID -> Status map
        private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();

        // Registered availablility callbacks and their handlers
        private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
            new ArrayMap<AvailabilityCallback, Handler>();

        private final Object mLock = new Object();

        // Access only through getCameraService to deal with binder death
        private ICameraService mCameraService;

        // Singleton, don't allow construction
        private CameraManagerGlobal() {
        }

        public static CameraManagerGlobal get() {
            return gCameraManager;
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        /**
         * Return a best-effort ICameraService.
         *
         * <p>This will be null if the camera service is not currently available. If the camera
         * service has died since the last use of the camera service, will try to reconnect to the
         * service.</p>
         */
        public ICameraService getCameraService() {
            synchronized(mLock) {
                if (mCameraService == null) {
                    Log.i(TAG, "getCameraService: Reconnecting to camera service");
                    connectCameraServiceLocked();
                    if (mCameraService == null) {
                        Log.e(TAG, "Camera service is unavailable");
                    }
                }
                return mCameraService;
            }
        }

        /**
         * Connect to the camera service if it's available, and set up listeners.
         *
@@ -590,7 +634,7 @@ public final class CameraManager {
                return;
            }
            try {
            cameraServiceBinder.linkToDeath(new CameraServiceDeathListener(), /*flags*/ 0);
                cameraServiceBinder.linkToDeath(this, /*flags*/ 0);
            } catch (RemoteException e) {
                // Camera service is now down, leave mCameraService as null
                return;
@@ -602,7 +646,8 @@ public final class CameraManager {
             * Wrap the camera service in a decorator which automatically translates return codes
             * into exceptions.
             */
        ICameraService cameraService = CameraServiceBinderDecorator.newInstance(cameraServiceRaw);
            ICameraService cameraService =
                CameraServiceBinderDecorator.newInstance(cameraServiceRaw);

            try {
                CameraServiceBinderDecorator.throwOnError(
@@ -612,7 +657,7 @@ public final class CameraManager {
            }

            try {
            cameraService.addListener(mServiceListener);
                cameraService.addListener(this);
                mCameraService = cameraService;
            } catch(CameraRuntimeException e) {
                // Unexpected failure
@@ -623,74 +668,16 @@ public final class CameraManager {
            }
        }

    /**
     * Return a best-effort ICameraService.
     *
     * <p>This will be null if the camera service
     * is not currently available. If the camera service has died since the last
     * use of the camera service, will try to reconnect to the service.</p>
     */
    private ICameraService getCameraServiceLocked() {
        if (mCameraService == null) {
            Log.i(TAG, "getCameraServiceLocked: Reconnecting to camera service");
            connectCameraServiceLocked();
            if (mCameraService == null) {
                Log.e(TAG, "Camera service is unavailable");
            }
        }
        return mCameraService;
    }

    /**
     * Listener for camera service death.
     *
     * <p>The camera service isn't supposed to die under any normal circumstances, but can be turned
     * off during debug, or crash due to bugs.  So detect that and null out the interface object, so
     * that the next calls to the manager can try to reconnect.</p>
     */
    private class CameraServiceDeathListener implements IBinder.DeathRecipient {
        public void binderDied() {
            synchronized(mLock) {
                mCameraService = null;
                // Tell listeners that the cameras are _available_, because any existing clients
                // will have gotten disconnected. This is optimistic under the assumption that the
                // service will be back shortly.
                //
                // Without this, a camera service crash while a camera is open will never signal to
                // listeners that previously in-use cameras are now available.
                for (String cameraId : mDeviceIdList) {
                    mServiceListener.onStatusChangedLocked(CameraServiceListener.STATUS_PRESENT,
                            cameraId);
                }
            }
        }
        private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
            int problem = e.getReason();
            switch (problem) {
            case CameraAccessException.CAMERA_DISCONNECTED:
                String errorMsg = CameraAccessException.getDefaultMessage(problem);
                Log.w(TAG, msg + ": " + errorMsg);
                break;
            default:
                throw new IllegalStateException(msg, e.asChecked());
            }

    // TODO: this class needs unit tests
    // TODO: extract class into top level
    private class CameraServiceListener extends ICameraServiceListener.Stub {

        // Keep up-to-date with ICameraServiceListener.h

        // Device physically unplugged
        public static final int STATUS_NOT_PRESENT = 0;
        // Device physically has been plugged in
        // and the camera can be used exclusively
        public static final int STATUS_PRESENT = 1;
        // Device physically has been plugged in
        // but it will not be connect-able until enumeration is complete
        public static final int STATUS_ENUMERATING = 2;
        // Camera is in use by another app and cannot be used exclusively
        public static final int STATUS_NOT_AVAILABLE = 0x80000000;

        // Camera ID -> Status map
        private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();

        private static final String TAG = "CameraServiceListener";

        @Override
        public IBinder asBinder() {
            return this;
        }

        private boolean isAvailable(int status) {
@@ -739,7 +726,7 @@ public final class CameraManager {
         * Send the state of all known cameras to the provided listener, to initialize
         * the listener's knowledge of camera state.
         */
        public void updateCallbackLocked(AvailabilityCallback callback, Handler handler) {
        private void updateCallbackLocked(AvailabilityCallback callback, Handler handler) {
            for (int i = 0; i < mDeviceStatus.size(); i++) {
                String id = mDeviceStatus.keyAt(i);
                Integer status = mDeviceStatus.valueAt(i);
@@ -747,14 +734,7 @@ public final class CameraManager {
            }
        }

        @Override
        public void onStatusChanged(int status, int cameraId) throws RemoteException {
            synchronized(CameraManager.this.mLock) {
                onStatusChangedLocked(status, String.valueOf(cameraId));
            }
        }

        public void onStatusChangedLocked(int status, String id) {
        private void onStatusChangedLocked(int status, String id) {
            if (DEBUG) {
                Log.v(TAG,
                        String.format("Camera id %s has status changed to 0x%x", id, status));
@@ -811,5 +791,72 @@ public final class CameraManager {
            }
        } // onStatusChangedLocked

    } // CameraServiceListener
        /**
         * Register a callback to be notified about camera device availability with the
         * global listener singleton.
         *
         * @param callback the new callback to send camera availability notices to
         * @param handler The handler on which the callback should be invoked. May not be null.
         */
        public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
            synchronized (mLock) {
                Handler oldHandler = mCallbackMap.put(callback, handler);
                // For new callbacks, provide initial availability information
                if (oldHandler == null) {
                    updateCallbackLocked(callback, handler);
                }
            }
        }

        /**
         * Remove a previously-added callback; the callback will no longer receive connection and
         * disconnection callbacks, and is no longer referenced by the global listener singleton.
         *
         * @param callback The callback to remove from the notification list
         */
        public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
            synchronized (mLock) {
                mCallbackMap.remove(callback);
            }
        }

        /**
         * Callback from camera service notifying the process about camera availability changes
         */
        @Override
        public void onStatusChanged(int status, int cameraId) throws RemoteException {
            synchronized(mLock) {
                onStatusChangedLocked(status, String.valueOf(cameraId));
            }
        }

        /**
         * Listener for camera service death.
         *
         * <p>The camera service isn't supposed to die under any normal circumstances, but can be
         * turned off during debug, or crash due to bugs.  So detect that and null out the interface
         * object, so that the next calls to the manager can try to reconnect.</p>
         */
        public void binderDied() {
            synchronized(mLock) {
                // Only do this once per service death
                if (mCameraService == null) return;

                mCameraService = null;

                // Tell listeners that the cameras are _available_, because any existing clients
                // will have gotten disconnected. This is optimistic under the assumption that
                // the service will be back shortly.
                //
                // Without this, a camera service crash while a camera is open will never signal
                // to listeners that previously in-use cameras are now available.
                for (int i = 0; i < mDeviceStatus.size(); i++) {
                    String cameraId = mDeviceStatus.keyAt(i);
                    onStatusChangedLocked(STATUS_PRESENT, cameraId);
                }
            }
        }

    } // CameraManagerGlobal

} // CameraManager