blob: 69601bfe78c1979a4bf8c7c3dd681bc5a90a597e [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import javax.annotation.concurrent.GuardedBy;
/**
* Manages the proximity sensor and notifies a listener when enabled.
*/
public class ProximitySensorManager {
/**
* Listener of the state of the proximity sensor.
* <p>
* This interface abstracts two possible states for the proximity sensor, near and far.
* <p>
* The actual meaning of these states depends on the actual sensor.
*/
public interface Listener {
/** Called when the proximity sensor transitions from the far to the near state. */
public void onNear();
/** Called when the proximity sensor transitions from the near to the far state. */
public void onFar();
}
public static enum State {
NEAR, FAR
}
private final ProximitySensorEventListener mProximitySensorListener;
/**
* The current state of the manager, i.e., whether it is currently tracking the state of the
* sensor.
*/
private boolean mManagerEnabled;
/**
* The listener to the state of the sensor.
* <p>
* Contains most of the logic concerning tracking of the sensor.
* <p>
* After creating an instance of this object, one should call {@link #register()} and
* {@link #unregister()} to enable and disable the notifications.
* <p>
* Instead of calling unregister, one can call {@link #unregisterWhenFar()} to unregister the
* listener the next time the sensor reaches the {@link State#FAR} state if currently in the
* {@link State#NEAR} state.
*/
private static class ProximitySensorEventListener implements SensorEventListener {
private static final float FAR_THRESHOLD = 5.0f;
private final SensorManager mSensorManager;
private final Sensor mProximitySensor;
private final float mMaxValue;
private final Listener mListener;
/**
* The last state of the sensor.
* <p>
* Before registering and after unregistering we are always in the {@link State#FAR} state.
*/
@GuardedBy("this") private State mLastState;
/**
* If this flag is set to true, we are waiting to reach the {@link State#FAR} state and
* should notify the listener and unregister when that happens.
*/
@GuardedBy("this") private boolean mWaitingForFarState;
public ProximitySensorEventListener(SensorManager sensorManager, Sensor proximitySensor,
Listener listener) {
mSensorManager = sensorManager;
mProximitySensor = proximitySensor;
mMaxValue = proximitySensor.getMaximumRange();
mListener = listener;
// Initialize at far state.
mLastState = State.FAR;
mWaitingForFarState = false;
}
@Override
public void onSensorChanged(SensorEvent event) {
// Make sure we have a valid value.
if (event.values == null) return;
if (event.values.length == 0) return;
float value = event.values[0];
// Convert the sensor into a NEAR/FAR state.
State state = getStateFromValue(value);
synchronized (this) {
// No change in state, do nothing.
if (state == mLastState) return;
// Keep track of the current state.
mLastState = state;
// If we are waiting to reach the far state and we are now in it, unregister.
if (mWaitingForFarState && mLastState == State.FAR) {
unregisterWithoutNotification();
}
}
// Notify the listener of the state change.
switch (state) {
case NEAR:
mListener.onNear();
break;
case FAR:
mListener.onFar();
break;
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Nothing to do here.
}
/** Returns the state of the sensor given its current value. */
private State getStateFromValue(float value) {
// Determine if the current value corresponds to the NEAR or FAR state.
// Take case of the case where the proximity sensor is binary: if the current value is
// equal to the maximum, we are always in the FAR state.
return (value > FAR_THRESHOLD || value == mMaxValue) ? State.FAR : State.NEAR;
}
/**
* Unregister the next time the sensor reaches the {@link State#FAR} state.
*/
public synchronized void unregisterWhenFar() {
if (mLastState == State.FAR) {
// We are already in the far state, just unregister now.
unregisterWithoutNotification();
} else {
mWaitingForFarState = true;
}
}
/** Register the listener and call the listener as necessary. */
public synchronized void register() {
// It is okay to register multiple times.
mSensorManager.registerListener(this, mProximitySensor, SensorManager.SENSOR_DELAY_UI);
// We should no longer be waiting for the far state if we are registering again.
mWaitingForFarState = false;
}
public void unregister() {
State lastState;
synchronized (this) {
unregisterWithoutNotification();
lastState = mLastState;
// Always go back to the FAR state. That way, when we register again we will get a
// transition when the sensor gets into the NEAR state.
mLastState = State.FAR;
}
// Notify the listener if we changed the state to FAR while unregistering.
if (lastState != State.FAR) {
mListener.onFar();
}
}
@GuardedBy("this")
private void unregisterWithoutNotification() {
mSensorManager.unregisterListener(this);
mWaitingForFarState = false;
}
}
public ProximitySensorManager(Context context, Listener listener) {
SensorManager sensorManager =
(SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
Sensor proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (proximitySensor == null) {
// If there is no sensor, we should not do anything.
mProximitySensorListener = null;
} else {
mProximitySensorListener =
new ProximitySensorEventListener(sensorManager, proximitySensor, listener);
}
}
/**
* Enables the proximity manager.
* <p>
* The listener will start getting notifications of events.
* <p>
* This method is idempotent.
*/
public void enable() {
if (mProximitySensorListener != null && !mManagerEnabled) {
mProximitySensorListener.register();
mManagerEnabled = true;
}
}
/**
* Disables the proximity manager.
* <p>
* The listener will stop receiving notifications of events, possibly after receiving a last
* {@link Listener#onFar()} callback.
* <p>
* If {@code waitForFarState} is true, if the sensor is not currently in the {@link State#FAR}
* state, the listener will receive a {@link Listener#onFar()} callback the next time the sensor
* actually reaches the {@link State#FAR} state.
* <p>
* If {@code waitForFarState} is false, the listener will receive a {@link Listener#onFar()}
* callback immediately if the sensor is currently not in the {@link State#FAR} state.
* <p>
* This method is idempotent.
*/
public void disable(boolean waitForFarState) {
if (mProximitySensorListener != null && mManagerEnabled) {
if (waitForFarState) {
mProximitySensorListener.unregisterWhenFar();
} else {
mProximitySensorListener.unregister();
}
mManagerEnabled = false;
}
}
}