• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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