summaryrefslogtreecommitdiffstats
path: root/camera2
diff options
context:
space:
mode:
authorIgor Murashkin <iam@google.com>2014-06-04 15:39:15 -0700
committerIgor Murashkin <iam@google.com>2014-06-09 17:54:09 -0700
commit77b7bbe039393c2d8c1107c07c65028886eef252 (patch)
tree21d0b05fd4d2c28d21fe45ae22af326a28c2531c /camera2
parent00a9b46b5a85c2e7ff03d94e517f732d397eb020 (diff)
downloadandroid_frameworks_ex-77b7bbe039393c2d8c1107c07c65028886eef252.tar.gz
android_frameworks_ex-77b7bbe039393c2d8c1107c07c65028886eef252.tar.bz2
android_frameworks_ex-77b7bbe039393c2d8c1107c07c65028886eef252.zip
camera2: Add state waiters for the session state listener
Bug: 15474402 Change-Id: I4ae86166b5ce99a854c754c8ac6d6c23c683b14a
Diffstat (limited to 'camera2')
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java228
-rw-r--r--camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java8
-rw-r--r--camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java21
-rw-r--r--camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java220
4 files changed, 477 insertions, 0 deletions
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java
new file mode 100644
index 0000000..725b368
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingSessionListener.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2014 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.CameraCaptureSession;
+import android.os.ConditionVariable;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+import com.android.ex.camera2.utils.StateChangeListener;
+import com.android.ex.camera2.utils.StateWaiter;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+
+/**
+ * A camera session listener that implements blocking operations on session state changes.
+ *
+ * <p>Provides a waiter that can be used to block until the next unobserved state of the
+ * requested type arrives.</p>
+ *
+ * <p>Pass-through all StateListener changes to the proxy.</p>
+ *
+ * @see #getStateWaiter
+ */
+public class BlockingSessionListener extends CameraCaptureSession.StateListener {
+ /**
+ * Session is configured, ready for captures
+ */
+ public static final int SESSION_CONFIGURED = 0;
+
+ /**
+ * Session has failed to configure, can't do any captures
+ */
+ public static final int SESSION_CONFIGURE_FAILED = 1;
+
+ /**
+ * Session is ready
+ */
+ public static final int SESSION_READY = 2;
+
+ /**
+ * Session is active (transitory)
+ */
+ public static final int SESSION_ACTIVE = 3;
+
+ /**
+ * Session is closed
+ */
+ public static final int SESSION_CLOSED = 4;
+
+ private final int NUM_STATES = 5;
+
+ /*
+ * Private fields
+ */
+ private static final String TAG = "BlockingSessionListener";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final CameraCaptureSession.StateListener mProxy;
+ private final SessionFuture mSessionFuture = new SessionFuture();
+
+ private final StateWaiter mStateWaiter = new StateWaiter(mStateNames);
+ private final StateChangeListener mStateChangeListener = mStateWaiter.getListener();
+
+ private static final String[] mStateNames = {
+ "SESSION_CONFIGURED",
+ "SESSION_CONFIGURE_FAILED",
+ "SESSION_READY",
+ "SESSION_ACTIVE",
+ "SESSION_CLOSED"
+ };
+
+ /**
+ * Create a blocking session listener without forwarding the session listener invocations
+ * to another session listener.
+ */
+ public BlockingSessionListener() {
+ mProxy = null;
+ }
+
+ /**
+ * Create a blocking session listener; forward original listener invocations
+ * into {@code listener}.
+ *
+ * @param listener a non-{@code null} listener to forward invocations into
+ *
+ * @throws NullPointerException if {@code listener} was {@code null}
+ */
+ public BlockingSessionListener(CameraCaptureSession.StateListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener must not be null");
+ }
+ mProxy = listener;
+ }
+
+ /**
+ * Acquire the state waiter; can be used to block until a set of state transitions have
+ * been reached.
+ *
+ * <p>Only one thread should wait at a time.</p>
+ */
+ public StateWaiter getStateWaiter() {
+ return mStateWaiter;
+ }
+
+ /**
+ * Return session if already have it; otherwise wait until any of the session listener
+ * invocations fire and the session is available.
+ *
+ * <p>Does not consume any of the states from the state waiter.</p>
+ *
+ * @param timeoutMs how many milliseconds to wait for
+ * @return a non-{@code null} {@link CameraCaptureSession} instance
+ *
+ * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs}
+ */
+ public CameraCaptureSession waitAndGetSession(long timeoutMs) {
+ try {
+ return mSessionFuture.get(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ throw new TimeoutRuntimeException(
+ String.format("Failed to get session after %s milliseconds", timeoutMs), e);
+ }
+ }
+
+ /*
+ * CameraCaptureSession.StateListener implementation
+ */
+
+ @Override
+ public void onActive(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onActive(session);
+ mStateChangeListener.onStateChanged(SESSION_ACTIVE);
+ }
+
+ @Override
+ public void onClosed(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onClosed(session);
+ mStateChangeListener.onStateChanged(SESSION_CLOSED);
+ }
+
+ @Override
+ public void onConfigured(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onConfigured(session);
+ mStateChangeListener.onStateChanged(SESSION_CONFIGURED);
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onConfigureFailed(session);
+ mStateChangeListener.onStateChanged(SESSION_CONFIGURE_FAILED);
+ }
+
+ @Override
+ public void onReady(CameraCaptureSession session) {
+ mSessionFuture.setSession(session);
+ if (mProxy != null) mProxy.onReady(session);
+ mStateChangeListener.onStateChanged(SESSION_READY);
+ }
+
+ private static class SessionFuture implements Future<CameraCaptureSession> {
+ private volatile CameraCaptureSession mSession;
+ ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
+
+ public void setSession(CameraCaptureSession session) {
+ mSession = session;
+ mCondVar.open();
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return false; // don't allow canceling this task
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false; // can never cancel this task
+ }
+
+ @Override
+ public boolean isDone() {
+ return mSession != null;
+ }
+
+ @Override
+ public CameraCaptureSession get() {
+ mCondVar.block();
+ return mSession;
+ }
+
+ @Override
+ public CameraCaptureSession get(long timeout, TimeUnit unit) throws TimeoutException {
+ long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
+ if (!mCondVar.block(timeoutMs)) {
+ throw new TimeoutException(
+ "Failed to receive session after " + timeout + " " + unit);
+ }
+
+ if (mSession == null) {
+ throw new AssertionError();
+ }
+ return mSession;
+ }
+
+ }
+}
diff --git a/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
index 1f173eb..ef3b293 100644
--- a/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
+++ b/camera2/public/src/com/android/ex/camera2/blocking/BlockingStateListener.java
@@ -86,21 +86,25 @@ public class BlockingStateListener extends CameraDevice.StateListener {
/**
* Device is unconfigured
*/
+ @Deprecated
public static final int STATE_UNCONFIGURED = 1;
/**
* Device is idle
*/
+ @Deprecated
public static final int STATE_IDLE = 2;
/**
* Device is active (transitory)
*/
+ @Deprecated
public static final int STATE_ACTIVE = 3;
/**
* Device is busy (transitory)
*/
+ @Deprecated
public static final int STATE_BUSY = 4;
/**
@@ -150,24 +154,28 @@ public class BlockingStateListener extends CameraDevice.StateListener {
}
@Override
+ @Deprecated
public void onUnconfigured(CameraDevice camera) {
if (mProxy != null) mProxy.onUnconfigured(camera);
setCurrentState(STATE_UNCONFIGURED);
}
@Override
+ @Deprecated
public void onIdle(CameraDevice camera) {
if (mProxy != null) mProxy.onIdle(camera);
setCurrentState(STATE_IDLE);
}
@Override
+ @Deprecated
public void onActive(CameraDevice camera) {
if (mProxy != null) mProxy.onActive(camera);
setCurrentState(STATE_ACTIVE);
}
@Override
+ @Deprecated
public void onBusy(CameraDevice camera) {
if (mProxy != null) mProxy.onBusy(camera);
setCurrentState(STATE_BUSY);
diff --git a/camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java b/camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java
new file mode 100644
index 0000000..48cca17
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/utils/StateChangeListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2014 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.utils;
+
+public interface StateChangeListener {
+ public void onStateChanged(int state);
+}
diff --git a/camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java b/camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java
new file mode 100644
index 0000000..ec57829
--- /dev/null
+++ b/camera2/public/src/com/android/ex/camera2/utils/StateWaiter.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2014 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.utils;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Block until a specific state change occurs.
+ *
+ * <p>Provides wait calls that block until the next unobserved state of the
+ * requested type arrives. Unobserved states are states that have occurred since
+ * the last wait, or that will be received from the camera device in the
+ * future.</p>
+ *
+ * <p>Thread interruptions are not supported; interrupting a thread that is either
+ * waiting with {@link #waitForState} / {@link #waitForAnyOfStates} or is currently in
+ * {@link StateChangeListener#onStateChanged} (provided by {@link #getListener}) will result in an
+ * {@link UnsupportedOperationException} being raised on that thread.</p>
+ */
+public final class StateWaiter {
+
+ private static final String TAG = "StateWaiter";
+ private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private final String[] mStateNames;
+ private final int mStateCount;
+ private final StateChangeListener mListener;
+
+ /** Guard waitForState, waitForAnyState to only have one waiter */
+ private final AtomicBoolean mWaiting = new AtomicBoolean(false);
+
+ private final LinkedBlockingQueue<Integer> mQueuedStates = new LinkedBlockingQueue<>();
+
+ /**
+ * Create a new state waiter.
+ *
+ * <p>All {@code state}/{@code states} arguments used in other methods must be
+ * in the range of {@code [0, stateNames.length - 1]}.</p>
+ *
+ * @param stateNames an array of string names, used to mark the range of the valid states
+ */
+ public StateWaiter(String[] stateNames) {
+ mStateCount = stateNames.length;
+ mStateNames = new String[mStateCount];
+ System.arraycopy(stateNames, /*srcPos*/0, mStateNames, /*dstPos*/0, mStateCount);
+
+ mListener = new StateChangeListener() {
+ @Override
+ public void onStateChanged(int state) {
+ queueStateTransition(checkStateInRange(state));
+ }
+ };
+ }
+
+ public StateChangeListener getListener() {
+ return mListener;
+ }
+
+ /**
+ * Wait until the desired state is observed, checking all state
+ * transitions since the last time a state was waited on.
+ *
+ * <p>Any intermediate state transitions that is not {@code state} are ignored.</p>
+ *
+ * <p>Note: Only one waiter allowed at a time!</p>
+ *
+ * @param desired state to observe a transition to
+ * @param timeoutMs how long to wait in milliseconds
+ *
+ * @throws IllegalArgumentException if {@code state} was out of range
+ * @throws TimeoutRuntimeException if the desired state is not observed before timeout.
+ * @throws IllegalStateException if another thread is already waiting for a state transition
+ */
+ public void waitForState(int state, long timeoutMs) {
+ Integer[] stateArray = { checkStateInRange(state) };
+
+ waitForAnyOfStates(Arrays.asList(stateArray), timeoutMs);
+ }
+
+ /**
+ * Wait until the one of the desired {@code states} is observed, checking all
+ * state transitions since the last time a state was waited on.
+ *
+ * <p>Any intermediate state transitions that are not in {@code states} are ignored.</p>
+ *
+ * <p>Note: Only one waiter allowed at a time!</p>
+ *
+ * @param states Set of desired states to observe a transition to.
+ * @param timeoutMs how long to wait in milliseconds
+ *
+ * @return the state reached
+ *
+ * @throws IllegalArgumentException if {@code state} was out of range
+ * @throws TimeoutRuntimeException if none of the states is observed before timeout.
+ * @throws IllegalStateException if another thread is already waiting for a state transition
+ */
+ public int waitForAnyOfStates(Collection<Integer> states, final long timeoutMs) {
+ checkStateCollectionInRange(states);
+
+ // Acquire exclusive waiting privileges
+ if (mWaiting.getAndSet(true)) {
+ throw new IllegalStateException("Only one waiter allowed at a time");
+ }
+
+ Integer nextState = null;
+ try {
+ if (VERBOSE) {
+ StringBuilder s = new StringBuilder("Waiting for state(s) ");
+ appendStateNames(s, states);
+ Log.v(TAG, s.toString());
+ }
+
+ long timeoutLeft = timeoutMs;
+ long startMs = SystemClock.elapsedRealtime();
+ while ((nextState = mQueuedStates.poll(timeoutLeft, TimeUnit.MILLISECONDS)) != null) {
+ if (VERBOSE) {
+ Log.v(TAG, " Saw transition to " + getStateName(nextState));
+ }
+
+ if (states.contains(nextState)) {
+ break;
+ }
+
+ long endMs = SystemClock.elapsedRealtime();
+ timeoutLeft -= (endMs - startMs);
+ startMs = endMs;
+ }
+ } catch (InterruptedException e) {
+ throw new UnsupportedOperationException("Does not support interrupts on waits", e);
+ } finally {
+ // Release exclusive waiting privileges
+ mWaiting.set(false);
+ }
+
+ if (!states.contains(nextState)) {
+ StringBuilder s = new StringBuilder("Timed out after ");
+ s.append(timeoutMs);
+ s.append(" ms waiting for state(s) ");
+ appendStateNames(s, states);
+
+ throw new TimeoutRuntimeException(s.toString());
+ }
+
+ return nextState;
+ }
+
+ /**
+ * Convert state integer to a String
+ */
+ public String getStateName(int state) {
+ return mStateNames[checkStateInRange(state)];
+ }
+
+ /**
+ * Append all states to string
+ */
+ public void appendStateNames(StringBuilder s, Collection<Integer> states) {
+ checkStateCollectionInRange(states);
+
+ boolean start = true;
+ for (Integer state : states) {
+ if (!start) {
+ s.append(" ");
+ }
+
+ s.append(getStateName(state));
+ start = false;
+ }
+ }
+
+ private void queueStateTransition(int state) {
+ if (VERBOSE) Log.v(TAG, "setCurrentState - state now " + getStateName(state));
+
+ try {
+ mQueuedStates.put(state);
+ } catch(InterruptedException e) {
+ throw new UnsupportedOperationException("Unable to set current state", e);
+ }
+ }
+
+ private int checkStateInRange(int state) {
+ if (state < 0 || state >= mStateCount) {
+ throw new IllegalArgumentException("State out of range " + state);
+ }
+
+ return state;
+ }
+
+ private Collection<Integer> checkStateCollectionInRange(Collection<Integer> states) {
+ for (int state : states) {
+ checkStateInRange(state);
+ }
+
+ return states;
+ }
+
+}