summaryrefslogtreecommitdiffstats
path: root/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java
diff options
context:
space:
mode:
Diffstat (limited to 'camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java')
-rw-r--r--camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java436
1 files changed, 295 insertions, 141 deletions
diff --git a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java
index dd4f77c..66762fd 100644
--- a/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java
+++ b/camera2/portability/src/com/android/ex/camera2/portability/CameraAgent.java
@@ -23,6 +23,7 @@ import android.hardware.Camera.OnZoomChangeListener;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.view.SurfaceHolder;
import com.android.ex.camera2.portability.debug.Log;
@@ -42,7 +43,7 @@ import com.android.ex.camera2.portability.debug.Log;
* {@code android.hardware.Camera.OnZoomChangeListener}, and
*/
public abstract class CameraAgent {
- public static final long CAMERA_OPERATION_TIMEOUT_MS = 2500;
+ public static final long CAMERA_OPERATION_TIMEOUT_MS = 3500;
private static final Log.Tag TAG = new Log.Tag("CamAgnt");
@@ -154,14 +155,6 @@ public abstract class CameraAgent {
}
/**
- * A handler for all camera api runtime exceptions.
- * The default behavior is to throw the runtime exception.
- */
- public static interface CameraExceptionCallback {
- public void onCameraException(RuntimeException e);
- }
-
- /**
* An interface which wraps
* {@link android.hardware.Camera.ErrorCallback}
*/
@@ -292,12 +285,17 @@ public abstract class CameraAgent {
*/
public void openCamera(final Handler handler, final int cameraId,
final CameraOpenCallback callback) {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0,
- CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget();
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.OPEN_CAMERA, cameraId, 0,
+ CameraOpenCallbackForward.getNewInstance(handler, callback)).sendToTarget();
+ }
+ });
+ } catch (final RuntimeException ex) {
+ getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -307,22 +305,30 @@ public abstract class CameraAgent {
* @param synced Whether this call should be synchronous.
*/
public void closeCamera(CameraProxy camera, boolean synced) {
- if (synced) {
- final WaitDoneBundle bundle = new WaitDoneBundle();
-
- getDispatchThread().runJobSync(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget();
- getCameraHandler().post(bundle.mUnlockRunnable);
- }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera release");
- } else {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().removeCallbacksAndMessages(null);
- getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget();
- }});
+ try {
+ if (synced) {
+ // Don't bother to wait since camera is in bad state.
+ if (getCameraState().isInvalid()) {
+ return;
+ }
+ final WaitDoneBundle bundle = new WaitDoneBundle();
+
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget();
+ getCameraHandler().post(bundle.mUnlockRunnable);
+ }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera release");
+ } else {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().removeCallbacksAndMessages(null);
+ getCameraHandler().obtainMessage(CameraActions.RELEASE).sendToTarget();
+ }});
+ }
+ } catch (final RuntimeException ex) {
+ getCameraExceptionHandler().onDispatchThreadException(ex);
}
}
@@ -330,8 +336,7 @@ public abstract class CameraAgent {
* Sets a callback for handling camera api runtime exceptions on
* a handler.
*/
- public abstract void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
- Handler handler);
+ public abstract void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler);
/**
* Recycles the resources used by this instance. CameraAgent will be in
@@ -355,11 +360,21 @@ public abstract class CameraAgent {
protected abstract DispatchThread getDispatchThread();
/**
+ * @return The state machine tracking the camera API's current status.
+ */
+ protected abstract CameraStateHolder getCameraState();
+
+ /**
+ * @return The exception handler.
+ */
+ protected abstract CameraExceptionHandler getCameraExceptionHandler();
+
+ /**
* An interface that takes camera operation requests and post messages to the
* camera handler thread. All camera operations made through this interface is
* asynchronous by default except those mentioned specifically.
*/
- public static abstract class CameraProxy {
+ public abstract static class CameraProxy {
/**
* Returns the underlying {@link android.hardware.Camera} object used
@@ -387,6 +402,11 @@ public abstract class CameraAgent {
public abstract CameraCapabilities getCapabilities();
/**
+ * @return The camera agent which creates this proxy.
+ */
+ public abstract CameraAgent getAgent();
+
+ /**
* Reconnects to the camera device. On success, the camera device will
* be returned through {@link CameraAgent
* .CameraOpenCallback#onCameraOpened(com.android.camera.cameradevice.CameraAgent
@@ -398,12 +418,16 @@ public abstract class CameraAgent {
* @param cb The callback when any error happens.
*/
public void reconnect(final Handler handler, final CameraOpenCallback cb) {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0,
- CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget();
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.RECONNECT, getCameraId(), 0,
+ CameraOpenCallbackForward.getNewInstance(handler, cb)).sendToTarget();
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -412,13 +436,22 @@ public abstract class CameraAgent {
* @see android.hardware.Camera#unlock()
*/
public void unlock() {
+ // Don't bother to wait since camera is in bad state.
+ if (getCameraState().isInvalid()) {
+ return;
+ }
final WaitDoneBundle bundle = new WaitDoneBundle();
- getDispatchThread().runJobSync(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK);
- getCameraHandler().post(bundle.mUnlockRunnable);
- }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock");
+ try {
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.UNLOCK);
+ getCameraHandler().post(bundle.mUnlockRunnable);
+ }
+ }, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "camera unlock");
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -426,44 +459,84 @@ public abstract class CameraAgent {
* @see android.hardware.Camera#lock()
*/
public void lock() {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().sendEmptyMessage(CameraActions.LOCK);
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.LOCK);
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
* Sets the {@link android.graphics.SurfaceTexture} for preview.
*
+ * <p>Note that, once this operation has been performed, it is no longer
+ * possible to change the preview or photo sizes in the
+ * {@link CameraSettings} instance for this camera, and the mutators for
+ * these fields are allowed to ignore all further invocations until the
+ * preview is stopped with {@link #stopPreview}.</p>
+ *
* @param surfaceTexture The {@link SurfaceTexture} for preview.
- */
+ *
+ * @see CameraSettings#setPhotoSize
+ * @see CameraSettings#setPreviewSize
+ */
+ // XXX: Despite the above documentation about locking the sizes, the API
+ // 1 implementation doesn't currently enforce this at all, although the
+ // Camera class warns that preview sizes shouldn't be changed while a
+ // preview is running. Furthermore, the API 2 implementation doesn't yet
+ // unlock the sizes when stopPreview() is invoked (see related FIXME on
+ // the STOP_PREVIEW case in its handler; in the meantime, changing API 2
+ // sizes would require closing and reopening the camera.
public void setPreviewTexture(final SurfaceTexture surfaceTexture) {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler()
- .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
- .sendToTarget();
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
+ .sendToTarget();
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
* Blocks until a {@link android.graphics.SurfaceTexture} has been set
* for preview.
*
+ * <p>Note that, once this operation has been performed, it is no longer
+ * possible to change the preview or photo sizes in the
+ * {@link CameraSettings} instance for this camera, and the mutators for
+ * these fields are allowed to ignore all further invocations.</p>
+ *
* @param surfaceTexture The {@link SurfaceTexture} for preview.
+ *
+ * @see CameraSettings#setPhotoSize
+ * @see CameraSettings#setPreviewSize
*/
public void setPreviewTextureSync(final SurfaceTexture surfaceTexture) {
+ // Don't bother to wait since camera is in bad state.
+ if (getCameraState().isInvalid()) {
+ return;
+ }
final WaitDoneBundle bundle = new WaitDoneBundle();
- getDispatchThread().runJobSync(new Runnable() {
- @Override
- public void run() {
- getCameraHandler()
- .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
- .sendToTarget();
- getCameraHandler().post(bundle.mUnlockRunnable);
- }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "set preview texture");
+ try {
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture)
+ .sendToTarget();
+ getCameraHandler().post(bundle.mUnlockRunnable);
+ }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "set preview texture");
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -472,25 +545,33 @@ public abstract class CameraAgent {
* @param surfaceHolder The {@link SurfaceHolder} for preview.
*/
public void setPreviewDisplay(final SurfaceHolder surfaceHolder) {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler()
- .obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder)
- .sendToTarget();
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder)
+ .sendToTarget();
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
* Starts the camera preview.
*/
public void startPreview() {
+ try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
getCameraHandler()
.obtainMessage(CameraActions.START_PREVIEW_ASYNC, null).sendToTarget();
}});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -498,6 +579,7 @@ public abstract class CameraAgent {
* the preview starts.
*/
public void startPreviewWithCallback(final Handler h, final CameraStartPreviewCallback cb) {
+ try {
getDispatchThread().runJob(new Runnable() {
@Override
public void run() {
@@ -505,6 +587,9 @@ public abstract class CameraAgent {
CameraStartPreviewCallbackForward.getNewInstance(h, cb))
.sendToTarget();
}});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -513,13 +598,21 @@ public abstract class CameraAgent {
* continues to release resources related to camera preview.
*/
public void stopPreview() {
+ // Don't bother to wait since camera is in bad state.
+ if (getCameraState().isInvalid()) {
+ return;
+ }
final WaitDoneBundle bundle = new WaitDoneBundle();
- getDispatchThread().runJobSync(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().sendEmptyMessage(CameraActions.STOP_PREVIEW);
- getCameraHandler().post(bundle.mUnlockRunnable);
- }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview");
+ try {
+ getDispatchThread().runJobSync(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().obtainMessage(CameraActions.STOP_PREVIEW, bundle)
+ .sendToTarget();
+ }}, bundle.mWaitLock, CAMERA_OPERATION_TIMEOUT_MS, "stop preview");
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -558,14 +651,18 @@ public abstract class CameraAgent {
* @param callbackBuffer The buffer allocated for the preview data.
*/
public void addCallbackBuffer(final byte[] callbackBuffer) {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler()
- .obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer)
- .sendToTarget();
- }
- });
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.ADD_CALLBACK_BUFFER, callbackBuffer)
+ .sendToTarget();
+ }
+ });
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -578,14 +675,18 @@ public abstract class CameraAgent {
/**
* Cancels the auto-focus process.
+ *
+ * <p>This action has the highest priority and will get processed before anything
+ * else that is pending. Moreover, any pending auto-focuses that haven't yet
+ * began will also be ignored.</p>
*/
public void cancelAutoFocus() {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().removeMessages(CameraActions.AUTO_FOCUS);
- getCameraHandler().sendEmptyMessage(CameraActions.CANCEL_AUTO_FOCUS);
- }});
+ // Do not use the dispatch thread since we want to avoid a wait-cycle
+ // between applySettingsHelper which waits until the state is not FOCUSING.
+ // cancelAutoFocus should get executed asap, set the state back to idle.
+ getCameraHandler().sendMessageAtFrontOfQueue(
+ getCameraHandler().obtainMessage(CameraActions.CANCEL_AUTO_FOCUS));
+ getCameraHandler().sendEmptyMessage(CameraActions.CANCEL_AUTO_FOCUS_FINISH);
}
/**
@@ -638,14 +739,32 @@ public abstract class CameraAgent {
* @param capture Whether to adjust the JPEG capture orientation as well as the preview one.
*/
public void setDisplayOrientation(final int degrees, final boolean capture) {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler()
- .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees,
- capture ? 1 : 0)
- .sendToTarget();
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_DISPLAY_ORIENTATION, degrees,
+ capture ? 1 : 0)
+ .sendToTarget();
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
+ }
+
+ public void setJpegOrientation(final int degrees) {
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.SET_JPEG_ORIENTATION, degrees, 0)
+ .sendToTarget();
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -668,34 +787,33 @@ public abstract class CameraAgent {
* Starts the face detection.
*/
public void startFaceDetection() {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION);
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.START_FACE_DETECTION);
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
* Stops the face detection.
*/
public void stopFaceDetection() {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION);
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.STOP_FACE_DETECTION);
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
- * Registers an error callback.
- *
- * @param handler The handler on which the callback will be invoked.
- * @param cb The error callback.
- * @see android.hardware.Camera#setErrorCallback(android.hardware.Camera.ErrorCallback)
- */
- public abstract void setErrorCallback(Handler handler, CameraErrorCallback cb);
-
- /**
* Sets the camera parameters.
*
* @param params The camera parameters to use.
@@ -732,27 +850,40 @@ public abstract class CameraAgent {
protected boolean applySettingsHelper(CameraSettings settings,
final int statesToAwait) {
if (settings == null) {
- Log.v(TAG, "null parameters in applySettings()");
+ Log.v(TAG, "null argument in applySettings()");
return false;
}
if (!getCapabilities().supports(settings)) {
+ Log.w(TAG, "Unsupported settings in applySettings()");
return false;
}
final CameraSettings copyOfSettings = settings.copy();
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraState().waitForStates(statesToAwait);
- getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings)
- .sendToTarget();
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ CameraStateHolder cameraState = getCameraState();
+ // Don't bother to wait since camera is in bad state.
+ if (cameraState.isInvalid()) {
+ return;
+ }
+ cameraState.waitForStates(statesToAwait);
+ getCameraHandler().obtainMessage(CameraActions.APPLY_SETTINGS, copyOfSettings)
+ .sendToTarget();
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
return true;
}
/**
* Applies the settings to the camera device.
*
+ * <p>If the camera is either focusing or capturing; settings applications
+ * will be (asynchronously) deferred until those operations complete.</p>
+ *
* @param settings The settings to use on the device.
* @return Whether the settings can be applied.
*/
@@ -763,11 +894,15 @@ public abstract class CameraAgent {
* settings regardless of the dirty bit.
*/
public void refreshSettings() {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS);
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler().sendEmptyMessage(CameraActions.REFRESH_PARAMETERS);
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -777,13 +912,17 @@ public abstract class CameraAgent {
* {@code false} to disable it.
*/
public void enableShutterSound(final boolean enable) {
- getDispatchThread().runJob(new Runnable() {
- @Override
- public void run() {
- getCameraHandler()
- .obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0)
- .sendToTarget();
- }});
+ try {
+ getDispatchThread().runJob(new Runnable() {
+ @Override
+ public void run() {
+ getCameraHandler()
+ .obtainMessage(CameraActions.ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0)
+ .sendToTarget();
+ }});
+ } catch (final RuntimeException ex) {
+ getAgent().getCameraExceptionHandler().onDispatchThreadException(ex);
+ }
}
/**
@@ -826,5 +965,20 @@ public abstract class CameraAgent {
}
}};
}
+
+ /**
+ * Notify all synchronous waiters waiting on message completion with {@link #mWaitLock}.
+ *
+ * <p>This assumes that the message was sent with {@code this} as the {@code Message#obj}.
+ * Otherwise the message is ignored.</p>
+ */
+ /*package*/ static void unblockSyncWaiters(Message msg) {
+ if (msg == null) return;
+
+ if (msg.obj instanceof WaitDoneBundle) {
+ WaitDoneBundle bundle = (WaitDoneBundle)msg.obj;
+ bundle.mUnlockRunnable.run();
+ }
+ }
}
}