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 StateCallback changes to the proxy.</p>
40 *
41 */
42public class BlockingStateCallback extends CameraDevice.StateCallback {
43    private static final String TAG = "BlockingStateCallback";
44    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
45
46    private final CameraDevice.StateCallback 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_CLOSED",
68        "STATE_DISCONNECTED",
69        "STATE_ERROR"
70    };
71
72    /**
73     * Device has not reported any state yet
74     */
75    public static final int STATE_UNINITIALIZED = -1;
76
77    /**
78     * Device is in the first-opened state (transitory)
79     */
80    public static final int STATE_OPENED = 0;
81
82    /**
83     * Device is closed
84     */
85    public static final int STATE_CLOSED = 1;
86
87    /**
88     * Device is disconnected
89     */
90    public static final int STATE_DISCONNECTED = 2;
91
92    /**
93     * Device has encountered a fatal error
94     */
95    public static final int STATE_ERROR = 3;
96
97    /**
98     * Total number of reachable states
99     */
100    private static int NUM_STATES = 4;
101
102    public BlockingStateCallback() {
103        mProxy = null;
104    }
105
106    public BlockingStateCallback(CameraDevice.StateCallback listener) {
107        mProxy = listener;
108    }
109
110    @Override
111    public void onOpened(CameraDevice camera) {
112        if (mProxy != null) mProxy.onOpened(camera);
113        setCurrentState(STATE_OPENED);
114    }
115
116    @Override
117    public void onDisconnected(CameraDevice camera) {
118        if (mProxy != null) mProxy.onDisconnected(camera);
119        setCurrentState(STATE_DISCONNECTED);
120    }
121
122    @Override
123    public void onError(CameraDevice camera, int error) {
124        if (mProxy != null) mProxy.onError(camera, error);
125        setCurrentState(STATE_ERROR);
126    }
127
128    @Override
129    public void onClosed(CameraDevice camera) {
130        if (mProxy != null) mProxy.onClosed(camera);
131        setCurrentState(STATE_CLOSED);
132    }
133
134    /**
135     * Wait until the desired state is observed, checking all state
136     * transitions since the last state that was waited on.
137     *
138     * <p>Note: Only one waiter allowed at a time!</p>
139     *
140     * @param desired state to observe a transition to
141     * @param timeout how long to wait in milliseconds
142     *
143     * @throws TimeoutRuntimeException if the desired state is not observed before timeout.
144     */
145    public void waitForState(int state, long timeout) {
146        Integer[] stateArray = { state };
147
148        waitForAnyOfStates(Arrays.asList(stateArray), timeout);
149    }
150
151    /**
152     * Wait until the one of the desired states is observed, checking all
153     * state transitions since the last state that was waited on.
154     *
155     * <p>Note: Only one waiter allowed at a time!</p>
156     *
157     * @param states Set of desired states to observe a transition to.
158     * @param timeout how long to wait in milliseconds
159     *
160     * @return the state reached
161     * @throws TimeoutRuntimeException if none of the states is observed before timeout.
162     *
163     */
164    public int waitForAnyOfStates(Collection<Integer> states, final long timeout) {
165        synchronized(mLock) {
166            if (mWaiting) throw new IllegalStateException("Only one waiter allowed at a time");
167            mWaiting = true;
168        }
169        if (VERBOSE) {
170            StringBuilder s = new StringBuilder("Waiting for state(s) ");
171            appendStates(s, states);
172            Log.v(TAG, s.toString());
173        }
174
175        Integer nextState = null;
176        long timeoutLeft = timeout;
177        long startMs = SystemClock.elapsedRealtime();
178        try {
179            while ((nextState = mRecentStates.poll(timeoutLeft, TimeUnit.MILLISECONDS))
180                    != null) {
181                if (VERBOSE) {
182                    Log.v(TAG, "  Saw transition to " + stateToString(nextState));
183                }
184                if (states.contains(nextState)) break;
185                long endMs = SystemClock.elapsedRealtime();
186                timeoutLeft -= (endMs - startMs);
187                startMs = endMs;
188            }
189        } catch (InterruptedException e) {
190            throw new UnsupportedOperationException("Does not support interrupts on waits", e);
191        }
192
193        synchronized(mLock) {
194            mWaiting = false;
195        }
196
197        if (!states.contains(nextState)) {
198            StringBuilder s = new StringBuilder("Timed out after ");
199            s.append(timeout);
200            s.append(" ms waiting for state(s) ");
201            appendStates(s, states);
202
203            throw new TimeoutRuntimeException(s.toString());
204        }
205
206        return nextState;
207    }
208
209    /**
210     * Convert state integer to a String
211     */
212    public static String stateToString(int state) {
213        return mStateNames[state + 1];
214    }
215
216    /**
217     * Append all states to string
218     */
219    public static void appendStates(StringBuilder s, Collection<Integer> states) {
220        boolean start = true;
221        for (Integer state: states) {
222            if (!start) s.append(" ");
223            s.append(stateToString(state));
224            start = false;
225        }
226    }
227}
228