/* * 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. * *

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.

* *

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.

*/ 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 mQueuedStates = new LinkedBlockingQueue<>(); /** * Create a new state waiter. * *

All {@code state}/{@code states} arguments used in other methods must be * in the range of {@code [0, stateNames.length - 1]}.

* * @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. * *

Any intermediate state transitions that is not {@code state} are ignored.

* *

Note: Only one waiter allowed at a time!

* * @param state 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. * *

Any intermediate state transitions that are not in {@code states} are ignored.

* *

Note: Only one waiter allowed at a time!

* * @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 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 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 checkStateCollectionInRange(Collection states) { for (int state : states) { checkStateInRange(state); } return states; } }