Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd38aa51 authored by Salvador Martinez's avatar Salvador Martinez
Browse files

Update triggering logic for hybrid notification

Currently in the worst case a user can receive 4 battery
notifications if the time estimates and percentages line up right.
This CL makes it so a user can at MOST receive one "low" battery
warning and one "critical" battery warning per charge cycle. A
charge cycle is restarted when a user charges to at least 45%
battery AND has 6 hours remaining. This does not affect the
behavior of the non-hybrid notification.

Test: robotests
Bug: 76203825
Change-Id: Ib3c7fe589f1ce4c0cdb821e1f21d1139a56fad62
parent 0d5bbf77
Loading
Loading
Loading
Loading
+46 −30
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import com.android.systemui.statusbar.phone.StatusBar;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.Arrays;

public class PowerUI extends SystemUI {
@@ -61,6 +62,8 @@ public class PowerUI extends SystemUI {
    private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
    private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
    static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
    private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
    private static final long SIX_HOURS_MILLIS = Duration.ofHours(6).toMillis();

    private final Handler mHandler = new Handler();
    private final Receiver mReceiver = new Receiver();
@@ -69,7 +72,6 @@ public class PowerUI extends SystemUI {
    private HardwarePropertiesManager mHardwarePropertiesManager;
    private WarningsUI mWarnings;
    private final Configuration mLastConfiguration = new Configuration();
    private int mBatteryLevel = 100;
    private long mTimeRemaining = Long.MAX_VALUE;
    private int mPlugType = 0;
    private int mInvalidCharger = 0;
@@ -88,6 +90,7 @@ public class PowerUI extends SystemUI {
    private long mNextLogTime;
    private IThermalService mThermalService;

    @VisibleForTesting int mBatteryLevel = 100;
    @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;

    // by using the same instance (method references are not guaranteed to be the same object
@@ -205,12 +208,6 @@ public class PowerUI extends SystemUI {

                final boolean plugged = mPlugType != 0;
                final boolean oldPlugged = oldPlugType != 0;
                // if we are now unplugged but we were previously plugged in we should allow the
                // time based trigger again.
                if (!plugged && plugged != oldPlugged) {
                    mLowWarningShownThisChargeCycle = false;
                    mSevereWarningShownThisChargeCycle = false;
                }

                int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
                int bucket = findBatteryLevelBucket(mBatteryLevel);
@@ -261,7 +258,8 @@ public class PowerUI extends SystemUI {
        boolean isPowerSaver = mPowerManager.isPowerSaveMode();
        // only play SFX when the dialog comes up or the bucket changes
        final boolean playSound = bucket != oldBucket || oldPlugged;
        if (mEnhancedEstimates.isHybridNotificationEnabled()) {
        final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
        if (hybridEnabled) {
            final Estimate estimate = mEnhancedEstimates.getEstimate();
            // Turbo is not always booted once SysUI is running so we have ot make sure we actually
            // get data back
@@ -270,6 +268,14 @@ public class PowerUI extends SystemUI {
                mWarnings.updateEstimate(estimate);
                mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
                        mEnhancedEstimates.getSevereWarningThreshold());

                // if we are now over 45% battery & 6 hours remaining we can trigger hybrid
                // notification again
                if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET
                        && mTimeRemaining > SIX_HOURS_MILLIS) {
                    mLowWarningShownThisChargeCycle = false;
                    mSevereWarningShownThisChargeCycle = false;
                }
            }
        }

@@ -277,14 +283,16 @@ public class PowerUI extends SystemUI {
                mTimeRemaining, isPowerSaver, mBatteryStatus)) {
            mWarnings.showLowBatteryWarning(playSound);

            // mark if we've already shown a warning this cycle. This will prevent the time based
            // trigger from spamming users since the time remaining can vary based on current
            // device usage.
            if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()) {
            // mark if we've already shown a warning this cycle. This will prevent the notification
            // trigger from spamming users by only showing low/critical warnings once per cycle
            if (hybridEnabled) {
                if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
                        || mBatteryLevel < mLowBatteryReminderLevels[1]) {
                    mSevereWarningShownThisChargeCycle = true;
                } else {
                    mLowWarningShownThisChargeCycle = true;
                }
            }
        } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
                isPowerSaver)) {
            mWarnings.dismissLowBatteryWarning();
@@ -295,12 +303,16 @@ public class PowerUI extends SystemUI {

    @VisibleForTesting
    boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
            int bucket, long timeRemaining, boolean isPowerSaver, int mBatteryStatus) {
            int bucket, long timeRemaining, boolean isPowerSaver, int batteryStatus) {
        if (mEnhancedEstimates.isHybridNotificationEnabled()) {
            // triggering logic when enhanced estimate is available
            return isEnhancedTrigger(plugged, timeRemaining, isPowerSaver, batteryStatus);
        }
        // legacy triggering logic
        return !plugged
                && !isPowerSaver
                && (((bucket < oldBucket || oldPlugged) && bucket < 0)
                        || isTimeBasedTrigger(timeRemaining))
                && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
                && (((bucket < oldBucket || oldPlugged) && bucket < 0))
                && batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
    }

    @VisibleForTesting
@@ -315,19 +327,23 @@ public class PowerUI extends SystemUI {
                        || hybridWouldDismiss));
    }

    private boolean isTimeBasedTrigger(long timeRemaining) {
        if (!mEnhancedEstimates.isHybridNotificationEnabled()) {
    private boolean isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver,
            int batteryStatus) {
        if (plugged || isPowerSaver || batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
            return false;
        }
        int warnLevel = mLowBatteryReminderLevels[0];
        int critLevel = mLowBatteryReminderLevels[1];

        // Only show the time based warning once per charge cycle
        final boolean canShowWarning = timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
                && !mLowWarningShownThisChargeCycle;
        // Only show the low warning once per charge cycle
        final boolean canShowWarning = !mLowWarningShownThisChargeCycle
                && (timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
                        || mBatteryLevel <= warnLevel);

        // Only show the severe time based warning once per charge cycle
        final boolean canShowSevereWarning =
                timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
                        && !mSevereWarningShownThisChargeCycle;
        // Only show the severe warning once per charge cycle
        final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
                && (timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
                        || mBatteryLevel <= critLevel);

        return canShowWarning || canShowSevereWarning;
    }
+9 −2
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ public class PowerUITest extends SysuiTestCase {
    public static final int BELOW_WARNING_BUCKET = -1;
    public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
    public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
    private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis();
    private HardwarePropertiesManager mHardProps;
    private WarningsUI mMockWarnings;
    private PowerUI mPowerUI;
@@ -198,6 +199,7 @@ public class PowerUITest extends SysuiTestCase {
        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
        mPowerUI.mBatteryLevel = 10;
        mPowerUI.start();

        // unplugged device that would show the non-hybrid notification and the hybrid
@@ -213,6 +215,7 @@ public class PowerUITest extends SysuiTestCase {
        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
        mPowerUI.mBatteryLevel = 10;
        mPowerUI.start();

        // unplugged device that would show the non-hybrid but not the hybrid
@@ -254,13 +257,14 @@ public class PowerUITest extends SysuiTestCase {
   }

    @Test
    public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() {
    public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnknown_returnsNoShow() {
        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
        mPowerUI.start();

        // Unknown battery status device that would show the neither due
        // Unknown battery status device that would show the neither due to the battery status being
        // unknown
        boolean shouldShow =
                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
                        BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,
@@ -295,6 +299,9 @@ public class PowerUITest extends SysuiTestCase {

        mPowerUI.maybeShowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
                ABOVE_WARNING_BUCKET);

        // reduce battery level to handle time based trigger -> level trigger interactions
        mPowerUI.mBatteryLevel = 10;
        boolean shouldShow =
                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
                        ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD,