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