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 package com.android.ex.camera2.blocking; 17 18 import android.hardware.camera2.CameraCaptureSession; 19 import android.os.ConditionVariable; 20 import android.os.SystemClock; 21 import android.util.Log; 22 import android.view.Surface; 23 24 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 25 import com.android.ex.camera2.utils.StateChangeListener; 26 import com.android.ex.camera2.utils.StateWaiter; 27 28 import java.util.List; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.concurrent.Future; 32 import java.util.concurrent.TimeUnit; 33 import java.util.concurrent.TimeoutException; 34 35 36 /** 37 * A camera session listener that implements blocking operations on session state changes. 38 * 39 * <p>Provides a waiter that can be used to block until the next unobserved state of the 40 * requested type arrives.</p> 41 * 42 * <p>Pass-through all StateCallback changes to the proxy.</p> 43 * 44 * @see #getStateWaiter 45 */ 46 public class BlockingSessionCallback extends CameraCaptureSession.StateCallback { 47 /** 48 * Session is configured, ready for captures 49 */ 50 public static final int SESSION_CONFIGURED = 0; 51 52 /** 53 * Session has failed to configure, can't do any captures 54 */ 55 public static final int SESSION_CONFIGURE_FAILED = 1; 56 57 /** 58 * Session is ready 59 */ 60 public static final int SESSION_READY = 2; 61 62 /** 63 * Session is active (transitory) 64 */ 65 public static final int SESSION_ACTIVE = 3; 66 67 /** 68 * Session is closed 69 */ 70 public static final int SESSION_CLOSED = 4; 71 72 private static final int NUM_STATES = 5; 73 74 /* 75 * Private fields 76 */ 77 private static final String TAG = "BlockingSessionCallback"; 78 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 79 80 private final CameraCaptureSession.StateCallback mProxy; 81 private final SessionFuture mSessionFuture = new SessionFuture(); 82 83 private final StateWaiter mStateWaiter = new StateWaiter(sStateNames); 84 private final StateChangeListener mStateChangeListener = mStateWaiter.getListener(); 85 private final HashMap<CameraCaptureSession, List<Surface> > mPreparedSurfaces = new HashMap<>(); 86 87 private static final String[] sStateNames = { 88 "SESSION_CONFIGURED", 89 "SESSION_CONFIGURE_FAILED", 90 "SESSION_READY", 91 "SESSION_ACTIVE", 92 "SESSION_CLOSED" 93 }; 94 95 /** 96 * Create a blocking session listener without forwarding the session listener invocations 97 * to another session listener. 98 */ BlockingSessionCallback()99 public BlockingSessionCallback() { 100 mProxy = null; 101 } 102 103 /** 104 * Create a blocking session listener; forward original listener invocations 105 * into {@code listener}. 106 * 107 * @param listener a non-{@code null} listener to forward invocations into 108 * 109 * @throws NullPointerException if {@code listener} was {@code null} 110 */ BlockingSessionCallback(CameraCaptureSession.StateCallback listener)111 public BlockingSessionCallback(CameraCaptureSession.StateCallback listener) { 112 if (listener == null) { 113 throw new NullPointerException("listener must not be null"); 114 } 115 mProxy = listener; 116 } 117 118 /** 119 * Acquire the state waiter; can be used to block until a set of state transitions have 120 * been reached. 121 * 122 * <p>Only one thread should wait at a time.</p> 123 */ getStateWaiter()124 public StateWaiter getStateWaiter() { 125 return mStateWaiter; 126 } 127 128 /** 129 * Return session if already have it; otherwise wait until any of the session listener 130 * invocations fire and the session is available. 131 * 132 * <p>Does not consume any of the states from the state waiter.</p> 133 * 134 * @param timeoutMs how many milliseconds to wait for 135 * @return a non-{@code null} {@link CameraCaptureSession} instance 136 * 137 * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs} 138 */ waitAndGetSession(long timeoutMs)139 public CameraCaptureSession waitAndGetSession(long timeoutMs) { 140 try { 141 return mSessionFuture.get(timeoutMs, TimeUnit.MILLISECONDS); 142 } catch (TimeoutException e) { 143 throw new TimeoutRuntimeException( 144 String.format("Failed to get session after %s milliseconds", timeoutMs), e); 145 } 146 } 147 148 /* 149 * CameraCaptureSession.StateCallback implementation 150 */ 151 152 @Override onActive(CameraCaptureSession session)153 public void onActive(CameraCaptureSession session) { 154 mSessionFuture.setSession(session); 155 if (mProxy != null) mProxy.onActive(session); 156 mStateChangeListener.onStateChanged(SESSION_ACTIVE); 157 } 158 159 @Override onClosed(CameraCaptureSession session)160 public void onClosed(CameraCaptureSession session) { 161 mSessionFuture.setSession(session); 162 if (mProxy != null) mProxy.onClosed(session); 163 mStateChangeListener.onStateChanged(SESSION_CLOSED); 164 synchronized (mPreparedSurfaces) { 165 mPreparedSurfaces.remove(session); 166 } 167 } 168 169 @Override onConfigured(CameraCaptureSession session)170 public void onConfigured(CameraCaptureSession session) { 171 mSessionFuture.setSession(session); 172 if (mProxy != null) { 173 mProxy.onConfigured(session); 174 } 175 mStateChangeListener.onStateChanged(SESSION_CONFIGURED); 176 } 177 178 @Override onConfigureFailed(CameraCaptureSession session)179 public void onConfigureFailed(CameraCaptureSession session) { 180 mSessionFuture.setSession(session); 181 if (mProxy != null) { 182 mProxy.onConfigureFailed(session); 183 } 184 mStateChangeListener.onStateChanged(SESSION_CONFIGURE_FAILED); 185 } 186 187 @Override onReady(CameraCaptureSession session)188 public void onReady(CameraCaptureSession session) { 189 mSessionFuture.setSession(session); 190 if (mProxy != null) { 191 mProxy.onReady(session); 192 } 193 mStateChangeListener.onStateChanged(SESSION_READY); 194 } 195 196 @Override onSurfacePrepared(CameraCaptureSession session, Surface surface)197 public void onSurfacePrepared(CameraCaptureSession session, Surface surface) { 198 mSessionFuture.setSession(session); 199 if (mProxy != null) { 200 mProxy.onSurfacePrepared(session, surface); 201 } 202 // Surface prepared doesn't cause a session state change, so don't trigger the 203 // state change listener 204 synchronized (mPreparedSurfaces) { 205 List<Surface> preparedSurfaces = mPreparedSurfaces.get(session); 206 if (preparedSurfaces == null) { 207 preparedSurfaces = new ArrayList<Surface>(); 208 } 209 preparedSurfaces.add(surface); 210 mPreparedSurfaces.put(session, preparedSurfaces); 211 mPreparedSurfaces.notifyAll(); 212 } 213 } 214 215 @Override onCaptureQueueEmpty(CameraCaptureSession session)216 public void onCaptureQueueEmpty(CameraCaptureSession session) { 217 mSessionFuture.setSession(session); 218 if (mProxy != null) { 219 mProxy.onCaptureQueueEmpty(session); 220 } 221 } 222 223 /** 224 * Wait until the designated surface is prepared by the camera capture session. 225 * 226 * @param session the input {@link CameraCaptureSession} to wait for 227 * @param surface the input {@link Surface} to wait for 228 * @param timeoutMs how many milliseconds to wait for 229 * 230 * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs} 231 */ waitForSurfacePrepared( CameraCaptureSession session, Surface surface, long timeoutMs)232 public void waitForSurfacePrepared( 233 CameraCaptureSession session, Surface surface, long timeoutMs) { 234 synchronized (mPreparedSurfaces) { 235 List<Surface> preparedSurfaces = mPreparedSurfaces.get(session); 236 if (preparedSurfaces != null && preparedSurfaces.contains(surface)) { 237 return; 238 } 239 try { 240 long waitTimeRemaining = timeoutMs; 241 while (waitTimeRemaining > 0) { 242 long waitStartTime = SystemClock.elapsedRealtime(); 243 mPreparedSurfaces.wait(timeoutMs); 244 long waitTime = SystemClock.elapsedRealtime() - waitStartTime; 245 waitTimeRemaining -= waitTime; 246 preparedSurfaces = mPreparedSurfaces.get(session); 247 if (waitTimeRemaining >= 0 && preparedSurfaces != null && 248 preparedSurfaces.contains(surface)) { 249 return; 250 } 251 } 252 throw new TimeoutRuntimeException( 253 "Unable to get Surface prepared in " + timeoutMs + "ms"); 254 } catch (InterruptedException ie) { 255 throw new AssertionError(); 256 } 257 } 258 } 259 260 private static class SessionFuture implements Future<CameraCaptureSession> { 261 private volatile CameraCaptureSession mSession; 262 ConditionVariable mCondVar = new ConditionVariable(/*opened*/false); 263 setSession(CameraCaptureSession session)264 public void setSession(CameraCaptureSession session) { 265 mSession = session; 266 mCondVar.open(); 267 } 268 269 @Override cancel(boolean mayInterruptIfRunning)270 public boolean cancel(boolean mayInterruptIfRunning) { 271 return false; // don't allow canceling this task 272 } 273 274 @Override isCancelled()275 public boolean isCancelled() { 276 return false; // can never cancel this task 277 } 278 279 @Override isDone()280 public boolean isDone() { 281 return mSession != null; 282 } 283 284 @Override get()285 public CameraCaptureSession get() { 286 mCondVar.block(); 287 return mSession; 288 } 289 290 @Override get(long timeout, TimeUnit unit)291 public CameraCaptureSession get(long timeout, TimeUnit unit) throws TimeoutException { 292 long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS); 293 if (!mCondVar.block(timeoutMs)) { 294 throw new TimeoutException( 295 "Failed to receive session after " + timeout + " " + unit); 296 } 297 298 if (mSession == null) { 299 throw new AssertionError(); 300 } 301 return mSession; 302 } 303 304 } 305 } 306