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

Commit 474c79e8 authored by Eino-Ville Talvala's avatar Eino-Ville Talvala Committed by Android Git Automerger
Browse files

am ad45fa64: Merge "Camera2: Fire all callbacks" into klp-dev

* commit 'ad45fa64':
  Camera2: Fire all callbacks
parents aa24442e ad45fa64
Loading
Loading
Loading
Loading
+42 −43
Original line number Original line Diff line number Diff line
@@ -197,26 +197,33 @@ public interface CameraDevice extends AutoCloseable {
     * if the format is user-visible, it must be one of android.scaler.availableFormats;
     * if the format is user-visible, it must be one of android.scaler.availableFormats;
     * and the size must be one of android.scaler.available[Processed|Jpeg]Sizes).</p>
     * and the size must be one of android.scaler.available[Processed|Jpeg]Sizes).</p>
     *
     *
     * <p>To change the output, the camera device must be idle. The device is considered
     * <p>When this method is called with valid Surfaces, the device will transition to the {@link
     * to be idle once all in-flight and pending capture requests have been processed,
     * StateListener#onBusy busy state}. Once configuration is complete, the device will transition
     * and all output image buffers from the captures have been sent to their destination
     * into the {@link StateListener#onIdle idle state}. Capture requests using the newly-configured
     * Surfaces.</p>
     * Surfaces may then be submitted with {@link #capture}, {@link #captureBurst}, {@link
     *
     * #setRepeatingRequest}, or {@link #setRepeatingBurst}.</p>
     * <p>To reach an idle state without cancelling any submitted captures, first
     *
     * stop any repeating request/burst with {@link #stopRepeating}, and then
     * <p>If this method is called while the camera device is still actively processing previously
     * wait for the {@link StateListener#onIdle} callback to be
     * submitted captures, then the following sequence of events occurs: The device transitions to
     * called. To idle as fast as possible, use {@link #flush} and wait for the
     * the busy state and calls the {@link StateListener#onBusy} callback. Second, if a repeating
     * idle callback.</p>
     * request is set it is cleared.  Third, the device finishes up all in-flight and pending
     * requests. Finally, once the device is idle, it then reconfigures its outputs, and calls the
     * {@link StateListener#onIdle} method once it is again ready to accept capture
     * requests. Therefore, no submitted work is discarded. To idle as fast as possible, use {@link
     * #flush} and wait for the idle callback before calling configureOutputs. This will discard
     * work, but reaches the new configuration sooner.</p>
     *
     *
     * <p>Using larger resolution outputs, or more outputs, can result in slower
     * <p>Using larger resolution outputs, or more outputs, can result in slower
     * output rate from the device.</p>
     * output rate from the device.</p>
     *
     *
     * <p>Configuring the outputs with an empty or null list will transition
     * <p>Configuring the outputs with an empty or null list will transition the camera into an
     * the camera into an {@link StateListener#onUnconfigured unconfigured state}.
     * {@link StateListener#onUnconfigured unconfigured state} instead of the {@link
     * </p>
     * StateListener#onIdle idle state}.  </p>
     *
     *
     * <p>Calling configureOutputs with the same arguments as the last call to
     * <p>Calling configureOutputs with the same arguments as the last call to
     * configureOutputs has no effect.</p>
     * configureOutputs has no effect, and the {@link StateListener#onBusy busy}
     * and {@link StateListener#onIdle idle} state transitions will happen
     * immediately.</p>
     *
     *
     * @param outputs The new set of Surfaces that should be made available as
     * @param outputs The new set of Surfaces that should be made available as
     * targets for captured image data.
     * targets for captured image data.
@@ -228,7 +235,10 @@ public interface CameraDevice extends AutoCloseable {
     * @throws IllegalStateException if the camera device is not idle, or
     * @throws IllegalStateException if the camera device is not idle, or
     *                               if the camera device has been closed
     *                               if the camera device has been closed
     *
     *
     * @see StateListener#onBusy
     * @see StateListener#onIdle
     * @see StateListener#onIdle
     * @see StateListener#onActive
     * @see StateListener#onUnconfigured
     * @see #stopRepeating
     * @see #stopRepeating
     * @see #flush
     * @see #flush
     */
     */
@@ -515,31 +525,6 @@ public interface CameraDevice extends AutoCloseable {
     */
     */
    public void waitUntilIdle() throws CameraAccessException;
    public void waitUntilIdle() throws CameraAccessException;


    /**
     * Set the listener object to call when an asynchronous device event occurs,
     * such as errors or idle notifications.
     *
     * <p>The events reported here are device-wide; notifications about
     * individual capture requests or capture results are reported through
     * {@link CaptureListener}.</p>
     *
     * <p>If the camera device is idle when the listener is set, then the
     * {@link StateListener#onIdle} method will be immediately called,
     * even if the device has never been active before.
     * </p>
     *
     * @param listener the CameraDeviceListener to send device-level event
     * notifications to. Setting this to null will stop notifications.
     * @param handler the handler on which the listener should be invoked, or
     * {@code null} to use the current thread's {@link android.os.Looper looper}.
     *
     * @throws IllegalArgumentException if handler is null, the listener is
     * not null, and the calling thread has no looper
     *
     * @hide
     */
    public void setDeviceListener(StateListener listener, Handler handler);

    /**
    /**
     * Flush all captures currently pending and in-progress as fast as
     * Flush all captures currently pending and in-progress as fast as
     * possible.
     * possible.
@@ -577,13 +562,24 @@ public interface CameraDevice extends AutoCloseable {
    public void flush() throws CameraAccessException;
    public void flush() throws CameraAccessException;


    /**
    /**
     * Close the connection to this camera device. After this call, all calls to
     * Close the connection to this camera device.
     *
     * <p>After this call, all calls to
     * the camera device interface will throw a {@link IllegalStateException},
     * the camera device interface will throw a {@link IllegalStateException},
     * except for calls to close().
     * except for calls to close(). Once the device has fully shut down, the
     * {@link StateListener#onClosed} callback will be called, and the camera is
     * free to be re-opened.</p>
     *
     * <p>After this call, besides the final {@link StateListener#onClosed} call, no calls to the
     * device's {@link StateListener} will occur, and any remaining submitted capture requests will
     * not fire their {@link CaptureListener} callbacks.</p>
     *
     * <p>To shut down as fast as possible, call the {@link #flush} method and then {@link #close}
     * once the flush completes. This will discard some capture requests, but results in faster
     * shutdown.</p>
     */
     */
    @Override
    @Override
    public void close();
    public void close();
    // TODO: We should decide on the behavior of in-flight requests should be on close.


    /**
    /**
     * <p>A listener for tracking the progress of a {@link CaptureRequest}
     * <p>A listener for tracking the progress of a {@link CaptureRequest}
@@ -713,6 +709,9 @@ public interface CameraDevice extends AutoCloseable {
     * A listener for notifications about the state of a camera
     * A listener for notifications about the state of a camera
     * device.
     * device.
     *
     *
     * <p>A listener must be provided to the {@link CameraManager#openCamera}
     * method to open a camera device.</p>
     *
     * <p>These events include notifications about the device becoming idle (
     * <p>These events include notifications about the device becoming idle (
     * allowing for {@link #configureOutputs} to be called), about device
     * allowing for {@link #configureOutputs} to be called), about device
     * disconnection, and about unexpected device errors.</p>
     * disconnection, and about unexpected device errors.</p>
@@ -722,7 +721,7 @@ public interface CameraDevice extends AutoCloseable {
     * the {@link #capture}, {@link #captureBurst}, {@link
     * the {@link #capture}, {@link #captureBurst}, {@link
     * #setRepeatingRequest}, or {@link #setRepeatingBurst} methods.
     * #setRepeatingRequest}, or {@link #setRepeatingBurst} methods.
     *
     *
     * @see #setDeviceListener
     * @see CameraManager#openCamera
     */
     */
    public static abstract class StateListener {
    public static abstract class StateListener {
       /**
       /**
+13 −16
Original line number Original line Diff line number Diff line
@@ -197,6 +197,8 @@ public final class CameraManager {
     * {@link #openCamera}.
     * {@link #openCamera}.
     *
     *
     * @param cameraId The unique identifier of the camera device to open
     * @param cameraId The unique identifier of the camera device to open
     * @param listener The listener for the camera. Must not be null.
     * @param handler  The handler to call the listener on. Must not be null.
     *
     *
     * @throws CameraAccessException if the camera is disabled by device policy,
     * @throws CameraAccessException if the camera is disabled by device policy,
     * or too many camera devices are already open, or the cameraId does not match
     * or too many camera devices are already open, or the cameraId does not match
@@ -204,11 +206,14 @@ public final class CameraManager {
     *
     *
     * @throws SecurityException if the application does not have permission to
     * @throws SecurityException if the application does not have permission to
     * access the camera
     * access the camera
     * @throws IllegalArgumentException if listener or handler is null.
     *
     *
     * @see #getCameraIdList
     * @see #getCameraIdList
     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
     * @see android.app.admin.DevicePolicyManager#setCameraDisabled
     */
     */
    private CameraDevice openCamera(String cameraId) throws CameraAccessException {
    private void openCameraDeviceUserAsync(String cameraId,
            CameraDevice.StateListener listener, Handler handler)
            throws CameraAccessException {
        try {
        try {


            synchronized (mLock) {
            synchronized (mLock) {
@@ -216,7 +221,10 @@ public final class CameraManager {
                ICameraDeviceUser cameraUser;
                ICameraDeviceUser cameraUser;


                android.hardware.camera2.impl.CameraDevice device =
                android.hardware.camera2.impl.CameraDevice device =
                        new android.hardware.camera2.impl.CameraDevice(cameraId);
                        new android.hardware.camera2.impl.CameraDevice(
                                cameraId,
                                listener,
                                handler);


                BinderHolder holder = new BinderHolder();
                BinderHolder holder = new BinderHolder();
                mCameraService.connectDevice(device.getCallbacks(),
                mCameraService.connectDevice(device.getCallbacks(),
@@ -225,10 +233,9 @@ public final class CameraManager {
                cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
                cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());


                // TODO: factor out listener to be non-nested, then move setter to constructor
                // TODO: factor out listener to be non-nested, then move setter to constructor
                // For now, calling setRemoteDevice will fire initial
                // onOpened/onUnconfigured callbacks.
                device.setRemoteDevice(cameraUser);
                device.setRemoteDevice(cameraUser);

                return device;

            }
            }


        } catch (NumberFormatException e) {
        } catch (NumberFormatException e) {
@@ -238,7 +245,6 @@ public final class CameraManager {
            throw e.asChecked();
            throw e.asChecked();
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            // impossible
            // impossible
            return null;
        }
        }
    }
    }


@@ -303,16 +309,7 @@ public final class CameraManager {
            }
            }
        }
        }


        final CameraDevice camera = openCamera(cameraId);
        openCameraDeviceUserAsync(cameraId, listener, handler);
        camera.setDeviceListener(listener, handler);

        // TODO: make truly async in the camera service
        handler.post(new Runnable() {
            @Override
            public void run() {
                listener.onOpened(camera);
            }
        });
    }
    }


    /**
    /**
+148 −55
Original line number Original line Diff line number Diff line
@@ -55,8 +55,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    private final Object mLock = new Object();
    private final Object mLock = new Object();
    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();


    private StateListener mDeviceListener;
    private final StateListener mDeviceListener;
    private Handler mDeviceHandler;
    private final Handler mDeviceHandler;

    private boolean mIdle = true;


    private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
    private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
            new SparseArray<CaptureListenerHolder>();
            new SparseArray<CaptureListenerHolder>();
@@ -67,8 +69,72 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {


    private final String mCameraId;
    private final String mCameraId;


    public CameraDevice(String cameraId) {
    // Runnables for all state transitions, except error, which needs the
    // error code argument

    private final Runnable mCallOnOpened = new Runnable() {
        public void run() {
            if (!CameraDevice.this.isClosed()) {
                mDeviceListener.onOpened(CameraDevice.this);
            }
        }
    };

    private final Runnable mCallOnUnconfigured = new Runnable() {
        public void run() {
            if (!CameraDevice.this.isClosed()) {
                mDeviceListener.onUnconfigured(CameraDevice.this);
            }
        }
    };

    private final Runnable mCallOnActive = new Runnable() {
        public void run() {
            if (!CameraDevice.this.isClosed()) {
                mDeviceListener.onActive(CameraDevice.this);
            }
        }
    };

    private final Runnable mCallOnBusy = new Runnable() {
        public void run() {
            if (!CameraDevice.this.isClosed()) {
                mDeviceListener.onBusy(CameraDevice.this);
            }
        }
    };

    private final Runnable mCallOnClosed = new Runnable() {
        public void run() {
            if (!CameraDevice.this.isClosed()) {
                mDeviceListener.onClosed(CameraDevice.this);
            }
        }
    };

    private final Runnable mCallOnIdle = new Runnable() {
        public void run() {
            if (!CameraDevice.this.isClosed()) {
                mDeviceListener.onIdle(CameraDevice.this);
            }
        }
    };

    private final Runnable mCallOnDisconnected = new Runnable() {
        public void run() {
            if (!CameraDevice.this.isClosed()) {
                mDeviceListener.onDisconnected(CameraDevice.this);
            }
        }
    };

    public CameraDevice(String cameraId, StateListener listener, Handler handler) {
        if (cameraId == null || listener == null || handler == null) {
            throw new IllegalArgumentException("Null argument given");
        }
        mCameraId = cameraId;
        mCameraId = cameraId;
        mDeviceListener = listener;
        mDeviceHandler = handler;
        TAG = String.format("CameraDevice-%s-JV", mCameraId);
        TAG = String.format("CameraDevice-%s-JV", mCameraId);
        DEBUG = Log.isLoggable(TAG, Log.DEBUG);
        DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    }
    }
@@ -79,7 +145,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {


    public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
    public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
        // TODO: Move from decorator to direct binder-mediated exceptions
        // TODO: Move from decorator to direct binder-mediated exceptions
        synchronized(mLock) {
            mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
            mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);

            mDeviceHandler.post(mCallOnOpened);
            mDeviceHandler.post(mCallOnUnconfigured);
        }
    }
    }


    @Override
    @Override
@@ -89,7 +160,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {


    @Override
    @Override
    public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
    public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
        // Treat a null input the same an empty list
        if (outputs == null) {
            outputs = new ArrayList<Surface>();
        }
        synchronized (mLock) {
        synchronized (mLock) {
            checkIfCameraClosed();

            HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
            HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
            List<Integer> deleteList = new ArrayList<Integer>();        // Streams to delete
            List<Integer> deleteList = new ArrayList<Integer>();        // Streams to delete


@@ -105,9 +182,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                }
                }
            }
            }


            mDeviceHandler.post(mCallOnBusy);
            stopRepeating();

            try {
            try {
                // TODO: mRemoteDevice.beginConfigure
                mRemoteDevice.waitUntilIdle();


                // TODO: mRemoteDevice.beginConfigure
                // Delete all streams first (to free up HW resources)
                // Delete all streams first (to free up HW resources)
                for (Integer streamId : deleteList) {
                for (Integer streamId : deleteList) {
                    mRemoteDevice.deleteStream(streamId);
                    mRemoteDevice.deleteStream(streamId);
@@ -126,7 +207,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
            } catch (CameraRuntimeException e) {
            } catch (CameraRuntimeException e) {
                if (e.getReason() == CAMERA_IN_USE) {
                if (e.getReason() == CAMERA_IN_USE) {
                    throw new IllegalStateException("The camera is currently busy." +
                    throw new IllegalStateException("The camera is currently busy." +
                            " You must call waitUntilIdle before trying to reconfigure.");
                            " You must wait until the previous operation completes.");
                }
                }


                throw e.asChecked();
                throw e.asChecked();
@@ -134,6 +215,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                // impossible
                // impossible
                return;
                return;
            }
            }

            if (outputs.size() > 0) {
                mDeviceHandler.post(mCallOnIdle);
            } else {
                mDeviceHandler.post(mCallOnUnconfigured);
            }
        }
        }
    }
    }


@@ -141,6 +228,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    public CaptureRequest.Builder createCaptureRequest(int templateType)
    public CaptureRequest.Builder createCaptureRequest(int templateType)
            throws CameraAccessException {
            throws CameraAccessException {
        synchronized (mLock) {
        synchronized (mLock) {
            checkIfCameraClosed();


            CameraMetadataNative templatedRequest = new CameraMetadataNative();
            CameraMetadataNative templatedRequest = new CameraMetadataNative();


@@ -188,7 +276,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
        }
        }


        synchronized (mLock) {
        synchronized (mLock) {

            checkIfCameraClosed();
            int requestId;
            int requestId;


            try {
            try {
@@ -208,6 +296,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                mRepeatingRequestIdStack.add(requestId);
                mRepeatingRequestIdStack.add(requestId);
            }
            }


            if (mIdle) {
                mDeviceHandler.post(mCallOnActive);
            }
            mIdle = false;

            return requestId;
            return requestId;
        }
        }
    }
    }
@@ -233,7 +326,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
    public void stopRepeating() throws CameraAccessException {
    public void stopRepeating() throws CameraAccessException {


        synchronized (mLock) {
        synchronized (mLock) {

            checkIfCameraClosed();
            while (!mRepeatingRequestIdStack.isEmpty()) {
            while (!mRepeatingRequestIdStack.isEmpty()) {
                int requestId = mRepeatingRequestIdStack.pop();
                int requestId = mRepeatingRequestIdStack.pop();


@@ -269,21 +362,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
        }
        }
    }
    }


    @Override
    public void setDeviceListener(StateListener listener, Handler handler) {
        synchronized (mLock) {
            if (listener != null) {
                handler = checkHandler(handler);
            }

            mDeviceListener = listener;
            mDeviceHandler = handler;
        }
    }

    @Override
    @Override
    public void flush() throws CameraAccessException {
    public void flush() throws CameraAccessException {
        synchronized (mLock) {
        synchronized (mLock) {
            checkIfCameraClosed();

            mDeviceHandler.post(mCallOnBusy);
            try {
            try {
                mRemoteDevice.flush();
                mRemoteDevice.flush();
            } catch (CameraRuntimeException e) {
            } catch (CameraRuntimeException e) {
@@ -297,9 +381,6 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {


    @Override
    @Override
    public void close() {
    public void close() {

        // TODO: every method should throw IllegalStateException after close has been called

        synchronized (mLock) {
        synchronized (mLock) {


            try {
            try {
@@ -312,8 +393,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                // impossible
                // impossible
            }
            }


            mRemoteDevice = null;
            if (mRemoteDevice != null) {
                mDeviceHandler.post(mCallOnClosed);
            }


            mRemoteDevice = null;
        }
        }
    }
    }


@@ -399,49 +483,44 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {


        @Override
        @Override
        public void onCameraError(final int errorCode) {
        public void onCameraError(final int errorCode) {
            synchronized (mLock) {
                if (CameraDevice.this.mDeviceListener == null) return;
                final StateListener listener = CameraDevice.this.mDeviceListener;
            Runnable r = null;
            Runnable r = null;
            if (isClosed()) return;

            synchronized(mLock) {
                switch (errorCode) {
                switch (errorCode) {
                    case ERROR_CAMERA_DISCONNECTED:
                    case ERROR_CAMERA_DISCONNECTED:
                        r = new Runnable() {
                        r = mCallOnDisconnected;
                            public void run() {
                                listener.onDisconnected(CameraDevice.this);
                            }
                        };
                        break;
                        break;
                    default:
                        Log.e(TAG, "Unknown error from camera device: " + errorCode);
                        // no break
                    case ERROR_CAMERA_DEVICE:
                    case ERROR_CAMERA_DEVICE:
                    case ERROR_CAMERA_SERVICE:
                    case ERROR_CAMERA_SERVICE:
                        r = new Runnable() {
                        r = new Runnable() {
                            public void run() {
                            public void run() {
                                listener.onError(CameraDevice.this, errorCode);
                                if (!CameraDevice.this.isClosed()) {
                                    mDeviceListener.onError(CameraDevice.this, errorCode);
                                }
                            }
                            }
                        };
                        };
                        break;
                        break;
                    default:
                        Log.e(TAG, "Unknown error from camera device: " + errorCode);
                }
                }
                if (r != null) {
                CameraDevice.this.mDeviceHandler.post(r);
                CameraDevice.this.mDeviceHandler.post(r);
            }
            }
        }
        }
        }


        @Override
        @Override
        public void onCameraIdle() {
        public void onCameraIdle() {
            if (isClosed()) return;

            if (DEBUG) {
            if (DEBUG) {
                Log.d(TAG, "Camera now idle");
                Log.d(TAG, "Camera now idle");
            }
            }
            synchronized (mLock) {
            synchronized (mLock) {
                if (CameraDevice.this.mDeviceListener == null) return;
                if (!CameraDevice.this.mIdle) {
                final StateListener listener = CameraDevice.this.mDeviceListener;
                    CameraDevice.this.mDeviceHandler.post(mCallOnIdle);
                Runnable r = new Runnable() {
                    public void run() {
                        listener.onIdle(CameraDevice.this);
                }
                }
                };
                CameraDevice.this.mIdle = true;
                CameraDevice.this.mDeviceHandler.post(r);
            }
            }
        }
        }


@@ -461,15 +540,19 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                return;
                return;
            }
            }


            if (isClosed()) return;

            // Dispatch capture start notice
            // Dispatch capture start notice
            holder.getHandler().post(
            holder.getHandler().post(
                new Runnable() {
                new Runnable() {
                    public void run() {
                    public void run() {
                        if (!CameraDevice.this.isClosed()) {
                            holder.getListener().onCaptureStarted(
                            holder.getListener().onCaptureStarted(
                                CameraDevice.this,
                                CameraDevice.this,
                                holder.getRequest(),
                                holder.getRequest(),
                                timestamp);
                                timestamp);
                        }
                        }
                    }
                });
                });
        }
        }


@@ -503,6 +586,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                return;
                return;
            }
            }


            if (isClosed()) return;

            final CaptureRequest request = holder.getRequest();
            final CaptureRequest request = holder.getRequest();
            final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
            final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);


@@ -510,11 +595,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
                new Runnable() {
                new Runnable() {
                    @Override
                    @Override
                    public void run() {
                    public void run() {
                        if (!CameraDevice.this.isClosed()){
                            holder.getListener().onCaptureCompleted(
                            holder.getListener().onCaptureCompleted(
                                CameraDevice.this,
                                CameraDevice.this,
                                request,
                                request,
                                resultAsCapture);
                                resultAsCapture);
                        }
                        }
                    }
                });
                });
        }
        }


@@ -541,4 +628,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
            throw new IllegalStateException("CameraDevice was already closed");
            throw new IllegalStateException("CameraDevice was already closed");
        }
        }
    }
    }

    private boolean isClosed() {
        synchronized(mLock) {
            return (mRemoteDevice == null);
        }
    }
}
}