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