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.util.Log;
21
22import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
23import com.android.ex.camera2.utils.StateChangeListener;
24import com.android.ex.camera2.utils.StateWaiter;
25
26import java.util.ArrayList;
27import java.util.concurrent.ExecutionException;
28import java.util.concurrent.Future;
29import java.util.concurrent.TimeUnit;
30import 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 */
43public 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