diff options
author | Eino-Ville Talvala <etalvala@google.com> | 2013-10-03 11:15:21 -0700 |
---|---|---|
committer | Eino-Ville Talvala <etalvala@google.com> | 2013-10-07 12:40:49 -0700 |
commit | 868d904306c6a96d94fa0da03515c51c86eefc63 (patch) | |
tree | 5f633e51d92ad8818e9b763f62391812824e6b7c /core/java/android/hardware | |
parent | dd88879ce19332a5905699bc008504fd43d983d7 (diff) | |
download | frameworks_base-868d904306c6a96d94fa0da03515c51c86eefc63.zip frameworks_base-868d904306c6a96d94fa0da03515c51c86eefc63.tar.gz frameworks_base-868d904306c6a96d94fa0da03515c51c86eefc63.tar.bz2 |
Camera2: Fire all callbacks
- Allow configureOutputs to be called when device is
actively working, and document what happens then.
- At the managed level, trigger proper sequence of callbacks for
device state changes, for all the callbacks that are not yet
managed by the camera service.
- Restructure CameraManager.openDevice to have the device itself fire the
initial callbacks.
- Make CameraDevice.configureOutputs(null) work.
- Make CameraDeviec.configureOutputs(identical surfaces) work.
- Ensure proper checking for camera closed state.
Bug: 10360518
Change-Id: I9db348ee9c5ce4d3fe02fd34e779acc85cba68dc
Diffstat (limited to 'core/java/android/hardware')
3 files changed, 203 insertions, 114 deletions
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index a9a72b0..7095e4d 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -197,26 +197,33 @@ public interface CameraDevice extends AutoCloseable { * 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> * - * <p>To change the output, the camera device must be idle. The device is considered - * to be idle once all in-flight and pending capture requests have been processed, - * and all output image buffers from the captures have been sent to their destination - * Surfaces.</p> - * - * <p>To reach an idle state without cancelling any submitted captures, first - * stop any repeating request/burst with {@link #stopRepeating}, and then - * wait for the {@link StateListener#onIdle} callback to be - * called. To idle as fast as possible, use {@link #flush} and wait for the - * idle callback.</p> + * <p>When this method is called with valid Surfaces, the device will transition to the {@link + * StateListener#onBusy busy state}. Once configuration is complete, the device will transition + * into the {@link StateListener#onIdle idle state}. Capture requests using the newly-configured + * Surfaces may then be submitted with {@link #capture}, {@link #captureBurst}, {@link + * #setRepeatingRequest}, or {@link #setRepeatingBurst}.</p> + * + * <p>If this method is called while the camera device is still actively processing previously + * submitted captures, then the following sequence of events occurs: The device transitions to + * the busy state and calls the {@link StateListener#onBusy} callback. Second, if a repeating + * 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 * output rate from the device.</p> * - * <p>Configuring the outputs with an empty or null list will transition - * the camera into an {@link StateListener#onUnconfigured unconfigured state}. - * </p> + * <p>Configuring the outputs with an empty or null list will transition the camera into an + * {@link StateListener#onUnconfigured unconfigured state} instead of the {@link + * StateListener#onIdle idle state}. </p> * * <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 * targets for captured image data. @@ -228,7 +235,10 @@ public interface CameraDevice extends AutoCloseable { * @throws IllegalStateException if the camera device is not idle, or * if the camera device has been closed * + * @see StateListener#onBusy * @see StateListener#onIdle + * @see StateListener#onActive + * @see StateListener#onUnconfigured * @see #stopRepeating * @see #flush */ @@ -516,31 +526,6 @@ public interface CameraDevice extends AutoCloseable { 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 * possible. * @@ -577,13 +562,24 @@ public interface CameraDevice extends AutoCloseable { 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}, - * 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 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} @@ -713,6 +709,9 @@ public interface CameraDevice extends AutoCloseable { * A listener for notifications about the state of a camera * 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 ( * allowing for {@link #configureOutputs} to be called), about device * disconnection, and about unexpected device errors.</p> @@ -722,7 +721,7 @@ public interface CameraDevice extends AutoCloseable { * the {@link #capture}, {@link #captureBurst}, {@link * #setRepeatingRequest}, or {@link #setRepeatingBurst} methods. * - * @see #setDeviceListener + * @see CameraManager#openCamera */ public static abstract class StateListener { /** diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index f5ee367..65b6c7a 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -197,6 +197,8 @@ public final class CameraManager { * {@link #openCamera}. * * @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, * 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 * access the camera + * @throws IllegalArgumentException if listener or handler is null. * * @see #getCameraIdList * @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 { synchronized (mLock) { @@ -216,7 +221,10 @@ public final class CameraManager { ICameraDeviceUser cameraUser; 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(); mCameraService.connectDevice(device.getCallbacks(), @@ -225,10 +233,9 @@ public final class CameraManager { cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); // 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); - - return device; - } } catch (NumberFormatException e) { @@ -238,7 +245,6 @@ public final class CameraManager { throw e.asChecked(); } catch (RemoteException e) { // impossible - return null; } } @@ -303,16 +309,7 @@ public final class CameraManager { } } - final CameraDevice camera = openCamera(cameraId); - camera.setDeviceListener(listener, handler); - - // TODO: make truly async in the camera service - handler.post(new Runnable() { - @Override - public void run() { - listener.onOpened(camera); - } - }); + openCameraDeviceUserAsync(cameraId, listener, handler); } /** diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index 463063c..c5d0999 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -55,8 +55,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Object mLock = new Object(); private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); - private StateListener mDeviceListener; - private Handler mDeviceHandler; + private final StateListener mDeviceListener; + private final Handler mDeviceHandler; + + private boolean mIdle = true; private final SparseArray<CaptureListenerHolder> mCaptureListenerMap = new SparseArray<CaptureListenerHolder>(); @@ -67,8 +69,72 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { 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; + mDeviceListener = listener; + mDeviceHandler = handler; TAG = String.format("CameraDevice-%s-JV", mCameraId); DEBUG = Log.isLoggable(TAG, Log.DEBUG); } @@ -79,7 +145,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public void setRemoteDevice(ICameraDeviceUser remoteDevice) { // TODO: Move from decorator to direct binder-mediated exceptions - mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); + synchronized(mLock) { + mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); + + mDeviceHandler.post(mCallOnOpened); + mDeviceHandler.post(mCallOnUnconfigured); + } } @Override @@ -89,7 +160,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override 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) { + checkIfCameraClosed(); + HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create 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 { - // TODO: mRemoteDevice.beginConfigure + mRemoteDevice.waitUntilIdle(); + // TODO: mRemoteDevice.beginConfigure // Delete all streams first (to free up HW resources) for (Integer streamId : deleteList) { mRemoteDevice.deleteStream(streamId); @@ -126,7 +207,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } catch (CameraRuntimeException e) { if (e.getReason() == CAMERA_IN_USE) { 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(); @@ -134,6 +215,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible 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) throws CameraAccessException { synchronized (mLock) { + checkIfCameraClosed(); CameraMetadataNative templatedRequest = new CameraMetadataNative(); @@ -188,7 +276,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } synchronized (mLock) { - + checkIfCameraClosed(); int requestId; try { @@ -208,6 +296,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { mRepeatingRequestIdStack.add(requestId); } + if (mIdle) { + mDeviceHandler.post(mCallOnActive); + } + mIdle = false; + return requestId; } } @@ -233,7 +326,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public void stopRepeating() throws CameraAccessException { synchronized (mLock) { - + checkIfCameraClosed(); while (!mRepeatingRequestIdStack.isEmpty()) { int requestId = mRepeatingRequestIdStack.pop(); @@ -270,20 +363,11 @@ 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 public void flush() throws CameraAccessException { synchronized (mLock) { + checkIfCameraClosed(); + + mDeviceHandler.post(mCallOnBusy); try { mRemoteDevice.flush(); } catch (CameraRuntimeException e) { @@ -297,9 +381,6 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void close() { - - // TODO: every method should throw IllegalStateException after close has been called - synchronized (mLock) { try { @@ -312,8 +393,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible } - mRemoteDevice = null; + if (mRemoteDevice != null) { + mDeviceHandler.post(mCallOnClosed); + } + mRemoteDevice = null; } } @@ -399,49 +483,44 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override 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) { case ERROR_CAMERA_DISCONNECTED: - r = new Runnable() { - public void run() { - listener.onDisconnected(CameraDevice.this); - } - }; + r = mCallOnDisconnected; break; + default: + Log.e(TAG, "Unknown error from camera device: " + errorCode); + // no break case ERROR_CAMERA_DEVICE: case ERROR_CAMERA_SERVICE: r = new Runnable() { public void run() { - listener.onError(CameraDevice.this, errorCode); + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onError(CameraDevice.this, errorCode); + } } }; 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 public void onCameraIdle() { + if (isClosed()) return; + if (DEBUG) { Log.d(TAG, "Camera now idle"); } synchronized (mLock) { - if (CameraDevice.this.mDeviceListener == null) return; - final StateListener listener = CameraDevice.this.mDeviceListener; - Runnable r = new Runnable() { - public void run() { - listener.onIdle(CameraDevice.this); - } - }; - CameraDevice.this.mDeviceHandler.post(r); + if (!CameraDevice.this.mIdle) { + CameraDevice.this.mDeviceHandler.post(mCallOnIdle); + } + CameraDevice.this.mIdle = true; } } @@ -461,14 +540,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return; } + if (isClosed()) return; + // Dispatch capture start notice holder.getHandler().post( new Runnable() { public void run() { - holder.getListener().onCaptureStarted( - CameraDevice.this, - holder.getRequest(), - timestamp); + if (!CameraDevice.this.isClosed()) { + holder.getListener().onCaptureStarted( + CameraDevice.this, + holder.getRequest(), + timestamp); + } } }); } @@ -503,6 +586,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return; } + if (isClosed()) return; + final CaptureRequest request = holder.getRequest(); final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); @@ -510,10 +595,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { new Runnable() { @Override public void run() { - holder.getListener().onCaptureCompleted( - CameraDevice.this, - request, - resultAsCapture); + if (!CameraDevice.this.isClosed()){ + holder.getListener().onCaptureCompleted( + CameraDevice.this, + request, + resultAsCapture); + } } }); } @@ -541,4 +628,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { throw new IllegalStateException("CameraDevice was already closed"); } } + + private boolean isClosed() { + synchronized(mLock) { + return (mRemoteDevice == null); + } + } } |