1/*
2 * Copyright (C) 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
17package com.android.ex.camera2.portability;
18
19import android.os.SystemClock;
20
21import com.android.ex.camera2.portability.debug.Log;
22
23public abstract class CameraStateHolder {
24    private static final Log.Tag TAG = new Log.Tag("CamStateHolder");
25
26    private int mState;
27    private boolean mInvalid;
28
29    /**
30     * Construct a new instance of @{link CameraStateHolder} with an initial state.
31     *
32     * @param state The initial state.
33     */
34    public CameraStateHolder(int state) {
35        setState(state);
36        mInvalid = false;
37    }
38
39    /**
40     * Change to a new state.
41     *
42     * @param state The new state.
43     */
44    public synchronized void setState(int state) {
45        if (mState != state) {
46            Log.v(TAG, "setState - state = " + Integer.toBinaryString(state));
47        }
48        mState = state;
49        this.notifyAll();
50    }
51
52    /**
53     * Obtain the current state.
54     *
55     * @return The current state.
56     */
57    public synchronized int getState() {
58        return mState;
59    }
60
61    /**
62     * Change the state to be invalid. Once invalidated, the state will be invalid forever.
63     */
64    public synchronized void invalidate() {
65        mInvalid = true;
66    }
67
68    /**
69     * Whether the state is invalid.
70     *
71     * @return True if the state is invalid.
72     */
73    public synchronized boolean isInvalid() {
74        return mInvalid;
75    }
76
77    private static interface ConditionChecker {
78        /**
79         * @return Whether the condition holds.
80         */
81        boolean success();
82    }
83
84    /**
85     * A helper method used by {@link #waitToAvoidStates(int)} and
86     * {@link #waitForStates(int)}. This method will wait until the
87     * condition is successful.
88     *
89     * @param stateChecker The state checker to be used.
90     * @param timeoutMs The timeout limit in milliseconds.
91     * @return {@code false} if the wait is interrupted or timeout limit is
92     *         reached.
93     */
94    private boolean waitForCondition(ConditionChecker stateChecker,
95            long timeoutMs) {
96        long timeBound = SystemClock.uptimeMillis() + timeoutMs;
97        synchronized (this) {
98            while (!stateChecker.success()) {
99                try {
100                    this.wait(timeoutMs);
101                } catch (InterruptedException ex) {
102                    if (SystemClock.uptimeMillis() > timeBound) {
103                        // Timeout.
104                        Log.w(TAG, "Timeout waiting.");
105                    }
106                    return false;
107                }
108            }
109        }
110        return true;
111    }
112
113    /**
114     * Block the current thread until the state becomes one of the
115     * specified.
116     *
117     * @param states Expected states.
118     * @return {@code false} if the wait is interrupted or timeout limit is
119     *         reached.
120     */
121    public boolean waitForStates(final int states) {
122        Log.v(TAG, "waitForStates - states = " + Integer.toBinaryString(states));
123        return waitForCondition(new ConditionChecker() {
124            @Override
125            public boolean success() {
126                return (states | getState()) == states;
127            }
128        }, CameraAgent.CAMERA_OPERATION_TIMEOUT_MS);
129    }
130
131    /**
132     * Block the current thread until the state becomes NOT one of the
133     * specified.
134     *
135     * @param states States to avoid.
136     * @return {@code false} if the wait is interrupted or timeout limit is
137     *         reached.
138     */
139    public boolean waitToAvoidStates(final int states) {
140        Log.v(TAG, "waitToAvoidStates - states = " + Integer.toBinaryString(states));
141        return waitForCondition(new ConditionChecker() {
142            @Override
143            public boolean success() {
144                return (states & getState()) == 0;
145            }
146        }, CameraAgent.CAMERA_OPERATION_TIMEOUT_MS);
147    }
148}
149