summaryrefslogtreecommitdiffstats
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
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
-rw-r--r--camera2/Android.mk15
-rw-r--r--camera2/public/Android.mk26
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java347
-rw-r--r--camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java33
4 files changed, 421 insertions, 0 deletions
diff --git a/camera2/Android.mk b/camera2/Android.mk
new file mode 100644
index 0000000..9ac4a8a
--- /dev/null
+++ b/camera2/Android.mk
@@ -0,0 +1,15 @@
+# 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.
+
+include $(call all-subdir-makefiles)
diff --git a/camera2/public/Android.mk b/camera2/public/Android.mk
new file mode 100644
index 0000000..a0d1489
--- /dev/null
+++ b/camera2/public/Android.mk
@@ -0,0 +1,26 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := android-ex-camera2
+LOCAL_SDK_VERSION := current
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
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)");
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java b/camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java
new file mode 100644
index 0000000..c86b5fd
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.exceptions;
+
+/**
+ * Used instead of TimeoutException when something times out in a non-recoverable manner.
+ *
+ * <p>This typically happens due to a deadlock or bug in the camera service,
+ * so please file a bug for this. This should never ever happen in normal operation, which is
+ * why this exception is unchecked.</p>
+ */
+public class TimeoutRuntimeException extends RuntimeException {
+ public TimeoutRuntimeException(String message) {
+ super(message);
+ }
+
+ public TimeoutRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}