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