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 runOnBgThread(this::triggerDynamicModeNotification); 777 } else if (!enable) { 778 runOnBgThread(this::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 NotificationManager manager = mContext.getSystemService(NotificationManager.class); 791 ensureNotificationChannelExists(manager, DYNAMIC_MODE_NOTIF_CHANNEL_ID, 792 R.string.dynamic_mode_notification_channel_name); 793 794 manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID, 795 buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID, 796 mContext.getResources().getString(R.string.dynamic_mode_notification_title), 797 R.string.dynamic_mode_notification_summary, 798 Intent.ACTION_POWER_USAGE_SUMMARY), 799 UserHandle.ALL); 800 } 801 802 @VisibleForTesting triggerStickyDisabledNotification()803 void triggerStickyDisabledNotification() { 804 NotificationManager manager = mContext.getSystemService(NotificationManager.class); 805 ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID, 806 R.string.battery_saver_notification_channel_name); 807 808 final String percentage = NumberFormat.getPercentInstance() 809 .format((double) mBatteryLevel / 100.0); 810 manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID, 811 buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID, 812 mContext.getResources().getString( 813 R.string.battery_saver_charged_notification_title, percentage), 814 R.string.battery_saver_off_notification_summary, 815 Settings.ACTION_BATTERY_SAVER_SETTINGS), 816 UserHandle.ALL); 817 } 818 ensureNotificationChannelExists(NotificationManager manager, @NonNull String channelId, @StringRes int nameId)819 private void ensureNotificationChannelExists(NotificationManager manager, 820 @NonNull String channelId, @StringRes int nameId) { 821 NotificationChannel channel = new NotificationChannel( 822 channelId, mContext.getText(nameId), NotificationManager.IMPORTANCE_DEFAULT); 823 channel.setSound(null, null); 824 channel.setBlockableSystem(true); 825 manager.createNotificationChannel(channel); 826 } 827 buildNotification(@onNull String channelId, @NonNull String title, @StringRes int summaryId, @NonNull String intentAction)828 private Notification buildNotification(@NonNull String channelId, @NonNull String title, 829 @StringRes int summaryId, @NonNull String intentAction) { 830 Resources res = mContext.getResources(); 831 Intent intent = new Intent(intentAction); 832 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 833 PendingIntent batterySaverIntent = PendingIntent.getActivity( 834 mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT); 835 final String summary = res.getString(summaryId); 836 837 return new Notification.Builder(mContext, channelId) 838 .setSmallIcon(R.drawable.ic_battery) 839 .setContentTitle(title) 840 .setContentText(summary) 841 .setContentIntent(batterySaverIntent) 842 .setStyle(new Notification.BigTextStyle().bigText(summary)) 843 .setOnlyAlertOnce(true) 844 .setAutoCancel(true) 845 .build(); 846 } 847 hideDynamicModeNotification()848 private void hideDynamicModeNotification() { 849 hideNotification(DYNAMIC_MODE_NOTIFICATION_ID); 850 } 851 hideStickyDisabledNotification()852 private void hideStickyDisabledNotification() { 853 hideNotification(STICKY_AUTO_DISABLED_NOTIFICATION_ID); 854 } 855 hideNotification(int notificationId)856 private void hideNotification(int notificationId) { 857 NotificationManager manager = mContext.getSystemService(NotificationManager.class); 858 manager.cancel(notificationId); 859 } 860 setStickyActive(boolean active)861 private void setStickyActive(boolean active) { 862 mSettingBatterySaverEnabledSticky = active; 863 putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY, 864 mSettingBatterySaverEnabledSticky ? 1 : 0); 865 } 866 867 @VisibleForTesting putGlobalSetting(String key, int value)868 protected void putGlobalSetting(String key, int value) { 869 Settings.Global.putInt(mContext.getContentResolver(), key, value); 870 } 871 872 @VisibleForTesting getGlobalSetting(String key, int defValue)873 protected int getGlobalSetting(String key, int defValue) { 874 return Settings.Global.getInt(mContext.getContentResolver(), key, defValue); 875 } 876 dump(PrintWriter pw)877 public void dump(PrintWriter pw) { 878 synchronized (mLock) { 879 pw.println(); 880 pw.println("Battery saver state machine:"); 881 882 pw.print(" Enabled="); 883 pw.println(mBatterySaverController.isEnabled()); 884 pw.print(" full="); 885 pw.println(mBatterySaverController.isFullEnabled()); 886 pw.print(" adaptive="); 887 pw.print(mBatterySaverController.isAdaptiveEnabled()); 888 if (mBatterySaverController.isAdaptiveEnabled()) { 889 pw.print(" (advertise="); 890 pw.print( 891 mBatterySaverController.getBatterySaverPolicy().shouldAdvertiseIsEnabled()); 892 pw.print(")"); 893 } 894 pw.println(); 895 pw.print(" mState="); 896 pw.println(mState); 897 898 pw.print(" mLastChangedIntReason="); 899 pw.println(mLastChangedIntReason); 900 pw.print(" mLastChangedStrReason="); 901 pw.println(mLastChangedStrReason); 902 903 pw.print(" mBootCompleted="); 904 pw.println(mBootCompleted); 905 pw.print(" mSettingsLoaded="); 906 pw.println(mSettingsLoaded); 907 pw.print(" mBatteryStatusSet="); 908 pw.println(mBatteryStatusSet); 909 910 pw.print(" mIsPowered="); 911 pw.println(mIsPowered); 912 pw.print(" mBatteryLevel="); 913 pw.println(mBatteryLevel); 914 pw.print(" mIsBatteryLevelLow="); 915 pw.println(mIsBatteryLevelLow); 916 917 pw.print(" mSettingBatterySaverEnabled="); 918 pw.println(mSettingBatterySaverEnabled); 919 pw.print(" mSettingBatterySaverEnabledSticky="); 920 pw.println(mSettingBatterySaverEnabledSticky); 921 pw.print(" mSettingBatterySaverStickyAutoDisableEnabled="); 922 pw.println(mSettingBatterySaverStickyAutoDisableEnabled); 923 pw.print(" mSettingBatterySaverStickyAutoDisableThreshold="); 924 pw.println(mSettingBatterySaverStickyAutoDisableThreshold); 925 pw.print(" mSettingBatterySaverTriggerThreshold="); 926 pw.println(mSettingBatterySaverTriggerThreshold); 927 pw.print(" mBatterySaverStickyBehaviourDisabled="); 928 pw.println(mBatterySaverStickyBehaviourDisabled); 929 930 pw.print(" mLastAdaptiveBatterySaverChangedExternallyElapsed="); 931 pw.println(mLastAdaptiveBatterySaverChangedExternallyElapsed); 932 } 933 } 934 dumpProto(ProtoOutputStream proto, long tag)935 public void dumpProto(ProtoOutputStream proto, long tag) { 936 synchronized (mLock) { 937 final long token = proto.start(tag); 938 939 proto.write(BatterySaverStateMachineProto.ENABLED, 940 mBatterySaverController.isEnabled()); 941 proto.write(BatterySaverStateMachineProto.STATE, mState); 942 proto.write(BatterySaverStateMachineProto.IS_FULL_ENABLED, 943 mBatterySaverController.isFullEnabled()); 944 proto.write(BatterySaverStateMachineProto.IS_ADAPTIVE_ENABLED, 945 mBatterySaverController.isAdaptiveEnabled()); 946 proto.write(BatterySaverStateMachineProto.SHOULD_ADVERTISE_IS_ENABLED, 947 mBatterySaverController.getBatterySaverPolicy().shouldAdvertiseIsEnabled()); 948 949 proto.write(BatterySaverStateMachineProto.BOOT_COMPLETED, mBootCompleted); 950 proto.write(BatterySaverStateMachineProto.SETTINGS_LOADED, mSettingsLoaded); 951 proto.write(BatterySaverStateMachineProto.BATTERY_STATUS_SET, mBatteryStatusSet); 952 953 954 proto.write(BatterySaverStateMachineProto.IS_POWERED, mIsPowered); 955 proto.write(BatterySaverStateMachineProto.BATTERY_LEVEL, mBatteryLevel); 956 proto.write(BatterySaverStateMachineProto.IS_BATTERY_LEVEL_LOW, mIsBatteryLevelLow); 957 958 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED, 959 mSettingBatterySaverEnabled); 960 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED_STICKY, 961 mSettingBatterySaverEnabledSticky); 962 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD, 963 mSettingBatterySaverTriggerThreshold); 964 proto.write( 965 BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_ENABLED, 966 mSettingBatterySaverStickyAutoDisableEnabled); 967 proto.write( 968 BatterySaverStateMachineProto 969 .SETTING_BATTERY_SAVER_STICKY_AUTO_DISABLE_THRESHOLD, 970 mSettingBatterySaverStickyAutoDisableThreshold); 971 972 proto.write( 973 BatterySaverStateMachineProto 974 .LAST_ADAPTIVE_BATTERY_SAVER_CHANGED_EXTERNALLY_ELAPSED, 975 mLastAdaptiveBatterySaverChangedExternallyElapsed); 976 977 proto.end(token); 978 } 979 } 980 } 981