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 android.content.ContentResolver; 19 import android.content.Context; 20 import android.database.ContentObserver; 21 import android.os.Handler; 22 import android.os.UserHandle; 23 import android.provider.Settings; 24 import android.provider.Settings.Global; 25 import android.util.Slog; 26 import android.util.proto.ProtoOutputStream; 27 28 import com.android.internal.annotations.GuardedBy; 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.os.BackgroundThread; 31 import com.android.server.EventLogTags; 32 import com.android.server.power.BatterySaverPolicy; 33 import com.android.server.power.BatterySaverStateMachineProto; 34 35 import java.io.PrintWriter; 36 37 /** 38 * Decides when to enable / disable battery saver. 39 * 40 * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy. 41 * Do not call out with the lock held. (Settings provider is okay.) 42 * 43 * Test: 44 atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java 45 */ 46 public class BatterySaverStateMachine { 47 private static final String TAG = "BatterySaverStateMachine"; 48 private final Object mLock; 49 50 private static final boolean DEBUG = BatterySaverPolicy.DEBUG; 51 52 private final Context mContext; 53 private final BatterySaverController mBatterySaverController; 54 55 /** Whether the system has booted. */ 56 @GuardedBy("mLock") 57 private boolean mBootCompleted; 58 59 /** Whether global settings have been loaded already. */ 60 @GuardedBy("mLock") 61 private boolean mSettingsLoaded; 62 63 /** Whether the first battery status has arrived. */ 64 @GuardedBy("mLock") 65 private boolean mBatteryStatusSet; 66 67 /** Whether the device is connected to any power source. */ 68 @GuardedBy("mLock") 69 private boolean mIsPowered; 70 71 /** Current battery level in %, 0-100. (Currently only used in dumpsys.) */ 72 @GuardedBy("mLock") 73 private int mBatteryLevel; 74 75 /** Whether the battery level is considered to be "low" or not.*/ 76 @GuardedBy("mLock") 77 private boolean mIsBatteryLevelLow; 78 79 /** Previously known value of Global.LOW_POWER_MODE. */ 80 @GuardedBy("mLock") 81 private boolean mSettingBatterySaverEnabled; 82 83 /** Previously known value of Global.LOW_POWER_MODE_STICKY. */ 84 @GuardedBy("mLock") 85 private boolean mSettingBatterySaverEnabledSticky; 86 87 /** 88 * Previously known value of Global.LOW_POWER_MODE_TRIGGER_LEVEL. 89 * (Currently only used in dumpsys.) 90 */ 91 @GuardedBy("mLock") 92 private int mSettingBatterySaverTriggerThreshold; 93 94 /** 95 * Whether BS has been manually disabled while the battery level is low, in which case we 96 * shouldn't auto re-enable it until the battery level is not low. 97 */ 98 @GuardedBy("mLock") 99 private boolean mBatterySaverSnoozing; 100 101 /** 102 * Last reason passed to {@link #enableBatterySaverLocked}. 103 */ 104 @GuardedBy("mLock") 105 private int mLastChangedIntReason; 106 107 /** 108 * Last reason passed to {@link #enableBatterySaverLocked}. 109 */ 110 @GuardedBy("mLock") 111 private String mLastChangedStrReason; 112 113 private final ContentObserver mSettingsObserver = new ContentObserver(null) { 114 @Override 115 public void onChange(boolean selfChange) { 116 synchronized (mLock) { 117 refreshSettingsLocked(); 118 } 119 } 120 }; 121 BatterySaverStateMachine(Object lock, Context context, BatterySaverController batterySaverController)122 public BatterySaverStateMachine(Object lock, 123 Context context, BatterySaverController batterySaverController) { 124 mLock = lock; 125 mContext = context; 126 mBatterySaverController = batterySaverController; 127 } 128 isBatterySaverEnabled()129 private boolean isBatterySaverEnabled() { 130 return mBatterySaverController.isEnabled(); 131 } 132 isAutoBatterySaverConfigured()133 private boolean isAutoBatterySaverConfigured() { 134 return mSettingBatterySaverTriggerThreshold > 0; 135 } 136 137 /** 138 * {@link com.android.server.power.PowerManagerService} calls it when the system is booted. 139 */ onBootCompleted()140 public void onBootCompleted() { 141 if (DEBUG) { 142 Slog.d(TAG, "onBootCompleted"); 143 } 144 // Just booted. We don't want LOW_POWER_MODE to be persisted, so just always clear it. 145 putGlobalSetting(Global.LOW_POWER_MODE, 0); 146 147 // This is called with the power manager lock held. Don't do anything that may call to 148 // upper services. (e.g. don't call into AM directly) 149 // So use a BG thread. 150 runOnBgThread(() -> { 151 152 final ContentResolver cr = mContext.getContentResolver(); 153 cr.registerContentObserver(Settings.Global.getUriFor( 154 Settings.Global.LOW_POWER_MODE), 155 false, mSettingsObserver, UserHandle.USER_SYSTEM); 156 cr.registerContentObserver(Settings.Global.getUriFor( 157 Settings.Global.LOW_POWER_MODE_STICKY), 158 false, mSettingsObserver, UserHandle.USER_SYSTEM); 159 cr.registerContentObserver(Settings.Global.getUriFor( 160 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL), 161 false, mSettingsObserver, UserHandle.USER_SYSTEM); 162 163 synchronized (mLock) { 164 165 mBootCompleted = true; 166 167 refreshSettingsLocked(); 168 169 doAutoBatterySaverLocked(); 170 } 171 }); 172 } 173 174 /** 175 * Run a {@link Runnable} on a background handler. 176 */ 177 @VisibleForTesting runOnBgThread(Runnable r)178 void runOnBgThread(Runnable r) { 179 BackgroundThread.getHandler().post(r); 180 } 181 182 /** 183 * Run a {@link Runnable} on a background handler, but lazily. If the same {@link Runnable}, 184 * it'll be first removed before a new one is posted. 185 */ 186 @VisibleForTesting runOnBgThreadLazy(Runnable r, int delayMillis)187 void runOnBgThreadLazy(Runnable r, int delayMillis) { 188 final Handler h = BackgroundThread.getHandler(); 189 h.removeCallbacks(r); 190 h.postDelayed(r, delayMillis); 191 } 192 refreshSettingsLocked()193 void refreshSettingsLocked() { 194 final boolean lowPowerModeEnabled = getGlobalSetting( 195 Settings.Global.LOW_POWER_MODE, 0) != 0; 196 final boolean lowPowerModeEnabledSticky = getGlobalSetting( 197 Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0; 198 final int lowPowerModeTriggerLevel = getGlobalSetting( 199 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); 200 201 setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky, 202 lowPowerModeTriggerLevel); 203 } 204 205 /** 206 * {@link com.android.server.power.PowerManagerService} calls it when relevant global settings 207 * have changed. 208 * 209 * Note this will be called before {@link #onBootCompleted} too. 210 */ 211 @VisibleForTesting setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky, int batterySaverTriggerThreshold)212 void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky, 213 int batterySaverTriggerThreshold) { 214 if (DEBUG) { 215 Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled 216 + " sticky=" + batterySaverEnabledSticky 217 + " threshold=" + batterySaverTriggerThreshold); 218 } 219 220 mSettingsLoaded = true; 221 222 final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled; 223 final boolean stickyChanged = 224 mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky; 225 final boolean thresholdChanged 226 = mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold; 227 228 if (!(enabledChanged || stickyChanged || thresholdChanged)) { 229 return; 230 } 231 232 mSettingBatterySaverEnabled = batterySaverEnabled; 233 mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky; 234 mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold; 235 236 if (thresholdChanged) { 237 // To avoid spamming the event log, we throttle logging here. 238 runOnBgThreadLazy(mThresholdChangeLogger, 2000); 239 } 240 241 if (enabledChanged) { 242 final String reason = batterySaverEnabled 243 ? "Global.low_power changed to 1" : "Global.low_power changed to 0"; 244 enableBatterySaverLocked(/*enable=*/ batterySaverEnabled, /*manual=*/ true, 245 BatterySaverController.REASON_SETTING_CHANGED, reason); 246 } 247 } 248 249 private final Runnable mThresholdChangeLogger = () -> { 250 EventLogTags.writeBatterySaverSetting(mSettingBatterySaverTriggerThreshold); 251 }; 252 253 /** 254 * {@link com.android.server.power.PowerManagerService} calls it when battery state changes. 255 * 256 * Note this may be called before {@link #onBootCompleted} too. 257 */ setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow)258 public void setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow) { 259 if (DEBUG) { 260 Slog.d(TAG, "setBatteryStatus: powered=" + newPowered + " level=" + newLevel 261 + " low=" + newBatteryLevelLow); 262 } 263 synchronized (mLock) { 264 mBatteryStatusSet = true; 265 266 final boolean poweredChanged = mIsPowered != newPowered; 267 final boolean levelChanged = mBatteryLevel != newLevel; 268 final boolean lowChanged = mIsBatteryLevelLow != newBatteryLevelLow; 269 270 if (!(poweredChanged || levelChanged || lowChanged)) { 271 return; 272 } 273 274 mIsPowered = newPowered; 275 mBatteryLevel = newLevel; 276 mIsBatteryLevelLow = newBatteryLevelLow; 277 278 doAutoBatterySaverLocked(); 279 } 280 } 281 282 /** 283 * Decide whether to auto-start / stop battery saver. 284 */ doAutoBatterySaverLocked()285 private void doAutoBatterySaverLocked() { 286 if (DEBUG) { 287 Slog.d(TAG, "doAutoBatterySaverLocked: mBootCompleted=" + mBootCompleted 288 + " mSettingsLoaded=" + mSettingsLoaded 289 + " mBatteryStatusSet=" + mBatteryStatusSet 290 + " mIsBatteryLevelLow=" + mIsBatteryLevelLow 291 + " mBatterySaverSnoozing=" + mBatterySaverSnoozing 292 + " mIsPowered=" + mIsPowered 293 + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky); 294 } 295 if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) { 296 return; // Not fully initialized yet. 297 } 298 if (!mIsBatteryLevelLow) { 299 updateSnoozingLocked(false, "Battery not low"); 300 } 301 if (mIsPowered) { 302 updateSnoozingLocked(false, "Plugged in"); 303 enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false, 304 BatterySaverController.REASON_PLUGGED_IN, 305 "Plugged in"); 306 307 } else if (mSettingBatterySaverEnabledSticky) { 308 // Re-enable BS. 309 enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true, 310 BatterySaverController.REASON_STICKY_RESTORE, 311 "Sticky restore"); 312 313 } else if (mIsBatteryLevelLow) { 314 if (!mBatterySaverSnoozing && isAutoBatterySaverConfigured()) { 315 enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ false, 316 BatterySaverController.REASON_AUTOMATIC_ON, 317 "Auto ON"); 318 } 319 } else { // Battery not low 320 enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false, 321 BatterySaverController.REASON_AUTOMATIC_OFF, 322 "Auto OFF"); 323 } 324 } 325 326 /** 327 * {@link com.android.server.power.PowerManagerService} calls it when 328 * {@link android.os.PowerManager#setPowerSaveMode} is called. 329 * 330 * Note this could? be called before {@link #onBootCompleted} too. 331 */ setBatterySaverEnabledManually(boolean enabled)332 public void setBatterySaverEnabledManually(boolean enabled) { 333 if (DEBUG) { 334 Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled); 335 } 336 synchronized (mLock) { 337 enableBatterySaverLocked(/*enable=*/ enabled, /*manual=*/ true, 338 (enabled ? BatterySaverController.REASON_MANUAL_ON 339 : BatterySaverController.REASON_MANUAL_OFF), 340 (enabled ? "Manual ON" : "Manual OFF")); 341 } 342 } 343 344 /** 345 * Actually enable / disable battery saver. Write the new state to the global settings 346 * and propagate it to {@link #mBatterySaverController}. 347 */ enableBatterySaverLocked(boolean enable, boolean manual, int intReason, String strReason)348 private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason, 349 String strReason) { 350 if (DEBUG) { 351 Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual 352 + " reason=" + strReason + "(" + intReason + ")"); 353 } 354 final boolean wasEnabled = mBatterySaverController.isEnabled(); 355 356 if (wasEnabled == enable) { 357 if (DEBUG) { 358 Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled")); 359 } 360 return; 361 } 362 if (enable && mIsPowered) { 363 if (DEBUG) Slog.d(TAG, "Can't enable: isPowered"); 364 return; 365 } 366 mLastChangedIntReason = intReason; 367 mLastChangedStrReason = strReason; 368 369 if (manual) { 370 if (enable) { 371 updateSnoozingLocked(false, "Manual snooze OFF"); 372 } else { 373 // When battery saver is disabled manually (while battery saver is enabled) 374 // when the battery level is low, we "snooze" BS -- i.e. disable auto battery saver. 375 // We resume auto-BS once the battery level is not low, or the device is plugged in. 376 if (isBatterySaverEnabled() && mIsBatteryLevelLow) { 377 updateSnoozingLocked(true, "Manual snooze"); 378 } 379 } 380 } 381 382 mSettingBatterySaverEnabled = enable; 383 putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0); 384 385 if (manual) { 386 mSettingBatterySaverEnabledSticky = enable; 387 putGlobalSetting(Global.LOW_POWER_MODE_STICKY, enable ? 1 : 0); 388 } 389 mBatterySaverController.enableBatterySaver(enable, intReason); 390 391 if (DEBUG) { 392 Slog.d(TAG, "Battery saver: Enabled=" + enable 393 + " manual=" + manual 394 + " reason=" + strReason + "(" + intReason + ")"); 395 } 396 } 397 updateSnoozingLocked(boolean snoozing, String reason)398 private void updateSnoozingLocked(boolean snoozing, String reason) { 399 if (mBatterySaverSnoozing == snoozing) { 400 return; 401 } 402 if (DEBUG) Slog.d(TAG, "Snooze: " + (snoozing ? "start" : "stop") + " reason=" + reason); 403 mBatterySaverSnoozing = snoozing; 404 } 405 406 @VisibleForTesting putGlobalSetting(String key, int value)407 protected void putGlobalSetting(String key, int value) { 408 Global.putInt(mContext.getContentResolver(), key, value); 409 } 410 411 @VisibleForTesting getGlobalSetting(String key, int defValue)412 protected int getGlobalSetting(String key, int defValue) { 413 return Global.getInt(mContext.getContentResolver(), key, defValue); 414 } 415 dump(PrintWriter pw)416 public void dump(PrintWriter pw) { 417 synchronized (mLock) { 418 pw.println(); 419 pw.println("Battery saver state machine:"); 420 421 pw.print(" Enabled="); 422 pw.println(mBatterySaverController.isEnabled()); 423 424 pw.print(" mLastChangedIntReason="); 425 pw.println(mLastChangedIntReason); 426 pw.print(" mLastChangedStrReason="); 427 pw.println(mLastChangedStrReason); 428 429 pw.print(" mBootCompleted="); 430 pw.println(mBootCompleted); 431 pw.print(" mSettingsLoaded="); 432 pw.println(mSettingsLoaded); 433 pw.print(" mBatteryStatusSet="); 434 pw.println(mBatteryStatusSet); 435 436 pw.print(" mBatterySaverSnoozing="); 437 pw.println(mBatterySaverSnoozing); 438 439 pw.print(" mIsPowered="); 440 pw.println(mIsPowered); 441 pw.print(" mBatteryLevel="); 442 pw.println(mBatteryLevel); 443 pw.print(" mIsBatteryLevelLow="); 444 pw.println(mIsBatteryLevelLow); 445 446 pw.print(" mSettingBatterySaverEnabled="); 447 pw.println(mSettingBatterySaverEnabled); 448 pw.print(" mSettingBatterySaverEnabledSticky="); 449 pw.println(mSettingBatterySaverEnabledSticky); 450 pw.print(" mSettingBatterySaverTriggerThreshold="); 451 pw.println(mSettingBatterySaverTriggerThreshold); 452 } 453 } 454 dumpProto(ProtoOutputStream proto, long tag)455 public void dumpProto(ProtoOutputStream proto, long tag) { 456 synchronized (mLock) { 457 final long token = proto.start(tag); 458 459 proto.write(BatterySaverStateMachineProto.ENABLED, 460 mBatterySaverController.isEnabled()); 461 462 proto.write(BatterySaverStateMachineProto.BOOT_COMPLETED, mBootCompleted); 463 proto.write(BatterySaverStateMachineProto.SETTINGS_LOADED, mSettingsLoaded); 464 proto.write(BatterySaverStateMachineProto.BATTERY_STATUS_SET, mBatteryStatusSet); 465 466 proto.write(BatterySaverStateMachineProto.BATTERY_SAVER_SNOOZING, 467 mBatterySaverSnoozing); 468 469 proto.write(BatterySaverStateMachineProto.IS_POWERED, mIsPowered); 470 proto.write(BatterySaverStateMachineProto.BATTERY_LEVEL, mBatteryLevel); 471 proto.write(BatterySaverStateMachineProto.IS_BATTERY_LEVEL_LOW, mIsBatteryLevelLow); 472 473 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED, 474 mSettingBatterySaverEnabled); 475 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED_STICKY, 476 mSettingBatterySaverEnabledSticky); 477 proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD, 478 mSettingBatterySaverTriggerThreshold); 479 480 proto.end(token); 481 } 482 } 483 } 484