Merge "Add sensor for flip to screen off. See go/flip-to-screen-off-design-doc for design details." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3da8947ade
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.power;
|
||||
|
||||
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityThread;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Class used to detect when the phone is placed face down. This is used for Flip to Screen Off. A
|
||||
* client can use this detector to trigger state changes like screen off when the phone is face
|
||||
* down.
|
||||
*/
|
||||
public class FaceDownDetector implements SensorEventListener {
|
||||
|
||||
private static final String TAG = "FaceDownDetector";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int SCREEN_OFF_RESULT =
|
||||
FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__SCREEN_OFF;
|
||||
private static final int USER_INTERACTION =
|
||||
FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__USER_INTERACTION;
|
||||
private static final int UNFLIP =
|
||||
FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__UNFLIP;
|
||||
|
||||
/**
|
||||
* Used by the ExponentialMovingAverage accelerations, this determines how quickly the
|
||||
* average can change. A number closer to 1 will mean it will take longer to change.
|
||||
*/
|
||||
private static final float MOVING_AVERAGE_WEIGHT = 0.5f;
|
||||
|
||||
/** DeviceConfig flag name, if {@code true}, enables Face Down features. */
|
||||
private static final String KEY_FEATURE_ENABLED = "enable_flip_to_screen_off";
|
||||
|
||||
/** Default value in absence of {@link DeviceConfig} override. */
|
||||
private static final boolean DEFAULT_FEATURE_ENABLED = true;
|
||||
|
||||
private boolean mIsEnabled;
|
||||
|
||||
/**
|
||||
* DeviceConfig flag name, determines how long to disable sensor when user interacts while
|
||||
* device is flipped.
|
||||
*/
|
||||
private static final String KEY_INTERACTION_BACKOFF = "face_down_interaction_backoff_millis";
|
||||
|
||||
/** Default value in absence of {@link DeviceConfig} override. */
|
||||
private static final long DEFAULT_INTERACTION_BACKOFF = 60_000;
|
||||
|
||||
private long mUserInteractionBackoffMillis;
|
||||
|
||||
/**
|
||||
* DeviceConfig flag name, defines the max change in acceleration which will prevent face down
|
||||
* due to movement.
|
||||
*/
|
||||
static final String KEY_ACCELERATION_THRESHOLD = "acceleration_threshold";
|
||||
|
||||
/** Default value in absence of {@link DeviceConfig} override. */
|
||||
static final float DEFAULT_ACCELERATION_THRESHOLD = 0.2f;
|
||||
|
||||
private float mAccelerationThreshold;
|
||||
|
||||
/**
|
||||
* DeviceConfig flag name, defines the maximum z-axis acceleration that will indicate the phone
|
||||
* is face down.
|
||||
*/
|
||||
static final String KEY_Z_ACCELERATION_THRESHOLD = "z_acceleration_threshold";
|
||||
|
||||
/** Default value in absence of {@link DeviceConfig} override. */
|
||||
static final float DEFAULT_Z_ACCELERATION_THRESHOLD = -9.5f;
|
||||
|
||||
private float mZAccelerationThreshold;
|
||||
|
||||
/**
|
||||
* After going face down, we relax the threshold to make it more difficult to exit face down
|
||||
* than to enter it.
|
||||
*/
|
||||
private float mZAccelerationThresholdLenient;
|
||||
|
||||
/**
|
||||
* DeviceConfig flag name, defines the minimum amount of time that has to pass while the phone
|
||||
* is face down and not moving in order to trigger face down behavior, in milliseconds.
|
||||
*/
|
||||
static final String KEY_TIME_THRESHOLD_MILLIS = "time_threshold_millis";
|
||||
|
||||
/** Default value in absence of {@link DeviceConfig} override. */
|
||||
static final long DEFAULT_TIME_THRESHOLD_MILLIS = 1_000L;
|
||||
|
||||
private Duration mTimeThreshold;
|
||||
|
||||
private Sensor mAccelerometer;
|
||||
private SensorManager mSensorManager;
|
||||
private final Consumer<Boolean> mOnFlip;
|
||||
|
||||
/** Values we store for logging purposes. */
|
||||
private long mLastFlipTime = 0L;
|
||||
public int mPreviousResultType = 0;
|
||||
public long mPreviousResultTime = 0L;
|
||||
private long mMillisSaved = 0L;
|
||||
|
||||
private final ExponentialMovingAverage mCurrentXYAcceleration =
|
||||
new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT);
|
||||
private final ExponentialMovingAverage mCurrentZAcceleration =
|
||||
new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT);
|
||||
|
||||
private boolean mFaceDown = false;
|
||||
private boolean mActive = false;
|
||||
|
||||
private float mPrevAcceleration = 0;
|
||||
private long mPrevAccelerationTime = 0;
|
||||
|
||||
private boolean mZAccelerationIsFaceDown = false;
|
||||
private long mZAccelerationFaceDownTime = 0L;
|
||||
|
||||
private final Handler mHandler;
|
||||
private final Runnable mUserActivityRunnable;
|
||||
|
||||
public FaceDownDetector(@NonNull Consumer<Boolean> onFlip) {
|
||||
mOnFlip = Objects.requireNonNull(onFlip);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mUserActivityRunnable = () -> {
|
||||
if (mFaceDown) {
|
||||
exitFaceDown(USER_INTERACTION, SystemClock.uptimeMillis() - mLastFlipTime);
|
||||
checkAndUpdateActiveState(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Initializes the FaceDownDetector and all necessary listeners. */
|
||||
public void systemReady(Context context) {
|
||||
mSensorManager = context.getSystemService(SensorManager.class);
|
||||
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
readValuesFromDeviceConfig();
|
||||
checkAndUpdateActiveState(true);
|
||||
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
|
||||
ActivityThread.currentApplication().getMainExecutor(),
|
||||
(properties) -> onDeviceConfigChange(properties.getKeyset()));
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
|
||||
context.registerReceiver(new ScreenStateReceiver(), intentFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active state of the detector. If false, we will not process accelerometer changes.
|
||||
*/
|
||||
private void checkAndUpdateActiveState(boolean active) {
|
||||
if (mIsEnabled && mActive != active) {
|
||||
final long currentTime = SystemClock.uptimeMillis();
|
||||
// Don't make active if there was recently a user interaction while face down.
|
||||
if (active && mPreviousResultType == USER_INTERACTION
|
||||
&& currentTime - mPreviousResultTime < mUserInteractionBackoffMillis) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Slog.d(TAG, "Update active - " + active);
|
||||
mActive = active;
|
||||
if (!active) {
|
||||
if (mFaceDown && mPreviousResultTime != USER_INTERACTION) {
|
||||
mPreviousResultType = SCREEN_OFF_RESULT;
|
||||
mPreviousResultTime = currentTime;
|
||||
}
|
||||
mSensorManager.unregisterListener(this);
|
||||
mFaceDown = false;
|
||||
mOnFlip.accept(false);
|
||||
} else {
|
||||
if (mPreviousResultType == SCREEN_OFF_RESULT) {
|
||||
logScreenOff();
|
||||
}
|
||||
mSensorManager.registerListener(
|
||||
this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Prints state information about FaceDownDetector */
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println("FaceDownDetector:");
|
||||
pw.println(" mFaceDown=" + mFaceDown);
|
||||
pw.println(" mActive=" + mActive);
|
||||
pw.println(" mLastFlipTime=" + mLastFlipTime);
|
||||
pw.println(" mUserInteractionBackoffMillis=" + mUserInteractionBackoffMillis);
|
||||
pw.println(" mPreviousResultTime=" + mPreviousResultTime);
|
||||
pw.println(" mPreviousResultType=" + mPreviousResultType);
|
||||
pw.println(" mMillisSaved=" + mMillisSaved);
|
||||
pw.println(" mZAccelerationThreshold=" + mZAccelerationThreshold);
|
||||
pw.println(" mAccelerationThreshold=" + mAccelerationThreshold);
|
||||
pw.println(" mTimeThreshold=" + mTimeThreshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) return;
|
||||
if (!mActive || !mIsEnabled) return;
|
||||
|
||||
final float x = event.values[0];
|
||||
final float y = event.values[1];
|
||||
mCurrentXYAcceleration.updateMovingAverage(x * x + y * y);
|
||||
mCurrentZAcceleration.updateMovingAverage(event.values[2]);
|
||||
|
||||
// Detect movement
|
||||
// If the x, y acceleration is within the acc threshold for at least a length of time longer
|
||||
// than the time threshold, we set moving to true.
|
||||
final long curTime = event.timestamp;
|
||||
if (Math.abs(mCurrentXYAcceleration.mMovingAverage - mPrevAcceleration)
|
||||
> mAccelerationThreshold) {
|
||||
mPrevAcceleration = mCurrentXYAcceleration.mMovingAverage;
|
||||
mPrevAccelerationTime = curTime;
|
||||
}
|
||||
final boolean moving = curTime - mPrevAccelerationTime <= mTimeThreshold.toNanos();
|
||||
|
||||
// If the z acceleration is beyond the gravity/z-acceleration threshold for at least a
|
||||
// length of time longer than the time threshold, we set isFaceDownForPeriod to true.
|
||||
final float zAccelerationThreshold =
|
||||
mFaceDown ? mZAccelerationThresholdLenient : mZAccelerationThreshold;
|
||||
final boolean isCurrentlyFaceDown =
|
||||
mCurrentZAcceleration.mMovingAverage < zAccelerationThreshold;
|
||||
final boolean isFaceDownForPeriod = isCurrentlyFaceDown
|
||||
&& mZAccelerationIsFaceDown
|
||||
&& curTime - mZAccelerationFaceDownTime > mTimeThreshold.toNanos();
|
||||
if (isCurrentlyFaceDown && !mZAccelerationIsFaceDown) {
|
||||
mZAccelerationFaceDownTime = curTime;
|
||||
mZAccelerationIsFaceDown = true;
|
||||
} else if (!isCurrentlyFaceDown) {
|
||||
mZAccelerationIsFaceDown = false;
|
||||
}
|
||||
|
||||
|
||||
if (!moving && isFaceDownForPeriod && !mFaceDown) {
|
||||
faceDownDetected();
|
||||
} else if (!isFaceDownForPeriod && mFaceDown) {
|
||||
unFlipDetected();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
|
||||
|
||||
private void faceDownDetected() {
|
||||
if (DEBUG) Slog.d(TAG, "Triggered faceDownDetected.");
|
||||
mLastFlipTime = SystemClock.uptimeMillis();
|
||||
mFaceDown = true;
|
||||
mOnFlip.accept(true);
|
||||
}
|
||||
|
||||
private void unFlipDetected() {
|
||||
if (DEBUG) Slog.d(TAG, "Triggered exitFaceDown");
|
||||
exitFaceDown(UNFLIP, SystemClock.uptimeMillis() - mLastFlipTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user interacted with the screen while face down, indicated the phone is in use.
|
||||
* We log this event and temporarily make this detector inactive.
|
||||
*/
|
||||
public void userActivity() {
|
||||
mHandler.post(mUserActivityRunnable);
|
||||
}
|
||||
|
||||
private void exitFaceDown(int resultType, long millisSinceFlip) {
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.FACE_DOWN_REPORTED,
|
||||
resultType,
|
||||
millisSinceFlip,
|
||||
/* millis_until_normal_timeout= */ 0L,
|
||||
/* millis_until_next_screen_on= */ 0L);
|
||||
mFaceDown = false;
|
||||
mLastFlipTime = 0L;
|
||||
mPreviousResultType = resultType;
|
||||
mPreviousResultTime = SystemClock.uptimeMillis();
|
||||
mOnFlip.accept(false);
|
||||
}
|
||||
|
||||
private void logScreenOff() {
|
||||
if (mPreviousResultType == SCREEN_OFF_RESULT) {
|
||||
final long currentTime = SystemClock.uptimeMillis();
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.FACE_DOWN_REPORTED,
|
||||
mPreviousResultType,
|
||||
/* millis_since_flip= */ mPreviousResultTime - mLastFlipTime,
|
||||
mMillisSaved,
|
||||
/* millis_until_next_screen_on= */ currentTime - mPreviousResultTime);
|
||||
mPreviousResultType = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED,
|
||||
DEFAULT_FEATURE_ENABLED);
|
||||
}
|
||||
|
||||
private float getAccelerationThreshold() {
|
||||
return getFloatFlagValue(KEY_ACCELERATION_THRESHOLD,
|
||||
DEFAULT_ACCELERATION_THRESHOLD,
|
||||
-2.0f,
|
||||
2.0f);
|
||||
}
|
||||
|
||||
private float getZAccelerationThreshold() {
|
||||
return getFloatFlagValue(KEY_Z_ACCELERATION_THRESHOLD,
|
||||
DEFAULT_Z_ACCELERATION_THRESHOLD,
|
||||
-15.0f,
|
||||
0.0f);
|
||||
}
|
||||
|
||||
private long getUserInteractionBackoffMillis() {
|
||||
return getLongFlagValue(KEY_INTERACTION_BACKOFF,
|
||||
DEFAULT_INTERACTION_BACKOFF,
|
||||
0,
|
||||
3600_000);
|
||||
}
|
||||
|
||||
private float getFloatFlagValue(String key, float defaultValue, float min, float max) {
|
||||
final float value = DeviceConfig.getFloat(NAMESPACE_ATTENTION_MANAGER_SERVICE,
|
||||
key,
|
||||
defaultValue);
|
||||
|
||||
if (value < min || value > max) {
|
||||
Slog.w(TAG, "Bad flag value supplied for: " + key);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private long getLongFlagValue(String key, long defaultValue, long min, long max) {
|
||||
final long value = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
|
||||
key,
|
||||
defaultValue);
|
||||
|
||||
if (value < min || value > max) {
|
||||
Slog.w(TAG, "Bad flag value supplied for: " + key);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private Duration getTimeThreshold() {
|
||||
final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
|
||||
KEY_TIME_THRESHOLD_MILLIS,
|
||||
DEFAULT_TIME_THRESHOLD_MILLIS);
|
||||
|
||||
if (millis < 0 || millis > 15_000) {
|
||||
Slog.w(TAG, "Bad flag value supplied for: " + KEY_TIME_THRESHOLD_MILLIS);
|
||||
return Duration.ofMillis(DEFAULT_TIME_THRESHOLD_MILLIS);
|
||||
}
|
||||
|
||||
return Duration.ofMillis(millis);
|
||||
}
|
||||
|
||||
private void onDeviceConfigChange(@NonNull Set<String> keys) {
|
||||
for (String key : keys) {
|
||||
switch (key) {
|
||||
case KEY_ACCELERATION_THRESHOLD:
|
||||
case KEY_Z_ACCELERATION_THRESHOLD:
|
||||
case KEY_TIME_THRESHOLD_MILLIS:
|
||||
case KEY_FEATURE_ENABLED:
|
||||
readValuesFromDeviceConfig();
|
||||
return;
|
||||
default:
|
||||
Slog.i(TAG, "Ignoring change on " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readValuesFromDeviceConfig() {
|
||||
mAccelerationThreshold = getAccelerationThreshold();
|
||||
mZAccelerationThreshold = getZAccelerationThreshold();
|
||||
mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f;
|
||||
mTimeThreshold = getTimeThreshold();
|
||||
mIsEnabled = isEnabled();
|
||||
mUserInteractionBackoffMillis = getUserInteractionBackoffMillis();
|
||||
|
||||
Slog.i(TAG, "readValuesFromDeviceConfig():"
|
||||
+ "\nmAccelerationThreshold=" + mAccelerationThreshold
|
||||
+ "\nmZAccelerationThreshold=" + mZAccelerationThreshold
|
||||
+ "\nmTimeThreshold=" + mTimeThreshold
|
||||
+ "\nmIsEnabled=" + mIsEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how much screen on time might be saved as a result of this detector. Currently used for
|
||||
* logging purposes.
|
||||
*/
|
||||
public void setMillisSaved(long millisSaved) {
|
||||
mMillisSaved = millisSaved;
|
||||
}
|
||||
|
||||
private final class ScreenStateReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
|
||||
checkAndUpdateActiveState(false);
|
||||
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
|
||||
checkAndUpdateActiveState(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ExponentialMovingAverage {
|
||||
private final float mAlpha;
|
||||
private final float mInitialAverage;
|
||||
private float mMovingAverage;
|
||||
|
||||
ExponentialMovingAverage(float alpha) {
|
||||
this(alpha, 0.0f);
|
||||
}
|
||||
|
||||
ExponentialMovingAverage(float alpha, float initialAverage) {
|
||||
this.mAlpha = alpha;
|
||||
this.mInitialAverage = initialAverage;
|
||||
this.mMovingAverage = initialAverage;
|
||||
}
|
||||
|
||||
void updateMovingAverage(float newValue) {
|
||||
mMovingAverage = newValue + mAlpha * (mMovingAverage - newValue);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
mMovingAverage = this.mInitialAverage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,7 @@ public class Notifier {
|
||||
private final AppOpsManager mAppOps;
|
||||
private final SuspendBlocker mSuspendBlocker;
|
||||
private final WindowManagerPolicy mPolicy;
|
||||
private final FaceDownDetector mFaceDownDetector;
|
||||
private final ActivityManagerInternal mActivityManagerInternal;
|
||||
private final InputManagerInternal mInputManagerInternal;
|
||||
private final InputMethodManagerInternal mInputMethodManagerInternal;
|
||||
@@ -165,12 +166,14 @@ public class Notifier {
|
||||
private boolean mUserActivityPending;
|
||||
|
||||
public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
|
||||
FaceDownDetector faceDownDetector) {
|
||||
mContext = context;
|
||||
mBatteryStats = batteryStats;
|
||||
mAppOps = mContext.getSystemService(AppOpsManager.class);
|
||||
mSuspendBlocker = suspendBlocker;
|
||||
mPolicy = policy;
|
||||
mFaceDownDetector = faceDownDetector;
|
||||
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
|
||||
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
|
||||
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
|
||||
@@ -654,6 +657,7 @@ public class Notifier {
|
||||
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
|
||||
tm.notifyUserActivity();
|
||||
mPolicy.userActivity();
|
||||
mFaceDownDetector.userActivity();
|
||||
}
|
||||
|
||||
void postEnhancedDischargePredictionBroadcast(long delayMs) {
|
||||
|
||||
@@ -176,6 +176,8 @@ public final class PowerManagerService extends SystemService
|
||||
private static final int DIRTY_VR_MODE_CHANGED = 1 << 13;
|
||||
// Dirty bit: attentive timer may have timed out
|
||||
private static final int DIRTY_ATTENTIVE = 1 << 14;
|
||||
// Dirty bit: phone flipped to face down
|
||||
private static final int DIRTY_FACE_DOWN = 1 << 15;
|
||||
|
||||
// Summarizes the state of all active wakelocks.
|
||||
private static final int WAKE_LOCK_CPU = 1 << 0;
|
||||
@@ -263,6 +265,7 @@ public final class PowerManagerService extends SystemService
|
||||
private final BatterySaverStateMachine mBatterySaverStateMachine;
|
||||
private final BatterySavingStats mBatterySavingStats;
|
||||
private final AttentionDetector mAttentionDetector;
|
||||
private final FaceDownDetector mFaceDownDetector;
|
||||
private final BinderService mBinderService;
|
||||
private final LocalService mLocalService;
|
||||
private final NativeWrapper mNativeWrapper;
|
||||
@@ -547,6 +550,10 @@ public final class PowerManagerService extends SystemService
|
||||
public final float mScreenBrightnessMaximumVr;
|
||||
public final float mScreenBrightnessDefaultVr;
|
||||
|
||||
// Value we store for tracking face down behavior.
|
||||
private boolean mIsFaceDown = false;
|
||||
private long mLastFlipTime = 0L;
|
||||
|
||||
// The screen brightness mode.
|
||||
// One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
|
||||
private int mScreenBrightnessModeSetting;
|
||||
@@ -791,8 +798,10 @@ public final class PowerManagerService extends SystemService
|
||||
@VisibleForTesting
|
||||
static class Injector {
|
||||
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
|
||||
return new Notifier(looper, context, batteryStats, suspendBlocker, policy);
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
|
||||
FaceDownDetector faceDownDetector) {
|
||||
return new Notifier(
|
||||
looper, context, batteryStats, suspendBlocker, policy, faceDownDetector);
|
||||
}
|
||||
|
||||
SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
|
||||
@@ -906,6 +915,7 @@ public final class PowerManagerService extends SystemService
|
||||
mAmbientDisplaySuppressionController =
|
||||
mInjector.createAmbientDisplaySuppressionController(context);
|
||||
mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
|
||||
mFaceDownDetector = new FaceDownDetector(this::onFlip);
|
||||
|
||||
mBatterySavingStats = new BatterySavingStats(mLock);
|
||||
mBatterySaverPolicy =
|
||||
@@ -1011,6 +1021,26 @@ public final class PowerManagerService extends SystemService
|
||||
}
|
||||
}
|
||||
|
||||
private void onFlip(boolean isFaceDown) {
|
||||
long millisUntilNormalTimeout = 0;
|
||||
synchronized (mLock) {
|
||||
mIsFaceDown = isFaceDown;
|
||||
if (isFaceDown) {
|
||||
final long currentTime = mClock.uptimeMillis();
|
||||
mLastFlipTime = currentTime;
|
||||
final long sleepTimeout = getSleepTimeoutLocked(-1L);
|
||||
final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout, -1L);
|
||||
millisUntilNormalTimeout =
|
||||
mLastUserActivityTime + screenOffTimeout - mClock.uptimeMillis();
|
||||
mDirty |= DIRTY_FACE_DOWN;
|
||||
updatePowerStateLocked();
|
||||
}
|
||||
}
|
||||
if (isFaceDown) {
|
||||
mFaceDownDetector.setMillisSaved(millisUntilNormalTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.POWER_SERVICE, mBinderService, /* allowIsolated= */ false,
|
||||
@@ -1065,7 +1095,7 @@ public final class PowerManagerService extends SystemService
|
||||
mBatteryStats = BatteryStatsService.getService();
|
||||
mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats,
|
||||
mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"),
|
||||
mPolicy);
|
||||
mPolicy, mFaceDownDetector);
|
||||
|
||||
mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
|
||||
mInjector.createSuspendBlocker(
|
||||
@@ -1099,6 +1129,7 @@ public final class PowerManagerService extends SystemService
|
||||
|
||||
mBatterySaverController.systemReady();
|
||||
mBatterySaverPolicy.systemReady();
|
||||
mFaceDownDetector.systemReady(mContext);
|
||||
|
||||
// Register for settings changes.
|
||||
resolver.registerContentObserver(Settings.Secure.getUriFor(
|
||||
@@ -2283,9 +2314,11 @@ public final class PowerManagerService extends SystemService
|
||||
|| getWakefulnessLocked() == WAKEFULNESS_DOZING) {
|
||||
final long attentiveTimeout = getAttentiveTimeoutLocked();
|
||||
final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
|
||||
final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
|
||||
long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
|
||||
attentiveTimeout);
|
||||
final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
|
||||
screenOffTimeout =
|
||||
getScreenOffTimeoutWithFaceDownLocked(screenOffTimeout, screenDimDuration);
|
||||
final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
|
||||
final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
|
||||
|
||||
@@ -2541,6 +2574,16 @@ public final class PowerManagerService extends SystemService
|
||||
(long)(screenOffTimeout * mMaximumScreenDimRatioConfig));
|
||||
}
|
||||
|
||||
private long getScreenOffTimeoutWithFaceDownLocked(
|
||||
long screenOffTimeout, long screenDimDuration) {
|
||||
// If face down, we decrease the timeout to equal the dim duration so that the
|
||||
// device will go into a dim state.
|
||||
if (mIsFaceDown) {
|
||||
return Math.min(screenDimDuration, screenOffTimeout);
|
||||
}
|
||||
return screenOffTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the wakefulness of the device.
|
||||
*
|
||||
@@ -3859,6 +3902,8 @@ public final class PowerManagerService extends SystemService
|
||||
pw.println(" mDisplayReady=" + mDisplayReady);
|
||||
pw.println(" mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker);
|
||||
pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
|
||||
pw.println(" mLastFlipTime=" + mLastFlipTime);
|
||||
pw.println(" mIsFaceDown=" + mIsFaceDown);
|
||||
|
||||
pw.println();
|
||||
pw.println("Settings and Configuration:");
|
||||
@@ -4003,6 +4048,8 @@ public final class PowerManagerService extends SystemService
|
||||
mNotifier.dump(pw);
|
||||
}
|
||||
|
||||
mFaceDownDetector.dump(pw);
|
||||
|
||||
mAmbientDisplaySuppressionController.dump(pw);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.power;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorManager;
|
||||
import android.testing.TestableContext;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.display.TestUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.time.Duration;
|
||||
|
||||
public class FaceDownDetectorTest {
|
||||
@ClassRule
|
||||
public static final TestableContext sContext = new TestableContext(
|
||||
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
|
||||
|
||||
private final FaceDownDetector mFaceDownDetector =
|
||||
new FaceDownDetector(this::onFlip);
|
||||
|
||||
@Mock private SensorManager mSensorManager;
|
||||
|
||||
private long mCurrentTime;
|
||||
private int mOnFaceDownCalls = 0;
|
||||
private int mOnFaceDownExitCalls = 0;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
sContext.addMockSystemService(SensorManager.class, mSensorManager);
|
||||
mCurrentTime = 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void faceDownFor2Seconds_triggersFaceDown() throws Exception {
|
||||
mFaceDownDetector.systemReady(sContext);
|
||||
|
||||
// Face up
|
||||
// Using 0.5 on x to simulate constant acceleration, such as a sloped surface.
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
|
||||
|
||||
for (int i = 0; i < 200; i++) {
|
||||
advanceTime(Duration.ofMillis(20));
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
|
||||
}
|
||||
|
||||
assertThat(mOnFaceDownCalls).isEqualTo(1);
|
||||
assertThat(mOnFaceDownExitCalls).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void faceDownFor2Seconds_withMotion_DoesNotTriggerFaceDown() throws Exception {
|
||||
mFaceDownDetector.systemReady(sContext);
|
||||
|
||||
// Face up
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
advanceTime(Duration.ofMillis(20));
|
||||
//Move along x direction
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f * i, 0.0f, -10.0f));
|
||||
}
|
||||
|
||||
assertThat(mOnFaceDownCalls).isEqualTo(0);
|
||||
assertThat(mOnFaceDownExitCalls).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void faceDownForHalfSecond_DoesNotTriggerFaceDown() throws Exception {
|
||||
mFaceDownDetector.systemReady(sContext);
|
||||
|
||||
// Face up
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
advanceTime(Duration.ofMillis(5));
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
|
||||
}
|
||||
|
||||
assertThat(mOnFaceDownCalls).isEqualTo(0);
|
||||
assertThat(mOnFaceDownExitCalls).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void faceDownFor2Seconds_followedByFaceUp_triggersFaceDownExit() throws Exception {
|
||||
mFaceDownDetector.systemReady(sContext);
|
||||
|
||||
// Face up
|
||||
// Using 0.5 on x to simulate constant acceleration, such as a sloped surface.
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, 10.0f));
|
||||
|
||||
// Trigger face down
|
||||
for (int i = 0; i < 100; i++) {
|
||||
advanceTime(Duration.ofMillis(20));
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 0.0f, -10.0f));
|
||||
}
|
||||
|
||||
// Phone flips
|
||||
for (int i = 0; i < 10; i++) {
|
||||
advanceTime(Duration.ofMillis(5));
|
||||
mFaceDownDetector.onSensorChanged(createTestEvent(0.5f, 1.0f, 0.0f));
|
||||
}
|
||||
|
||||
assertThat(mOnFaceDownCalls).isEqualTo(1);
|
||||
assertThat(mOnFaceDownExitCalls).isEqualTo(1);
|
||||
}
|
||||
|
||||
private void advanceTime(Duration duration) {
|
||||
mCurrentTime += duration.toNanos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test event to replicate an accelerometer sensor event.
|
||||
* @param x Acceleration along the x dimension.
|
||||
* @param y Acceleration along the y dimension.
|
||||
* @param gravity Acceleration along the Z dimension. Relates to
|
||||
*/
|
||||
private SensorEvent createTestEvent(float x, float y, float gravity) throws Exception {
|
||||
final Constructor<SensorEvent> constructor =
|
||||
SensorEvent.class.getDeclaredConstructor(int.class);
|
||||
constructor.setAccessible(true);
|
||||
final SensorEvent event = constructor.newInstance(3);
|
||||
event.sensor =
|
||||
TestUtils.createSensor(Sensor.TYPE_ACCELEROMETER, Sensor.STRING_TYPE_ACCELEROMETER);
|
||||
event.values[0] = x;
|
||||
event.values[1] = y;
|
||||
event.values[2] = gravity;
|
||||
event.timestamp = mCurrentTime;
|
||||
return event;
|
||||
}
|
||||
|
||||
private void onFlip(boolean isFaceDown) {
|
||||
if (isFaceDown) {
|
||||
mOnFaceDownCalls++;
|
||||
} else {
|
||||
mOnFaceDownExitCalls++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,8 @@ public class NotifierTest {
|
||||
private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
|
||||
@Override
|
||||
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
|
||||
FaceDownDetector faceDownDetector) {
|
||||
return mNotifierMock;
|
||||
}
|
||||
|
||||
@@ -296,6 +297,7 @@ public class NotifierTest {
|
||||
IBatteryStats.Stub.asInterface(ServiceManager.getService(
|
||||
BatteryStats.SERVICE_NAME)),
|
||||
mInjector.createSuspendBlocker(mService, "testBlocker"),
|
||||
null,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,8 @@ public class PowerManagerServiceTest {
|
||||
mService = new PowerManagerService(mContextSpy, new Injector() {
|
||||
@Override
|
||||
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
|
||||
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
|
||||
FaceDownDetector faceDownDetector) {
|
||||
return mNotifierMock;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user