1/*
2 * Copyright 2013 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.CameraDevice;
19import android.os.Handler;
20import android.os.SystemClock;
21import android.util.Log;
22
23import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
24
25import java.util.Arrays;
26import java.util.Collection;
27import java.util.concurrent.LinkedBlockingQueue;
28import java.util.concurrent.TimeUnit;
29
30
31/**
32 * A camera device listener that implements blocking operations on state changes.
33 *
34 * <p>Provides wait calls that block until the next unobserved state of the
35 * requested type arrives. Unobserved states are states that have occurred since
36 * the last wait, or that will be received from the camera device in the
37 * future.</p>
38 *
39 * <p>Pass-through all StateListener changes to the proxy.</p>
40 *
41 */
42public class BlockingStateListener extends CameraDevice.StateListener {
43    private static final String TAG = "BlockingStateListener";
44    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
45
46    private final CameraDevice.StateListener mProxy;
47
48    // Guards mWaiting
49    private final Object mLock = new Object();
50    private boolean mWaiting = false;
51
52    private final LinkedBlockingQueue<Integer> mRecentStates =
53            new LinkedBlockingQueue<Integer>();
54
55    private void setCurrentState(int state) {
56        if (VERBOSE) Log.v(TAG, "Camera device state now " + stateToString(state));
57        try {
58            mRecentStates.put(state);
59        } catch(InterruptedException e) {
60            throw new RuntimeException("Unable to set device state", e);
61        }
62    }
63
64    private static final String[] mStateNames = {
65        "STATE_UNINITIALIZED",
66        "STATE_OPENED",
67        "STATE_UNCONFIGURED",
68        "STATE_IDLE",
69        "STATE_ACTIVE",
70        "STATE_BUSY",
71        "STATE_CLOSED",
72        "STATE_DISCONNECTED",
73        "STATE_ERROR"
74    };
75
76    /**
77     * Device has not reported any state yet
78     */
79    public static final int STATE_UNINITIALIZED = -1;
80
81    /**
82     * Device is in the first-opened state (transitory)
83     */
84    public static final int STATE_OPENED = 0;
85
86    /**
87     * Device is unconfigured
88     */
89    public static final int STATE_UNCONFIGURED = 1;
90
91    /**
92     * Device is idle
93     */
94    public static final int STATE_IDLE = 2;
95
96    /**
97     * Device is active (transitory)
98     */
99    public static final int STATE_ACTIVE = 3;
100
101    /**
102     * Device is busy (transitory)
103     */
104    public static final int STATE_BUSY = 4;
105
106    /**
107     * Device is closed
108     */
109    public static final int STATE_CLOSED = 5;
110
111    /**
112     * Device is disconnected
113     */
114    public static final int STATE_DISCONNECTED = 6;
115
116    /**
117     * Device has encountered a fatal error
118     */
119    public static final int STATE_ERROR = 7;
120
121    /**
122     * Total number of reachable states
123     */
124    private static int NUM_STATES = 8;
125
126    public BlockingStateListener() {
127        mProxy = null;
128    }
129
130    public BlockingStateListener(CameraDevice.StateListener listener) {
131        mProxy = listener;
132    }
133
134    @Override
135    public void onOpened(CameraDevice camera) {
136        setCurrentState(STATE_OPENED);
137        if (mProxy != null) mProxy.onOpened(camera);
138    }
139
140    @Override
141    public void onDisconnected(CameraDevice camera) {
142        setCurrentState(STATE_DISCONNECTED);
143        if (mProxy != null) mProxy.onDisconnected(camera);
144    }
145
146    @Override
147    public void onError(CameraDevice camera, int error) {
148        setCurrentState(STATE_ERROR);
149        if (mProxy != null) mProxy.onError(camera, error);
150    }
151
152    @Override
153    public void onUnconfigured(CameraDevice camera) {
154        setCurrentState(STATE_UNCONFIGURED);
155        if (mProxy != null) mProxy.onUnconfigured(camera);
156    }
157
158    @Override
159    public void onIdle(CameraDevice camera) {
160        setCurrentState(STATE_IDLE);
161        if (mProxy != null) mProxy.onIdle(camera);
162    }
163
164    @Override
165    public void onActive(CameraDevice camera) {
166        setCurrentState(STATE_ACTIVE);
167        if (mProxy != null) mProxy.onActive(camera);
168    }
169
170    @Override
171    public void onBusy(CameraDevice camera) {
172        setCurrentState(STATE_BUSY);
173        if (mProxy != null) mProxy.onBusy(camera);
174    }
175
176    @Override
177    public void onClosed(CameraDevice camera) {
178        setCurrentState(STATE_CLOSED);
179        if (mProxy != null) mProxy.onClosed(camera);
180    }
181
182    /**
183     * Wait until the desired state is observed, checking all state
184     * transitions since the last state that was waited on.
185     *
186     * <p>Note: Only one waiter allowed at a time!</p>
187     *
188     * @param desired state to observe a transition to
189     * @param timeout how long to wait in milliseconds
190     *
191     * @throws TimeoutRuntimeException if the desired state is not observed before timeout.
192     */
193    public void waitForState(int state, long timeout) {
194        Integer[] stateArray = { state };
195
196        waitForAnyOfStates(Arrays.asList(stateArray), timeout);
197    }
198
199    /**
200     * Wait until the one of the desired states is observed, checking all
201     * state transitions since the last state that was waited on.
202     *
203     * <p>Note: Only one waiter allowed at a time!</p>
204     *
205     * @param states Set of desired states to observe a transition to.
206     * @param timeout how long to wait in milliseconds
207     *
208     * @return the state reached
209     * @throws TimeoutRuntimeException if none of the states is observed before timeout.
210     *
211     */
212    public int waitForAnyOfStates(Collection<Integer> states, final long timeout) {
213        synchronized(mLock) {
214            if (mWaiting) throw new IllegalStateException("Only one waiter allowed at a time");
215            mWaiting = true;
216        }
217        if (VERBOSE) {
218            StringBuilder s = new StringBuilder("Waiting for state(s) ");
219            appendStates(s, states);
220            Log.v(TAG, s.toString());
221        }
222
223        Integer nextState = null;
224        long timeoutLeft = timeout;
225        long startMs = SystemClock.elapsedRealtime();
226        try {
227            while ((nextState = mRecentStates.poll(timeoutLeft, TimeUnit.MILLISECONDS))
228                    != null) {
229                if (VERBOSE) {
230                    Log.v(TAG, "  Saw transition to " + stateToString(nextState));
231                }
232                if (states.contains(nextState)) break;
233                long endMs = SystemClock.elapsedRealtime();
234                timeoutLeft -= (endMs - startMs);
235                startMs = endMs;
236            }
237        } catch (InterruptedException e) {
238            throw new UnsupportedOperationException("Does not support interrupts on waits", e);
239        }
240
241        synchronized(mLock) {
242            mWaiting = false;
243        }
244
245        if (!states.contains(nextState)) {
246            StringBuilder s = new StringBuilder("Timed out after ");
247            s.append(timeout);
248            s.append(" ms waiting for state(s) ");
249            appendStates(s, states);
250
251            throw new TimeoutRuntimeException(s.toString());
252        }
253
254        return nextState;
255    }
256
257    /**
258     * Convert state integer to a String
259     */
260    public static String stateToString(int state) {
261        return mStateNames[state + 1];
262    }
263
264    /**
265     * Append all states to string
266     */
267    public static void appendStates(StringBuilder s, Collection<Integer> states) {
268        boolean start = true;
269        for (Integer state: states) {
270            if (!start) s.append(" ");
271            s.append(stateToString(state));
272            start = false;
273        }
274    }
275}
276