diff options
author | Igor Murashkin <iam@google.com> | 2013-09-19 16:28:36 -0700 |
---|---|---|
committer | Igor Murashkin <iam@google.com> | 2013-09-20 15:14:22 -0700 |
commit | 07f09b47112dc1094649da00f7e86024b67d5777 (patch) | |
tree | 03b9e4054c3f688e254421cfa87bd43876373da4 /camera2/public/src/com/android | |
parent | 432e542fc574cc5ff42cd964bc645789b13786f6 (diff) | |
download | android_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')
-rw-r--r-- | camera2/public/src/com/android/ex/camera2/blocking/BlockingCameraManager.java | 347 | ||||
-rw-r--r-- | camera2/public/src/com/android/ex/camera2/exceptions/TimeoutRuntimeException.java | 33 |
2 files changed, 380 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)"); + } + } + } + } + } +} 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); + } +} |