diff options
4 files changed, 591 insertions, 110 deletions
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index 8fce94edb104..9bf1825a8da6 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -318,6 +318,16 @@ message BatterySaverStateMachineProto {      // Whether battery saver is enabled.      optional bool enabled = 1; +    enum StateEnum { +        STATE_UNKNOWN = 0; +        STATE_OFF = 1; +        STATE_MANUAL_ON = 2; +        STATE_AUTOMATIC_ON = 3; +        STATE_OFF_AUTOMATIC_SNOOZED = 4; +        STATE_PENDING_STICKY_ON = 5; +    } +    optional StateEnum state = 18; +      // Whether full battery saver is enabled.      optional bool is_full_enabled = 14; @@ -337,8 +347,7 @@ message BatterySaverStateMachineProto {      // 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; +    reserved 5; // battery_saver_snoozing      // Whether the device is connected to any power source.      optional bool is_powered = 6; @@ -373,5 +382,5 @@ message BatterySaverStateMachineProto {      // using elapsed realtime as the timebase.      optional int64 last_adaptive_battery_saver_changed_externally_elapsed = 17; -    // Next tag: 18 +    // Next tag: 19  } diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java index 5adcf352e260..f0e462503769 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java @@ -115,9 +115,41 @@ public class BatterySaverController implements BatterySaverPolicyListener {      public static final int REASON_SETTING_CHANGED = 8;      public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON = 9;      public static final int REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF = 10; -    public static final int REASON_STICKY_RESTORE_OFF = 11; -    public static final int REASON_ADAPTIVE_DYNAMIC_POWER_SAVINGS_CHANGED = 12; -    public static final int REASON_TIMEOUT = 13; +    public static final int REASON_ADAPTIVE_DYNAMIC_POWER_SAVINGS_CHANGED = 11; +    public static final int REASON_TIMEOUT = 12; + +    static String reasonToString(int reason) { +        switch (reason) { +            case BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_ON: +                return "Percentage Auto ON"; +            case BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_OFF: +                return "Percentage Auto OFF"; +            case BatterySaverController.REASON_MANUAL_ON: +                return "Manual ON"; +            case BatterySaverController.REASON_MANUAL_OFF: +                return "Manual OFF"; +            case BatterySaverController.REASON_STICKY_RESTORE: +                return "Sticky restore"; +            case BatterySaverController.REASON_INTERACTIVE_CHANGED: +                return "Interactivity changed"; +            case BatterySaverController.REASON_POLICY_CHANGED: +                return "Policy changed"; +            case BatterySaverController.REASON_PLUGGED_IN: +                return "Plugged in"; +            case BatterySaverController.REASON_SETTING_CHANGED: +                return "Setting changed"; +            case BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON: +                return "Dynamic Warning Auto ON"; +            case BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF: +                return "Dynamic Warning Auto OFF"; +            case BatterySaverController.REASON_ADAPTIVE_DYNAMIC_POWER_SAVINGS_CHANGED: +                return "Adaptive Power Savings changed"; +            case BatterySaverController.REASON_TIMEOUT: +                return "timeout"; +            default: +                return "Unknown reason: " + reason; +        } +    }      /**       * Plugin interface. All methods are guaranteed to be called on the same (handler) thread. diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java index af5d40bfbcec..8f2e997d319e 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java @@ -15,6 +15,8 @@   */  package com.android.server.power.batterysaver; +import static com.android.server.power.batterysaver.BatterySaverController.reasonToString; +  import android.app.Notification;  import android.app.NotificationChannel;  import android.app.NotificationManager; @@ -49,6 +51,42 @@ import java.io.PrintWriter;   * Do not call out with the lock held. (Settings provider is okay.)   *   * Test: atest com.android.server.power.batterysaver.BatterySaverStateMachineTest + * + * Current state machine. This can be visualized using Graphviz: +   <pre> + +   digraph { +     STATE_OFF +     STATE_MANUAL_ON [label="STATE_MANUAL_ON\nTurned on manually by the user"] +     STATE_AUTOMATIC_ON [label="STATE_AUTOMATIC_ON\nTurned on automatically by the system"] +     STATE_OFF_AUTOMATIC_SNOOZED [ +       label="STATE_OFF_AUTOMATIC_SNOOZED\nTurned off manually by the user." +           + " The system should not turn it back on automatically." +     ] +     STATE_PENDING_STICKY_ON [ +       label="STATE_PENDING_STICKY_ON\n" +           + " Turned on manually by the user and then plugged in. Will turn back on after unplug." +     ] + +     STATE_OFF -> STATE_MANUAL_ON [label="manual"] +     STATE_OFF -> STATE_AUTOMATIC_ON [label="Auto on AND charge <= auto threshold"] + +     STATE_MANUAL_ON -> STATE_OFF [label="manual\nOR\nPlugged & sticky disabled"] +     STATE_MANUAL_ON -> STATE_PENDING_STICKY_ON [label="Plugged & sticky enabled"] + +     STATE_PENDING_STICKY_ON -> STATE_MANUAL_ON [label="Unplugged & sticky enabled"] +     STATE_PENDING_STICKY_ON -> STATE_OFF [ +       label="Sticky disabled\nOR\nSticky auto off enabled AND charge >= sticky auto off threshold" +     ] + +     STATE_AUTOMATIC_ON -> STATE_OFF [label="Plugged"] +     STATE_AUTOMATIC_ON -> STATE_OFF_AUTOMATIC_SNOOZED [label="Manual"] + +     STATE_OFF_AUTOMATIC_SNOOZED -> STATE_OFF [label="Plug\nOR\nCharge > auto threshold"] +     STATE_OFF_AUTOMATIC_SNOOZED -> STATE_MANUAL_ON [label="manual"] + +     </pre> +   }   */  public class BatterySaverStateMachine {      private static final String TAG = "BatterySaverStateMachine"; @@ -60,6 +98,25 @@ public class BatterySaverStateMachine {      private static final long ADAPTIVE_CHANGE_TIMEOUT_MS = 24 * 60 * 60 * 1000L; +    /** Turn off adaptive battery saver if the device has charged above this level. */ +    private static final int ADAPTIVE_AUTO_DISABLE_BATTERY_LEVEL = 80; + +    private static final int STATE_OFF = BatterySaverStateMachineProto.STATE_OFF; + +    /** Turned on manually by the user. */ +    private static final int STATE_MANUAL_ON = BatterySaverStateMachineProto.STATE_MANUAL_ON; + +    /** Turned on automatically by the system. */ +    private static final int STATE_AUTOMATIC_ON = BatterySaverStateMachineProto.STATE_AUTOMATIC_ON; + +    /** Turned off manually by the user. The system should not turn it back on automatically. */ +    private static final int STATE_OFF_AUTOMATIC_SNOOZED = +            BatterySaverStateMachineProto.STATE_OFF_AUTOMATIC_SNOOZED; + +    /** Turned on manually by the user and then plugged in. Will turn back on after unplug. */ +    private static final int STATE_PENDING_STICKY_ON = +            BatterySaverStateMachineProto.STATE_PENDING_STICKY_ON; +      private final Context mContext;      private final BatterySaverController mBatterySaverController; @@ -75,6 +132,9 @@ public class BatterySaverStateMachine {      @GuardedBy("mLock")      private boolean mBatteryStatusSet; +    @GuardedBy("mLock") +    private int mState; +      /** Whether the device is connected to any power source. */      @GuardedBy("mLock")      private boolean mIsPowered; @@ -142,13 +202,6 @@ public class BatterySaverStateMachine {      private boolean mDynamicPowerSavingsBatterySaver;      /** -     * 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; - -    /**       * Last reason passed to {@link #enableBatterySaverLocked}.       */      @GuardedBy("mLock") @@ -181,6 +234,7 @@ public class BatterySaverStateMachine {          mLock = lock;          mContext = context;          mBatterySaverController = batterySaverController; +        mState = STATE_OFF;          mBatterySaverStickyBehaviourDisabled = mContext.getResources().getBoolean(                  com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled); @@ -188,8 +242,36 @@ public class BatterySaverStateMachine {                  com.android.internal.R.integer.config_dynamicPowerSavingsDefaultDisableThreshold);      } -    private boolean isAutoBatterySaverConfiguredLocked() { -        return mSettingBatterySaverTriggerThreshold > 0; +    /** @return true if the automatic percentage based mode should be used */ +    private boolean isAutomaticModeActiveLocked() { +        return mSettingAutomaticBatterySaver == PowerManager.POWER_SAVER_MODE_PERCENTAGE +                && mSettingBatterySaverTriggerThreshold > 0; +    } + +    /** +     * The returned value won't necessarily make sense if {@link #isAutomaticModeActiveLocked()} +     * returns {@code false}. +     * +     * @return true if the battery level is below automatic's threshold. +     */ +    private boolean isInAutomaticLowZoneLocked() { +        return mIsBatteryLevelLow; +    } + +    /** @return true if the dynamic mode should be used */ +    private boolean isDynamicModeActiveLocked() { +        return mSettingAutomaticBatterySaver == PowerManager.POWER_SAVER_MODE_DYNAMIC +                && mDynamicPowerSavingsBatterySaver; +    } + +    /** +     * The returned value won't necessarily make sense if {@link #isDynamicModeActiveLocked()} +     * returns {@code false}. +     * +     * @return true if the battery level is below dynamic's threshold. +     */ +    private boolean isInDynamicLowZoneLocked() { +        return mBatteryLevel <= mDynamicPowerSavingsDisableThreshold;      }      /** @@ -233,7 +315,14 @@ public class BatterySaverStateMachine {                      Settings.Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL),                      false, mSettingsObserver, UserHandle.USER_SYSTEM); +              synchronized (mLock) { +                final boolean lowPowerModeEnabledSticky = getGlobalSetting( +                        Settings.Global.LOW_POWER_MODE_STICKY, 0) != 0; + +                if (lowPowerModeEnabledSticky) { +                    mState = STATE_PENDING_STICKY_ON; +                }                  mBootCompleted = true; @@ -362,6 +451,8 @@ public class BatterySaverStateMachine {                      ? "Global.low_power changed to 1" : "Global.low_power changed to 0";              enableBatterySaverLocked(/*enable=*/ batterySaverEnabled, /*manual=*/ true,                      BatterySaverController.REASON_SETTING_CHANGED, reason); +        } else { +            doAutoBatterySaverLocked();          }      } @@ -428,17 +519,6 @@ public class BatterySaverStateMachine {          }      } -    @GuardedBy("mLock") -    private boolean isBatteryLowLocked() { -        final boolean percentageLow = -                mSettingAutomaticBatterySaver == PowerManager.POWER_SAVER_MODE_PERCENTAGE -                && mIsBatteryLevelLow; -        final boolean dynamicPowerSavingsLow = -                mSettingAutomaticBatterySaver == PowerManager.POWER_SAVER_MODE_DYNAMIC -                && mBatteryLevel <= mDynamicPowerSavingsDisableThreshold; -        return percentageLow || dynamicPowerSavingsLow; -    } -      /**       * Decide whether to auto-start / stop battery saver.       */ @@ -449,7 +529,6 @@ public class BatterySaverStateMachine {                      + " mSettingsLoaded=" + mSettingsLoaded                      + " mBatteryStatusSet=" + mBatteryStatusSet                      + " mIsBatteryLevelLow=" + mIsBatteryLevelLow -                    + " mBatterySaverSnoozing=" + mBatterySaverSnoozing                      + " mIsPowered=" + mIsPowered                      + " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver                      + " mSettingBatterySaverEnabledSticky=" + mSettingBatterySaverEnabledSticky @@ -460,66 +539,166 @@ public class BatterySaverStateMachine {              return; // Not fully initialized yet.          } -        if (!isBatteryLowLocked()) { -            updateSnoozingLocked(false, "Battery not low"); -        } +        updateStateLocked(false, false); +        // Adaptive control.          if (SystemClock.elapsedRealtime() - mLastAdaptiveBatterySaverChangedExternallyElapsed                  > ADAPTIVE_CHANGE_TIMEOUT_MS) {              mBatterySaverController.setAdaptivePolicyEnabledLocked(                      false, BatterySaverController.REASON_TIMEOUT);              mBatterySaverController.resetAdaptivePolicyLocked(                      BatterySaverController.REASON_TIMEOUT); +        } else if (mIsPowered && mBatteryLevel >= ADAPTIVE_AUTO_DISABLE_BATTERY_LEVEL) { +            mBatterySaverController.setAdaptivePolicyEnabledLocked(false, +                    BatterySaverController.REASON_PLUGGED_IN); +        } +    } + +    /** +     * Update the state machine based on the current settings and battery/charge status. +     * +     * @param manual Whether the change was made by the user. +     * @param enable Whether the user wants to turn battery saver on or off. Is only used if {@param +     *               manual} is true. +     */ +    @GuardedBy("mLock") +    private void updateStateLocked(boolean manual, boolean enable) { +        if (!manual && !(mBootCompleted && mSettingsLoaded && mBatteryStatusSet)) { +            return; // Not fully initialized yet.          } -        if (mIsPowered) { -            updateSnoozingLocked(false, "Plugged in"); -            enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false, -                    BatterySaverController.REASON_PLUGGED_IN, -                    "Plugged in"); +        switch (mState) { +            case STATE_OFF: { +                if (!mIsPowered) { +                    if (manual) { +                        if (!enable) { +                            Slog.e(TAG, "Tried to disable BS when it's already OFF"); +                            return; +                        } +                        enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, +                                BatterySaverController.REASON_MANUAL_ON); +                        mState = STATE_MANUAL_ON; +                    } else if (isAutomaticModeActiveLocked() && isInAutomaticLowZoneLocked()) { +                        enableBatterySaverLocked(/*enable*/ true, /*manual*/ false, +                                BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_ON); +                        mState = STATE_AUTOMATIC_ON; +                    } else if (isDynamicModeActiveLocked() && isInDynamicLowZoneLocked()) { +                        enableBatterySaverLocked(/*enable*/ true, /*manual*/ false, +                                BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON); +                        mState = STATE_AUTOMATIC_ON; +                    } +                } +                break; +            } -            if (mBatteryLevel >= 80 /* Arbitrary level */) { -                mBatterySaverController.setAdaptivePolicyEnabledLocked( -                        false, BatterySaverController.REASON_PLUGGED_IN); +            case STATE_MANUAL_ON: { +                if (manual) { +                    if (enable) { +                        Slog.e(TAG, "Tried to enable BS when it's already MANUAL_ON"); +                        return; +                    } +                    enableBatterySaverLocked(/*enable*/ false, /*manual*/ true, +                            BatterySaverController.REASON_MANUAL_OFF); +                    mState = STATE_OFF; +                } else if (mIsPowered) { +                    enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, +                            BatterySaverController.REASON_PLUGGED_IN); +                    if (mSettingBatterySaverEnabledSticky +                            && !mBatterySaverStickyBehaviourDisabled) { +                        mState = STATE_PENDING_STICKY_ON; +                    } else { +                        mState = STATE_OFF; +                    } +                } +                break;              } -        } else if (mSettingBatterySaverEnabledSticky && !mBatterySaverStickyBehaviourDisabled) { -            if (mSettingBatterySaverStickyAutoDisableEnabled -                    && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold) { -                setStickyActive(false); -            } else { -                // Re-enable BS. -                enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ true, -                        BatterySaverController.REASON_STICKY_RESTORE, -                        "Sticky restore"); +            case STATE_AUTOMATIC_ON: { +                if (mIsPowered) { +                    enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, +                            BatterySaverController.REASON_PLUGGED_IN); +                    mState = STATE_OFF; +                } else if (manual) { +                    if (enable) { +                        Slog.e(TAG, "Tried to enable BS when it's already AUTO_ON"); +                        return; +                    } +                    enableBatterySaverLocked(/*enable*/ false, /*manual*/ true, +                            BatterySaverController.REASON_MANUAL_OFF); +                    // 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. +                    mState = STATE_OFF_AUTOMATIC_SNOOZED; +                } else if (isAutomaticModeActiveLocked() && !isInAutomaticLowZoneLocked()) { +                    enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, +                            BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_OFF); +                    mState = STATE_OFF; +                } else if (isDynamicModeActiveLocked() && !isInDynamicLowZoneLocked()) { +                    enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, +                            BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF); +                    mState = STATE_OFF; +                } else if (!isAutomaticModeActiveLocked() && !isDynamicModeActiveLocked()) { +                    enableBatterySaverLocked(/*enable*/ false, /*manual*/ false, +                            BatterySaverController.REASON_SETTING_CHANGED); +                    mState = STATE_OFF; +                } +                break;              } -        } else if (mSettingAutomaticBatterySaver -                == PowerManager.POWER_SAVER_MODE_PERCENTAGE -                && isAutoBatterySaverConfiguredLocked()) { -            if (mIsBatteryLevelLow && !mBatterySaverSnoozing) { -                enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ false, -                        BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_ON, -                        "Percentage Auto ON"); -            } else { -                // Battery not low -                enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false, -                        BatterySaverController.REASON_PERCENTAGE_AUTOMATIC_OFF, -                        "Percentage Auto OFF"); +            case STATE_OFF_AUTOMATIC_SNOOZED: { +                if (manual) { +                    if (!enable) { +                        Slog.e(TAG, "Tried to disable BS when it's already AUTO_SNOOZED"); +                        return; +                    } +                    enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, +                            BatterySaverController.REASON_MANUAL_ON); +                    mState = STATE_MANUAL_ON; +                } else if (mIsPowered // Plugging in resets snooze. +                        || (isAutomaticModeActiveLocked() && !isInAutomaticLowZoneLocked()) +                        || (isDynamicModeActiveLocked() && !isInDynamicLowZoneLocked()) +                        || (!isAutomaticModeActiveLocked() && !isDynamicModeActiveLocked())) { +                    mState = STATE_OFF; +                } +                break;              } -        } else if (mSettingAutomaticBatterySaver -                == PowerManager.POWER_SAVER_MODE_DYNAMIC) { -            if (mBatteryLevel >= mDynamicPowerSavingsDisableThreshold) { -                enableBatterySaverLocked(/*enable=*/ false, /*manual=*/ false, -                        BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_OFF, -                        "Dynamic Warning Auto OFF"); -            } else if (mDynamicPowerSavingsBatterySaver && !mBatterySaverSnoozing) { -                enableBatterySaverLocked(/*enable=*/ true, /*manual=*/ false, -                        BatterySaverController.REASON_DYNAMIC_POWER_SAVINGS_AUTOMATIC_ON, -                        "Dynamic Warning Auto ON"); + +            case STATE_PENDING_STICKY_ON: { +                if (manual) { +                    // This shouldn't be possible. We'll only be in this state when the device is +                    // plugged in, so the user shouldn't be able to manually change state. +                    Slog.e(TAG, "Tried to manually change BS state from PENDING_STICKY_ON"); +                    return; +                } +                final boolean shouldTurnOffSticky = mSettingBatterySaverStickyAutoDisableEnabled +                        && mBatteryLevel >= mSettingBatterySaverStickyAutoDisableThreshold; +                final boolean isStickyDisabled = +                        mBatterySaverStickyBehaviourDisabled || !mSettingBatterySaverEnabledSticky; +                if (isStickyDisabled || shouldTurnOffSticky) { +                    setStickyActive(false); +                    mState = STATE_OFF; +                } else if (!mIsPowered) { +                    // Re-enable BS. +                    enableBatterySaverLocked(/*enable*/ true, /*manual*/ true, +                            BatterySaverController.REASON_STICKY_RESTORE); +                    mState = STATE_MANUAL_ON; +                } +                break;              } + +            default: +                Slog.wtf(TAG, "Unknown state: " + mState); +                break; +        } +    } + +    @VisibleForTesting +    int getState() { +        synchronized (mLock) { +            return mState;          } -        // do nothing if automatic battery saver mode = PERCENTAGE and low warning threshold = 0%      }      /** @@ -533,13 +712,17 @@ public class BatterySaverStateMachine {              Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled);          }          synchronized (mLock) { -            enableBatterySaverLocked(/*enable=*/ enabled, /*manual=*/ true, -                    (enabled ? BatterySaverController.REASON_MANUAL_ON -                            : BatterySaverController.REASON_MANUAL_OFF), -                    (enabled ? "Manual ON" : "Manual OFF")); +            updateStateLocked(true, enabled); +            // TODO: maybe turn off adaptive if it's on and advertiseIsEnabled is true and +            //  enabled is false          }      } +    @GuardedBy("mLock") +    private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason) { +        enableBatterySaverLocked(enable, manual, intReason, reasonToString(intReason)); +    } +      /**       * Actually enable / disable battery saver. Write the new state to the global settings       * and propagate it to {@link #mBatterySaverController}. @@ -566,20 +749,6 @@ public class BatterySaverStateMachine {          mLastChangedIntReason = intReason;          mLastChangedStrReason = strReason; -        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 (mBatterySaverController.isFullEnabled() && isBatteryLowLocked()) { -                    updateSnoozingLocked(true, "Manual snooze"); -                } -                // TODO: maybe turn off adaptive if it's on and advertiseIsEnabled is true -            } -        } -          mSettingBatterySaverEnabled = enable;          putGlobalSetting(Settings.Global.LOW_POWER_MODE, enable ? 1 : 0); @@ -641,15 +810,6 @@ public class BatterySaverStateMachine {          manager.cancel(DYNAMIC_MODE_NOTIFICATION_ID);      } -    @GuardedBy("mLock") -    private void updateSnoozingLocked(boolean snoozing, String reason) { -        if (mBatterySaverSnoozing == snoozing) { -            return; -        } -        if (DEBUG) Slog.d(TAG, "Snooze: " + (snoozing ? "start" : "stop")  + " reason=" + reason); -        mBatterySaverSnoozing = snoozing; -    } -      private void setStickyActive(boolean active) {          mSettingBatterySaverEnabledSticky = active;          putGlobalSetting(Settings.Global.LOW_POWER_MODE_STICKY, @@ -684,6 +844,8 @@ public class BatterySaverStateMachine {                  pw.print(")");              }              pw.println(); +            pw.print("  mState="); +            pw.println(mState);              pw.print("  mLastChangedIntReason=");              pw.println(mLastChangedIntReason); @@ -697,9 +859,6 @@ public class BatterySaverStateMachine {              pw.print("  mBatteryStatusSet=");              pw.println(mBatteryStatusSet); -            pw.print("  mBatterySaverSnoozing="); -            pw.println(mBatterySaverSnoozing); -              pw.print("  mIsPowered=");              pw.println(mIsPowered);              pw.print("  mBatteryLevel="); @@ -731,6 +890,7 @@ public class BatterySaverStateMachine {              proto.write(BatterySaverStateMachineProto.ENABLED,                      mBatterySaverController.isEnabled()); +            proto.write(BatterySaverStateMachineProto.STATE, mState);              proto.write(BatterySaverStateMachineProto.IS_FULL_ENABLED,                      mBatterySaverController.isFullEnabled());              proto.write(BatterySaverStateMachineProto.IS_ADAPTIVE_ENABLED, @@ -742,8 +902,6 @@ public class BatterySaverStateMachine {              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); diff --git a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java index 86e859809ffd..2e5efbd1deef 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java @@ -28,6 +28,7 @@ import android.app.NotificationManager;  import android.content.ContentResolver;  import android.content.Context;  import android.content.res.Resources; +import android.os.PowerManager;  import android.provider.Settings.Global;  import androidx.test.filters.SmallTest; @@ -452,6 +453,21 @@ public class BatterySaverStateMachineTest {          assertEquals(true, mDevice.batterySaverEnabled);          assertEquals(30, mPersistedState.batteryLevel);          assertEquals(true, mPersistedState.batteryLow); + +        // Disable auto battery saver. +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); +        mDevice.setBatteryLevel(25); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(25, mPersistedState.batteryLevel); +        assertEquals(false, mPersistedState.batteryLow); + +        // PowerManager sets batteryLow to true at 15% if battery saver trigger level is lower. +        mDevice.setBatteryLevel(15); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(15, mPersistedState.batteryLevel); +        assertEquals(true, mPersistedState.batteryLow);      }      @Test @@ -542,6 +558,12 @@ public class BatterySaverStateMachineTest {          assertEquals(100, mPersistedState.batteryLevel);          assertEquals(false, mPersistedState.batteryLow); +        mDevice.setBatteryLevel(97); + +        assertEquals(true, mDevice.batterySaverEnabled); // Stays on. +        assertEquals(97, mPersistedState.batteryLevel); +        assertEquals(false, mPersistedState.batteryLow); +          mDevice.setBatteryLevel(95);          assertEquals(true, mDevice.batterySaverEnabled); // Stays on. @@ -719,6 +741,48 @@ public class BatterySaverStateMachineTest {      }      @Test +    public void testAutoBatterySaver_withSticky_withAutoOffToggled() { +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50); +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1); +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90); + +        // Scenario 1: User turns BS on manually above the threshold, it shouldn't turn off even +        // with battery level change above threshold. +        mDevice.setBatteryLevel(100); +        mTarget.setBatterySaverEnabledManually(true); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(100, mPersistedState.batteryLevel); +        assertEquals(false, mPersistedState.batteryLow); + +        mDevice.setBatteryLevel(95); + +        assertEquals(true, mDevice.batterySaverEnabled); // Stays on. +        assertEquals(95, mPersistedState.batteryLevel); +        assertEquals(false, mPersistedState.batteryLow); + +        // Disable auto disable while in the pending sticky state. BS should reactivate after +        // unplug. +        mDevice.setPowered(true); +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 0); +        mDevice.setPowered(false); + +        assertEquals(true, mDevice.batterySaverEnabled); // Sticky BS should activate. +        assertEquals(95, mPersistedState.batteryLevel); +        assertEquals(false, mPersistedState.batteryLow); + +        // Enable auto disable while in the pending sticky state. Sticky should turn off after +        // unplug. +        mDevice.setPowered(true); +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1); +        mDevice.setPowered(false); + +        assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled. +        assertEquals(95, mPersistedState.batteryLevel); +        assertEquals(false, mPersistedState.batteryLow); +    } + +    @Test      public void testAutoBatterySaver_withStickyDisabled() {          when(mMockResources.getBoolean(                  com.android.internal.R.bool.config_batterySaverStickyBehaviourDisabled)) @@ -739,7 +803,9 @@ public class BatterySaverStateMachineTest {          assertEquals(30, mPersistedState.batteryLevel);          assertEquals(true, mPersistedState.batteryLow); +        mDevice.setPowered(true);          mDevice.setBatteryLevel(80); +        mDevice.setPowered(false);          assertEquals(false, mDevice.batterySaverEnabled); // Not sticky.          assertEquals(80, mPersistedState.batteryLevel); @@ -830,10 +896,9 @@ public class BatterySaverStateMachineTest {          assertEquals(90, mPersistedState.batteryLevel);          assertEquals(false, mPersistedState.batteryLow); -        // Reboot -- setting BS from adb is also sticky. +        // Reboot -- LOW_POWER_MODE shouldn't be persisted.          initDevice(); - -        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(false, mDevice.batterySaverEnabled);          assertEquals(90, mPersistedState.batteryLevel);          assertEquals(false, mPersistedState.batteryLow);      } @@ -841,7 +906,8 @@ public class BatterySaverStateMachineTest {      @Test      public void testAutoBatterySaver_smartBatterySaverEnabled() {          mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 50); -        mDevice.putGlobalSetting(Global.AUTOMATIC_POWER_SAVER_MODE, 1); +        mDevice.putGlobalSetting(Global.AUTOMATIC_POWER_SAVER_MODE, +                PowerManager.POWER_SAVER_MODE_DYNAMIC);          mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0);          assertEquals(false, mDevice.batterySaverEnabled); @@ -922,8 +988,8 @@ public class BatterySaverStateMachineTest {          mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 71);          mDevice.setBatteryLevel(mPersistedState.batteryLevel); -        // changes are only registered if some battery level changed -        assertEquals(false, mDevice.batterySaverEnabled); +        // Changes should register immediately. +        assertEquals(true, mDevice.batterySaverEnabled);          assertEquals(70, mPersistedState.batteryLevel);          mDevice.setBatteryLevel(69); @@ -935,8 +1001,8 @@ public class BatterySaverStateMachineTest {          mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 60);          mDevice.setBatteryLevel(mPersistedState.batteryLevel); -        // changes are only registered if battery level changed -        assertEquals(true, mDevice.batterySaverEnabled); +        // Changes should register immediately. +        assertEquals(false, mDevice.batterySaverEnabled);          assertEquals(69, mPersistedState.batteryLevel);          mDevice.setBatteryLevel(68); @@ -956,4 +1022,220 @@ public class BatterySaverStateMachineTest {          assertEquals(true, mDevice.batterySaverEnabled);          assertEquals(30, mPersistedState.batteryLevel);      } + +    @Test +    public void testAutoBatterySaver_snoozed_autoEnabled() { +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 30); +        // Test dynamic threshold higher than automatic to make sure it doesn't interfere when it's +        // not enabled. +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 50); +        mDevice.putGlobalSetting(Global.AUTOMATIC_POWER_SAVER_MODE, +                PowerManager.POWER_SAVER_MODE_PERCENTAGE); +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(100, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(90); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(90, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(51); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(51, mPersistedState.batteryLevel); + +        // Hit dynamic threshold. BS should be disabled since dynamic is off +        mDevice.setBatteryLevel(50); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(50, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(30); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mDevice.setPowered(true); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mDevice.setPowered(false); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze. + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(20); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(20, mPersistedState.batteryLevel); + +        // Lower threshold. Level is still below, so should still be snoozed. +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 25); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(20, mPersistedState.batteryLevel); + +        // Lower threshold even more. Battery no longer considered "low" so snoozing should be +        // disabled. +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 10); +        // "batteryLow" is set in setBatteryLevel. +        mDevice.setBatteryLevel(19); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(19, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(10); + +        assertEquals(true, mDevice.batterySaverEnabled); // No longer snoozing. +        assertEquals(10, mPersistedState.batteryLevel); + +        mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze. + +        // Plug in and out, snooze will reset. +        mDevice.setPowered(true); +        mDevice.setPowered(false); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(10, mPersistedState.batteryLevel); + +        mDevice.setPowered(true); +        mDevice.setBatteryLevel(60); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(60, mPersistedState.batteryLevel); + +        // Test toggling resets snooze. +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50); +        mDevice.setPowered(false); +        mDevice.setBatteryLevel(45); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(45, mPersistedState.batteryLevel); + +        mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze. +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(45, mPersistedState.batteryLevel); + +        // Disable and re-enable. +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50); + +        assertEquals(true, mDevice.batterySaverEnabled); // Snooze reset +        assertEquals(45, mPersistedState.batteryLevel); +    } + +    @Test +    public void testAutoBatterySaver_snoozed_dynamicEnabled() { +        // Test auto threshold higher than dynamic to make sure it doesn't interfere when it's +        // not enabled. +        mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50); +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 30); +        mDevice.putGlobalSetting(Global.AUTOMATIC_POWER_SAVER_MODE, +                PowerManager.POWER_SAVER_MODE_DYNAMIC); +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_ENABLED, 1); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(100, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(90); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(90, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(51); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(51, mPersistedState.batteryLevel); + +        // Hit automatic threshold. BS should be disabled since automatic is off +        mDevice.setBatteryLevel(50); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(50, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(30); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mDevice.setPowered(true); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mDevice.setPowered(false); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze. + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(30, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(20); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(20, mPersistedState.batteryLevel); + +        // Lower threshold. Level is still below, so should still be snoozed. +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 25); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(20, mPersistedState.batteryLevel); + +        // Lower threshold even more. Battery no longer considered "low" so snoozing should be +        // disabled. +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 10); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(20, mPersistedState.batteryLevel); + +        mDevice.setBatteryLevel(10); + +        assertEquals(true, mDevice.batterySaverEnabled); // No longer snoozing. +        assertEquals(10, mPersistedState.batteryLevel); + +        mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze. + +        // Plug in and out, snooze will reset. +        mDevice.setPowered(true); +        mDevice.setPowered(false); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(10, mPersistedState.batteryLevel); + +        mDevice.setPowered(true); +        mDevice.setBatteryLevel(60); + +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(60, mPersistedState.batteryLevel); + +        // Test toggling resets snooze. +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, 50); +        mDevice.setPowered(false); +        mDevice.setBatteryLevel(45); + +        assertEquals(true, mDevice.batterySaverEnabled); +        assertEquals(45, mPersistedState.batteryLevel); + +        mTarget.setBatterySaverEnabledManually(false); // Manually disable -> snooze. +        assertEquals(false, mDevice.batterySaverEnabled); +        assertEquals(45, mPersistedState.batteryLevel); + +        // Disable and re-enable. +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_ENABLED, 0); +        mDevice.putGlobalSetting(Global.DYNAMIC_POWER_SAVINGS_ENABLED, 1); + +        assertEquals(true, mDevice.batterySaverEnabled); // Snooze reset +        assertEquals(45, mPersistedState.batteryLevel); +    }  }  |