summaryrefslogtreecommitdiffstats
path: root/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
diff options
context:
space:
mode:
authorIgor Murashkin <iam@google.com>2013-09-19 16:28:36 -0700
committerIgor Murashkin <iam@google.com>2013-09-20 15:14:22 -0700
commit07f09b47112dc1094649da00f7e86024b67d5777 (patch)
tree03b9e4054c3f688e254421cfa87bd43876373da4 /camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
parent432e542fc574cc5ff42cd964bc645789b13786f6 (diff)
downloadandroid_frameworks_ex-07f09b47112dc1094649da00f7e86024b67d5777.tar.gz
android_frameworks_ex-07f09b47112dc1094649da00f7e86024b67d5777.tar.bz2
android_frameworks_ex-07f09b47112dc1094649da00f7e86024b67d5777.zip
camera2: New directory with camera2 extras/helpers
* Simplify using camera2 when possible * Offer a blocking openCamera call to not have to deal with listeners Bug: 10360518 Change-Id: I7f154db3a5300437785d6e8d0bfff5499ef9bc15
Diffstat (limited to 'camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java')
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java347
1 files changed, 347 insertions, 0 deletions
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
new file mode 100644
index 0000000..de036e1
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ex.camera2.blocking;
+
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import java.util.Objects;
+
+/**
+ * Expose {@link CameraManager} functionality with blocking functions.
+ *
+ * <p>Safe to use at the same time as the regular CameraManager, so this does not
+ * duplicate any functionality that is already blocking.</p>
+ *
+ * <p>Be careful when using this from UI thread! This function will typically block
+ * for about 500ms when successful, and as long as {@value #OPEN_TIME_OUT}ms when timing out.</p>
+ */
+public class BlockingCameraManager {
+
+ private static final String TAG = "CameraBlockingOpener";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int OPEN_TIME_OUT = 2000; // ms time out for openCamera
+
+ /**
+ * Exception thrown by {@link #openCamera} if the open fails asynchronously.
+ */
+ public static class BlockingOpenException extends Exception {
+ /**
+ * Suppress eclipse warning
+ */
+ private static final long serialVersionUID = 12397123891238912L;
+
+ public static final int ERROR_DISCONNECTED = 0; // Does not clash with ERROR_...
+
+ private final int mError;
+
+ public boolean wasDisconnected() {
+ return mError == ERROR_DISCONNECTED;
+ }
+
+ public boolean wasError() {
+ return mError != ERROR_DISCONNECTED;
+ }
+
+ /**
+ * Returns the error code {@link ERROR_DISCONNECTED} if disconnected, or one of
+ * {@code CameraDevice.StateListener#ERROR_*} if there was another error.
+ *
+ * @return int Disconnect/error code
+ */
+ public int getCode() {
+ return mError;
+ }
+
+ /**
+ * Thrown when camera device enters error state during open, or if
+ * it disconnects.
+ *
+ * @param errorCode
+ * @param message
+ *
+ * @see {@link CameraDevice.StateListener#ERROR_CAMERA_DEVICE}
+ */
+ public BlockingOpenException(int errorCode, String message) {
+ super(message);
+ mError = errorCode;
+ }
+ }
+
+ private final CameraManager mManager;
+
+ /**
+ * Create a new blocking camera manager.
+ *
+ * @param manager
+ * CameraManager returned by
+ * {@code Context.getSystemService(Context.CAMERA_SERVICE)}
+ */
+ public BlockingCameraManager(CameraManager manager) {
+ if (manager == null) {
+ throw new IllegalArgumentException("manager must not be null");
+ }
+ mManager = manager;
+ }
+
+ /**
+ * Open the camera, blocking it until it succeeds or fails.
+ *
+ * <p>Note that the Handler provided must not be null. Furthermore, if there is a handler,
+ * its Looper must not be the current thread's Looper. Otherwise we'd never receive
+ * the callbacks from the CameraDevice since this function would prevent them from being
+ * processed.</p>
+ *
+ * <p>Throws {@link CameraAccessException} for the same reason {@link CameraManager#openCamera}
+ * does.</p>
+ *
+ * <p>Throws {@link BlockingOpenException} when the open fails asynchronously (due to
+ * {@link CameraDevice.StateListener#onDisconnected(CameraDevice)} or
+ * ({@link CameraDevice.StateListener#onError(CameraDevice)}.</p>
+ *
+ * <p>Throws {@link TimeoutRuntimeException} if opening times out. This is usually
+ * highly unrecoverable, and all future calls to opening that camera will fail since the
+ * service will think it's busy. This class will do its best to clean up eventually.</p>
+ *
+ * @param cameraId
+ * Id of the camera
+ * @param listener
+ * Listener to the camera. onOpened, onDisconnected, onError need not be implemented.
+ * @param handler
+ * Handler which to run the listener on. Must not be null.
+ *
+ * @return CameraDevice
+ *
+ * @throws IllegalArgumentException
+ * If the handler is null, or if the handler's looper is current.
+ * @throws CameraAccessException
+ * If open fails immediately.
+ * @throws BlockingOpenException
+ * If open fails after blocking for some amount of time.
+ * @throws TimeoutRuntimeException
+ * If opening times out. Typically unrecoverable.
+ */
+ public CameraDevice openCamera(String cameraId, CameraDevice.StateListener listener,
+ Handler handler) throws CameraAccessException, BlockingOpenException {
+
+ if (handler == null) {
+ throw new IllegalArgumentException("handler must not be null");
+ } else if (handler.getLooper() == Looper.myLooper()) {
+ throw new IllegalArgumentException("handler's looper must not be the current looper");
+ }
+
+ return (new OpenListener(mManager, cameraId, listener, handler)).blockUntilOpen();
+ }
+
+ private static void assertEquals(Object a, Object b) {
+ if (!Objects.equals(a, b)) {
+ throw new AssertionError("Expected " + a + ", but got " + b);
+ }
+ }
+
+ /**
+ * Block until CameraManager#openCamera finishes with onOpened/onError/onDisconnected
+ *
+ * <p>Pass-through all StateListener changes to the proxy.</p>
+ *
+ * <p>Time out after {@link #OPEN_TIME_OUT} and unblock. Clean up camera if it arrives
+ * later.</p>
+ */
+ private class OpenListener extends CameraDevice.StateListener {
+ private static final int ERROR_UNINITIALIZED = -1;
+
+ private final String mCameraId;
+
+ private final CameraDevice.StateListener mProxy;
+
+ private final Object mLock = new Object();
+ private final ConditionVariable mDeviceReady = new ConditionVariable();
+
+ private CameraDevice mDevice = null;
+ private boolean mSuccess = false;
+ private int mError = ERROR_UNINITIALIZED;
+ private boolean mDisconnected = false;
+
+ private boolean mNoReply = true; // Start with no reply until proven otherwise
+ private boolean mTimedOut = false;
+
+ OpenListener(CameraManager manager, String cameraId,
+ CameraDevice.StateListener listener, Handler handler)
+ throws CameraAccessException {
+ mCameraId = cameraId;
+ mProxy = listener;
+ manager.openCamera(cameraId, this, handler);
+ }
+
+ // Freebie check to make sure we aren't calling functions multiple times.
+ // We should still test the state interactions in a separate more thorough test.
+ private void assertInitialState() {
+ assertEquals(null, mDevice);
+ assertEquals(false, mDisconnected);
+ assertEquals(ERROR_UNINITIALIZED, mError);
+ assertEquals(false, mSuccess);
+ }
+
+ @Override
+ public void onOpened(CameraDevice camera) {
+ if (VERBOSE) {
+ Log.v(TAG, "onOpened: camera " + ((camera != null) ? camera.getId() : "null"));
+ }
+
+ synchronized (mLock) {
+ assertInitialState();
+ mNoReply = false;
+ mSuccess = true;
+ mDevice = camera;
+ mDeviceReady.open();
+
+ if (mTimedOut && camera != null) {
+ camera.close();
+ return;
+ }
+ }
+
+ if (mProxy != null) mProxy.onOpened(camera);
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice camera) {
+ if (VERBOSE) {
+ Log.v(TAG, "onDisconnected: camera "
+ + ((camera != null) ? camera.getId() : "null"));
+ }
+
+ synchronized (mLock) {
+ assertInitialState();
+ mNoReply = false;
+ mDisconnected = true;
+ mDevice = camera;
+ mDeviceReady.open();
+
+ if (mTimedOut && camera != null) {
+ camera.close();
+ return;
+ }
+ }
+
+ if (mProxy != null) mProxy.onDisconnected(camera);
+ }
+
+ @Override
+ public void onError(CameraDevice camera, int error) {
+ if (VERBOSE) {
+ Log.v(TAG, "onError: camera " + ((camera != null) ? camera.getId() : "null"));
+ }
+
+ if (error <= 0) {
+ throw new AssertionError("Expected error to be a positive number");
+ }
+
+ synchronized (mLock) {
+ // Don't assert initial state. Error can happen later.
+ mNoReply = false;
+ mError = error;
+ mDevice = camera;
+ mDeviceReady.open();
+
+ if (mTimedOut && camera != null) {
+ camera.close();
+ return;
+ }
+ }
+
+ if (mProxy != null) mProxy.onError(camera, error);
+ }
+
+ @Override
+ public void onUnconfigured(CameraDevice camera) {
+ if (mProxy != null) mProxy.onUnconfigured(camera);
+ }
+
+ @Override
+ public void onIdle(CameraDevice camera) {
+ if (mProxy != null) mProxy.onIdle(camera);
+ }
+
+ @Override
+ public void onActive(CameraDevice camera) {
+ if (mProxy != null) mProxy.onActive(camera);
+ }
+
+ @Override
+ public void onBusy(CameraDevice camera) {
+ if (mProxy != null) mProxy.onBusy(camera);
+ }
+
+ @Override
+ public void onClosed(CameraDevice camera) {
+ if (mProxy != null) mProxy.onClosed(camera);
+ }
+
+ CameraDevice blockUntilOpen() throws BlockingOpenException {
+ /**
+ * Block until onOpened, onError, or onDisconnected
+ */
+ if (!mDeviceReady.block(OPEN_TIME_OUT)) {
+
+ synchronized (mLock) {
+ if (mNoReply) { // Give the async camera a fighting chance (required)
+ mTimedOut = true; // Clean up camera if it ever arrives later
+ throw new TimeoutRuntimeException(String.format(
+ "Timed out after %d ms while trying to open camera device %s",
+ OPEN_TIME_OUT, mCameraId));
+ }
+ }
+ }
+
+ synchronized (mLock) {
+ /**
+ * Determine which state we ended up in:
+ *
+ * - Throw exceptions for onError/onDisconnected
+ * - Return device for onOpened
+ */
+ if (!mSuccess && mDevice != null) {
+ mDevice.close();
+ }
+
+ if (mSuccess) {
+ return mDevice;
+ } else {
+ if (mDisconnected) {
+ throw new BlockingOpenException(
+ BlockingOpenException.ERROR_DISCONNECTED,
+ "Failed to open camera device: it is disconnected");
+ } else if (mError != ERROR_UNINITIALIZED) {
+ throw new BlockingOpenException(
+ mError,
+ "Failed to open camera device: error code " + mError);
+ } else {
+ throw new AssertionError("Failed to open camera device (impl bug)");
+ }
+ }
+ }
+ }
+ }
+}