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