1 /* 2 * Copyright 2013 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 package com.android.ex.camera2.blocking; 17 18 import android.hardware.camera2.CameraDevice; 19 import android.os.Handler; 20 import android.os.SystemClock; 21 import android.util.Log; 22 23 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 24 25 import java.util.Arrays; 26 import java.util.Collection; 27 import java.util.concurrent.LinkedBlockingQueue; 28 import java.util.concurrent.TimeUnit; 29 30 31 /** 32 * A camera device listener that implements blocking operations on state changes. 33 * 34 * <p>Provides wait calls that block until the next unobserved state of the 35 * requested type arrives. Unobserved states are states that have occurred since 36 * the last wait, or that will be received from the camera device in the 37 * future.</p> 38 * 39 * <p>Pass-through all StateListener changes to the proxy.</p> 40 * 41 */ 42 public class BlockingStateListener extends CameraDevice.StateListener { 43 private static final String TAG = "BlockingStateListener"; 44 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 45 46 private final CameraDevice.StateListener mProxy; 47 48 // Guards mWaiting 49 private final Object mLock = new Object(); 50 private boolean mWaiting = false; 51 52 private final LinkedBlockingQueue<Integer> mRecentStates = 53 new LinkedBlockingQueue<Integer>(); 54 55 private void setCurrentState(int state) { 56 if (VERBOSE) Log.v(TAG, "Camera device state now " + stateToString(state)); 57 try { 58 mRecentStates.put(state); 59 } catch(InterruptedException e) { 60 throw new RuntimeException("Unable to set device state", e); 61 } 62 } 63 64 private static final String[] mStateNames = { 65 "STATE_UNINITIALIZED", 66 "STATE_OPENED", 67 "STATE_UNCONFIGURED", 68 "STATE_IDLE", 69 "STATE_ACTIVE", 70 "STATE_BUSY", 71 "STATE_CLOSED", 72 "STATE_DISCONNECTED", 73 "STATE_ERROR" 74 }; 75 76 /** 77 * Device has not reported any state yet 78 */ 79 public static final int STATE_UNINITIALIZED = -1; 80 81 /** 82 * Device is in the first-opened state (transitory) 83 */ 84 public static final int STATE_OPENED = 0; 85 86 /** 87 * Device is unconfigured 88 */ 89 public static final int STATE_UNCONFIGURED = 1; 90 91 /** 92 * Device is idle 93 */ 94 public static final int STATE_IDLE = 2; 95 96 /** 97 * Device is active (transitory) 98 */ 99 public static final int STATE_ACTIVE = 3; 100 101 /** 102 * Device is busy (transitory) 103 */ 104 public static final int STATE_BUSY = 4; 105 106 /** 107 * Device is closed 108 */ 109 public static final int STATE_CLOSED = 5; 110 111 /** 112 * Device is disconnected 113 */ 114 public static final int STATE_DISCONNECTED = 6; 115 116 /** 117 * Device has encountered a fatal error 118 */ 119 public static final int STATE_ERROR = 7; 120 121 /** 122 * Total number of reachable states 123 */ 124 private static int NUM_STATES = 8; 125 126 public BlockingStateListener() { 127 mProxy = null; 128 } 129 130 public BlockingStateListener(CameraDevice.StateListener listener) { 131 mProxy = listener; 132 } 133 134 @Override 135 public void onOpened(CameraDevice camera) { 136 setCurrentState(STATE_OPENED); 137 if (mProxy != null) mProxy.onOpened(camera); 138 } 139 140 @Override 141 public void onDisconnected(CameraDevice camera) { 142 setCurrentState(STATE_DISCONNECTED); 143 if (mProxy != null) mProxy.onDisconnected(camera); 144 } 145 146 @Override 147 public void onError(CameraDevice camera, int error) { 148 setCurrentState(STATE_ERROR); 149 if (mProxy != null) mProxy.onError(camera, error); 150 } 151 152 @Override 153 public void onUnconfigured(CameraDevice camera) { 154 setCurrentState(STATE_UNCONFIGURED); 155 if (mProxy != null) mProxy.onUnconfigured(camera); 156 } 157 158 @Override 159 public void onIdle(CameraDevice camera) { 160 setCurrentState(STATE_IDLE); 161 if (mProxy != null) mProxy.onIdle(camera); 162 } 163 164 @Override 165 public void onActive(CameraDevice camera) { 166 setCurrentState(STATE_ACTIVE); 167 if (mProxy != null) mProxy.onActive(camera); 168 } 169 170 @Override 171 public void onBusy(CameraDevice camera) { 172 setCurrentState(STATE_BUSY); 173 if (mProxy != null) mProxy.onBusy(camera); 174 } 175 176 @Override 177 public void onClosed(CameraDevice camera) { 178 setCurrentState(STATE_CLOSED); 179 if (mProxy != null) mProxy.onClosed(camera); 180 } 181 182 /** 183 * Wait until the desired state is observed, checking all state 184 * transitions since the last state that was waited on. 185 * 186 * <p>Note: Only one waiter allowed at a time!</p> 187 * 188 * @param desired state to observe a transition to 189 * @param timeout how long to wait in milliseconds 190 * 191 * @throws TimeoutRuntimeException if the desired state is not observed before timeout. 192 */ 193 public void waitForState(int state, long timeout) { 194 Integer[] stateArray = { state }; 195 196 waitForAnyOfStates(Arrays.asList(stateArray), timeout); 197 } 198 199 /** 200 * Wait until the one of the desired states is observed, checking all 201 * state transitions since the last state that was waited on. 202 * 203 * <p>Note: Only one waiter allowed at a time!</p> 204 * 205 * @param states Set of desired states to observe a transition to. 206 * @param timeout how long to wait in milliseconds 207 * 208 * @return the state reached 209 * @throws TimeoutRuntimeException if none of the states is observed before timeout. 210 * 211 */ 212 public int waitForAnyOfStates(Collection<Integer> states, final long timeout) { 213 synchronized(mLock) { 214 if (mWaiting) throw new IllegalStateException("Only one waiter allowed at a time"); 215 mWaiting = true; 216 } 217 if (VERBOSE) { 218 StringBuilder s = new StringBuilder("Waiting for state(s) "); 219 appendStates(s, states); 220 Log.v(TAG, s.toString()); 221 } 222 223 Integer nextState = null; 224 long timeoutLeft = timeout; 225 long startMs = SystemClock.elapsedRealtime(); 226 try { 227 while ((nextState = mRecentStates.poll(timeoutLeft, TimeUnit.MILLISECONDS)) 228 != null) { 229 if (VERBOSE) { 230 Log.v(TAG, " Saw transition to " + stateToString(nextState)); 231 } 232 if (states.contains(nextState)) break; 233 long endMs = SystemClock.elapsedRealtime(); 234 timeoutLeft -= (endMs - startMs); 235 startMs = endMs; 236 } 237 } catch (InterruptedException e) { 238 throw new UnsupportedOperationException("Does not support interrupts on waits", e); 239 } 240 241 synchronized(mLock) { 242 mWaiting = false; 243 } 244 245 if (!states.contains(nextState)) { 246 StringBuilder s = new StringBuilder("Timed out after "); 247 s.append(timeout); 248 s.append(" ms waiting for state(s) "); 249 appendStates(s, states); 250 251 throw new TimeoutRuntimeException(s.toString()); 252 } 253 254 return nextState; 255 } 256 257 /** 258 * Convert state integer to a String 259 */ 260 public static String stateToString(int state) { 261 return mStateNames[state + 1]; 262 } 263 264 /** 265 * Append all states to string 266 */ 267 public static void appendStates(StringBuilder s, Collection<Integer> states) { 268 boolean start = true; 269 for (Integer state: states) { 270 if (!start) s.append(" "); 271 s.append(stateToString(state)); 272 start = false; 273 } 274 } 275 } 276