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