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:
Michael Wright
2021-02-23 03:15:14 +00:00
committed by Android (Google) Code Review
6 changed files with 682 additions and 7 deletions

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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++;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}