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

This commit is contained in:
Eino-Ville Talvala
2013-10-07 21:10:40 +00:00
committed by Android (Google) Code Review
3 changed files with 203 additions and 114 deletions

View File

@@ -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>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>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>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
*/
@@ -515,31 +525,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 {
/**

View File

@@ -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);
}
/**

View File

@@ -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 {
}
}
try {
// TODO: mRemoteDevice.beginConfigure
mDeviceHandler.post(mCallOnBusy);
stopRepeating();
try {
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();
@@ -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
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);
}
}
}