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

Commit 5d02d02a authored by Emilian Peev's avatar Emilian Peev
Browse files

Camera: Ensure device events are processed in order

Camera devices will use a client provided executor
to handle all internal events. Typically executors
provided by clients are limited and running in a single
thread which makes it safe for the current implementation
to make assumptions about the ordering.
In some cases like the Kotlin 'Dispatchers.IO', the device
executor can utilize a pool of threads. The ordering
of events in this case could be random and depend on timing
and scheduling decisions resulting in different kinds of
errors and unexpected states.
To avoid this, ensure that the critical device state
handling is done by a single thread executor and is always
in the order expected by the implementation.

Bug: 305857746
Test: Manual using camera application
Change-Id: I33eed3db275be92422db2a646fcfc68d5a64f880
parent f23316eb
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -1052,6 +1052,12 @@ public final class CameraManager {
     * {@link java.util.concurrent.Executor} as an argument instead of
     * {@link android.os.Handler}.</p>
     *
     * <p>Do note that typically callbacks are expected to be dispatched
     * by the executor in a single thread. If the executor uses two or
     * more threads to dispatch callbacks, then clients must ensure correct
     * synchronization and must also be able to handle potentially different
     * ordering of the incoming callbacks.</p>
     *
     * @param cameraId
     *             The unique identifier of the camera device to open
     * @param executor
+56 −2
Original line number Diff line number Diff line
@@ -290,6 +290,55 @@ public class CameraDeviceImpl extends CameraDevice
        }
    };

    private class ClientStateCallback extends StateCallback {
        private final Executor mClientExecutor;
        private final StateCallback mClientStateCallback;

        private ClientStateCallback(@NonNull Executor clientExecutor,
                @NonNull StateCallback clientStateCallback) {
            mClientExecutor = clientExecutor;
            mClientStateCallback = clientStateCallback;
        }

        public void onClosed(@NonNull CameraDevice camera) {
            mClientExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    mClientStateCallback.onClosed(camera);
                }
            });
        }
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mClientExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    mClientStateCallback.onOpened(camera);
                }
            });
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            mClientExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    mClientStateCallback.onDisconnected(camera);
                }
            });
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            mClientExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    mClientStateCallback.onError(camera, error);
                }
            });
        }
    }

    public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor,
                        CameraCharacteristics characteristics,
                        Map<String, CameraCharacteristics> physicalIdsToChars,
@@ -300,8 +349,13 @@ public class CameraDeviceImpl extends CameraDevice
            throw new IllegalArgumentException("Null argument given");
        }
        mCameraId = cameraId;
        if (Flags.singleThreadExecutor()) {
            mDeviceCallback = new ClientStateCallback(executor, callback);
            mDeviceExecutor = Executors.newSingleThreadExecutor();
        } else {
            mDeviceCallback = callback;
            mDeviceExecutor = executor;
        }
        mCharacteristics = characteristics;
        mPhysicalIdsToChars = physicalIdsToChars;
        mAppTargetSdkVersion = appTargetSdkVersion;