1 /*
2  * Copyright (C) 2020 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.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
20 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
21 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
22 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
23 import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
24 
25 import android.annotation.NonNull;
26 import android.content.Context;
27 import android.os.PowerManager;
28 import android.os.SystemClock;
29 import android.provider.DeviceConfig;
30 import android.util.Slog;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.util.FrameworkStatsLog;
34 
35 import java.util.Set;
36 import java.util.concurrent.TimeUnit;
37 
38 /**
39  * Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen
40  * on temporarily (without changing the screen timeout setting).
41  */
42 public class ScreenUndimDetector {
43     private static final String TAG = "ScreenUndimDetector";
44     private static final boolean DEBUG = false;
45 
46     private static final String UNDIM_DETECTOR_WAKE_LOCK = "UndimDetectorWakeLock";
47 
48     /** DeviceConfig flag: is keep screen on feature enabled. */
49     static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled";
50     private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true;
51     private static final int OUTCOME_POWER_BUTTON =
52             FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON;
53     private static final int OUTCOME_TIMEOUT =
54             FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__TIMEOUT;
55     private boolean mKeepScreenOnEnabled;
56 
57     /** DeviceConfig flag: how long should we keep the screen on. */
58     @VisibleForTesting
59     static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis";
60     @VisibleForTesting
61     static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10);
62     private long mKeepScreenOnForMillis;
63 
64     /** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */
65     @VisibleForTesting
66     static final String KEY_UNDIMS_REQUIRED = "undims_required";
67     @VisibleForTesting
68     static final int DEFAULT_UNDIMS_REQUIRED = 2;
69     private int mUndimsRequired;
70 
71     /**
72      * DeviceConfig flag: what is the maximum duration between undims to still consider them
73      * consecutive.
74      */
75     @VisibleForTesting
76     static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS =
77             "max_duration_between_undims_millis";
78     @VisibleForTesting
79     static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5);
80     private long mMaxDurationBetweenUndimsMillis;
81 
82     @VisibleForTesting
83     PowerManager.WakeLock mWakeLock;
84 
85     @VisibleForTesting
86     int mCurrentScreenPolicy;
87     @VisibleForTesting
88     int mUndimCounter = 0;
89     @VisibleForTesting
90     long mUndimCounterStartedMillis;
91     private long mUndimOccurredTime = -1;
92     private long mInteractionAfterUndimTime = -1;
93     private InternalClock mClock;
94 
ScreenUndimDetector()95     public ScreenUndimDetector() {
96         mClock = new InternalClock();
97     }
98 
ScreenUndimDetector(InternalClock clock)99     ScreenUndimDetector(InternalClock clock) {
100         mClock = clock;
101     }
102 
103     static class InternalClock {
getCurrentTime()104         public long getCurrentTime() {
105             return SystemClock.elapsedRealtime();
106         }
107     }
108 
109     /** Should be called in parent's systemReady() */
systemReady(Context context)110     public void systemReady(Context context) {
111         readValuesFromDeviceConfig();
112         DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
113                 context.getMainExecutor(),
114                 (properties) -> onDeviceConfigChange(properties.getKeyset()));
115 
116         final PowerManager powerManager = context.getSystemService(PowerManager.class);
117         mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
118                         | PowerManager.ON_AFTER_RELEASE,
119                 UNDIM_DETECTOR_WAKE_LOCK);
120     }
121 
122     /**
123      * Launches a message that figures out the screen transitions and detects user undims. Must be
124      * called by the parent that is trying to update the screen policy.
125      */
recordScreenPolicy(int newPolicy)126     public void recordScreenPolicy(int newPolicy) {
127         if (newPolicy == mCurrentScreenPolicy) {
128             return;
129         }
130 
131         if (DEBUG) {
132             Slog.d(TAG,
133                     "Screen policy transition: " + mCurrentScreenPolicy + " -> " + newPolicy);
134         }
135 
136         // update the current policy with the new one immediately so we don't accidentally get
137         // into a loop (which is possible if the switch below triggers a new policy).
138         final int currentPolicy = mCurrentScreenPolicy;
139         mCurrentScreenPolicy = newPolicy;
140 
141         if (!mKeepScreenOnEnabled) {
142             return;
143         }
144 
145         switch (currentPolicy) {
146             case POLICY_DIM:
147                 if (newPolicy == POLICY_BRIGHT) {
148                     final long now = mClock.getCurrentTime();
149                     final long timeElapsedSinceFirstUndim = now - mUndimCounterStartedMillis;
150                     if (timeElapsedSinceFirstUndim >= mMaxDurationBetweenUndimsMillis) {
151                         reset();
152                     }
153                     if (mUndimCounter == 0) {
154                         mUndimCounterStartedMillis = now;
155                     }
156 
157                     mUndimCounter++;
158 
159                     if (DEBUG) {
160                         Slog.d(TAG, "User undim, counter=" + mUndimCounter
161                                 + " (required=" + mUndimsRequired + ")"
162                                 + ", timeElapsedSinceFirstUndim=" + timeElapsedSinceFirstUndim
163                                 + " (max=" + mMaxDurationBetweenUndimsMillis + ")");
164                     }
165                     if (mUndimCounter >= mUndimsRequired) {
166                         reset();
167                         if (DEBUG) {
168                             Slog.d(TAG, "Acquiring a wake lock for " + mKeepScreenOnForMillis);
169                         }
170                         if (mWakeLock != null) {
171                             mUndimOccurredTime = mClock.getCurrentTime();
172                             mWakeLock.acquire(mKeepScreenOnForMillis);
173                         }
174                     }
175                 } else {
176                     if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
177                         checkAndLogUndim(OUTCOME_TIMEOUT);
178                     }
179                     reset();
180                 }
181                 break;
182             case POLICY_BRIGHT:
183                 if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
184                     checkAndLogUndim(OUTCOME_POWER_BUTTON);
185                 }
186                 if (newPolicy != POLICY_DIM) {
187                     reset();
188                 }
189                 break;
190         }
191     }
192 
193     @VisibleForTesting
reset()194     void reset() {
195         if (DEBUG) {
196             Slog.d(TAG, "Resetting the undim detector");
197         }
198         mUndimCounter = 0;
199         mUndimCounterStartedMillis = 0;
200         if (mWakeLock != null && mWakeLock.isHeld()) {
201             mWakeLock.release();
202         }
203     }
204 
readKeepScreenOnNotificationEnabled()205     private boolean readKeepScreenOnNotificationEnabled() {
206         return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
207                 KEY_KEEP_SCREEN_ON_ENABLED,
208                 DEFAULT_KEEP_SCREEN_ON_ENABLED);
209     }
210 
readKeepScreenOnForMillis()211     private long readKeepScreenOnForMillis() {
212         return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
213                 KEY_KEEP_SCREEN_ON_FOR_MILLIS,
214                 DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS);
215     }
216 
readUndimsRequired()217     private int readUndimsRequired() {
218         int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE,
219                 KEY_UNDIMS_REQUIRED,
220                 DEFAULT_UNDIMS_REQUIRED);
221 
222         if (undimsRequired < 1 || undimsRequired > 5) {
223             Slog.e(TAG, "Provided undimsRequired=" + undimsRequired
224                     + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED);
225             return DEFAULT_UNDIMS_REQUIRED;
226         }
227 
228         return undimsRequired;
229     }
230 
readMaxDurationBetweenUndimsMillis()231     private long readMaxDurationBetweenUndimsMillis() {
232         return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
233                 KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
234                 DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS);
235     }
236 
onDeviceConfigChange(@onNull Set<String> keys)237     private void onDeviceConfigChange(@NonNull Set<String> keys) {
238         for (String key : keys) {
239             Slog.i(TAG, "onDeviceConfigChange; key=" + key);
240             switch (key) {
241                 case KEY_KEEP_SCREEN_ON_ENABLED:
242                 case KEY_KEEP_SCREEN_ON_FOR_MILLIS:
243                 case KEY_UNDIMS_REQUIRED:
244                 case KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS:
245                     readValuesFromDeviceConfig();
246                     return;
247                 default:
248                     Slog.i(TAG, "Ignoring change on " + key);
249             }
250         }
251     }
252 
253     @VisibleForTesting
readValuesFromDeviceConfig()254     void readValuesFromDeviceConfig() {
255         mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled();
256         mKeepScreenOnForMillis = readKeepScreenOnForMillis();
257         mUndimsRequired = readUndimsRequired();
258         mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis();
259 
260         Slog.i(TAG, "readValuesFromDeviceConfig():"
261                 + "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis
262                 + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled
263                 + "\nmUndimsRequired=" + mUndimsRequired);
264 
265     }
266 
267     /**
268      * The user interacted with the screen after an undim, indicating the phone is in use.
269      * We use this event for logging.
270      */
userActivity()271     public void userActivity() {
272         if (mUndimOccurredTime != 1 && mInteractionAfterUndimTime == -1) {
273             mInteractionAfterUndimTime = mClock.getCurrentTime();
274         }
275     }
276 
277     /**
278      * Checks and logs if an undim occurred.
279      *
280      * A log will occur if an undim seems to have resulted in a timeout or a direct screen off such
281      * as from a power button. Some outcomes may not be correctly assigned to a
282      * TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME value.
283      */
checkAndLogUndim(int outcome)284     private void checkAndLogUndim(int outcome) {
285         if (mUndimOccurredTime != -1) {
286             long now = mClock.getCurrentTime();
287             FrameworkStatsLog.write(FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED,
288                     outcome,
289                     /* time_to_outcome_millis=*/  now - mUndimOccurredTime,
290                     /* time_to_first_interaction_millis= */
291                     mInteractionAfterUndimTime != -1 ? now - mInteractionAfterUndimTime : -1
292             );
293             mUndimOccurredTime = -1;
294             mInteractionAfterUndimTime = -1;
295         }
296     }
297 }
298