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.util.Log; 21 22 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 23 import com.android.ex.camera2.utils.StateChangeListener; 24 import com.android.ex.camera2.utils.StateWaiter; 25 26 import java.util.ArrayList; 27 import java.util.concurrent.ExecutionException; 28 import java.util.concurrent.Future; 29 import java.util.concurrent.TimeUnit; 30 import java.util.concurrent.TimeoutException; 31 32 33 /** 34 * A camera session listener that implements blocking operations on session state changes. 35 * 36 * <p>Provides a waiter that can be used to block until the next unobserved state of the 37 * requested type arrives.</p> 38 * 39 * <p>Pass-through all StateCallback changes to the proxy.</p> 40 * 41 * @see #getStateWaiter 42 */ 43 public class BlockingSessionCallback extends CameraCaptureSession.StateCallback { 44 /** 45 * Session is configured, ready for captures 46 */ 47 public static final int SESSION_CONFIGURED = 0; 48 49 /** 50 * Session has failed to configure, can't do any captures 51 */ 52 public static final int SESSION_CONFIGURE_FAILED = 1; 53 54 /** 55 * Session is ready 56 */ 57 public static final int SESSION_READY = 2; 58 59 /** 60 * Session is active (transitory) 61 */ 62 public static final int SESSION_ACTIVE = 3; 63 64 /** 65 * Session is closed 66 */ 67 public static final int SESSION_CLOSED = 4; 68 69 private final int NUM_STATES = 5; 70 71 /* 72 * Private fields 73 */ 74 private static final String TAG = "BlockingSessionCallback"; 75 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 76 77 private final CameraCaptureSession.StateCallback mProxy; 78 private final SessionFuture mSessionFuture = new SessionFuture(); 79 80 private final StateWaiter mStateWaiter = new StateWaiter(sStateNames); 81 private final StateChangeListener mStateChangeListener = mStateWaiter.getListener(); 82 83 private static final String[] sStateNames = { 84 "SESSION_CONFIGURED", 85 "SESSION_CONFIGURE_FAILED", 86 "SESSION_READY", 87 "SESSION_ACTIVE", 88 "SESSION_CLOSED" 89 }; 90 91 /** 92 * Create a blocking session listener without forwarding the session listener invocations 93 * to another session listener. 94 */ 95 public BlockingSessionCallback() { 96 mProxy = null; 97 } 98 99 /** 100 * Create a blocking session listener; forward original listener invocations 101 * into {@code listener}. 102 * 103 * @param listener a non-{@code null} listener to forward invocations into 104 * 105 * @throws NullPointerException if {@code listener} was {@code null} 106 */ 107 public BlockingSessionCallback(CameraCaptureSession.StateCallback listener) { 108 if (listener == null) { 109 throw new NullPointerException("listener must not be null"); 110 } 111 mProxy = listener; 112 } 113 114 /** 115 * Acquire the state waiter; can be used to block until a set of state transitions have 116 * been reached. 117 * 118 * <p>Only one thread should wait at a time.</p> 119 */ 120 public StateWaiter getStateWaiter() { 121 return mStateWaiter; 122 } 123 124 /** 125 * Return session if already have it; otherwise wait until any of the session listener 126 * invocations fire and the session is available. 127 * 128 * <p>Does not consume any of the states from the state waiter.</p> 129 * 130 * @param timeoutMs how many milliseconds to wait for 131 * @return a non-{@code null} {@link CameraCaptureSession} instance 132 * 133 * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs} 134 */ 135 public CameraCaptureSession waitAndGetSession(long timeoutMs) { 136 try { 137 return mSessionFuture.get(timeoutMs, TimeUnit.MILLISECONDS); 138 } catch (TimeoutException e) { 139 throw new TimeoutRuntimeException( 140 String.format("Failed to get session after %s milliseconds", timeoutMs), e); 141 } 142 } 143 144 /* 145 * CameraCaptureSession.StateCallback implementation 146 */ 147 148 @Override 149 public void onActive(CameraCaptureSession session) { 150 mSessionFuture.setSession(session); 151 if (mProxy != null) mProxy.onActive(session); 152 mStateChangeListener.onStateChanged(SESSION_ACTIVE); 153 } 154 155 @Override 156 public void onClosed(CameraCaptureSession session) { 157 mSessionFuture.setSession(session); 158 if (mProxy != null) mProxy.onClosed(session); 159 mStateChangeListener.onStateChanged(SESSION_CLOSED); 160 } 161 162 @Override 163 public void onConfigured(CameraCaptureSession session) { 164 mSessionFuture.setSession(session); 165 if (mProxy != null) mProxy.onConfigured(session); 166 mStateChangeListener.onStateChanged(SESSION_CONFIGURED); 167 } 168 169 @Override 170 public void onConfigureFailed(CameraCaptureSession session) { 171 mSessionFuture.setSession(session); 172 if (mProxy != null) mProxy.onConfigureFailed(session); 173 mStateChangeListener.onStateChanged(SESSION_CONFIGURE_FAILED); 174 } 175 176 @Override 177 public void onReady(CameraCaptureSession session) { 178 mSessionFuture.setSession(session); 179 if (mProxy != null) mProxy.onReady(session); 180 mStateChangeListener.onStateChanged(SESSION_READY); 181 } 182 183 private static class SessionFuture implements Future<CameraCaptureSession> { 184 private volatile CameraCaptureSession mSession; 185 ConditionVariable mCondVar = new ConditionVariable(/*opened*/false); 186 187 public void setSession(CameraCaptureSession session) { 188 mSession = session; 189 mCondVar.open(); 190 } 191 192 @Override 193 public boolean cancel(boolean mayInterruptIfRunning) { 194 return false; // don't allow canceling this task 195 } 196 197 @Override 198 public boolean isCancelled() { 199 return false; // can never cancel this task 200 } 201 202 @Override 203 public boolean isDone() { 204 return mSession != null; 205 } 206 207 @Override 208 public CameraCaptureSession get() { 209 mCondVar.block(); 210 return mSession; 211 } 212 213 @Override 214 public CameraCaptureSession get(long timeout, TimeUnit unit) throws TimeoutException { 215 long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS); 216 if (!mCondVar.block(timeoutMs)) { 217 throw new TimeoutException( 218 "Failed to receive session after " + timeout + " " + unit); 219 } 220 221 if (mSession == null) { 222 throw new AssertionError(); 223 } 224 return mSession; 225 } 226 227 } 228 } 229