blob: a3433d9570a4fd617680322abee5c4a87c614484 [file] [log] [blame]
/*
* Copyright (C) 2022 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.server.display;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Slog;
import android.util.TimeUtils;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
/**
* Maintains the proximity state of the display.
* Internally listens for proximity updates and schedules a power state update when the proximity
* state changes.
*/
public final class DisplayPowerProximityStateController {
@VisibleForTesting
static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
@VisibleForTesting
static final int PROXIMITY_UNKNOWN = -1;
@VisibleForTesting
static final int PROXIMITY_POSITIVE = 1;
@VisibleForTesting
static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
private static final int MSG_IGNORE_PROXIMITY = 2;
private static final int PROXIMITY_NEGATIVE = 0;
private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
// Proximity sensor debounce delay in milliseconds for positive transitions.
// Proximity sensor debounce delay in milliseconds for negative transitions.
private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
// Trigger proximity if distance is less than 5 cm.
private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
private final String mTag;
// A lock to handle the deadlock and race conditions.
private final Object mLock = new Object();
// The manager which lets us access the device's ProximitySensor
private final SensorManager mSensorManager;
// An entity which manages the wakelocks.
private final WakelockController mWakelockController;
// A handler to process all the events on this thread in a synchronous manner
private final DisplayPowerProximityStateHandler mHandler;
// A runnable to execute the utility to update the power state.
private final Runnable mNudgeUpdatePowerState;
private Clock mClock;
// A listener which listen's to the events emitted by the proximity sensor.
private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (mProximitySensorEnabled) {
final long time = mClock.uptimeMillis();
final float distance = event.values[0];
boolean positive = distance >= 0.0f && distance < mProximityThreshold;
handleProximitySensorEvent(time, positive);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Not used.
}
};
// The proximity sensor, or null if not available or needed.
private Sensor mProximitySensor;
// The configurations for the associated display
private DisplayDeviceConfig mDisplayDeviceConfig;
// True if a request has been made to wait for the proximity sensor to go negative.
@GuardedBy("mLock")
private boolean mPendingWaitForNegativeProximityLocked;
// True if the device should wait for negative proximity sensor before
// waking up the screen. This is set to false as soon as a negative
// proximity sensor measurement is observed or when the device is forced to
// go to sleep by the user. While true, the screen remains off.
private boolean mWaitingForNegativeProximity;
// True if the device should not take into account the proximity sensor
// until either the proximity sensor state changes, or there is no longer a
// request to listen to proximity sensor.
private boolean mIgnoreProximityUntilChanged;
// Set to true if the proximity sensor listener has been registered
// with the sensor manager.
private boolean mProximitySensorEnabled;
// The raw non-debounced proximity sensor state.
private int mPendingProximity = PROXIMITY_UNKNOWN;
// -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will
// be removed. Applies for both positive and negative proximity flips.
private long mPendingProximityDebounceTime = -1;
// True if the screen was turned off because of the proximity sensor.
// When the screen turns on again, we report user activity to the power manager.
private boolean mScreenOffBecauseOfProximity;
// The debounced proximity sensor state.
private int mProximity = PROXIMITY_UNKNOWN;
// The actual proximity sensor threshold value.
private float mProximityThreshold;
// A flag representing if the ramp is to be skipped when the proximity changes from positive
// to negative
private boolean mSkipRampBecauseOfProximityChangeToNegative = false;
// The DisplayId of the associated Logical Display.
private int mDisplayId;
/**
* Create a new instance of DisplayPowerProximityStateController.
*
* @param wakeLockController WakelockController used to acquire/release wakelocks
* @param displayDeviceConfig DisplayDeviceConfig instance from which the configs(Proximity
* Sensor) are to be loaded
* @param looper A looper onto which the handler is to be associated.
* @param nudgeUpdatePowerState A runnable to execute the utility to update the power state
* @param displayId The DisplayId of the associated Logical Display.
* @param sensorManager The manager which lets us access the display's ProximitySensor
*/
public DisplayPowerProximityStateController(
WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig,
Looper looper,
Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager,
Injector injector) {
if (injector == null) {
injector = new Injector();
}
mClock = injector.createClock();
mWakelockController = wakeLockController;
mHandler = new DisplayPowerProximityStateHandler(looper);
mNudgeUpdatePowerState = nudgeUpdatePowerState;
mDisplayDeviceConfig = displayDeviceConfig;
mDisplayId = displayId;
mTag = "DisplayPowerProximityStateController[" + mDisplayId + "]";
mSensorManager = sensorManager;
loadProximitySensor();
}
/**
* Manages the pending state of the proximity.
*/
public void updatePendingProximityRequestsLocked() {
synchronized (mLock) {
mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
mPendingWaitForNegativeProximityLocked = false;
if (mIgnoreProximityUntilChanged) {
// Also, lets stop waiting for negative proximity if we're ignoring it.
mWaitingForNegativeProximity = false;
}
}
}
/**
* Clean up all resources that are accessed via the {@link #mHandler} thread.
*/
public void cleanup() {
setProximitySensorEnabled(false);
}
/**
* Returns true if the proximity sensor screen-off function is available.
*/
public boolean isProximitySensorAvailable() {
return mProximitySensor != null;
}
/**
* Sets the flag to indicate that the system is waiting for the negative proximity event
*/
public boolean setPendingWaitForNegativeProximityLocked(
boolean requestWaitForNegativeProximity) {
synchronized (mLock) {
if (requestWaitForNegativeProximity
&& !mPendingWaitForNegativeProximityLocked) {
mPendingWaitForNegativeProximityLocked = true;
return true;
}
return false;
}
}
/**
* Updates the proximity state of the display, based on the newly received DisplayPowerRequest
* and the target display state
*/
public void updateProximityState(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int displayState) {
mSkipRampBecauseOfProximityChangeToNegative = false;
if (mProximitySensor != null) {
if (displayPowerRequest.useProximitySensor && displayState != Display.STATE_OFF) {
// At this point the policy says that the screen should be on, but we've been
// asked to listen to the prox sensor to adjust the display state, so lets make
// sure the sensor is on.
setProximitySensorEnabled(true);
if (!mScreenOffBecauseOfProximity
&& mProximity == PROXIMITY_POSITIVE
&& !mIgnoreProximityUntilChanged) {
// Prox sensor already reporting "near" so we should turn off the screen.
// Also checked that we aren't currently set to ignore the proximity sensor
// temporarily.
mScreenOffBecauseOfProximity = true;
sendOnProximityPositiveWithWakelock();
}
} else if (mWaitingForNegativeProximity
&& mScreenOffBecauseOfProximity
&& mProximity == PROXIMITY_POSITIVE
&& displayState != Display.STATE_OFF) {
// The policy says that we should have the screen on, but it's off due to the prox
// and we've been asked to wait until the screen is far from the user to turn it
// back on. Let keep the prox sensor on so we can tell when it's far again.
setProximitySensorEnabled(true);
} else {
// We haven't been asked to use the prox sensor and we're not waiting on the screen
// to turn back on...so let's shut down the prox sensor.
setProximitySensorEnabled(false);
mWaitingForNegativeProximity = false;
}
if (mScreenOffBecauseOfProximity
&& (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
// The screen *was* off due to prox being near, but now it's "far" so lets turn
// the screen back on. Also turn it back on if we've been asked to ignore the
// prox sensor temporarily.
mScreenOffBecauseOfProximity = false;
mSkipRampBecauseOfProximityChangeToNegative = true;
sendOnProximityNegativeWithWakelock();
}
} else {
mWaitingForNegativeProximity = false;
mIgnoreProximityUntilChanged = false;
}
}
/**
* A utility to check if the brightness change ramp is to be skipped because the proximity was
* changed from positive to negative.
*/
public boolean shouldSkipRampBecauseOfProximityChangeToNegative() {
return mSkipRampBecauseOfProximityChangeToNegative;
}
/**
* Represents of the screen is currently turned off because of the proximity state.
*/
public boolean isScreenOffBecauseOfProximity() {
return mScreenOffBecauseOfProximity;
}
/**
* Ignores the proximity sensor until the sensor state changes, but only if the sensor is
* currently enabled and forcing the screen to be dark.
*/
public void ignoreProximitySensorUntilChanged() {
mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
}
/**
* This adjusts the state of this class when a change in the DisplayDevice is detected.
*/
public void notifyDisplayDeviceChanged(DisplayDeviceConfig displayDeviceConfig) {
this.mDisplayDeviceConfig = displayDeviceConfig;
loadProximitySensor();
}
/**
* Used to dump the state.
*
* @param pw The PrintWriter used to dump the state.
*/
public void dumpLocal(PrintWriter pw) {
pw.println();
pw.println("DisplayPowerProximityStateController:");
synchronized (mLock) {
pw.println(" mPendingWaitForNegativeProximityLocked="
+ mPendingWaitForNegativeProximityLocked);
}
pw.println(" mDisplayId=" + mDisplayId);
pw.println(" mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
pw.println(" mIgnoreProximityUntilChanged=" + mIgnoreProximityUntilChanged);
pw.println(" mProximitySensor=" + mProximitySensor);
pw.println(" mProximitySensorEnabled=" + mProximitySensorEnabled);
pw.println(" mProximityThreshold=" + mProximityThreshold);
pw.println(" mProximity=" + proximityToString(mProximity));
pw.println(" mPendingProximity=" + proximityToString(mPendingProximity));
pw.println(" mPendingProximityDebounceTime="
+ TimeUtils.formatUptime(mPendingProximityDebounceTime));
pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
pw.println(" mSkipRampBecauseOfProximityChangeToNegative="
+ mSkipRampBecauseOfProximityChangeToNegative);
}
void ignoreProximitySensorUntilChangedInternal() {
if (!mIgnoreProximityUntilChanged
&& mProximity == PROXIMITY_POSITIVE) {
// Only ignore if it is still reporting positive (near)
mIgnoreProximityUntilChanged = true;
Slog.i(mTag, "Ignoring proximity");
mNudgeUpdatePowerState.run();
}
}
private void sendOnProximityPositiveWithWakelock() {
mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
mHandler.post(mWakelockController.getOnProximityPositiveRunnable());
}
private void sendOnProximityNegativeWithWakelock() {
mWakelockController.acquireWakelock(WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
mHandler.post(mWakelockController.getOnProximityNegativeRunnable());
}
private void loadProximitySensor() {
if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
return;
}
final DisplayDeviceConfig.SensorData proxSensor =
mDisplayDeviceConfig.getProximitySensor();
final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
fallbackType);
if (mProximitySensor != null) {
mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
TYPICAL_PROXIMITY_THRESHOLD);
}
}
private void setProximitySensorEnabled(boolean enable) {
if (enable) {
if (!mProximitySensorEnabled) {
// Register the listener.
// Proximity sensor state already cleared initially.
mProximitySensorEnabled = true;
mIgnoreProximityUntilChanged = false;
mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
SensorManager.SENSOR_DELAY_NORMAL, mHandler);
}
} else {
if (mProximitySensorEnabled) {
// Unregister the listener.
// Clear the proximity sensor state for next time.
mProximitySensorEnabled = false;
mProximity = PROXIMITY_UNKNOWN;
mIgnoreProximityUntilChanged = false;
mPendingProximity = PROXIMITY_UNKNOWN;
mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
mSensorManager.unregisterListener(mProximitySensorListener);
// release wake lock(must be last)
boolean proxDebounceSuspendBlockerReleased =
mWakelockController.releaseWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
if (proxDebounceSuspendBlockerReleased) {
mPendingProximityDebounceTime = -1;
}
}
}
}
private void handleProximitySensorEvent(long time, boolean positive) {
if (mProximitySensorEnabled) {
if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
return; // no change
}
if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
return; // no change
}
// Only accept a proximity sensor reading if it remains
// stable for the entire debounce delay. We hold a wake lock while
// debouncing the sensor.
mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
if (positive) {
mPendingProximity = PROXIMITY_POSITIVE;
mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY;
mWakelockController.acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
} else {
mPendingProximity = PROXIMITY_NEGATIVE;
mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY;
mWakelockController.acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE); // acquire wake lock
}
// Debounce the new sensor reading.
debounceProximitySensor();
}
}
private void debounceProximitySensor() {
if (mProximitySensorEnabled
&& mPendingProximity != PROXIMITY_UNKNOWN
&& mPendingProximityDebounceTime >= 0) {
final long now = mClock.uptimeMillis();
if (mPendingProximityDebounceTime <= now) {
if (mProximity != mPendingProximity) {
// if the status of the sensor changed, stop ignoring.
mIgnoreProximityUntilChanged = false;
Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
}
// Sensor reading accepted. Apply the change then release the wake lock.
mProximity = mPendingProximity;
mNudgeUpdatePowerState.run();
// (must be last)
boolean proxDebounceSuspendBlockerReleased =
mWakelockController.releaseWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
if (proxDebounceSuspendBlockerReleased) {
mPendingProximityDebounceTime = -1;
}
} else {
// Need to wait a little longer.
// Debounce again later. We continue holding a wake lock while waiting.
Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
}
}
}
private class DisplayPowerProximityStateHandler extends Handler {
DisplayPowerProximityStateHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PROXIMITY_SENSOR_DEBOUNCED:
debounceProximitySensor();
break;
case MSG_IGNORE_PROXIMITY:
ignoreProximitySensorUntilChangedInternal();
break;
}
}
}
private String proximityToString(int state) {
switch (state) {
case PROXIMITY_UNKNOWN:
return "Unknown";
case PROXIMITY_NEGATIVE:
return "Negative";
case PROXIMITY_POSITIVE:
return "Positive";
default:
return Integer.toString(state);
}
}
@VisibleForTesting
boolean getPendingWaitForNegativeProximityLocked() {
synchronized (mLock) {
return mPendingWaitForNegativeProximityLocked;
}
}
@VisibleForTesting
boolean getWaitingForNegativeProximity() {
return mWaitingForNegativeProximity;
}
@VisibleForTesting
boolean shouldIgnoreProximityUntilChanged() {
return mIgnoreProximityUntilChanged;
}
boolean isProximitySensorEnabled() {
return mProximitySensorEnabled;
}
@VisibleForTesting
Handler getHandler() {
return mHandler;
}
@VisibleForTesting
int getPendingProximity() {
return mPendingProximity;
}
@VisibleForTesting
int getProximity() {
return mProximity;
}
@VisibleForTesting
long getPendingProximityDebounceTime() {
return mPendingProximityDebounceTime;
}
@VisibleForTesting
SensorEventListener getProximitySensorListener() {
return mProximitySensorListener;
}
/** Functional interface for providing time. */
@VisibleForTesting
interface Clock {
/**
* Returns current time in milliseconds since boot, not counting time spent in deep sleep.
*/
long uptimeMillis();
}
@VisibleForTesting
static class Injector {
Clock createClock() {
return () -> SystemClock.uptimeMillis();
}
}
}