1 /* 2 * Copyright (C) 2018 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 package com.android.server.power.batterysaver; 17 18 import static com.android.server.power.batterysaver.BatterySaverController.reasonToString; 19 20 import android.annotation.NonNull; 21 import android.annotation.StringRes; 22 import android.app.Notification; 23 import android.app.NotificationChannel; 24 import android.app.NotificationManager; 25 import android.app.PendingIntent; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.res.Resources; 30 import android.database.ContentObserver; 31 import android.os.BatterySaverPolicyConfig; 32 import android.os.Handler; 33 import android.os.PowerManager; 34 import android.os.SystemClock; 35 import android.os.UserHandle; 36 import android.provider.Settings; 37 import android.util.Slog; 38 import android.util.proto.ProtoOutputStream; 39 40 import com.android.internal.R; 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.os.BackgroundThread; 44 import com.android.server.EventLogTags; 45 import com.android.server.power.BatterySaverStateMachineProto; 46 47 import java.io.PrintWriter; 48 import java.text.NumberFormat; 49 50 /** 51 * Decides when to enable / disable battery saver. 52 * 53 * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy. 54 * Do not call out with the lock held. (Settings provider is okay.) 55 * 56 * Test: atest com.android.server.power.batterysaver.BatterySaverStateMachineTest 57 * 58 * Current state machine. This can be visualized using Graphviz: 59 <pre> 60 61 digraph { 62 STATE_OFF 63 STATE_MANUAL_ON [label="STATE_MANUAL_ON\nTurned on manually by the user"] 64 STATE_AUTOMATIC_ON [label="STATE_AUTOMATIC_ON\nTurned on automatically by the system"] 65 STATE_OFF_AUTOMATIC_SNOOZED [ 66 label="STATE_OFF_AUTOMATIC_SNOOZED\nTurned off manually by the user." 67 + " The system should not turn it back on automatically." 68 ] 69 STATE_PENDING_STICKY_ON [ 70 label="STATE_PENDING_STICKY_ON\n" 71 + " Turned on manually by the user and then plugged in. Will turn back on after unplug." 72 ] 73 74 STATE_OFF -> STATE_MANUAL_ON [label="manual"] 75 STATE_OFF -> STATE_AUTOMATIC_ON [label="Auto on AND charge <= auto threshold"] 76 77 STATE_MANUAL_ON -> STATE_OFF [label="manual\nOR\nPlugged & sticky disabled"] 78 STATE_MANUAL_ON -> STATE_PENDING_STICKY_ON [label="Plugged & sticky enabled"] 79 80 STATE_PENDING_STICKY_ON -> STATE_MANUAL_ON [label="Unplugged & sticky enabled"] 81 STATE_PENDING_STICKY_ON -> STATE_OFF [ 82 label="Sticky disabled\nOR\nSticky auto off enabled AND charge >= sticky auto off threshold" 83 ] 84 85 STATE_AUTOMATIC_ON -> STATE_OFF [label="Plugged"] 86 STATE_AUTOMATIC_ON -> STATE_OFF_AUTOMATIC_SNOOZED [label="Manual"] 87 88 STATE_OFF_AUTOMATIC_SNOOZED -> STATE_OFF [label="Plug\nOR\nCharge > auto threshold"] 89 STATE_OFF_AUTOMATIC_SNOOZED -> STATE_MANUAL_ON [label="manual"] 90 91 </pre> 92 } 93 */ 94 public class BatterySaverStateMachine { 95 private static final String TAG = "BatterySaverStateMachine"; 96 private static final String DYNAMIC_MODE_NOTIF_CHANNEL_ID = "dynamic_mode_notification"; 97 private static final String BATTERY_SAVER_NOTIF_CHANNEL_ID = "battery_saver_channel"; 98 private static final int DYNAMIC_MODE_NOTIFICATION_ID = 1992; 99 private static final int STICKY_AUTO_DISABLED_NOTIFICATION_ID = 1993; 100 private final Object mLock; 101 102 private static final boolean DEBUG = BatterySaverPolicy.DEBUG; 103 104 private static final long ADAPTIVE_CHANGE_TIMEOUT_MS = 24 * 60 * 60 * 1000L; 105 106 /** Turn off adaptive battery saver if the device has charged above this level. */ 107 private static final int ADAPTIVE_AUTO_DISABLE_BATTERY_LEVEL = 80; 108 109 private static final int STATE_OFF = BatterySaverStateMachineProto.STATE_OFF; 110 111 /** Turned on manually by the user. */ 112 private static final int STATE_MANUAL_ON = BatterySaverStateMachineProto.STATE_MANUAL_ON; 113 114 /** Turned on automatically by the system. */ 115 private static final int STATE_AUTOMATIC_ON = BatterySaverStateMachineProto.STATE_AUTOMATIC_ON; 116 117 /** Turned off manually by the user. The system should not turn it back on automatically. */ 118 private static final int STATE_OFF_AUTOMATIC_SNOOZED = 119 BatterySaverStateMachineProto.STATE_OFF_AUTOMATIC_SNOOZED; 120 121 /** Turned on manually by the user and then plugged in. Will turn back on after unplug. */ 122 private static final int STATE_PENDING_STICKY_ON = 123 BatterySaverStateMachineProto.STATE_PENDING_STICKY_ON; 124 125 private final Context mContext; 126 private final BatterySaverController mBatterySaverController; 127 128 /** Whether the system has booted. */ 129 @GuardedBy("mLock") 130 private boolean mBootCompleted; 131 132 /** Whether global settings have been loaded already. */ 133 @GuardedBy("mLock") 134 private boolean mSettingsLoaded; 135 136 /** Whether the first battery status has arrived. */ 137 @GuardedBy("mLock") 138 private boolean mBatteryStatusSet; 139 140 @GuardedBy("mLock") 141 private int mState; 142 143 /** Whether the device is connected to any power source. */ 144 @GuardedBy("mLock") 145 private boolean mIsPowered; 146 147 /** Current battery level in %, 0-100. (Currently only used in dumpsys.) */ 148 @GuardedBy("mLock") 149 private int mBatteryLevel; 150 151 /** Whether the battery level is considered to be "low" or not. */ 152 @GuardedBy("mLock") 153 private boolean mIsBatteryLevelLow; 154 155 /** Previously known value of Settings.Global.LOW_POWER_MODE. */ 156 @GuardedBy("mLock") 157 private boolean mSettingBatterySaverEnabled; 158 159 /** Previously known value of Settings.Global.LOW_POWER_MODE_STICKY. */ 160 @GuardedBy("mLock") 161 private boolean mSettingBatterySaverEnabledSticky; 162 163 /** Config flag to track if battery saver's sticky behaviour is disabled. */ 164 private final boolean mBatterySaverStickyBehaviourDisabled; 165 166 /** 167 * Whether or not to end sticky battery saver upon reaching a level specified by 168 * {@link #mSettingBatterySaverStickyAutoDisableThreshold}. 169 */ 170 @GuardedBy("mLock") 171 private boolean mSettingBatterySaverStickyAutoDisableEnabled; 172 173 /** 174 * The battery level at which to end sticky battery saver. Only useful if 175 * {@link #mSettingBatterySaverStickyAutoDisableEnabled} is {@code true}. 176 */ 177 @GuardedBy("mLock") 178 private int mSettingBatterySaverStickyAutoDisableThreshold; 179 180 /** Config flag to track default disable threshold for Dynamic Power Savings enabled battery 181 * saver. */ 182 @GuardedBy("mLock") 183 private final int mDynamicPowerSavingsDefaultDisableThreshold; 184 185 /** 186 * Previously known value of Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL. 187 * (Currently only used in dumpsys.) 188 */ 189 @GuardedBy("mLock") 190 private int mSettingBatterySaverTriggerThreshold; 191 192 /** Previously known value of Settings.Global.AUTOMATIC_POWER_SAVE_MODE. */ 193 @GuardedBy("mLock") 194 private int mSettingAutomaticBatterySaver; 195 196 /** When to disable battery saver again if it was enabled due to an external suggestion. 197 * Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD. 198 */ 199 @GuardedBy("mLock") 200 private int mDynamicPowerSavingsDisableThreshold; 201 202 /** 203 * Whether we've received a suggestion that battery saver should be on from an external app. 204 * Updates when Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED changes. 205 */ 206 @GuardedBy("mLock") 207 private boolean mDynamicPowerSavingsBatterySaver; 208 209 /** 210 * Last reason passed to {@link #enableBatterySaverLocked}. 211 */ 212 @GuardedBy("mLock") 213 private int mLastChangedIntReason; 214 215 /** 216 * Last reason passed to {@link #enableBatterySaverLocked}. 217 */ 218 @GuardedBy("mLock") 219 private String mLastChangedStrReason; 220 221 /** 222 * The last time adaptive battery saver was changed by an external service, using elapsed 223 * realtime as the timebase. 224 */ 225 @GuardedBy("mLock") 226 private long mLastAdaptiveBatterySaverChangedExternallyElapsed; 227 228 private final ContentObserver mSettingsObserver = new ContentObserver(null) { 229 @Override 230 public void onChange(boolean selfChange) { 231 synchronized (mLock) { 232 refreshSettingsLocked(); 233 } 234 } 235 }; 236 BatterySaverStateMachine(Object lock, Context context, BatterySaverController batterySaverController)237 public BatterySaverStateMachine(Object lock, 238 Context context, BatterySaverController batterySaverController) { 239 mLock = lock; 240 mContext = context; 241 mBatterySaverController = batterySaverController; 242 mState = STATE_OFF; 243 244 mBatterySaverStickyBehaviourDisabled = mContext.getResources().getBoolean( 245 com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled); 246 mDynamicPowerSavingsDefaultDisableThreshold = mContext.getResources().getInteger( 247 com.android.internal.R.integer.config_dynamicPowerSavingsDefaultDisableThreshold); 248 } 249 250 /** @return true if the automatic percentage based mode should be used */ isAutomaticModeActiveLocked()251 private boolean isAutomaticModeActiveLocked() { 252 return mSettingAutomaticBatterySaver == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE 253 && mSettingBatterySaverTriggerThreshold > 0; 254 } 255 256 /** 257 * The returned value won't necessarily make sense if {@link #isAutomaticModeActiveLocked()} 258 * returns {@code false}. 259 * 260 * @return true if the battery level is below automatic's threshold. 261 */ isInAutomaticLowZoneLocked()262 private boolean isInAutomaticLowZoneLocked() { 263 return mIsBatteryLevelLow; 264 } 265 266 /** @return true if the dynamic mode should be used */ isDynamicModeActiveLocked()267 private boolean isDynamicModeActiveLocked() { 268 return mSettingAutomaticBatterySaver == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC 269 && mDynamicPowerSavingsBatterySaver; 270 } 271 272 /** 273 * The returned value won't necessarily make sense if {@link #isDynamicModeActiveLocked()} 274 * returns {@code false}. 275 * 276 * @return true if the battery level is below dynamic's threshold. 277 */ isInDynamicLowZoneLocked()278 private boolean isInDynamicLowZoneLocked() { 279 return mBatteryLevel <= mDynamicPowerSavingsDisableThreshold; 280 } 281 282 /** 283 * {@link com.android.server.power.PowerManagerService} calls it when the system is booted. 284 */ onBootCompleted()285 public void onBootCompleted() { 286 if (DEBUG) { 287 Slog.d(TAG, "onBootCompleted"); 288 } 289 // Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it. 290 putGlobalSetting(Settings.Global.LOW_POWER_MODE, 0); 291 292 // This is called with the power manager lock held. Don't do anything that may call to 293 // upper services. (e.g. don't call into AM directly) 294 // So use a BG thread. 295 runOnBgThread(() -> { 296 297 final ContentResolver cr = mContext.getContentResolver(); 298 cr.registerContentObserver(Settings.Global.getUriFor( 299 Settings.Global.LOW_POWER_MODE), 300 false, mSettingsObserver, UserHandle.USER_SYSTEM); 301 cr.registerContentObserver(Settings.Global.getUriFor( 302 Settings.Global.LOW_POWER_MODE_STICKY), 303 false, mSettingsObserver, UserHandle.USER_SYSTEM); 304 cr.registerContentObserver(Settings.Global.getUriFor( 305 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), 306 false, mSettingsObserver, UserHandle.USER_SYSTEM); 307 cr.registerContentObserver(Settings.Global.getUriFor( 308 Settings.Global.AUTOMATIC_POWER_SAVE_MODE), 309 false, mSettingsObserver, UserHandle.USER_SYSTEM); 310 cr.registerContentObserver(Settings.Global.getUriFor( 311 Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED), 312 false, mSettingsObserver, UserHandle.USER_SYSTEM); 313 cr.registerContentObserver(Settings.Global.getUriFor( 314 Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD), 315 false, mSettingsObserver, UserHandle.USER_SYSTEM); 316 cr.registerContentObserver(Settings.Global.getUriFor( 317 Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED), 318 false, mSettingsObserver, UserHandle.USER_SYSTEM); 319 cr.registerContentObserver(Settings.Global.getUriFor( 320 Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL), 321 false, mSettingsObserver, UserHandle.USER_SYSTEM); 322 323 324 synchronized (mLock) { 325 final boolean lowPowerModeEnabledSticky = getGlobalSetting( 326 Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0; 327 328 if (lowPowerModeEnabledSticky) { 329 mState = STATE_PENDING_STICKY_ON; 330 } 331 332 mBootCompleted = true; 333 334 refreshSettingsLocked(); 335 336 doAutoBatterySaverLocked(); 337 } 338 }); 339 } 340 341 /** 342 * Run a {@link Runnable} on a background handler. 343 */ 344 @VisibleForTesting runOnBgThread(Runnable r)345 void runOnBgThread(Runnable r) { 346 BackgroundThread.getHandler().post(r); 347 } 348 349 /** 350 * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable} is 351 * already registered, it'll be first removed before being re-posted. 352 */ 353 @VisibleForTesting runOnBgThreadLazy(Runnable r, int delayMillis)354 void runOnBgThreadLazy(Runnable r, int delayMillis) { 355 final Handler h = BackgroundThread.getHandler(); 356 h.removeCallbacks(r); 357 h.postDelayed(r, delayMillis); 358 } 359 360 @GuardedBy("mLock") refreshSettingsLocked()361 private void refreshSettingsLocked() { 362 final boolean lowPowerModeEnabled = getGlobalSetting( 363 Settings.Global.LOW_POWER_MODE, 0) != 0; 364 final boolean lowPowerModeEnabledSticky = getGlobalSetting( 365 Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0; 366 final boolean dynamicPowerSavingsBatterySaver = getGlobalSetting( 367 Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0) != 0; 368 final int lowPowerModeTriggerLevel = getGlobalSetting( 369 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); 370 final int automaticBatterySaverMode = getGlobalSetting( 371 Settings.Global.AUTOMATIC_POWER_SAVE_MODE, 372 PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); 373 final int dynamicPowerSavingsDisableThreshold = getGlobalSetting( 374 Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 375 mDynamicPowerSavingsDefaultDisableThreshold); 376 final boolean isStickyAutoDisableEnabled = getGlobalSetting( 377 Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1) != 0; 378 final int stickyAutoDisableThreshold = getGlobalSetting( 379 Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90); 380 381 setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky, 382 lowPowerModeTriggerLevel, 383 isStickyAutoDisableEnabled, stickyAutoDisableThreshold, 384 automaticBatterySaverMode, 385 dynamicPowerSavingsBatterySaver, dynamicPowerSavingsDisableThreshold); 386 } 387 388 /** 389 * {@link com.android.server.power.PowerManagerService} calls it when relevant global settings 390 * have changed. 391 * 392 * Note this will be called before {@link #onBootCompleted} too. 393 */ 394 @GuardedBy("mLock") 395 @VisibleForTesting setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky, int batterySaverTriggerThreshold, boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold, int automaticBatterySaver, boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold)396 void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky, 397 int batterySaverTriggerThreshold, 398 boolean isStickyAutoDisableEnabled, int stickyAutoDisableThreshold, 399 int automaticBatterySaver, 400 boolean dynamicPowerSavingsBatterySaver, int dynamicPowerSavingsDisableThreshold) { 401 if (DEBUG) { 402 Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled 403 + " sticky=" + batterySaverEnabledSticky 404 + " threshold=" + batterySaverTriggerThreshold 405 + " stickyAutoDisableEnabled=" + isStickyAutoDisableEnabled 406 + " stickyAutoDisableThreshold=" + stickyAutoDisableThreshold 407 + " automaticBatterySaver=" + automaticBatterySaver 408 + " dynamicPowerSavingsBatterySaver=" + dynamicPowerSavingsBatterySaver 409 + " dynamicPowerSavingsDisableThreshold=" 410 + dynamicPowerSavingsDisableThreshold); 411 } 412 413 mSettingsLoaded = true; 414 415 // Set sensible limits. 416 stickyAutoDisableThreshold = Math.max(stickyAutoDisableThreshold, 417 batterySaverTriggerThreshold); 418 419 final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled; 420 final boolean stickyChanged = 421 mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky; 422 final boolean thresholdChanged 423 = mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold; 424 final boolean stickyAutoDisableEnabledChanged = 425 mSettingBatterySaverStickyAutoDisableEnabled != isStickyAutoDisableEnabled; 426 final boolean stickyAutoDisableThresholdChanged = 427 mSettingBatterySaverStickyAutoDisableThreshold != stickyAutoDisableThreshold; 428 final boolean automaticModeChanged = mSettingAutomaticBatterySaver != automaticBatterySaver; 429 final boolean dynamicPowerSavingsThresholdChanged = 430 mDynamicPowerSavingsDisableThreshold != dynamicPowerSavingsDisableThreshold; 431 final boolean dynamicPowerSavingsBatterySaverChanged = 432 mDynamicPowerSavingsBatterySaver != dynamicPowerSavingsBatterySaver; 433 434 if (!(enabledChanged || stickyChanged || thresholdChanged || automaticModeChanged 435 || stickyAutoDisableEnabledChanged || stickyAutoDisableThresholdChanged 436 || dynamicPowerSavingsThresholdChanged || dynamicPowerSavingsBatterySaverChanged)) { 437 return; 438 } 439 440 mSettingBatterySaverEnabled = batterySaverEnabled; 441 mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky; 442 mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold; 443 mSettingBatterySaverStickyAutoDisableEnabled = isStickyAutoDisableEnabled; 444 mSettingBatterySaverStickyAutoDisableThreshold = stickyAutoDisableThreshold; 445 mSettingAutomaticBatterySaver = automaticBatterySaver; 446 mDynamicPowerSavingsDisableThreshold = dynamicPowerSavingsDisableThreshold; 447 mDynamicPowerSavingsBatterySaver = dynamicPowerSavingsBatterySaver; 448 449 if (thresholdChanged) { 450 // To avoid spamming the event log, we throttle logging here. 451 runOnBgThreadLazy(mThresholdChangeLogger, 2000); 452 } 453 454 if (!mSettingBatterySaverStickyAutoDisableEnabled) { 455 hideStickyDisabledNotification(); 456 } 457 458 if (enabledChanged) { 459 final String reason = batterySaverEnabled 460 ? "Global.low_power changed to 1" : "Global.low_power changed to 0"; 461 enableBatterySaverLocked(/*enable=*/ batterySaverEnabled, /*manual=*/ true, 462 BatterySaverController.REASON_SETTING_CHANGED, reason); 463 } else { 464 doAutoBatterySaverLocked(); 465 } 466 } 467 468 private final Runnable mThresholdChangeLogger = () -> { 469 EventLogTags.writeBatterySaverSetting(mSettingBatterySaverTriggerThreshold); 470 }; 471 472 /** 473 * {@link com.android.server.power.PowerManagerService} calls it when battery state changes. 474 * 475 * Note this may be called before {@link #onBootCompleted} too. 476 */ setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow)477 public void setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow) { 478 if (DEBUG) { 479 Slog.d(TAG, "setBatteryStatus: powered=" + newPowered + " level=" + newLevel 480 + " low=" + newBatteryLevelLow); 481 } 482 synchronized (mLock) { 483 mBatteryStatusSet = true; 484 485 final boolean poweredChanged = mIsPowered != newPowered; 486 final boolean levelChanged = mBatteryLevel != newLevel; 487 final boolean lowChanged = mIsBatteryLevelLow != newBatteryLevelLow; 488 489 if (!(poweredChanged || levelChanged || lowChanged)) { 490 return; 491 } 492 493 mIsPowered = newPowered; 494 mBatteryLevel = newLevel; 495 mIsBatteryLevelLow = newBatteryLevelLow; 496 497 doAutoBatterySaverLocked(); 498 } 499 } 500 501 /** 502 * Enable or disable the current adaptive battery saver policy. This may not change what's in 503 * effect if full battery saver is also enabled. 504 */ setAdaptiveBatterySaverEnabled(boolean enabled)505 public boolean setAdaptiveBatterySaverEnabled(boolean enabled) { 506 if (DEBUG) { 507 Slog.d(TAG, "setAdaptiveBatterySaverEnabled: enabled=" + enabled); 508 } 509 synchronized (mLock) { 510 mLastAdaptiveBatterySaverChangedExternallyElapsed = SystemClock.elapsedRealtime(); 511 return mBatterySaverController.setAdaptivePolicyEnabledLocked( 512 enabled, BatterySaverController.REASON_ADAPTIVE_DYNAMIC_POWER_SAVINGS_CHANGED); 513 } 514 } 515 516 /** 517 * Change the adaptive battery saver policy. 518 */ setAdaptiveBatterySaverPolicy(BatterySaverPolicyConfig config)519 public boolean setAdaptiveBatterySaverPolicy(BatterySaverPolicyConfig config) { 520 if (DEBUG) { 521 Slog.d(TAG, "setAdaptiveBatterySaverPolicy: config=" + config); 522 } 523 524 synchronized (mLock) { 525 mLastAdaptiveBatterySaverChangedExternallyElapsed = SystemClock.elapsedRealtime(); 526 return mBatterySaverController.setAdaptivePolicyLocked(config, 527 BatterySaverController.REASON_ADAPTIVE_DYNAMIC_POWER_SAVINGS_CHANGED); 528 } 529 } 530 531 /** 532 * Decide whether to auto-start / stop battery saver. 533 */ 534 @GuardedBy("mLock") doAutoBatterySaverLocked()535 private void doAutoBatterySaverLocked() { 536 if (DEBUG) { 537 Slog.d(TAG, "doAutoBatterySaverLocked: mBootCompleted=" + mBootCompleted 538 + " mSettingsLoaded=" + mSettingsLoaded 539 + " mBatteryStatusSet=" + mBatteryStatusSet 540 + " mState=" + mState 541 + " mIsBatteryLevelLow=" + mIsBatteryLevelLow 542 + " mIsPowered=" + mIsPowered 543 + " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver 544 + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky 545 + " mSettingBatterySaverStickyAutoDisableEnabled=" 546 + mSettingBatterySaverStickyAutoDisableEnabled); 547 } 548 if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) { 549 return; // Not fully initialized yet. 550 } 551 552 updateStateLocked(false, false); 553 554 // Adaptive control. 555 if (SystemClock.elapsedRealtime() - mLastAdaptiveBatterySaverChangedExternallyElapsed 556 > ADAPTIVE_CHANGE_TIMEOUT_MS) { 557 mBatterySaverController.setAdaptivePolicyEnabledLocked( 558 false, BatterySaverController.REASON_TIMEOUT); 559 mBatterySaverController.resetAdaptivePolicyLocked( 560 BatterySaverController.REASON_TIMEOUT); 561 } else if (mIsPowered && mBatteryLevel >= ADAPTIVE_AUTO_DISABLE_BATTERY_LEVEL) { 562 mBatterySaverController.setAdaptivePolicyEnabledLocked(false, 563 BatterySaverController.REASON_PLUGGED_IN); 564 } 565 } 566 567 /** 568 * Update the state machine based on the current settings and battery/charge status. 569 * 570 * @param manual Whether the change was made by the user. 571 * @param enable Whether the user wants to turn battery saver on or off. Is only used if {@param 572 * manual} is true. 573 */ 574 @GuardedBy("mLock") updateStateLocked(boolean manual, boolean enable)575 private void updateStateLocked(boolean manual, boolean enable) { 576 if (!manual && !(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) { 577 return; // Not fully initialized yet. 578 } 579 580 switch (mState) { 581 case STATE_OFF: { 582 if (!mIsPowered) { 583 if (manual) { 584 if (!enable) { 585 Slog.e(TAG, "Tried to disable BS when it's already OFF"); 586 return; 587 } 588 enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, 589 BatterySaverController.REASON_MANUAL_ON); 590 hideStickyDisabledNotification(); 591 mState = STATE_MANUAL_ON; 592 } else if (isAutomaticModeActiveLocked() && isInAutomaticLowZoneLocked()) { 593 enableBatterySaverLocked(/*enable*/ true, /*manual*/ false, 594 BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_ON); 595 hideStickyDisabledNotification(); 596 mState = STATE_AUTOMATIC_ON; 597 } else if (isDynamicModeActiveLocked() && isInDynamicLowZoneLocked()) { 598 enableBatterySaverLocked(/*enable*/ true, /*manual*/ false, 599 BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON); 600 hideStickyDisabledNotification(); 601 mState = STATE_AUTOMATIC_ON; 602 } 603 } 604 break; 605 } 606 607 case STATE_MANUAL_ON: { 608 if (manual) { 609 if (enable) { 610 Slog.e(TAG, "Tried to enable BS when it's already MANUAL_ON"); 611 return; 612 } 613 enableBatterySaverLocked(/*enable*/ false, /*manual*/ true, 614 BatterySaverController.REASON_MANUAL_OFF); 615 mState = STATE_OFF; 616 } else if (mIsPowered) { 617 enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, 618 BatterySaverController.REASON_PLUGGED_IN); 619 if (mSettingBatterySaverEnabledSticky 620 && !mBatterySaverStickyBehaviourDisabled) { 621 mState = STATE_PENDING_STICKY_ON; 622 } else { 623 mState = STATE_OFF; 624 } 625 } 626 break; 627 } 628 629 case STATE_AUTOMATIC_ON: { 630 if (mIsPowered) { 631 enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, 632 BatterySaverController.REASON_PLUGGED_IN); 633 mState = STATE_OFF; 634 } else if (manual) { 635 if (enable) { 636 Slog.e(TAG, "Tried to enable BS when it's already AUTO_ON"); 637 return; 638 } 639 enableBatterySaverLocked(/*enable*/ false, /*manual*/ true, 640 BatterySaverController.REASON_MANUAL_OFF); 641 // When battery saver is disabled manually (while battery saver is enabled) 642 // when the battery level is low, we "snooze" BS -- i.e. disable auto battery 643 // saver. 644 // We resume auto-BS once the battery level is not low, or the device is 645 // plugged in. 646 mState = STATE_OFF_AUTOMATIC_SNOOZED; 647 } else if (isAutomaticModeActiveLocked() && !isInAutomaticLowZoneLocked()) { 648 enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, 649 BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_OFF); 650 mState = STATE_OFF; 651 } else if (isDynamicModeActiveLocked() && !isInDynamicLowZoneLocked()) { 652 enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, 653 BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF); 654 mState = STATE_OFF; 655 } else if (!isAutomaticModeActiveLocked() && !isDynamicModeActiveLocked()) { 656 enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, 657 BatterySaverController.REASON_SETTING_CHANGED); 658 mState = STATE_OFF; 659 } 660 break; 661 } 662 663 case STATE_OFF_AUTOMATIC_SNOOZED: { 664 if (manual) { 665 if (!enable) { 666 Slog.e(TAG, "Tried to disable BS when it's already AUTO_SNOOZED"); 667 return; 668 } 669 enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, 670 BatterySaverController.REASON_MANUAL_ON); 671 mState = STATE_MANUAL_ON; 672 } else if (mIsPowered // Plugging in resets snooze. 673 || (isAutomaticModeActiveLocked() && !isInAutomaticLowZoneLocked()) 674 || (isDynamicModeActiveLocked() && !isInDynamicLowZoneLocked()) 675 || (!isAutomaticModeActiveLocked() && !isDynamicModeActiveLocked())) { 676 mState = STATE_OFF; 677 } 678 break; 679 } 680 681 case STATE_PENDING_STICKY_ON: { 682 if (manual) { 683 // This shouldn't be possible. We'll only be in this state when the device is 684 // plugged in, so the user shouldn't be able to manually change state. 685 Slog.e(TAG, "Tried to manually change BS state from PENDING_STICKY_ON"); 686 return; 687 } 688 final boolean shouldTurnOffSticky = mSettingBatterySaverStickyAutoDisableEnabled 689 && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold; 690 final boolean isStickyDisabled = 691 mBatterySaverStickyBehaviourDisabled || !mSettingBatterySaverEnabledSticky; 692 if (isStickyDisabled || shouldTurnOffSticky) { 693 mState = STATE_OFF; 694 setStickyActive(false); 695 triggerStickyDisabledNotification(); 696 } else if (!mIsPowered) { 697 // Re-enable BS. 698 enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, 699 BatterySaverController.REASON_STICKY_RESTORE); 700 mState = STATE_MANUAL_ON; 701 } 702 break; 703 } 704 705 default: 706 Slog.wtf(TAG, "Unknown state: " + mState); 707 break; 708 } 709 } 710 711 @VisibleForTesting getState()712 int getState() { 713 synchronized (mLock) { 714 return mState; 715 } 716 } 717 718 /** 719 * {@link com.android.server.power.PowerManagerService} calls it when 720 * {@link android.os.PowerManager#setPowerSaveModeEnabled} is called. 721 * 722 * Note this could? be called before {@link #onBootCompleted} too. 723 */ setBatterySaverEnabledManually(boolean enabled)724 public void setBatterySaverEnabledManually(boolean enabled) { 725 if (DEBUG) { 726 Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled); 727 } 728 synchronized (mLock) { 729 updateStateLocked(true, enabled); 730 // TODO: maybe turn off adaptive if it's on and advertiseIsEnabled is true and 731 // enabled is false 732 } 733 } 734 735 @GuardedBy("mLock") enableBatterySaverLocked(boolean enable, boolean manual, int intReason)736 private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason) { 737 enableBatterySaverLocked(enable, manual, intReason, reasonToString(intReason)); 738 } 739 740 /** 741 * Actually enable / disable battery saver. Write the new state to the global settings 742 * and propagate it to {@link #mBatterySaverController}. 743 */ 744 @GuardedBy("mLock") enableBatterySaverLocked(boolean enable, boolean manual, int intReason, String strReason)745 private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason, 746 String strReason) { 747 if (DEBUG) { 748 Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual 749 + " reason=" + strReason + "(" + intReason + ")"); 750 } 751 final boolean wasEnabled = mBatterySaverController.isFullEnabled(); 752 753 if (wasEnabled == enable) { 754 if (DEBUG) { 755 Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled")); 756 } 757 return; 758 } 759 if (enable && mIsPowered) { 760 if (DEBUG) Slog.d(TAG, "Can't enable: isPowered"); 761 return; 762 } 763 mLastChangedIntReason = intReason; 764 mLastChangedStrReason = strReason; 765 766 mSettingBatterySaverEnabled = enable; 767 putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0); 768 769 if (manual) { 770 setStickyActive(!mBatterySaverStickyBehaviourDisabled && enable); 771 } 772 mBatterySaverController.enableBatterySaver(enable, intReason); 773 774 // Handle triggering the notification to show/hide when appropriate 775 if (intReason == BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON) { 776 triggerDynamicModeNotification(); 777 } else if (!enable) { 778 hideDynamicModeNotification(); 779 } 780 781 if (DEBUG) { 782 Slog.d(TAG, "Battery saver: Enabled=" + enable 783 + " manual=" + manual 784 + " reason=" + strReason + "(" + intReason + ")"); 785 } 786 } 787 788 @VisibleForTesting triggerDynamicModeNotification()789 void triggerDynamicModeNotification() { 790 // The current lock is the PowerManager lock, which sits very low in the service lock 791 // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock. 792 runOnBgThread(() -> { 793 NotificationManager manager = mContext.getSystemService(NotificationManager.class); 794 ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID, 795 R.string.dynamic_mode_notification_channel_name); 796 797 manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID, 798 buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID, 799 mContext.getResources().getString( 800 R.string.dynamic_mode_notification_title), 801 R.string.dynamic_mode_notification_summary, 802 Intent.ACTION_POWER_USAGE_SUMMARY), 803 UserHandle.ALL); 804 }); 805 } 806 807 @VisibleForTesting triggerStickyDisabledNotification()808 void triggerStickyDisabledNotification() { 809 // The current lock is the PowerManager lock, which sits very low in the service lock 810 // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock. 811 runOnBgThread(() -> { 812 NotificationManager manager = mContext.getSystemService(NotificationManager.class); 813 ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID, 814 R.string.battery_saver_notification_channel_name); 815 816 final String percentage = NumberFormat.getPercentInstance() 817 .format((double) mBatteryLevel / 100.0); 818 manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID, 819 buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID, 820 mContext.getResources().getString( 821 R.string.battery_saver_charged_notification_title, percentage), 822 R.string.battery_saver_off_notification_summary, 823 Settings.ACTION_BATTERY_SAVER_SETTINGS), 824 UserHandle.ALL); 825 }); 826 } 827 ensureNotificationChannelExists(NotificationManager manager, @NonNull String channelId, @StringRes int nameId)828 private void ensureNotificationChannelExists(NotificationManager manager, 829 @NonNull String channelId, @StringRes int nameId) { 830 NotificationChannel channel = new NotificationChannel( 831 channelId, mContext.getText(nameId), NotificationManager.IMPORTANCE_DEFAULT); 832 channel.setSound(null, null); 833 channel.setBlockableSystem(true); 834 manager.createNotificationChannel(channel); 835 } 836 buildNotification(@onNull String channelId, @NonNull String title, @StringRes int summaryId, @NonNull String intentAction)837 private Notification buildNotification(@NonNull String channelId, @NonNull String title, 838 @StringRes int summaryId, @NonNull String intentAction) { 839 Resources res = mContext.getResources(); 840 Intent intent = new Intent(intentAction); 841 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 842 PendingIntent batterySaverIntent = PendingIntent.getActivity( 843 mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT); 844 final String summary = res.getString(summaryId); 845 846 return new Notification.Builder(mContext, channelId) 847 .setSmallIcon(R.drawable.ic_battery) 848 .setContentTitle(title) 849 .setContentText(summary) 850 .setContentIntent(batterySaverIntent) 851 .setStyle(new Notification.BigTextStyle().bigText(summary)) 852 .setOnlyAlertOnce(true) 853 .setAutoCancel(true) 854 .build(); 855 } 856 hideDynamicModeNotification()857 private void hideDynamicModeNotification() { 858 hideNotification(DYNAMIC_MODE_NOTIFICATION_ID); 859 } 860 hideStickyDisabledNotification()861 private void hideStickyDisabledNotification() { 862 hideNotification(STICKY_AUTO_DISABLED_NOTIFICATION_ID); 863 } 864 hideNotification(int notificationId)865 private void hideNotification(int notificationId) { 866 // The current lock is the PowerManager lock, which sits very low in the service lock 867 // hierarchy. We shouldn't call out to NotificationManager with the PowerManager lock. 868 runOnBgThread(() -> { 869 NotificationManager manager = mContext.getSystemService(NotificationManager.class); 870 manager.cancelAsUser(TAG, notificationId, UserHandle.ALL); 871 }); 872 } 873 setStickyActive(boolean active)874 private void setStickyActive(boolean active) { 875 mSettingBatterySaverEnabledSticky = active; 876 putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY, 877 mSettingBatterySaverEnabledSticky ? 1 : 0); 878 } 879 880 @VisibleForTesting putGlobalSetting(String key, int value)881 protected void putGlobalSetting(String key, int value) { 882 Settings.Global.putInt(mContext.getContentResolver(), key, value); 883 } 884 885 @VisibleForTesting getGlobalSetting(String key, int defValue)886 protected int getGlobalSetting(String key, int defValue) { 887 return Settings.Global.getInt(mContext.getContentResolver(), key, defValue); 888 } 889 dump(PrintWriter pw)890 public void dump(PrintWriter pw) { 891 synchronized (mLock) { 892 pw.println(); 893 pw.println("Battery saver state machine:"); 894 895 pw.print(" Enabled="); 896 pw.println(mBatterySaverController.isEnabled()); 897 pw.print(" full="); 898 pw.println(mBatterySaverController.isFullEnabled()); 899 pw.print(" adaptive="); 900 pw.print(mBatterySaverController.isAdaptiveEnabled()); 901 if (mBatterySaverController.isAdaptiveEnabled()) { 902 pw.print(" (advertise="); 903 pw.print( 904 mBatterySaverController.getBatterySaverPolicy().shouldAdvertiseIsEnabled()); 905 pw.print(")"); 906 } 907 pw.println(); 908 pw.print(" mState="); 909 pw.println(mState); 910 911 pw.print(" mLastChangedIntReason="); 912 pw.println(mLastChangedIntReason); 913 pw.print(" mLastChangedStrReason="); 914 pw.println(mLastChangedStrReason); 915 916 pw.print(" mBootCompleted="); 917 pw.println(mBootCompleted); 918 pw.print(" mSettingsLoaded="); 919 pw.println(mSettingsLoaded); 920 pw.print(" mBatteryStatusSet="); 921 pw.println(mBatteryStatusSet); 922 923 pw.print(" mIsPowered="); 924 pw.println(mIsPowered); 925 pw.print(" mBatteryLevel="); 926 pw.println(mBatteryLevel); 927 pw.print(" mIsBatteryLevelLow="); 928 pw.println(mIsBatteryLevelLow); 929 930 pw.print(" mSettingBatterySaverEnabled="); 931 pw.println(mSettingBatterySaverEnabled); 932 pw.print(" mSettingBatterySaverEnabledSticky="); 933 pw.println(mSettingBatterySaverEnabledSticky); 934 pw.print(" mSettingBatterySaverStickyAutoDisableEnabled="); 935 pw.println(mSettingBatterySaverStickyAutoDisableEnabled); 936 pw.print(" mSettingBatterySaverStickyAutoDisableThreshold="); 937 pw.println(mSettingBatterySaverStickyAutoDisableThreshold); 938 pw.print(" mSettingBatterySaverTriggerThreshold="); 939 pw.println(mSettingBatterySaverTriggerThreshold); 940 pw.print(" mBatterySaverStickyBehaviourDisabled="); 941 pw.println(mBatterySaverStickyBehaviourDisabled); 942 943 pw.print(" mLastAdaptiveBatterySaverChangedExternallyElapsed="); 944 pw.println(mLastAdaptiveBatterySaverChangedExternallyElapsed); 945 } 946 } 947 dumpProto(ProtoOutputStream proto, long tag)948 public void dumpProto(ProtoOutputStream proto, long tag) { 949 synchronized (mLock) { 950 final long token = proto.start(tag); 951 952 proto.write(BatterySaverStateMachineProto.ENABLED, 953 mBatterySaverController.isEnabled()); 954 proto.write(BatterySaverStateMachineProto.STATE, mState); 955 proto.write(BatterySaverStateMachineProto.IS_FULL_ENABLED, 956 mBatterySaverController.isFullEnabled()); 957 proto.write(BatterySaverStateMachineProto.IS_ADAPTIVE_ENABLED, 958 mBatterySaverController.isAdaptiveEnabled()); 959 proto.write(BatterySaverStateMachineProto.SHOULD_ADVERTISE_IS_ENABLED, 960 mBatterySaverController.getBatterySaverPolicy().shouldAdvertiseIsEnabled()); 961 962 proto.write(BatterySaverStateMachineProto.BOOT_COMPLETED, mBootCompleted); 963 proto.write(BatterySaverStateMachineProto.SETTINGS_LOADED, mSettingsLoaded); 964 proto.write(BatterySaverStateMachineProto.BATTERY_STATUS_SET, mBatteryStatusSet); 965 966 967 proto.write(BatterySaverStateMachineProto.IS_POWERED, mIsPowered); 968 proto.write(BatterySaverStateMachineProto.BATTERY_LEVEL, mBatteryLevel); 969 proto.write(BatterySaverStateMachineProto.IS_BATTERY_LEVEL_LOW, mIsBatteryLevelLow); 970 971 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED, 972 mSettingBatterySaverEnabled); 973 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED_STICKY, 974 mSettingBatterySaverEnabledSticky); 975 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD, 976 mSettingBatterySaverTriggerThreshold); 977 proto.write( 978 BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_ENABLED, 979 mSettingBatterySaverStickyAutoDisableEnabled); 980 proto.write( 981 BatterySaverStateMachineProto 982 .SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_THRESHOLD, 983 mSettingBatterySaverStickyAutoDisableThreshold); 984 985 proto.write( 986 BatterySaverStateMachineProto 987 .LAST_ADAPTIVE_BATTERY_SAVER_CHANGED_EXTERNALLY_ELAPSED, 988 mLastAdaptiveBatterySaverChangedExternallyElapsed); 989 990 proto.end(token); 991 } 992 } 993 } 994