1 /* 2 * Copyright (C) 2021 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 17 package com.android.server.power; 18 19 import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; 20 21 import android.annotation.NonNull; 22 import android.app.ActivityThread; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.hardware.Sensor; 28 import android.hardware.SensorEvent; 29 import android.hardware.SensorEventListener; 30 import android.hardware.SensorManager; 31 import android.os.Handler; 32 import android.os.Looper; 33 import android.os.PowerManager; 34 import android.os.SystemClock; 35 import android.provider.DeviceConfig; 36 import android.util.Slog; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.util.FrameworkStatsLog; 40 41 import java.io.PrintWriter; 42 import java.time.Duration; 43 import java.util.Objects; 44 import java.util.Set; 45 import java.util.function.Consumer; 46 47 /** 48 * Class used to detect when the phone is placed face down. This is used for Flip to Screen Off. A 49 * client can use this detector to trigger state changes like screen off when the phone is face 50 * down. 51 */ 52 public class FaceDownDetector implements SensorEventListener { 53 54 private static final String TAG = "FaceDownDetector"; 55 private static final boolean DEBUG = false; 56 57 private static final int SCREEN_OFF_RESULT = 58 FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__SCREEN_OFF; 59 private static final int USER_INTERACTION = 60 FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__USER_INTERACTION; 61 private static final int UNFLIP = 62 FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__UNFLIP; 63 private static final int UNKNOWN = 64 FrameworkStatsLog.FACE_DOWN_REPORTED__FACE_DOWN_RESPONSE__UNKNOWN; 65 66 /** 67 * Used by the ExponentialMovingAverage accelerations, this determines how quickly the 68 * average can change. A number closer to 1 will mean it will take longer to change. 69 */ 70 private static final float MOVING_AVERAGE_WEIGHT = 0.5f; 71 72 /** DeviceConfig flag name, if {@code true}, enables Face Down features. */ 73 static final String KEY_FEATURE_ENABLED = "enable_flip_to_screen_off"; 74 75 /** Default value in absence of {@link DeviceConfig} override. */ 76 private static final boolean DEFAULT_FEATURE_ENABLED = true; 77 78 private boolean mIsEnabled; 79 80 private int mSensorMaxLatencyMicros; 81 82 /** 83 * DeviceConfig flag name, determines how long to disable sensor when user interacts while 84 * device is flipped. 85 */ 86 private static final String KEY_INTERACTION_BACKOFF = "face_down_interaction_backoff_millis"; 87 88 /** Default value in absence of {@link DeviceConfig} override. */ 89 private static final long DEFAULT_INTERACTION_BACKOFF = 60_000; 90 91 private long mUserInteractionBackoffMillis; 92 93 /** 94 * DeviceConfig flag name, defines the max change in acceleration which will prevent face down 95 * due to movement. 96 */ 97 static final String KEY_ACCELERATION_THRESHOLD = "acceleration_threshold"; 98 99 /** Default value in absence of {@link DeviceConfig} override. */ 100 static final float DEFAULT_ACCELERATION_THRESHOLD = 0.2f; 101 102 private float mAccelerationThreshold; 103 104 /** 105 * DeviceConfig flag name, defines the maximum z-axis acceleration that will indicate the phone 106 * is face down. 107 */ 108 static final String KEY_Z_ACCELERATION_THRESHOLD = "z_acceleration_threshold"; 109 110 /** Default value in absence of {@link DeviceConfig} override. */ 111 static final float DEFAULT_Z_ACCELERATION_THRESHOLD = -9.5f; 112 113 private float mZAccelerationThreshold; 114 115 /** 116 * After going face down, we relax the threshold to make it more difficult to exit face down 117 * than to enter it. 118 */ 119 private float mZAccelerationThresholdLenient; 120 121 /** 122 * DeviceConfig flag name, defines the minimum amount of time that has to pass while the phone 123 * is face down and not moving in order to trigger face down behavior, in milliseconds. 124 */ 125 static final String KEY_TIME_THRESHOLD_MILLIS = "time_threshold_millis"; 126 127 /** Default value in absence of {@link DeviceConfig} override. */ 128 static final long DEFAULT_TIME_THRESHOLD_MILLIS = 1_000L; 129 130 private Duration mTimeThreshold; 131 132 private Sensor mAccelerometer; 133 private SensorManager mSensorManager; 134 private final Consumer<Boolean> mOnFlip; 135 136 /** Values we store for logging purposes. */ 137 private long mLastFlipTime = 0L; 138 public int mPreviousResultType = UNKNOWN; 139 public long mPreviousResultTime = 0L; 140 private long mMillisSaved = 0L; 141 142 private final ExponentialMovingAverage mCurrentXYAcceleration = 143 new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT); 144 private final ExponentialMovingAverage mCurrentZAcceleration = 145 new ExponentialMovingAverage(MOVING_AVERAGE_WEIGHT); 146 147 private boolean mFaceDown = false; 148 private boolean mInteractive = false; 149 private boolean mActive = false; 150 151 private float mPrevAcceleration = 0; 152 private long mPrevAccelerationTime = 0; 153 154 private boolean mZAccelerationIsFaceDown = false; 155 private long mZAccelerationFaceDownTime = 0L; 156 157 private final Handler mHandler; 158 private final Runnable mUserActivityRunnable; 159 @VisibleForTesting 160 final BroadcastReceiver mScreenReceiver; 161 162 private Context mContext; 163 FaceDownDetector(@onNull Consumer<Boolean> onFlip)164 public FaceDownDetector(@NonNull Consumer<Boolean> onFlip) { 165 mOnFlip = Objects.requireNonNull(onFlip); 166 mHandler = new Handler(Looper.getMainLooper()); 167 mScreenReceiver = new ScreenStateReceiver(); 168 mUserActivityRunnable = () -> { 169 if (mFaceDown) { 170 exitFaceDown(USER_INTERACTION, SystemClock.uptimeMillis() - mLastFlipTime); 171 updateActiveState(); 172 } 173 }; 174 } 175 176 /** Initializes the FaceDownDetector and all necessary listeners. */ systemReady(Context context)177 public void systemReady(Context context) { 178 mContext = context; 179 mSensorManager = context.getSystemService(SensorManager.class); 180 mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 181 readValuesFromDeviceConfig(); 182 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE, 183 ActivityThread.currentApplication().getMainExecutor(), 184 (properties) -> onDeviceConfigChange(properties.getKeyset())); 185 updateActiveState(); 186 } 187 registerScreenReceiver(Context context)188 private void registerScreenReceiver(Context context) { 189 IntentFilter intentFilter = new IntentFilter(); 190 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); 191 intentFilter.addAction(Intent.ACTION_SCREEN_ON); 192 intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 193 context.registerReceiver(mScreenReceiver, intentFilter); 194 } 195 196 /** 197 * Sets the active state of the detector. If false, we will not process accelerometer changes. 198 */ updateActiveState()199 private void updateActiveState() { 200 final long currentTime = SystemClock.uptimeMillis(); 201 final boolean sawRecentInteraction = mPreviousResultType == USER_INTERACTION 202 && currentTime - mPreviousResultTime < mUserInteractionBackoffMillis; 203 final boolean shouldBeActive = mInteractive && mIsEnabled && !sawRecentInteraction; 204 if (mActive != shouldBeActive) { 205 if (shouldBeActive) { 206 mSensorManager.registerListener( 207 this, 208 mAccelerometer, 209 SensorManager.SENSOR_DELAY_NORMAL, 210 mSensorMaxLatencyMicros 211 ); 212 if (mPreviousResultType == SCREEN_OFF_RESULT) { 213 logScreenOff(); 214 } 215 } else { 216 if (mFaceDown && !mInteractive) { 217 mPreviousResultType = SCREEN_OFF_RESULT; 218 mPreviousResultTime = currentTime; 219 } 220 mSensorManager.unregisterListener(this); 221 mFaceDown = false; 222 mOnFlip.accept(false); 223 } 224 mActive = shouldBeActive; 225 if (DEBUG) Slog.d(TAG, "Update active - " + shouldBeActive); 226 } 227 } 228 229 /** Prints state information about FaceDownDetector */ 230 public void dump(PrintWriter pw) { 231 pw.println("FaceDownDetector:"); 232 pw.println(" mFaceDown=" + mFaceDown); 233 pw.println(" mActive=" + mActive); 234 pw.println(" mLastFlipTime=" + mLastFlipTime); 235 pw.println(" mSensorMaxLatencyMicros=" + mSensorMaxLatencyMicros); 236 pw.println(" mUserInteractionBackoffMillis=" + mUserInteractionBackoffMillis); 237 pw.println(" mPreviousResultTime=" + mPreviousResultTime); 238 pw.println(" mPreviousResultType=" + mPreviousResultType); 239 pw.println(" mMillisSaved=" + mMillisSaved); 240 pw.println(" mZAccelerationThreshold=" + mZAccelerationThreshold); 241 pw.println(" mAccelerationThreshold=" + mAccelerationThreshold); 242 pw.println(" mTimeThreshold=" + mTimeThreshold); 243 } 244 245 @Override 246 public void onSensorChanged(SensorEvent event) { 247 if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) return; 248 if (!mActive || !mIsEnabled) return; 249 250 final float x = event.values[0]; 251 final float y = event.values[1]; 252 mCurrentXYAcceleration.updateMovingAverage(x * x + y * y); 253 mCurrentZAcceleration.updateMovingAverage(event.values[2]); 254 255 // Detect movement 256 // If the x, y acceleration is within the acc threshold for at least a length of time longer 257 // than the time threshold, we set moving to true. 258 final long curTime = event.timestamp; 259 if (Math.abs(mCurrentXYAcceleration.mMovingAverage - mPrevAcceleration) 260 > mAccelerationThreshold) { 261 mPrevAcceleration = mCurrentXYAcceleration.mMovingAverage; 262 mPrevAccelerationTime = curTime; 263 } 264 final boolean moving = curTime - mPrevAccelerationTime <= mTimeThreshold.toNanos(); 265 266 // If the z acceleration is beyond the gravity/z-acceleration threshold for at least a 267 // length of time longer than the time threshold, we set isFaceDownForPeriod to true. 268 final float zAccelerationThreshold = 269 mFaceDown ? mZAccelerationThresholdLenient : mZAccelerationThreshold; 270 final boolean isCurrentlyFaceDown = 271 mCurrentZAcceleration.mMovingAverage < zAccelerationThreshold; 272 final boolean isFaceDownForPeriod = isCurrentlyFaceDown 273 && mZAccelerationIsFaceDown 274 && curTime - mZAccelerationFaceDownTime > mTimeThreshold.toNanos(); 275 if (isCurrentlyFaceDown && !mZAccelerationIsFaceDown) { 276 mZAccelerationFaceDownTime = curTime; 277 mZAccelerationIsFaceDown = true; 278 } else if (!isCurrentlyFaceDown) { 279 mZAccelerationIsFaceDown = false; 280 } 281 282 283 if (!moving && isFaceDownForPeriod && !mFaceDown) { 284 faceDownDetected(); 285 } else if (!isFaceDownForPeriod && mFaceDown) { 286 unFlipDetected(); 287 } 288 } 289 290 @Override onAccuracyChanged(Sensor sensor, int accuracy)291 public void onAccuracyChanged(Sensor sensor, int accuracy) {} 292 faceDownDetected()293 private void faceDownDetected() { 294 if (DEBUG) Slog.d(TAG, "Triggered faceDownDetected."); 295 mLastFlipTime = SystemClock.uptimeMillis(); 296 mFaceDown = true; 297 mOnFlip.accept(true); 298 } 299 unFlipDetected()300 private void unFlipDetected() { 301 if (DEBUG) Slog.d(TAG, "Triggered exitFaceDown"); 302 exitFaceDown(UNFLIP, SystemClock.uptimeMillis() - mLastFlipTime); 303 } 304 305 /** 306 * The user interacted with the screen while face down, indicated the phone is in use. 307 * We log this event and temporarily make this detector inactive. 308 */ userActivity(int event)309 public void userActivity(int event) { 310 if (event != PowerManager.USER_ACTIVITY_EVENT_FACE_DOWN) { 311 mHandler.post(mUserActivityRunnable); 312 } 313 } 314 exitFaceDown(int resultType, long millisSinceFlip)315 private void exitFaceDown(int resultType, long millisSinceFlip) { 316 FrameworkStatsLog.write(FrameworkStatsLog.FACE_DOWN_REPORTED, 317 resultType, 318 millisSinceFlip, 319 /* millis_until_normal_timeout= */ 0L, 320 /* millis_until_next_screen_on= */ 0L); 321 mFaceDown = false; 322 mLastFlipTime = 0L; 323 mPreviousResultType = resultType; 324 mPreviousResultTime = SystemClock.uptimeMillis(); 325 mOnFlip.accept(false); 326 } 327 logScreenOff()328 private void logScreenOff() { 329 final long currentTime = SystemClock.uptimeMillis(); 330 FrameworkStatsLog.write(FrameworkStatsLog.FACE_DOWN_REPORTED, 331 SCREEN_OFF_RESULT, 332 /* millis_since_flip= */ mPreviousResultTime - mLastFlipTime, 333 mMillisSaved, 334 /* millis_until_next_screen_on= */ currentTime - mPreviousResultTime); 335 mPreviousResultType = UNKNOWN; 336 } 337 isEnabled()338 private boolean isEnabled() { 339 return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED, 340 DEFAULT_FEATURE_ENABLED) 341 && mContext.getResources().getBoolean( 342 com.android.internal.R.bool.config_flipToScreenOffEnabled); 343 } 344 getAccelerationThreshold()345 private float getAccelerationThreshold() { 346 return getFloatFlagValue(KEY_ACCELERATION_THRESHOLD, 347 DEFAULT_ACCELERATION_THRESHOLD, 348 -2.0f, 349 2.0f); 350 } 351 getZAccelerationThreshold()352 private float getZAccelerationThreshold() { 353 return getFloatFlagValue(KEY_Z_ACCELERATION_THRESHOLD, 354 DEFAULT_Z_ACCELERATION_THRESHOLD, 355 -15.0f, 356 0.0f); 357 } 358 getUserInteractionBackoffMillis()359 private long getUserInteractionBackoffMillis() { 360 return getLongFlagValue(KEY_INTERACTION_BACKOFF, 361 DEFAULT_INTERACTION_BACKOFF, 362 0, 363 3600_000); 364 } 365 getSensorMaxLatencyMicros()366 private int getSensorMaxLatencyMicros() { 367 return mContext.getResources().getInteger( 368 com.android.internal.R.integer.config_flipToScreenOffMaxLatencyMicros); 369 } 370 getFloatFlagValue(String key, float defaultValue, float min, float max)371 private float getFloatFlagValue(String key, float defaultValue, float min, float max) { 372 final float value = DeviceConfig.getFloat(NAMESPACE_ATTENTION_MANAGER_SERVICE, 373 key, 374 defaultValue); 375 376 if (value < min || value > max) { 377 Slog.w(TAG, "Bad flag value supplied for: " + key); 378 return defaultValue; 379 } 380 381 return value; 382 } 383 getLongFlagValue(String key, long defaultValue, long min, long max)384 private long getLongFlagValue(String key, long defaultValue, long min, long max) { 385 final long value = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE, 386 key, 387 defaultValue); 388 389 if (value < min || value > max) { 390 Slog.w(TAG, "Bad flag value supplied for: " + key); 391 return defaultValue; 392 } 393 394 return value; 395 } 396 getTimeThreshold()397 private Duration getTimeThreshold() { 398 final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE, 399 KEY_TIME_THRESHOLD_MILLIS, 400 DEFAULT_TIME_THRESHOLD_MILLIS); 401 402 if (millis < 0 || millis > 15_000) { 403 Slog.w(TAG, "Bad flag value supplied for: " + KEY_TIME_THRESHOLD_MILLIS); 404 return Duration.ofMillis(DEFAULT_TIME_THRESHOLD_MILLIS); 405 } 406 407 return Duration.ofMillis(millis); 408 } 409 onDeviceConfigChange(@onNull Set<String> keys)410 private void onDeviceConfigChange(@NonNull Set<String> keys) { 411 for (String key : keys) { 412 switch (key) { 413 case KEY_ACCELERATION_THRESHOLD: 414 case KEY_Z_ACCELERATION_THRESHOLD: 415 case KEY_TIME_THRESHOLD_MILLIS: 416 case KEY_FEATURE_ENABLED: 417 readValuesFromDeviceConfig(); 418 updateActiveState(); 419 return; 420 default: 421 Slog.i(TAG, "Ignoring change on " + key); 422 } 423 } 424 } 425 readValuesFromDeviceConfig()426 private void readValuesFromDeviceConfig() { 427 mAccelerationThreshold = getAccelerationThreshold(); 428 mZAccelerationThreshold = getZAccelerationThreshold(); 429 mZAccelerationThresholdLenient = mZAccelerationThreshold + 1.0f; 430 mTimeThreshold = getTimeThreshold(); 431 mSensorMaxLatencyMicros = getSensorMaxLatencyMicros(); 432 mUserInteractionBackoffMillis = getUserInteractionBackoffMillis(); 433 final boolean oldEnabled = mIsEnabled; 434 mIsEnabled = isEnabled(); 435 if (oldEnabled != mIsEnabled) { 436 if (!mIsEnabled) { 437 mContext.unregisterReceiver(mScreenReceiver); 438 mInteractive = false; 439 } else { 440 registerScreenReceiver(mContext); 441 mInteractive = mContext.getSystemService(PowerManager.class).isInteractive(); 442 } 443 } 444 445 Slog.i(TAG, "readValuesFromDeviceConfig():" 446 + "\nmAccelerationThreshold=" + mAccelerationThreshold 447 + "\nmZAccelerationThreshold=" + mZAccelerationThreshold 448 + "\nmTimeThreshold=" + mTimeThreshold 449 + "\nmIsEnabled=" + mIsEnabled); 450 } 451 452 /** 453 * Sets how much screen on time might be saved as a result of this detector. Currently used for 454 * logging purposes. 455 */ setMillisSaved(long millisSaved)456 public void setMillisSaved(long millisSaved) { 457 mMillisSaved = millisSaved; 458 } 459 460 private final class ScreenStateReceiver extends BroadcastReceiver { 461 @Override onReceive(Context context, Intent intent)462 public void onReceive(Context context, Intent intent) { 463 if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { 464 mInteractive = false; 465 updateActiveState(); 466 } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { 467 mInteractive = true; 468 updateActiveState(); 469 } 470 } 471 } 472 473 private final class ExponentialMovingAverage { 474 private final float mAlpha; 475 private final float mInitialAverage; 476 private float mMovingAverage; 477 ExponentialMovingAverage(float alpha)478 ExponentialMovingAverage(float alpha) { 479 this(alpha, 0.0f); 480 } 481 ExponentialMovingAverage(float alpha, float initialAverage)482 ExponentialMovingAverage(float alpha, float initialAverage) { 483 this.mAlpha = alpha; 484 this.mInitialAverage = initialAverage; 485 this.mMovingAverage = initialAverage; 486 } 487 updateMovingAverage(float newValue)488 void updateMovingAverage(float newValue) { 489 mMovingAverage = newValue + mAlpha * (mMovingAverage - newValue); 490 } 491 reset()492 void reset() { 493 mMovingAverage = this.mInitialAverage; 494 } 495 } 496 } 497