Sticky battery saver
- When battery saver is enabled manually (i.e. via PM.setPowerSaveMode()),
it'll stick, and we'll re-enable battery saver even after a reboot
or a charge.
- Extracted all battery saver state transition logic into a separate
class.
Fix: 75033216
Bug: 74120126
Test: Manual test with "dumpsys battery set ...."
Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
Change-Id: If020cd48f341b339783fe09dd35bc7199e737a52
Test: dumpsys power
Test: incident_report power
Test: atest CtsBatterySavingTestCases
diff --git a/api/test-current.txt b/api/test-current.txt
index dd16771..81b0662 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -584,6 +584,7 @@
field public static final java.lang.String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
field public static final java.lang.String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch";
field public static final java.lang.String LOW_POWER_MODE = "low_power";
+ field public static final java.lang.String LOW_POWER_MODE_STICKY = "low_power_sticky";
field public static final java.lang.String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 18fd67e..12f03be 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11004,13 +11004,21 @@
public static final String SHOW_PROCESSES = "show_processes";
/**
- * If 1 low power mode is enabled.
+ * If 1 low power mode (aka battery saver) is enabled.
* @hide
*/
@TestApi
public static final String LOW_POWER_MODE = "low_power";
/**
+ * If 1, battery saver ({@link #LOW_POWER_MODE}) will be re-activated after the device
+ * is unplugged from a charger or rebooted.
+ * @hide
+ */
+ @TestApi
+ public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
+
+ /**
* Battery level [1-100] at which low power mode automatically turns on.
* If 0, it will not automatically turn on.
* @hide
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index c58de56..eb60942 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -118,61 +118,60 @@
// True if the sandman has just been summoned for the first time since entering
// the dreaming or dozing state. Indicates whether a new dream should begin.
optional bool is_sandman_summoned = 23;
- // If true, the device is in low power mode.
- optional bool is_low_power_mode_enabled = 24;
// True if the battery level is currently considered low.
- optional bool is_battery_level_low = 25;
+ optional bool is_battery_level_low = 24;
// True if we are currently in light device idle mode.
- optional bool is_light_device_idle_mode = 26;
+ optional bool is_light_device_idle_mode = 25;
// True if we are currently in device idle mode.
- optional bool is_device_idle_mode = 27;
+ optional bool is_device_idle_mode = 26;
// Set of app ids that we will always respect the wake locks for.
- repeated int32 device_idle_whitelist = 28;
+ repeated int32 device_idle_whitelist = 27;
// Set of app ids that are temporarily allowed to acquire wakelocks due to
// high-pri message
- repeated int32 device_idle_temp_whitelist = 29;
+ repeated int32 device_idle_temp_whitelist = 28;
// Timestamp of the last time the device was awoken.
- optional int64 last_wake_time_ms = 30;
+ optional int64 last_wake_time_ms = 29;
// Timestamp of the last time the device was put to sleep.
- optional int64 last_sleep_time_ms = 31;
+ optional int64 last_sleep_time_ms = 30;
// Timestamp of the last call to user activity.
- optional int64 last_user_activity_time_ms = 32;
- optional int64 last_user_activity_time_no_change_lights_ms = 33;
+ optional int64 last_user_activity_time_ms = 31;
+ optional int64 last_user_activity_time_no_change_lights_ms = 32;
// Timestamp of last interactive power hint.
- optional int64 last_interactive_power_hint_time_ms = 34;
+ optional int64 last_interactive_power_hint_time_ms = 33;
// Timestamp of the last screen brightness boost.
- optional int64 last_screen_brightness_boost_time_ms = 35;
+ optional int64 last_screen_brightness_boost_time_ms = 34;
// True if screen brightness boost is in progress.
- optional bool is_screen_brightness_boost_in_progress = 36;
+ optional bool is_screen_brightness_boost_in_progress = 35;
// True if the display power state has been fully applied, which means the
// display is actually on or actually off or whatever was requested.
- optional bool is_display_ready = 37;
+ optional bool is_display_ready = 36;
// True if the wake lock suspend blocker has been acquired.
- optional bool is_holding_wake_lock_suspend_blocker = 38;
+ optional bool is_holding_wake_lock_suspend_blocker = 37;
// The suspend blocker used to keep the CPU alive when the display is on, the
// display is getting ready or there is user activity (in which case the
// display must be on).
- optional bool is_holding_display_suspend_blocker = 39;
+ optional bool is_holding_display_suspend_blocker = 38;
// Settings and configuration
- optional PowerServiceSettingsAndConfigurationDumpProto settings_and_configuration = 40;
+ optional PowerServiceSettingsAndConfigurationDumpProto settings_and_configuration = 39;
// Sleep timeout in ms. This can be -1.
- optional sint32 sleep_timeout_ms = 41;
+ optional sint32 sleep_timeout_ms = 40;
// Screen off timeout in ms
- optional int32 screen_off_timeout_ms = 42;
+ optional int32 screen_off_timeout_ms = 41;
// Screen dim duration in ms
- optional int32 screen_dim_duration_ms = 43;
+ optional int32 screen_dim_duration_ms = 42;
// We are currently in the middle of a batch change of uids.
- optional bool are_uids_changing = 44;
+ optional bool are_uids_changing = 43;
// Some uids have actually changed while mUidsChanging was true.
- optional bool are_uids_changed = 45;
+ optional bool are_uids_changed = 44;
// List of UIDs and their states
- repeated UidStateProto uid_states = 46;
- optional .android.os.LooperProto looper = 47;
+ repeated UidStateProto uid_states = 45;
+ optional .android.os.LooperProto looper = 46;
// List of all wake locks acquired by applications.
- repeated WakeLockProto wake_locks = 48;
+ repeated WakeLockProto wake_locks = 47;
// List of all suspend blockers.
- repeated SuspendBlockerProto suspend_blockers = 49;
- optional WirelessChargerDetectorProto wireless_charger_detector = 50;
+ repeated SuspendBlockerProto suspend_blockers = 48;
+ optional WirelessChargerDetectorProto wireless_charger_detector = 49;
+ optional BatterySaverStateMachineProto battery_saver_state_machine = 50;
}
// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
@@ -270,51 +269,80 @@
optional bool are_dreams_activate_on_dock_setting = 17;
// True if doze should not be started until after the screen off transition.
optional bool is_doze_after_screen_off_config = 18;
- // If true, the device is in low power mode.
- optional bool is_low_power_mode_setting = 19;
- // Current state of whether the settings are allowing auto low power mode.
- optional bool is_auto_low_power_mode_configured = 20;
- // The user turned off low power mode below the trigger level
- optional bool is_auto_low_power_mode_snoozing = 21;
// The minimum screen off timeout, in milliseconds.
- optional int32 minimum_screen_off_timeout_config_ms = 22;
+ optional int32 minimum_screen_off_timeout_config_ms = 19;
// The screen dim duration, in milliseconds.
- optional int32 maximum_screen_dim_duration_config_ms = 23;
+ optional int32 maximum_screen_dim_duration_config_ms = 20;
// The maximum screen dim time expressed as a ratio relative to the screen off timeout.
- optional float maximum_screen_dim_ratio_config = 24;
+ optional float maximum_screen_dim_ratio_config = 21;
// The screen off timeout setting value in milliseconds.
- optional int32 screen_off_timeout_setting_ms = 25;
+ optional int32 screen_off_timeout_setting_ms = 22;
// The sleep timeout setting value in milliseconds. Default value is -1.
- optional sint32 sleep_timeout_setting_ms = 26;
+ optional sint32 sleep_timeout_setting_ms = 23;
// The maximum allowable screen off timeout according to the device administration policy.
- optional int32 maximum_screen_off_timeout_from_device_admin_ms = 27;
- optional bool is_maximum_screen_off_timeout_from_device_admin_enforced_locked = 28;
+ optional int32 maximum_screen_off_timeout_from_device_admin_ms = 24;
+ optional bool is_maximum_screen_off_timeout_from_device_admin_enforced_locked = 25;
// The stay on while plugged in setting.
// A set of battery conditions under which to make the screen stay on.
- optional StayOnWhilePluggedInProto stay_on_while_plugged_in = 29;
+ optional StayOnWhilePluggedInProto stay_on_while_plugged_in = 26;
// The screen brightness mode.
- optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 30;
+ optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 27;
// The screen brightness setting override from the window manager
// to allow the current foreground activity to override the brightness.
// Use -1 to disable.
- optional sint32 screen_brightness_override_from_window_manager = 31;
+ optional sint32 screen_brightness_override_from_window_manager = 28;
// The user activity timeout override from the window manager
// to allow the current foreground activity to override the user activity
// timeout. Use -1 to disable.
- optional sint64 user_activity_timeout_override_from_window_manager_ms = 32;
+ optional sint64 user_activity_timeout_override_from_window_manager_ms = 29;
// The window manager has determined the user to be inactive via other means.
// Set this to false to disable.
- optional bool is_user_inactive_override_from_window_manager = 33;
+ optional bool is_user_inactive_override_from_window_manager = 30;
// The screen state to use while dozing.
- optional .android.view.DisplayStateEnum doze_screen_state_override_from_dream_manager = 34;
+ optional .android.view.DisplayStateEnum doze_screen_state_override_from_dream_manager = 31;
// The screen brightness to use while dozing.
- optional float dozed_screen_brightness_override_from_dream_manager = 35;
+ optional float dozed_screen_brightness_override_from_dream_manager = 32;
// Screen brightness settings limits.
- optional ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 36;
+ optional ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 33;
// True if double tap to wake is enabled
- optional bool is_double_tap_wake_enabled = 37;
+ optional bool is_double_tap_wake_enabled = 34;
// True if we are currently in VR Mode.
- optional bool is_vr_mode_enabled = 38;
+ optional bool is_vr_mode_enabled = 35;
// True if Sidekick is controlling the display and we shouldn't change its power mode.
- optional bool draw_wake_lock_override_from_sidekick = 39;
+ optional bool draw_wake_lock_override_from_sidekick = 36;
}
+
+message BatterySaverStateMachineProto {
+ // Whether battery saver is enabled.
+ optional bool enabled = 1;
+
+ // Whether system has booted.
+ optional bool boot_completed = 2;
+
+ // Whether settings have been loaded already.
+ optional bool settings_loaded = 3;
+
+ // Whether battery status has been set at least once.
+ optional bool battery_status_set = 4;
+
+ // Whether automatic battery saver has been canceled by the user.
+ optional bool battery_saver_snoozing = 5;
+
+ // Whether the device is connected to any power source.
+ optional bool is_powered = 6;
+
+ // Current battery level in %, 0-100.
+ optional int32 battery_level = 7;
+
+ // Whether battery level is low or not.
+ optional bool is_battery_level_low = 8;
+
+ // The value of Global.LOW_POWER_MODE.
+ optional bool setting_battery_saver_enabled = 9;
+
+ // The value of Global.LOW_POWER_MODE_STICKY.
+ optional bool setting_battery_saver_enabled_sticky = 10;
+
+ // The value of Global.LOW_POWER_MODE_TRIGGER_LEVEL.
+ optional int32 setting_battery_saver_trigger_threshold = 11;
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index a504ab9..6ef773a 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -266,6 +266,7 @@
Settings.Global.LOW_BATTERY_SOUND_TIMEOUT,
Settings.Global.LOW_POWER_MODE,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX,
+ Settings.Global.LOW_POWER_MODE_STICKY,
Settings.Global.LTE_SERVICE_FORCED,
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index dd88cd1..2ffc4e7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -64,6 +64,7 @@
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.provider.Settings;
+import android.provider.Settings.Global;
import android.provider.Settings.SettingNotFoundException;
import android.service.dreams.DreamManagerInternal;
import android.service.vr.IVrManager;
@@ -97,6 +98,7 @@
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.batterysaver.BatterySaverController;
+import com.android.server.power.batterysaver.BatterySaverStateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -225,6 +227,7 @@
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final BatterySaverPolicy mBatterySaverPolicy;
private final BatterySaverController mBatterySaverController;
+ private final BatterySaverStateMachine mBatterySaverStateMachine;
private LightsManager mLightsManager;
private BatteryManagerInternal mBatteryManagerInternal;
@@ -492,18 +495,6 @@
// Time when we last logged a warning about calling userActivity() without permission.
private long mLastWarningAboutUserActivityPermission = Long.MIN_VALUE;
- // If true, the device is in low power mode.
- private boolean mLowPowerModeEnabled;
-
- // Current state of the low power mode setting.
- private boolean mLowPowerModeSetting;
-
- // Current state of whether the settings are allowing auto low power mode.
- private boolean mAutoLowPowerModeConfigured;
-
- // The user turned off low power mode below the trigger level
- private boolean mAutoLowPowerModeSnoozing;
-
// True if the battery level is currently considered low.
private boolean mBatteryLevelLow;
@@ -667,6 +658,7 @@
mBatterySaverPolicy = new BatterySaverPolicy(mHandler);
mBatterySaverController = new BatterySaverController(mContext,
BackgroundThread.get().getLooper(), mBatterySaverPolicy);
+ mBatterySaverStateMachine = new BatterySaverStateMachine(mContext, mBatterySaverController);
synchronized (mLock) {
mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -704,6 +696,7 @@
mBatterySaverPolicy = batterySaverPolicy;
mBatterySaverController = new BatterySaverController(context,
BackgroundThread.getHandler().getLooper(), batterySaverPolicy);
+ mBatterySaverStateMachine = new BatterySaverStateMachine(mContext, mBatterySaverController);
}
@Override
@@ -725,6 +718,8 @@
final long now = SystemClock.uptimeMillis();
mBootCompleted = true;
mDirty |= DIRTY_BOOT_COMPLETED;
+
+ mBatterySaverStateMachine.onBootCompleted();
userActivityNoUpdateLocked(
now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
updatePowerStateLocked();
@@ -820,12 +815,6 @@
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
false, mSettingsObserver, UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.LOW_POWER_MODE),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.THEATER_MODE_ON),
false, mSettingsObserver, UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -953,17 +942,6 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
- final boolean lowPowerModeEnabled = Settings.Global.getInt(resolver,
- Settings.Global.LOW_POWER_MODE, 0) != 0;
- final boolean autoLowPowerModeConfigured = Settings.Global.getInt(resolver,
- Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) != 0;
- if (lowPowerModeEnabled != mLowPowerModeSetting
- || autoLowPowerModeConfigured != mAutoLowPowerModeConfigured) {
- mLowPowerModeSetting = lowPowerModeEnabled;
- mAutoLowPowerModeConfigured = autoLowPowerModeConfigured;
- updateLowPowerModeLocked();
- }
-
mDirty |= DIRTY_SETTINGS;
}
@@ -977,29 +955,6 @@
}
}
- private void updateLowPowerModeLocked() {
- if ((mIsPowered || !mBatteryLevelLow && !mBootCompleted) && mLowPowerModeSetting) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "updateLowPowerModeLocked: powered or booting with sufficient battery,"
- + " turning setting off");
- }
- // Turn setting off if powered
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.LOW_POWER_MODE, 0);
- mLowPowerModeSetting = false;
- }
- final boolean autoLowPowerModeEnabled = !mIsPowered && mAutoLowPowerModeConfigured
- && !mAutoLowPowerModeSnoozing && mBatteryLevelLow;
- final boolean lowPowerModeEnabled = mLowPowerModeSetting || autoLowPowerModeEnabled;
-
- if (mLowPowerModeEnabled != lowPowerModeEnabled) {
- mLowPowerModeEnabled = lowPowerModeEnabled;
-
- postAfterBootCompleted(() ->
- mBatterySaverController.enableBatterySaver(mLowPowerModeEnabled));
- }
- }
-
private void handleSettingsChangedLocked() {
updateSettingsLocked();
updatePowerStateLocked();
@@ -1751,15 +1706,7 @@
}
}
- if (wasPowered != mIsPowered || oldLevelLow != mBatteryLevelLow) {
- if (oldLevelLow != mBatteryLevelLow && !mBatteryLevelLow) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "updateIsPoweredLocked: resetting low power snooze");
- }
- mAutoLowPowerModeSnoozing = false;
- }
- updateLowPowerModeLocked();
- }
+ mBatterySaverStateMachine.setBatteryStatus(mIsPowered, mBatteryLevel, mBatteryLevelLow);
}
}
@@ -2733,36 +2680,20 @@
}
private boolean isLowPowerModeInternal() {
- synchronized (mLock) {
- return mLowPowerModeEnabled;
- }
+ return mBatterySaverController.isEnabled();
}
- private boolean setLowPowerModeInternal(boolean mode) {
+ private boolean setLowPowerModeInternal(boolean enabled) {
synchronized (mLock) {
- if (DEBUG) Slog.d(TAG, "setLowPowerModeInternal " + mode + " mIsPowered=" + mIsPowered);
+ if (DEBUG) {
+ Slog.d(TAG, "setLowPowerModeInternal " + enabled + " mIsPowered=" + mIsPowered);
+ }
if (mIsPowered) {
return false;
}
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.LOW_POWER_MODE, mode ? 1 : 0);
- mLowPowerModeSetting = mode;
- if (mAutoLowPowerModeConfigured && mBatteryLevelLow) {
- if (mode && mAutoLowPowerModeSnoozing) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "setLowPowerModeInternal: clearing low power mode snooze");
- }
- mAutoLowPowerModeSnoozing = false;
- } else if (!mode && !mAutoLowPowerModeSnoozing) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "setLowPowerModeInternal: snoozing low power mode");
- }
- mAutoLowPowerModeSnoozing = true;
- }
- }
+ mBatterySaverStateMachine.setBatterySaverEnabledManually(enabled);
- updateLowPowerModeLocked();
return true;
}
}
@@ -2848,7 +2779,8 @@
@VisibleForTesting
void updatePowerRequestFromBatterySaverPolicy(DisplayPowerRequest displayPowerRequest) {
PowerSaveState state = mBatterySaverPolicy.
- getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS, mLowPowerModeEnabled);
+ getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS,
+ mBatterySaverController.isEnabled());
displayPowerRequest.lowPowerMode = state.batterySaverEnabled;
displayPowerRequest.screenLowPowerBrightnessFactor = state.brightnessFactor;
}
@@ -3325,7 +3257,6 @@
pw.println(" mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity);
pw.println(" mSandmanScheduled=" + mSandmanScheduled);
pw.println(" mSandmanSummoned=" + mSandmanSummoned);
- pw.println(" mLowPowerModeEnabled=" + mLowPowerModeEnabled);
pw.println(" mBatteryLevelLow=" + mBatteryLevelLow);
pw.println(" mLightDeviceIdleMode=" + mLightDeviceIdleMode);
pw.println(" mDeviceIdleMode=" + mDeviceIdleMode);
@@ -3378,9 +3309,6 @@
pw.println(" mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting);
pw.println(" mDreamsActivateOnDockSetting=" + mDreamsActivateOnDockSetting);
pw.println(" mDozeAfterScreenOff=" + mDozeAfterScreenOff);
- pw.println(" mLowPowerModeSetting=" + mLowPowerModeSetting);
- pw.println(" mAutoLowPowerModeConfigured=" + mAutoLowPowerModeConfigured);
- pw.println(" mAutoLowPowerModeSnoozing=" + mAutoLowPowerModeSnoozing);
pw.println(" mMinimumScreenOffTimeoutConfig=" + mMinimumScreenOffTimeoutConfig);
pw.println(" mMaximumScreenDimDurationConfig=" + mMaximumScreenDimDurationConfig);
pw.println(" mMaximumScreenDimRatioConfig=" + mMaximumScreenDimRatioConfig);
@@ -3456,6 +3384,7 @@
pw.println("Display Power: " + mDisplayPowerCallbacks);
mBatterySaverPolicy.dump(pw);
+ mBatterySaverStateMachine.dump(pw);
pw.println();
final int numProfiles = mProfilePowerState.size();
@@ -3557,7 +3486,6 @@
mRequestWaitForNegativeProximity);
proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
- proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled);
proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
@@ -3663,15 +3591,6 @@
PowerServiceSettingsAndConfigurationDumpProto.IS_DOZE_AFTER_SCREEN_OFF_CONFIG,
mDozeAfterScreenOff);
proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.IS_LOW_POWER_MODE_SETTING,
- mLowPowerModeSetting);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.IS_AUTO_LOW_POWER_MODE_CONFIGURED,
- mAutoLowPowerModeConfigured);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.IS_AUTO_LOW_POWER_MODE_SNOOZING,
- mAutoLowPowerModeSnoozing);
- proto.write(
PowerServiceSettingsAndConfigurationDumpProto
.MINIMUM_SCREEN_OFF_TIMEOUT_CONFIG_MS,
mMinimumScreenOffTimeoutConfig);
@@ -3792,6 +3711,9 @@
proto.end(uIDToken);
}
+ mBatterySaverStateMachine.dumpProto(proto,
+ PowerManagerServiceDumpProto.BATTERY_SAVER_STATE_MACHINE);
+
mHandler.getLooper().writeToProto(proto, PowerManagerServiceDumpProto.LOOPER);
for (WakeLock wl : mWakeLocks) {
@@ -4432,12 +4354,12 @@
}
@Override // Binder call
- public boolean setPowerSaveMode(boolean mode) {
+ public boolean setPowerSaveMode(boolean enabled) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
final long ident = Binder.clearCallingIdentity();
try {
- return setLowPowerModeInternal(mode);
+ return setLowPowerModeInternal(enabled);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4752,7 +4674,8 @@
@Override
public PowerSaveState getLowPowerState(@ServiceType int serviceType) {
synchronized (mLock) {
- return mBatterySaverPolicy.getBatterySaverPolicy(serviceType, mLowPowerModeEnabled);
+ return mBatterySaverPolicy.getBatterySaverPolicy(serviceType,
+ mBatterySaverController.isEnabled());
}
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
new file mode 100644
index 0000000..5b3182e
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.batterysaver;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.power.BatterySaverPolicy;
+import com.android.server.power.BatterySaverStateMachineProto;
+
+import java.io.PrintWriter;
+
+/**
+ * Decides when to enable / disable battery saver.
+ *
+ * (n.b. This isn't really implemented as a "state machine" though.)
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+ */
+public class BatterySaverStateMachine {
+ private static final String TAG = "BatterySaverStateMachine";
+ private final Object mLock = new Object();
+
+ private static final boolean DEBUG = BatterySaverPolicy.DEBUG;
+
+ private final Context mContext;
+ private final BatterySaverController mBatterySaverController;
+
+ /** Whether the system has booted. */
+ @GuardedBy("mLock")
+ private boolean mBootCompleted;
+
+ /** Whether global settings have been loaded already. */
+ @GuardedBy("mLock")
+ private boolean mSettingsLoaded;
+
+ /** Whether the first battery status has arrived. */
+ @GuardedBy("mLock")
+ private boolean mBatteryStatusSet;
+
+ /** Whether the device is connected to any power source. */
+ @GuardedBy("mLock")
+ private boolean mIsPowered;
+
+ /** Current battery level in %, 0-100. (Currently only used in dumpsys.) */
+ @GuardedBy("mLock")
+ private int mBatteryLevel;
+
+ /** Whether the battery level is considered to be "low" or not.*/
+ @GuardedBy("mLock")
+ private boolean mIsBatteryLevelLow;
+
+ /** Previously known value of Global.LOW_POWER_MODE. */
+ @GuardedBy("mLock")
+ private boolean mSettingBatterySaverEnabled;
+
+ /** Previously known value of Global.LOW_POWER_MODE_STICKY. */
+ @GuardedBy("mLock")
+ private boolean mSettingBatterySaverEnabledSticky;
+
+ /**
+ * Previously known value of Global.LOW_POWER_MODE_TRIGGER_LEVEL.
+ * (Currently only used in dumpsys.)
+ */
+ @GuardedBy("mLock")
+ private int mSettingBatterySaverTriggerThreshold;
+
+ /**
+ * Whether BS has been manually disabled while the battery level is low, in which case we
+ * shouldn't auto re-enable it until the battery level is not low.
+ */
+ @GuardedBy("mLock")
+ private boolean mBatterySaverSnoozing;
+
+ private final ContentObserver mSettingsObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (mLock) {
+ refreshSettingsLocked();
+ }
+ }
+ };
+
+ public BatterySaverStateMachine(
+ Context context, BatterySaverController batterySaverController) {
+ mContext = context;
+ mBatterySaverController = batterySaverController;
+ }
+
+ private boolean isBatterySaverEnabled() {
+ return mBatterySaverController.isEnabled();
+ }
+
+ private boolean isAutoBatterySaverConfigured() {
+ return mSettingBatterySaverTriggerThreshold > 0;
+ }
+
+ /**
+ * {@link com.android.server.power.PowerManagerService} calls it when the system is booted.
+ */
+ public void onBootCompleted() {
+ if (DEBUG) {
+ Slog.d(TAG, "onBootCompleted");
+ }
+ synchronized (mLock) {
+
+ final ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE),
+ false, mSettingsObserver, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE_STICKY),
+ false, mSettingsObserver, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
+ false, mSettingsObserver, UserHandle.USER_SYSTEM);
+
+ mBootCompleted = true;
+
+ refreshSettingsLocked();
+
+ doAutoBatterySaverLocked();
+ }
+ }
+
+ void refreshSettingsLocked() {
+ final ContentResolver cr = mContext.getContentResolver();
+
+ final boolean lowPowerModeEnabled = getGlobalSetting(
+ Settings.Global.LOW_POWER_MODE, 0) != 0;
+ final boolean lowPowerModeEnabledSticky = getGlobalSetting(
+ Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0;
+ final int lowPowerModeTriggerLevel = getGlobalSetting(
+ Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+
+ setSettingsLocked(lowPowerModeEnabled, lowPowerModeEnabledSticky,
+ lowPowerModeTriggerLevel);
+ }
+
+ /**
+ * {@link com.android.server.power.PowerManagerService} calls it when relevant global settings
+ * have changed.
+ *
+ * Note this will be called before {@link #onBootCompleted} too.
+ */
+ @VisibleForTesting
+ void setSettingsLocked(boolean batterySaverEnabled, boolean batterySaverEnabledSticky,
+ int batterySaverTriggerThreshold) {
+ if (DEBUG) {
+ Slog.d(TAG, "setSettings: enabled=" + batterySaverEnabled
+ + " sticky=" + batterySaverEnabledSticky
+ + " threshold=" + batterySaverTriggerThreshold);
+ }
+
+ mSettingsLoaded = true;
+
+ final boolean enabledChanged = mSettingBatterySaverEnabled != batterySaverEnabled;
+ final boolean stickyChanged =
+ mSettingBatterySaverEnabledSticky != batterySaverEnabledSticky;
+ final boolean thresholdChanged
+ = mSettingBatterySaverTriggerThreshold != batterySaverTriggerThreshold;
+
+ if (!(enabledChanged || stickyChanged || thresholdChanged)) {
+ return;
+ }
+
+ mSettingBatterySaverEnabled = batterySaverEnabled;
+ mSettingBatterySaverEnabledSticky = batterySaverEnabledSticky;
+ mSettingBatterySaverTriggerThreshold = batterySaverTriggerThreshold;
+
+ if (enabledChanged) {
+ final String reason = batterySaverEnabled
+ ? "Global.low_power changed to 1" : "Global.low_power changed to 0";
+ enableBatterySaverLocked(/*enable=*/ batterySaverEnabled, /*manual=*/ true,
+ reason);
+ }
+ }
+
+ /**
+ * {@link com.android.server.power.PowerManagerService} calls it when battery state changes.
+ *
+ * Note this may be called before {@link #onBootCompleted} too.
+ */
+ public void setBatteryStatus(boolean newPowered, int newLevel, boolean newBatteryLevelLow) {
+ if (DEBUG) {
+ Slog.d(TAG, "setBatteryStatus: powered=" + newPowered + " level=" + newLevel
+ + " low=" + newBatteryLevelLow);
+ }
+ synchronized (mLock) {
+ mBatteryStatusSet = true;
+
+ final boolean poweredChanged = mIsPowered != newPowered;
+ final boolean levelChanged = mBatteryLevel != newLevel;
+ final boolean lowChanged = mIsBatteryLevelLow != newBatteryLevelLow;
+
+ if (!(poweredChanged || levelChanged || lowChanged)) {
+ return;
+ }
+
+ mIsPowered = newPowered;
+ mBatteryLevel = newLevel;
+ mIsBatteryLevelLow = newBatteryLevelLow;
+
+ doAutoBatterySaverLocked();
+ }
+ }
+
+ /**
+ * Decide whether to auto-start / stop battery saver.
+ */
+ private void doAutoBatterySaverLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "doAutoBatterySaverLocked: mBootCompleted=" + mBootCompleted
+ + " mSettingsLoaded=" + mSettingsLoaded
+ + " mBatteryStatusSet=" + mBatteryStatusSet
+ + " mIsBatteryLevelLow=" + mIsBatteryLevelLow
+ + " mBatterySaverSnoozing=" + mBatterySaverSnoozing
+ + " mIsPowered=" + mIsPowered
+ + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky);
+ }
+ if (!(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) {
+ return; // Not fully initialized yet.
+ }
+ if (!mIsBatteryLevelLow) {
+ updateSnoozingLocked(false, "Battery not low");
+ }
+ if (mIsPowered) {
+ updateSnoozingLocked(false, "Plugged in");
+ enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false, "Plugged in");
+
+ } else if (mSettingBatterySaverEnabledSticky) {
+ // Re-enable BS.
+ enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true, "Sticky restore");
+
+ } else if (mIsBatteryLevelLow) {
+ if (!mBatterySaverSnoozing && isAutoBatterySaverConfigured()) {
+ enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ false, "Auto ON");
+ }
+ } else { // Battery not low
+ enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false, "Auto OFF");
+ }
+ }
+
+ /**
+ * {@link com.android.server.power.PowerManagerService} calls it when
+ * {@link android.os.PowerManager#setPowerSaveMode} is called.
+ *
+ * Note this could? be called before {@link #onBootCompleted} too.
+ */
+ public void setBatterySaverEnabledManually(boolean enabled) {
+ if (DEBUG) {
+ Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled);
+ }
+ synchronized (mLock) {
+ enableBatterySaverLocked(/*enable=*/ enabled, /*manual=*/ true,
+ (enabled ? "Manual ON" : "Manual OFF"));
+ }
+ }
+
+ /**
+ * Actually enable / disable battery saver. Write the new state to the global settings
+ * and propagate it to {@link #mBatterySaverController}.
+ */
+ private void enableBatterySaverLocked(boolean enable, boolean manual, String reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual
+ + " reason=" + reason);
+ }
+ final boolean wasEnabled = mBatterySaverController.isEnabled();
+
+ if (wasEnabled == enable) {
+ if (DEBUG) {
+ Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled"));
+ }
+ return;
+ }
+ if (enable && mIsPowered) {
+ if (DEBUG) Slog.d(TAG, "Can't enable: isPowered");
+ return;
+ }
+
+ if (manual) {
+ if (enable) {
+ updateSnoozingLocked(false, "Manual snooze OFF");
+ } else {
+ // When battery saver is disabled manually (while battery saver is enabled)
+ // when the battery level is low, we "snooze" BS -- i.e. disable auto battery saver.
+ // We resume auto-BS once the battery level is not low, or the device is plugged in.
+ if (isBatterySaverEnabled() && mIsBatteryLevelLow) {
+ updateSnoozingLocked(true, "Manual snooze");
+ }
+ }
+ }
+
+ mSettingBatterySaverEnabled = enable;
+ putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);
+
+ if (manual) {
+ mSettingBatterySaverEnabledSticky = enable;
+ putGlobalSetting(Global.LOW_POWER_MODE_STICKY, enable ? 1 : 0);
+ }
+ mBatterySaverController.enableBatterySaver(enable);
+
+ if (DEBUG) {
+ Slog.d(TAG, "Battery saver: Enabled=" + enable
+ + " manual=" + manual
+ + " reason=" + reason);
+ }
+ }
+
+ private void updateSnoozingLocked(boolean snoozing, String reason) {
+ if (mBatterySaverSnoozing == snoozing) {
+ return;
+ }
+ if (DEBUG) Slog.d(TAG, "Snooze: " + (snoozing ? "start" : "stop") + " reason=" + reason);
+ mBatterySaverSnoozing = snoozing;
+ }
+
+ @VisibleForTesting
+ protected void putGlobalSetting(String key, int value) {
+ Global.putInt(mContext.getContentResolver(), key, value);
+ }
+
+ @VisibleForTesting
+ protected int getGlobalSetting(String key, int defValue) {
+ return Global.getInt(mContext.getContentResolver(), key, defValue);
+ }
+
+ public void dump(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("Battery saver state machine:");
+
+ pw.print(" Enabled=");
+ pw.println(mBatterySaverController.isEnabled());
+
+ pw.print(" mBootCompleted=");
+ pw.println(mBootCompleted);
+ pw.print(" mSettingsLoaded=");
+ pw.println(mSettingsLoaded);
+ pw.print(" mBatteryStatusSet=");
+ pw.println(mBatteryStatusSet);
+
+ pw.print(" mBatterySaverSnoozing=");
+ pw.println(mBatterySaverSnoozing);
+
+ pw.print(" mIsPowered=");
+ pw.println(mIsPowered);
+ pw.print(" mBatteryLevel=");
+ pw.println(mBatteryLevel);
+ pw.print(" mIsBatteryLevelLow=");
+ pw.println(mIsBatteryLevelLow);
+
+ pw.print(" mSettingBatterySaverEnabled=");
+ pw.println(mSettingBatterySaverEnabled);
+ pw.print(" mSettingBatterySaverEnabledSticky=");
+ pw.println(mSettingBatterySaverEnabledSticky);
+ pw.print(" mSettingBatterySaverTriggerThreshold=");
+ pw.println(mSettingBatterySaverTriggerThreshold);
+ }
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long tag) {
+ synchronized (mLock) {
+ final long token = proto.start(tag);
+
+ proto.write(BatterySaverStateMachineProto.ENABLED,
+ mBatterySaverController.isEnabled());
+
+ proto.write(BatterySaverStateMachineProto.BOOT_COMPLETED, mBootCompleted);
+ proto.write(BatterySaverStateMachineProto.SETTINGS_LOADED, mSettingsLoaded);
+ proto.write(BatterySaverStateMachineProto.BATTERY_STATUS_SET, mBatteryStatusSet);
+
+ proto.write(BatterySaverStateMachineProto.BATTERY_SAVER_SNOOZING,
+ mBatterySaverSnoozing);
+
+ proto.write(BatterySaverStateMachineProto.IS_POWERED, mIsPowered);
+ proto.write(BatterySaverStateMachineProto.BATTERY_LEVEL, mBatteryLevel);
+ proto.write(BatterySaverStateMachineProto.IS_BATTERY_LEVEL_LOW, mIsBatteryLevelLow);
+
+ proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED,
+ mSettingBatterySaverEnabled);
+ proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED_STICKY,
+ mSettingBatterySaverEnabledSticky);
+ proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_TRIGGER_THRESHOLD,
+ mSettingBatterySaverTriggerThreshold);
+
+ proto.end(token);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
new file mode 100644
index 0000000..ab640d6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.batterysaver;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.provider.Settings.Global;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContext;
+
+import com.google.common.base.Objects;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BatterySaverStateMachineTest {
+
+ private MyMockContext mMockContext;
+ private ContentResolver mMockContextResolver;
+ private BatterySaverController mMockBatterySaverController;
+ private Device mDevice;
+ private TestableBatterySaverStateMachine mTarget;
+
+ private class MyMockContext extends MockContext {
+ @Override
+ public ContentResolver getContentResolver() {
+ return mMockContextResolver;
+ }
+ }
+
+ private DevicePersistedState mPersistedState;
+
+ private class DevicePersistedState {
+ // Current battery level.
+ public int batteryLevel = 100;
+
+ // Whether battery level is currently low or not.
+ public boolean batteryLow = false;
+
+ // Whether the device is plugged in or not.
+ public boolean powered = false;
+
+ // Global settings.
+ public final HashMap<String, Integer> global = new HashMap<>();
+ }
+
+ /**
+ * This class simulates a device's volatile status that will be reset by {@link #initDevice()}.
+ */
+ private class Device {
+ public boolean batterySaverEnabled = false;
+
+ public int getLowPowerModeTriggerLevel() {
+ return mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+ }
+
+ public void setBatteryLevel(int level) {
+ mPersistedState.batteryLevel = level;
+ if (mPersistedState.batteryLevel <= Math.max(15, getLowPowerModeTriggerLevel())) {
+ mPersistedState.batteryLow = true;
+ } else if (mPersistedState.batteryLow
+ && (mPersistedState.batteryLevel >= (getLowPowerModeTriggerLevel() + 5))) {
+ mPersistedState.batteryLow = false;
+ }
+ pushBatteryStatus();
+ }
+
+ public void setPowered(boolean newPowered) {
+ mPersistedState.powered = newPowered;
+ pushBatteryStatus();
+ }
+
+ public void pushBatteryStatus() {
+ mTarget.setBatteryStatus(mPersistedState.powered, mPersistedState.batteryLevel,
+ mPersistedState.batteryLow);
+ }
+
+ public void pushGlobalSettings() {
+ mTarget.setSettingsLocked(
+ mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE, 0) != 0,
+ mPersistedState.global.getOrDefault(Global.LOW_POWER_MODE_STICKY, 0) != 0,
+ mDevice.getLowPowerModeTriggerLevel());
+ }
+
+ public void putGlobalSetting(String key, int value) {
+ mPersistedState.global.put(key, value);
+ pushGlobalSettings();
+ }
+
+ public int getGlobalSetting(String key, int defValue) {
+ return mPersistedState.global.getOrDefault(key, defValue);
+ }
+ }
+
+ /**
+ * Test target class.
+ */
+ private class TestableBatterySaverStateMachine extends BatterySaverStateMachine {
+ public TestableBatterySaverStateMachine() {
+ super(mMockContext, mMockBatterySaverController);
+ }
+
+ @Override
+ protected void putGlobalSetting(String key, int value) {
+ if (Objects.equal(mPersistedState.global.get(key), value)) {
+ return;
+ }
+ mDevice.putGlobalSetting(key, value);
+ }
+
+ @Override
+ protected int getGlobalSetting(String key, int defValue) {
+ return mDevice.getGlobalSetting(key, defValue);
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockContext = new MyMockContext();
+ mMockContextResolver = mock(ContentResolver.class);
+ mMockBatterySaverController = mock(BatterySaverController.class);
+
+ doAnswer((inv) -> mDevice.batterySaverEnabled = inv.getArgument(0))
+ .when(mMockBatterySaverController).enableBatterySaver(anyBoolean());
+ when(mMockBatterySaverController.isEnabled())
+ .thenAnswer((inv) -> mDevice.batterySaverEnabled);
+
+ mPersistedState = new DevicePersistedState();
+ initDevice();
+ }
+
+ private void initDevice() {
+ mDevice = new Device();
+
+ mTarget = new TestableBatterySaverStateMachine();
+
+ mDevice.pushBatteryStatus();
+ mDevice.pushGlobalSettings();
+ mTarget.onBootCompleted();
+ }
+
+ @Test
+ public void testNoAutoBatterySaver() {
+ assertEquals(0, mDevice.getLowPowerModeTriggerLevel());
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(90);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(50);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(50, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(16);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(16, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // When LOW_POWER_MODE_TRIGGER_LEVEL is 0, 15% will still trigger low-battery, but
+ // BS wont be enabled.
+ mDevice.setBatteryLevel(15);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(15, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(10);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(10, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ // Manually enable BS.
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(10, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(50);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(50, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Start charging. It'll disable BS.
+ mDevice.setPowered(true);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(50, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(60);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(60, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Unplug.
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(60, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(10);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(10, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(80);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Reboot the device.
+ initDevice();
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Sticky.
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(30);
+ initDevice();
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Still sticky.
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mTarget.setBatterySaverEnabledManually(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ initDevice(); // reboot.
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+ }
+
+ @Test
+ public void testAutoBatterySaver() {
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(90);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(51);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(51, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Hit the threshold. BS should be enabled.
+ mDevice.setBatteryLevel(50);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(50, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ // Battery goes up, but until it hits 55%, we still keep BS on.
+ mDevice.setBatteryLevel(54);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(54, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ // 50% + 5%, now BS will be off.
+ mDevice.setBatteryLevel(55);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(55, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(40);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(40, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(40, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(40, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze.
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(40, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(30);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ // Plug in and out, snooze will reset.
+ mDevice.setPowered(true);
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(60);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(60, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(60, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(50);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(50, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(70);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(70, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Bump ump the threshold.
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 70);
+ mDevice.setBatteryLevel(mPersistedState.batteryLevel);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(70, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ // Then down.
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 60);
+ mDevice.setBatteryLevel(mPersistedState.batteryLevel);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(70, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Reboot in low state -> automatically enable BS.
+ mDevice.setPowered(false);
+ mDevice.setBatteryLevel(30);
+ mTarget.setBatterySaverEnabledManually(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ initDevice();
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+ }
+
+ @Test
+ public void testAutoBatterySaver_withSticky() {
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
+
+ mTarget.setBatterySaverEnabledManually(true);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(30);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(80);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Still enabled.
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(80, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(30);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled); // Restores BS.
+ assertEquals(30, mPersistedState.batteryLevel);
+ assertEquals(true, mPersistedState.batteryLow);
+
+ mDevice.setPowered(true);
+ mDevice.setBatteryLevel(90);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ initDevice();
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setPowered(false);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mTarget.setBatterySaverEnabledManually(false);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ initDevice();
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+ }
+
+ @Test
+ public void testNoAutoBatterySaver_fromAdb() {
+
+ assertEquals(0, mDevice.getLowPowerModeTriggerLevel());
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(100, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ mDevice.setBatteryLevel(90);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Enable
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE, 1);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Disable
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE, 0);
+
+ assertEquals(false, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Enable again
+ mDevice.putGlobalSetting(Global.LOW_POWER_MODE, 1);
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+
+ // Reboot -- setting BS from adb is also sticky.
+ initDevice();
+
+ assertEquals(true, mDevice.batterySaverEnabled);
+ assertEquals(90, mPersistedState.batteryLevel);
+ assertEquals(false, mPersistedState.batteryLow);
+ }
+}