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