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 */ 16package com.android.ex.camera2.blocking; 17 18import android.hardware.camera2.CameraDevice; 19import android.os.Handler; 20import android.os.SystemClock; 21import android.util.Log; 22 23import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 24 25import java.util.Arrays; 26import java.util.Collection; 27import java.util.concurrent.LinkedBlockingQueue; 28import 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 */ 42public 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 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) mProxy.onOpened(camera); 113 setCurrentState(STATE_OPENED); 114 } 115 116 @Override 117 public void onDisconnected(CameraDevice camera) { 118 if (mProxy != null) mProxy.onDisconnected(camera); 119 setCurrentState(STATE_DISCONNECTED); 120 } 121 122 @Override 123 public void onError(CameraDevice camera, int error) { 124 if (mProxy != null) mProxy.onError(camera, error); 125 setCurrentState(STATE_ERROR); 126 } 127 128 @Override 129 public void onClosed(CameraDevice camera) { 130 if (mProxy != null) mProxy.onClosed(camera); 131 setCurrentState(STATE_CLOSED); 132 } 133 134 /** 135 * Wait until the desired state is observed, checking all state 136 * transitions since the last state that was waited on. 137 * 138 * <p>Note: Only one waiter allowed at a time!</p> 139 * 140 * @param desired state to observe a transition to 141 * @param timeout how long to wait in milliseconds 142 * 143 * @throws TimeoutRuntimeException if the desired state is not observed before timeout. 144 */ 145 public void waitForState(int state, long timeout) { 146 Integer[] stateArray = { state }; 147 148 waitForAnyOfStates(Arrays.asList(stateArray), timeout); 149 } 150 151 /** 152 * Wait until the one of the desired states is observed, checking all 153 * state transitions since the last state that was waited on. 154 * 155 * <p>Note: Only one waiter allowed at a time!</p> 156 * 157 * @param states Set of desired states to observe a transition to. 158 * @param timeout how long to wait in milliseconds 159 * 160 * @return the state reached 161 * @throws TimeoutRuntimeException if none of the states is observed before timeout. 162 * 163 */ 164 public int waitForAnyOfStates(Collection<Integer> states, final long timeout) { 165 synchronized(mLock) { 166 if (mWaiting) throw new IllegalStateException("Only one waiter allowed at a time"); 167 mWaiting = true; 168 } 169 if (VERBOSE) { 170 StringBuilder s = new StringBuilder("Waiting for state(s) "); 171 appendStates(s, states); 172 Log.v(TAG, s.toString()); 173 } 174 175 Integer nextState = null; 176 long timeoutLeft = timeout; 177 long startMs = SystemClock.elapsedRealtime(); 178 try { 179 while ((nextState = mRecentStates.poll(timeoutLeft, TimeUnit.MILLISECONDS)) 180 != null) { 181 if (VERBOSE) { 182 Log.v(TAG, " Saw transition to " + stateToString(nextState)); 183 } 184 if (states.contains(nextState)) break; 185 long endMs = SystemClock.elapsedRealtime(); 186 timeoutLeft -= (endMs - startMs); 187 startMs = endMs; 188 } 189 } catch (InterruptedException e) { 190 throw new UnsupportedOperationException("Does not support interrupts on waits", e); 191 } 192 193 synchronized(mLock) { 194 mWaiting = false; 195 } 196 197 if (!states.contains(nextState)) { 198 StringBuilder s = new StringBuilder("Timed out after "); 199 s.append(timeout); 200 s.append(" ms waiting for state(s) "); 201 appendStates(s, states); 202 203 throw new TimeoutRuntimeException(s.toString()); 204 } 205 206 return nextState; 207 } 208 209 /** 210 * Convert state integer to a String 211 */ 212 public static String stateToString(int state) { 213 return mStateNames[state + 1]; 214 } 215 216 /** 217 * Append all states to string 218 */ 219 public static void appendStates(StringBuilder s, Collection<Integer> states) { 220 boolean start = true; 221 for (Integer state: states) { 222 if (!start) s.append(" "); 223 s.append(stateToString(state)); 224 start = false; 225 } 226 } 227} 228