1 /*
2  * Copyright 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ex.camera2.utils;
18 
19 import android.os.SystemClock;
20 import android.util.Log;
21 
22 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
23 
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.concurrent.LinkedBlockingQueue;
27 import java.util.concurrent.TimeUnit;
28 import java.util.concurrent.atomic.AtomicBoolean;
29 
30 /**
31  * Block until a specific state change occurs.
32  *
33  * <p>Provides wait calls that block until the next unobserved state of the
34  * requested type arrives. Unobserved states are states that have occurred since
35  * the last wait, or that will be received from the camera device in the
36  * future.</p>
37  *
38  * <p>Thread interruptions are not supported; interrupting a thread that is either
39  * waiting with {@link #waitForState} / {@link #waitForAnyOfStates} or is currently in
40  * {@link StateChangeListener#onStateChanged} (provided by {@link #getListener}) will result in an
41  * {@link UnsupportedOperationException} being raised on that thread.</p>
42  */
43 public final class StateWaiter {
44 
45     private static final String TAG = "StateWaiter";
46     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
47 
48     private final String[] mStateNames;
49     private final int mStateCount;
50     private final StateChangeListener mListener;
51 
52     /** Guard waitForState, waitForAnyState to only have one waiter */
53     private final AtomicBoolean mWaiting = new AtomicBoolean(false);
54 
55     private final LinkedBlockingQueue<Integer> mQueuedStates = new LinkedBlockingQueue<>();
56 
57     /**
58      * Create a new state waiter.
59      *
60      * <p>All {@code state}/{@code states} arguments used in other methods must be
61      * in the range of {@code [0, stateNames.length - 1]}.</p>
62      *
63      * @param stateNames an array of string names, used to mark the range of the valid states
64      */
StateWaiter(String[] stateNames)65     public StateWaiter(String[] stateNames) {
66         mStateCount = stateNames.length;
67         mStateNames = new String[mStateCount];
68         System.arraycopy(stateNames, /*srcPos*/0, mStateNames, /*dstPos*/0, mStateCount);
69 
70         mListener = new StateChangeListener() {
71             @Override
72             public void onStateChanged(int state) {
73                 queueStateTransition(checkStateInRange(state));
74             }
75         };
76     }
77 
getListener()78     public StateChangeListener getListener() {
79         return mListener;
80     }
81 
82     /**
83      * Wait until the desired state is observed, checking all state
84      * transitions since the last time a state was waited on.
85      *
86      * <p>Any intermediate state transitions that is not {@code state} are ignored.</p>
87      *
88      * <p>Note: Only one waiter allowed at a time!</p>
89      *
90      * @param state state to observe a transition to
91      * @param timeoutMs how long to wait in milliseconds
92      *
93      * @throws IllegalArgumentException if {@code state} was out of range
94      * @throws TimeoutRuntimeException if the desired state is not observed before timeout.
95      * @throws IllegalStateException if another thread is already waiting for a state transition
96      */
waitForState(int state, long timeoutMs)97     public void waitForState(int state, long timeoutMs) {
98         Integer[] stateArray = { checkStateInRange(state) };
99 
100         waitForAnyOfStates(Arrays.asList(stateArray), timeoutMs);
101     }
102 
103     /**
104      * Wait until the one of the desired {@code states} is observed, checking all
105      * state transitions since the last time a state was waited on.
106      *
107      * <p>Any intermediate state transitions that are not in {@code states} are ignored.</p>
108      *
109      * <p>Note: Only one waiter allowed at a time!</p>
110      *
111      * @param states Set of desired states to observe a transition to.
112      * @param timeoutMs how long to wait in milliseconds
113      *
114      * @return the state reached
115      *
116      * @throws IllegalArgumentException if {@code state} was out of range
117      * @throws TimeoutRuntimeException if none of the states is observed before timeout.
118      * @throws IllegalStateException if another thread is already waiting for a state transition
119      */
waitForAnyOfStates(Collection<Integer> states, final long timeoutMs)120     public int waitForAnyOfStates(Collection<Integer> states, final long timeoutMs) {
121         checkStateCollectionInRange(states);
122 
123         // Acquire exclusive waiting privileges
124         if (mWaiting.getAndSet(true)) {
125             throw new IllegalStateException("Only one waiter allowed at a time");
126         }
127 
128         Integer nextState = null;
129         try {
130             if (VERBOSE) {
131                 StringBuilder s = new StringBuilder("Waiting for state(s) ");
132                 appendStateNames(s, states);
133                 Log.v(TAG, s.toString());
134             }
135 
136             long timeoutLeft = timeoutMs;
137             long startMs = SystemClock.elapsedRealtime();
138             while ((nextState = mQueuedStates.poll(timeoutLeft, TimeUnit.MILLISECONDS)) != null) {
139                 if (VERBOSE) {
140                     Log.v(TAG, "  Saw transition to " + getStateName(nextState));
141                 }
142 
143                 if (states.contains(nextState)) {
144                     break;
145                 }
146 
147                 long endMs = SystemClock.elapsedRealtime();
148                 timeoutLeft -= (endMs - startMs);
149                 startMs = endMs;
150             }
151         } catch (InterruptedException e) {
152             throw new UnsupportedOperationException("Does not support interrupts on waits", e);
153         } finally {
154             // Release exclusive waiting privileges
155             mWaiting.set(false);
156         }
157 
158         if (!states.contains(nextState)) {
159             StringBuilder s = new StringBuilder("Timed out after ");
160             s.append(timeoutMs);
161             s.append(" ms waiting for state(s) ");
162             appendStateNames(s, states);
163 
164             throw new TimeoutRuntimeException(s.toString());
165         }
166 
167         return nextState;
168     }
169 
170     /**
171      * Convert state integer to a String
172      */
getStateName(int state)173     public String getStateName(int state) {
174         return mStateNames[checkStateInRange(state)];
175     }
176 
177     /**
178      * Append all states to string
179      */
appendStateNames(StringBuilder s, Collection<Integer> states)180     public void appendStateNames(StringBuilder s, Collection<Integer> states) {
181         checkStateCollectionInRange(states);
182 
183         boolean start = true;
184         for (Integer state : states) {
185             if (!start) {
186                 s.append(" ");
187             }
188 
189             s.append(getStateName(state));
190             start = false;
191         }
192     }
193 
queueStateTransition(int state)194     private void queueStateTransition(int state) {
195         if (VERBOSE) Log.v(TAG, "setCurrentState - state now " + getStateName(state));
196 
197         try {
198             mQueuedStates.put(state);
199         } catch (InterruptedException e) {
200             throw new UnsupportedOperationException("Unable to set current state", e);
201         }
202     }
203 
checkStateInRange(int state)204     private int checkStateInRange(int state) {
205         if (state < 0 || state >= mStateCount) {
206             throw new IllegalArgumentException("State out of range " + state);
207         }
208 
209         return state;
210     }
211 
checkStateCollectionInRange(Collection<Integer> states)212     private Collection<Integer> checkStateCollectionInRange(Collection<Integer> states) {
213         for (int state : states) {
214             checkStateInRange(state);
215         }
216 
217         return states;
218     }
219 
220 }
221