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