BlockingSessionCallback.java revision 39c31d49ce38158b7708581df0813b03e32d6eea
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 */
16package com.android.ex.camera2.blocking;
17
18import android.hardware.camera2.CameraCaptureSession;
19import android.os.ConditionVariable;
20import android.os.SystemClock;
21import android.util.Log;
22import android.view.Surface;
23
24import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
25import com.android.ex.camera2.utils.StateChangeListener;
26import com.android.ex.camera2.utils.StateWaiter;
27
28import java.util.List;
29import java.util.ArrayList;
30import java.util.HashMap;
31import java.util.concurrent.Future;
32import java.util.concurrent.TimeUnit;
33import 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 */
46public 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     */
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     */
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     */
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     */
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
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
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
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
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
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
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    /**
216     * Wait until the designated surface is prepared by the camera capture session.
217     *
218     * @param session the input {@link CameraCaptureSession} to wait for
219     * @param surface the input {@link Surface} to wait for
220     * @param timeoutMs how many milliseconds to wait for
221     *
222     * @throws TimeoutRuntimeException if waiting for more than {@long timeoutMs}
223     */
224    public void waitForSurfacePrepared(
225            CameraCaptureSession session, Surface surface, long timeoutMs) {
226        synchronized (mPreparedSurfaces) {
227            List<Surface> preparedSurfaces = mPreparedSurfaces.get(session);
228            if (preparedSurfaces != null && preparedSurfaces.contains(surface)) {
229                return;
230            }
231            try {
232                long waitTimeRemaining = timeoutMs;
233                while (waitTimeRemaining > 0) {
234                    long waitStartTime = SystemClock.elapsedRealtime();
235                    mPreparedSurfaces.wait(timeoutMs);
236                    long waitTime = SystemClock.elapsedRealtime() - waitStartTime;
237                    waitTimeRemaining -= waitTime;
238                    preparedSurfaces = mPreparedSurfaces.get(session);
239                    if (waitTimeRemaining >= 0 && preparedSurfaces != null &&
240                            preparedSurfaces.contains(surface)) {
241                        return;
242                    }
243                }
244                throw new TimeoutRuntimeException(
245                        "Unable to get Surface prepared in " + timeoutMs + "ms");
246            } catch (InterruptedException ie) {
247                throw new AssertionError();
248            }
249        }
250    }
251
252    private static class SessionFuture implements Future<CameraCaptureSession> {
253        private volatile CameraCaptureSession mSession;
254        ConditionVariable mCondVar = new ConditionVariable(/*opened*/false);
255
256        public void setSession(CameraCaptureSession session) {
257            mSession = session;
258            mCondVar.open();
259        }
260
261        @Override
262        public boolean cancel(boolean mayInterruptIfRunning) {
263            return false; // don't allow canceling this task
264        }
265
266        @Override
267        public boolean isCancelled() {
268            return false; // can never cancel this task
269        }
270
271        @Override
272        public boolean isDone() {
273            return mSession != null;
274        }
275
276        @Override
277        public CameraCaptureSession get() {
278            mCondVar.block();
279            return mSession;
280        }
281
282        @Override
283        public CameraCaptureSession get(long timeout, TimeUnit unit) throws TimeoutException {
284            long timeoutMs = unit.convert(timeout, TimeUnit.MILLISECONDS);
285            if (!mCondVar.block(timeoutMs)) {
286                throw new TimeoutException(
287                        "Failed to receive session after " + timeout + " " + unit);
288            }
289
290            if (mSession == null) {
291                throw new AssertionError();
292            }
293            return mSession;
294        }
295
296    }
297}
298