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 */ 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 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 desired 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 */ 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 */ 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 */ 173 public String getStateName(int state) { 174 return mStateNames[checkStateInRange(state)]; 175 } 176 177 /** 178 * Append all states to string 179 */ 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 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 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 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