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