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 StateCallback changes to the proxy.</p> 40 * 41 */ 42 public class BlockingStateCallback extends CameraDevice.StateCallback { 43 private static final String TAG = "BlockingStateCallback"; 44 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 45 46 private final CameraDevice.StateCallback 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_CLOSED", 68 "STATE_DISCONNECTED", 69 "STATE_ERROR" 70 }; 71 72 /** 73 * Device has not reported any state yet 74 */ 75 public static final int STATE_UNINITIALIZED = -1; 76 77 /** 78 * Device is in the first-opened state (transitory) 79 */ 80 public static final int STATE_OPENED = 0; 81 82 /** 83 * Device is closed 84 */ 85 public static final int STATE_CLOSED = 1; 86 87 /** 88 * Device is disconnected 89 */ 90 public static final int STATE_DISCONNECTED = 2; 91 92 /** 93 * Device has encountered a fatal error 94 */ 95 public static final int STATE_ERROR = 3; 96 97 /** 98 * Total number of reachable states 99 */ 100 private static final int NUM_STATES = 4; 101 102 public BlockingStateCallback() { 103 mProxy = null; 104 } 105 106 public BlockingStateCallback(CameraDevice.StateCallback listener) { 107 mProxy = listener; 108 } 109 110 @Override 111 public void onOpened(CameraDevice camera) { 112 if (mProxy != null) { 113 mProxy.onOpened(camera); 114 } 115 setCurrentState(STATE_OPENED); 116 } 117 118 @Override 119 public void onDisconnected(CameraDevice camera) { 120 if (mProxy != null) { 121 mProxy.onDisconnected(camera); 122 } 123 setCurrentState(STATE_DISCONNECTED); 124 } 125 126 @Override 127 public void onError(CameraDevice camera, int error) { 128 if (mProxy != null) { 129 mProxy.onError(camera, error); 130 } 131 setCurrentState(STATE_ERROR); 132 } 133 134 @Override 135 public void onClosed(CameraDevice camera) { 136 if (mProxy != null) { 137 mProxy.onClosed(camera); 138 } 139 setCurrentState(STATE_CLOSED); 140 } 141 142 /** 143 * Wait until the desired state is observed, checking all state 144 * transitions since the last state that was waited on. 145 * 146 * <p>Note: Only one waiter allowed at a time!</p> 147 * 148 * @param state state to observe a transition to 149 * @param timeout how long to wait in milliseconds 150 * 151 * @throws TimeoutRuntimeException if the desired state is not observed before timeout. 152 */ 153 public void waitForState(int state, long timeout) { 154 Integer[] stateArray = { state }; 155 156 waitForAnyOfStates(Arrays.asList(stateArray), timeout); 157 } 158 159 /** 160 * Wait until the one of the desired states is observed, checking all 161 * state transitions since the last state that was waited on. 162 * 163 * <p>Note: Only one waiter allowed at a time!</p> 164 * 165 * @param states Set of desired states to observe a transition to. 166 * @param timeout how long to wait in milliseconds 167 * 168 * @return the state reached 169 * @throws TimeoutRuntimeException if none of the states is observed before timeout. 170 * 171 */ 172 public int waitForAnyOfStates(Collection<Integer> states, final long timeout) { 173 synchronized (mLock) { 174 if (mWaiting) { 175 throw new IllegalStateException("Only one waiter allowed at a time"); 176 } 177 mWaiting = true; 178 } 179 if (VERBOSE) { 180 StringBuilder s = new StringBuilder("Waiting for state(s) "); 181 appendStates(s, states); 182 Log.v(TAG, s.toString()); 183 } 184 185 Integer nextState = null; 186 long timeoutLeft = timeout; 187 long startMs = SystemClock.elapsedRealtime(); 188 try { 189 while ((nextState = mRecentStates.poll(timeoutLeft, TimeUnit.MILLISECONDS)) 190 != null) { 191 if (VERBOSE) { 192 Log.v(TAG, " Saw transition to " + stateToString(nextState)); 193 } 194 if (states.contains(nextState)) break; 195 long endMs = SystemClock.elapsedRealtime(); 196 timeoutLeft -= (endMs - startMs); 197 startMs = endMs; 198 } 199 } catch (InterruptedException e) { 200 throw new UnsupportedOperationException("Does not support interrupts on waits", e); 201 } 202 203 synchronized (mLock) { 204 mWaiting = false; 205 } 206 207 if (!states.contains(nextState)) { 208 StringBuilder s = new StringBuilder("Timed out after "); 209 s.append(timeout); 210 s.append(" ms waiting for state(s) "); 211 appendStates(s, states); 212 213 throw new TimeoutRuntimeException(s.toString()); 214 } 215 216 return nextState; 217 } 218 219 /** 220 * Convert state integer to a String 221 */ 222 public static String stateToString(int state) { 223 return mStateNames[state + 1]; 224 } 225 226 /** 227 * Append all states to string 228 */ 229 public static void appendStates(StringBuilder s, Collection<Integer> states) { 230 boolean start = true; 231 for (Integer state : states) { 232 if (!start) s.append(" "); 233 s.append(stateToString(state)); 234 start = false; 235 } 236 } 237 } 238