1/*
2 * Copyright (C) 2011 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.dialer;
18
19import android.content.Context;
20import android.hardware.Sensor;
21import android.hardware.SensorEvent;
22import android.hardware.SensorEventListener;
23import android.hardware.SensorManager;
24
25import javax.annotation.concurrent.GuardedBy;
26
27/**
28 * Manages the proximity sensor and notifies a listener when enabled.
29 */
30public class ProximitySensorManager {
31    /**
32     * Listener of the state of the proximity sensor.
33     * <p>
34     * This interface abstracts two possible states for the proximity sensor, near and far.
35     * <p>
36     * The actual meaning of these states depends on the actual sensor.
37     */
38    public interface Listener {
39        /** Called when the proximity sensor transitions from the far to the near state. */
40        public void onNear();
41        /** Called when the proximity sensor transitions from the near to the far state. */
42        public void onFar();
43    }
44
45    public static enum State {
46        NEAR, FAR
47    }
48
49    private final ProximitySensorEventListener mProximitySensorListener;
50
51    /**
52     * The current state of the manager, i.e., whether it is currently tracking the state of the
53     * sensor.
54     */
55    private boolean mManagerEnabled;
56
57    /**
58     * The listener to the state of the sensor.
59     * <p>
60     * Contains most of the logic concerning tracking of the sensor.
61     * <p>
62     * After creating an instance of this object, one should call {@link #register()} and
63     * {@link #unregister()} to enable and disable the notifications.
64     * <p>
65     * Instead of calling unregister, one can call {@link #unregisterWhenFar()} to unregister the
66     * listener the next time the sensor reaches the {@link State#FAR} state if currently in the
67     * {@link State#NEAR} state.
68     */
69    private static class ProximitySensorEventListener implements SensorEventListener {
70        private static final float FAR_THRESHOLD = 5.0f;
71
72        private final SensorManager mSensorManager;
73        private final Sensor mProximitySensor;
74        private final float mMaxValue;
75        private final Listener mListener;
76
77        /**
78         * The last state of the sensor.
79         * <p>
80         * Before registering and after unregistering we are always in the {@link State#FAR} state.
81         */
82        @GuardedBy("this") private State mLastState;
83        /**
84         * If this flag is set to true, we are waiting to reach the {@link State#FAR} state and
85         * should notify the listener and unregister when that happens.
86         */
87        @GuardedBy("this") private boolean mWaitingForFarState;
88
89        public ProximitySensorEventListener(SensorManager sensorManager, Sensor proximitySensor,
90                Listener listener) {
91            mSensorManager = sensorManager;
92            mProximitySensor = proximitySensor;
93            mMaxValue = proximitySensor.getMaximumRange();
94            mListener = listener;
95            // Initialize at far state.
96            mLastState = State.FAR;
97            mWaitingForFarState = false;
98        }
99
100        @Override
101        public void onSensorChanged(SensorEvent event) {
102            // Make sure we have a valid value.
103            if (event.values == null) return;
104            if (event.values.length == 0) return;
105            float value = event.values[0];
106            // Convert the sensor into a NEAR/FAR state.
107            State state = getStateFromValue(value);
108            synchronized (this) {
109                // No change in state, do nothing.
110                if (state == mLastState) return;
111                // Keep track of the current state.
112                mLastState = state;
113                // If we are waiting to reach the far state and we are now in it, unregister.
114                if (mWaitingForFarState && mLastState == State.FAR) {
115                    unregisterWithoutNotification();
116                }
117            }
118            // Notify the listener of the state change.
119            switch (state) {
120                case NEAR:
121                    mListener.onNear();
122                    break;
123
124                case FAR:
125                    mListener.onFar();
126                    break;
127            }
128        }
129
130        @Override
131        public void onAccuracyChanged(Sensor sensor, int accuracy) {
132            // Nothing to do here.
133        }
134
135        /** Returns the state of the sensor given its current value. */
136        private State getStateFromValue(float value) {
137            // Determine if the current value corresponds to the NEAR or FAR state.
138            // Take case of the case where the proximity sensor is binary: if the current value is
139            // equal to the maximum, we are always in the FAR state.
140            return (value > FAR_THRESHOLD || value == mMaxValue) ? State.FAR : State.NEAR;
141        }
142
143        /**
144         * Unregister the next time the sensor reaches the {@link State#FAR} state.
145         */
146        public synchronized void unregisterWhenFar() {
147            if (mLastState == State.FAR) {
148                // We are already in the far state, just unregister now.
149                unregisterWithoutNotification();
150            } else {
151                mWaitingForFarState = true;
152            }
153        }
154
155        /** Register the listener and call the listener as necessary. */
156        public synchronized void register() {
157            // It is okay to register multiple times.
158            mSensorManager.registerListener(this, mProximitySensor, SensorManager.SENSOR_DELAY_UI);
159            // We should no longer be waiting for the far state if we are registering again.
160            mWaitingForFarState = false;
161        }
162
163        public void unregister() {
164            State lastState;
165            synchronized (this) {
166                unregisterWithoutNotification();
167                lastState = mLastState;
168                // Always go back to the FAR state. That way, when we register again we will get a
169                // transition when the sensor gets into the NEAR state.
170                mLastState = State.FAR;
171            }
172            // Notify the listener if we changed the state to FAR while unregistering.
173            if (lastState != State.FAR) {
174                mListener.onFar();
175            }
176        }
177
178        @GuardedBy("this")
179        private void unregisterWithoutNotification() {
180            mSensorManager.unregisterListener(this);
181            mWaitingForFarState = false;
182        }
183    }
184
185    public ProximitySensorManager(Context context, Listener listener) {
186        SensorManager sensorManager =
187                (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
188        Sensor proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
189        if (proximitySensor == null) {
190            // If there is no sensor, we should not do anything.
191            mProximitySensorListener = null;
192        } else {
193            mProximitySensorListener =
194                    new ProximitySensorEventListener(sensorManager, proximitySensor, listener);
195        }
196    }
197
198    /**
199     * Enables the proximity manager.
200     * <p>
201     * The listener will start getting notifications of events.
202     * <p>
203     * This method is idempotent.
204     */
205    public void enable() {
206        if (mProximitySensorListener != null && !mManagerEnabled) {
207            mProximitySensorListener.register();
208            mManagerEnabled = true;
209        }
210    }
211
212    /**
213     * Disables the proximity manager.
214     * <p>
215     * The listener will stop receiving notifications of events, possibly after receiving a last
216     * {@link Listener#onFar()} callback.
217     * <p>
218     * If {@code waitForFarState} is true, if the sensor is not currently in the {@link State#FAR}
219     * state, the listener will receive a {@link Listener#onFar()} callback the next time the sensor
220     * actually reaches the {@link State#FAR} state.
221     * <p>
222     * If {@code waitForFarState} is false, the listener will receive a {@link Listener#onFar()}
223     * callback immediately if the sensor is currently not in the {@link State#FAR} state.
224     * <p>
225     * This method is idempotent.
226     */
227    public void disable(boolean waitForFarState) {
228        if (mProximitySensorListener != null && mManagerEnabled) {
229            if (waitForFarState) {
230                mProximitySensorListener.unregisterWhenFar();
231            } else {
232                mProximitySensorListener.unregister();
233            }
234            mManagerEnabled = false;
235        }
236    }
237}
238