summaryrefslogtreecommitdiffstats
path: root/core/java/android/hardware
diff options
context:
space:
mode:
authorEino-Ville Talvala <etalvala@google.com>2013-10-03 11:15:21 -0700
committerEino-Ville Talvala <etalvala@google.com>2013-10-07 12:40:49 -0700
commit868d904306c6a96d94fa0da03515c51c86eefc63 (patch)
tree5f633e51d92ad8818e9b763f62391812824e6b7c /core/java/android/hardware
parentdd88879ce19332a5905699bc008504fd43d983d7 (diff)
downloadframeworks_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')
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java85
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java29
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java203
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);
+ }
+ }
}