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

Commit 140650ce authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Refactor battery warning code to make it easier to read"

parents af597830 4387bd52
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
package com.android.systemui.power

import com.android.systemui.power.PowerUI.NO_ESTIMATE_AVAILABLE

/**
 * A simple data class to snapshot battery state when a particular check for the
 * low battery warning is running in the background.
 */
data class BatteryStateSnapshot(
    val batteryLevel: Int,
    val isPowerSaver: Boolean,
    val plugged: Boolean,
    val bucket: Int,
    val batteryStatus: Int,
    val severeLevelThreshold: Int,
    val lowLevelThreshold: Int,
    val timeRemainingMillis: Long,
    val severeThresholdMillis: Long,
    val lowThresholdMillis: Long,
    val isBasedOnUsage: Boolean
) {
    /**
     * Returns whether hybrid warning logic/copy should be used for this snapshot
     */
    var isHybrid: Boolean = false
        private set

    init {
        this.isHybrid = true
    }

    constructor(
        batteryLevel: Int,
        isPowerSaver: Boolean,
        plugged: Boolean,
        bucket: Int,
        batteryStatus: Int,
        severeLevelThreshold: Int,
        lowLevelThreshold: Int
    ) : this(
        batteryLevel,
        isPowerSaver,
        plugged,
        bucket,
        batteryStatus,
        severeLevelThreshold,
        lowLevelThreshold,
        NO_ESTIMATE_AVAILABLE.toLong(),
        NO_ESTIMATE_AVAILABLE.toLong(),
        NO_ESTIMATE_AVAILABLE.toLong(),
        false
    ) {
        this.isHybrid = false
    }
}
+0 −11
Original line number Diff line number Diff line
package com.android.systemui.power;

public class Estimate {
    public final long estimateMillis;
    public final boolean isBasedOnUsage;

    public Estimate(long estimateMillis, boolean isBasedOnUsage) {
        this.estimateMillis = estimateMillis;
        this.isBasedOnUsage = isBasedOnUsage;
    }
}
+3 −0
Original line number Diff line number Diff line
package com.android.systemui.power

data class Estimate(val estimateMillis: Long, val isBasedOnUsage: Boolean)
 No newline at end of file
+17 −26
Original line number Diff line number Diff line
@@ -134,10 +134,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
    private int mShowing;

    private long mWarningTriggerTimeMs;

    private Estimate mEstimate;
    private long mLowWarningThreshold;
    private long mSevereWarningThreshold;
    private boolean mWarning;
    private boolean mShowAutoSaverSuggestion;
    private boolean mPlaySound;
@@ -148,6 +144,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
    private SystemUIDialog mHighTempDialog;
    private SystemUIDialog mThermalShutdownDialog;
    @VisibleForTesting SystemUIDialog mUsbHighTempDialog;
    private BatteryStateSnapshot mCurrentBatterySnapshot;

    /**
     */
@@ -195,17 +192,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
    }

    @Override
    public void updateEstimate(Estimate estimate) {
        mEstimate = estimate;
        if (estimate.estimateMillis <= mLowWarningThreshold) {
            mWarningTriggerTimeMs = System.currentTimeMillis();
        }
    }

    @Override
    public void updateThresholds(long lowThreshold, long severeThreshold) {
        mLowWarningThreshold = lowThreshold;
        mSevereWarningThreshold = severeThreshold;
    public void updateSnapshot(BatteryStateSnapshot snapshot) {
        mCurrentBatterySnapshot = snapshot;
    }

    private void updateNotification() {
@@ -254,15 +242,17 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {

    protected void showWarningNotification() {
        final String percentage = NumberFormat.getPercentInstance()
                .format((double) mBatteryLevel / 100.0);
                .format((double) mCurrentBatterySnapshot.getBatteryLevel() / 100.0);

        // get standard notification copy
        // get shared standard notification copy
        String title = mContext.getString(R.string.battery_low_title);
        String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
        String contentText;

        // override notification copy if hybrid notification enabled
        if (mEstimate != null) {
        // get correct content text if notification is hybrid or not
        if (mCurrentBatterySnapshot.isHybrid()) {
            contentText = getHybridContentString(percentage);
        } else {
            contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
        }

        final Notification.Builder nb =
@@ -282,8 +272,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
        }
        // Make the notification red if the percentage goes below a certain amount or the time
        // remaining estimate is disabled
        if (mEstimate == null || mBucket < 0
                || mEstimate.estimateMillis < mSevereWarningThreshold) {
        if (!mCurrentBatterySnapshot.isHybrid() || mBucket < 0
                || mCurrentBatterySnapshot.getTimeRemainingMillis()
                        < mCurrentBatterySnapshot.getSevereThresholdMillis()) {
            nb.setColor(Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorError));
        }

@@ -325,9 +316,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
    private String getHybridContentString(String percentage) {
        return PowerUtil.getBatteryRemainingStringFormatted(
                mContext,
            mEstimate.estimateMillis,
                mCurrentBatterySnapshot.getTimeRemainingMillis(),
                percentage,
            mEstimate.isBasedOnUsage);
                mCurrentBatterySnapshot.isBasedOnUsage());
    }

    private PendingIntent pendingBroadcast(String action) {
+172 −91
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ import java.util.Arrays;
import java.util.concurrent.Future;

public class PowerUI extends SystemUI {

    static final String TAG = "PowerUI";
    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
@@ -63,6 +64,7 @@ public class PowerUI extends SystemUI {
    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();
    public static final int NO_ESTIMATE_AVAILABLE = -1;

    private final Handler mHandler = new Handler();
    @VisibleForTesting
@@ -71,13 +73,9 @@ public class PowerUI extends SystemUI {
    private PowerManager mPowerManager;
    private WarningsUI mWarnings;
    private final Configuration mLastConfiguration = new Configuration();
    private long mTimeRemaining = Long.MAX_VALUE;
    private int mPlugType = 0;
    private int mInvalidCharger = 0;
    private EnhancedEstimates mEnhancedEstimates;
    private Estimate mLastEstimate;
    private boolean mLowWarningShownThisChargeCycle;
    private boolean mSevereWarningShownThisChargeCycle;
    private Future mLastShowWarningTask;
    private boolean mEnableSkinTemperatureWarning;
    private boolean mEnableUsbTemperatureAlarm;
@@ -87,6 +85,10 @@ public class PowerUI extends SystemUI {

    private long mScreenOffTime = -1;

    @VisibleForTesting boolean mLowWarningShownThisChargeCycle;
    @VisibleForTesting boolean mSevereWarningShownThisChargeCycle;
    @VisibleForTesting BatteryStateSnapshot mCurrentBatteryStateSnapshot;
    @VisibleForTesting BatteryStateSnapshot mLastBatteryStateSnapshot;
    @VisibleForTesting IThermalService mThermalService;

    @VisibleForTesting int mBatteryLevel = 100;
@@ -205,6 +207,7 @@ public class PowerUI extends SystemUI {
                mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
                final int oldInvalidCharger = mInvalidCharger;
                mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
                mLastBatteryStateSnapshot = mCurrentBatteryStateSnapshot;

                final boolean plugged = mPlugType != 0;
                final boolean oldPlugged = oldPlugType != 0;
@@ -233,16 +236,22 @@ public class PowerUI extends SystemUI {
                    mWarnings.dismissInvalidChargerWarning();
                } else if (mWarnings.isInvalidChargerWarningShowing()) {
                    // if invalid charger is showing, don't show low battery
                    if (DEBUG) {
                        Slog.d(TAG, "Bad Charger");
                    }
                    return;
                }

                // Show the correct version of low battery warning if needed
                if (mLastShowWarningTask != null) {
                    mLastShowWarningTask.cancel(true);
                    if (DEBUG) {
                        Slog.d(TAG, "cancelled task");
                    }
                }
                mLastShowWarningTask = ThreadUtils.postOnBackgroundThread(() -> {
                    maybeShowBatteryWarning(
                            oldBatteryLevel, plugged, oldPlugged, oldBucket, bucket);
                    maybeShowBatteryWarningV2(
                            plugged, bucket);
                });

            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -257,118 +266,176 @@ public class PowerUI extends SystemUI {
        }
    }

    protected void maybeShowBatteryWarning(int oldBatteryLevel, boolean plugged, boolean oldPlugged,
            int oldBucket, int bucket) {
        boolean isPowerSaver = mPowerManager.isPowerSaveMode();
        // only play SFX when the dialog comes up or the bucket changes
        final boolean playSound = bucket != oldBucket || oldPlugged;
    protected void maybeShowBatteryWarningV2(boolean plugged, int bucket) {
        final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
        final boolean isPowerSaverMode = mPowerManager.isPowerSaveMode();

        // Stick current battery state into an immutable container to determine if we should show
        // a warning.
        if (DEBUG) {
            Slog.d(TAG, "evaluating which notification to show");
        }
        if (hybridEnabled) {
            Estimate estimate = mLastEstimate;
            if (estimate == null || mBatteryLevel != oldBatteryLevel) {
                estimate = mEnhancedEstimates.getEstimate();
                mLastEstimate = estimate;
            }
            // Turbo is not always booted once SysUI is running so we have to make sure we actually
            // get data back
            if (estimate != null) {
                mTimeRemaining = estimate.estimateMillis;
                mWarnings.updateEstimate(estimate);
                mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
                        mEnhancedEstimates.getSevereWarningThreshold());

                // if we are now over 45% battery & 6 hours remaining we can trigger hybrid
            if (DEBUG) {
                Slog.d(TAG, "using hybrid");
            }
            Estimate estimate = refreshEstimateIfNeeded();
            mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
                    plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
                    mLowBatteryReminderLevels[0], estimate.getEstimateMillis(),
                    mEnhancedEstimates.getSevereWarningThreshold(),
                    mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage());
        } else {
            if (DEBUG) {
                Slog.d(TAG, "using standard");
            }
            mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
                    plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
                    mLowBatteryReminderLevels[0]);
        }

        mWarnings.updateSnapshot(mCurrentBatteryStateSnapshot);
        if (mCurrentBatteryStateSnapshot.isHybrid()) {
            maybeShowHybridWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
        } else {
            maybeShowBatteryWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
        }
    }

    // updates the time estimate if we don't have one or battery level has changed.
    @VisibleForTesting
    Estimate refreshEstimateIfNeeded() {
        if (mLastBatteryStateSnapshot == null
                || mLastBatteryStateSnapshot.getTimeRemainingMillis() == NO_ESTIMATE_AVAILABLE
                || mBatteryLevel != mLastBatteryStateSnapshot.getBatteryLevel()) {
            final Estimate estimate = mEnhancedEstimates.getEstimate();
            if (DEBUG) {
                Slog.d(TAG, "updated estimate: " + estimate.getEstimateMillis());
            }
            return estimate;
        }
        return new Estimate(mLastBatteryStateSnapshot.getTimeRemainingMillis(),
                mLastBatteryStateSnapshot.isBasedOnUsage());
    }

    @VisibleForTesting
    void maybeShowHybridWarning(BatteryStateSnapshot currentSnapshot,
            BatteryStateSnapshot lastSnapshot) {
        // if we are now over 45% battery & 6 hours remaining so we can trigger hybrid
        // notification again
                if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET
                        && mTimeRemaining > SIX_HOURS_MILLIS) {
        if (currentSnapshot.getBatteryLevel() >= CHARGE_CYCLE_PERCENT_RESET
                && currentSnapshot.getTimeRemainingMillis() > SIX_HOURS_MILLIS) {
            mLowWarningShownThisChargeCycle = false;
            mSevereWarningShownThisChargeCycle = false;
                }
            if (DEBUG) {
                Slog.d(TAG, "Charge cycle reset! Can show warnings again");
            }
        }

        if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
                mTimeRemaining, isPowerSaver, mBatteryStatus)) {
            mWarnings.showLowBatteryWarning(playSound);
        final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
                || lastSnapshot.getPlugged();

        if (shouldShowHybridWarning(currentSnapshot)) {
            mWarnings.showLowBatteryWarning(playSound);
            // 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]) {
            if (currentSnapshot.getTimeRemainingMillis()
                    <= currentSnapshot.getSevereLevelThreshold()
                    || currentSnapshot.getBatteryLevel() <= mLowBatteryReminderLevels[1]) {
                mSevereWarningShownThisChargeCycle = true;
                mLowWarningShownThisChargeCycle = true;
                if (DEBUG) {
                    Slog.d(TAG, "Severe warning marked as shown this cycle");
                }
            } else {
                Slog.d(TAG, "Low warning marked as shown this cycle");
                mLowWarningShownThisChargeCycle = true;
            }

        } else if (shouldDismissHybridWarning(currentSnapshot)) {
            if (DEBUG) {
                Slog.d(TAG, "Dismissing warning");
            }
        } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
                isPowerSaver)) {
            mWarnings.dismissLowBatteryWarning();
        } else {
            mWarnings.updateLowBatteryWarning();
        }
            if (DEBUG) {
                Slog.d(TAG, "Updating warning");
            }

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

    @VisibleForTesting
    boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
            long timeRemaining, boolean isPowerSaver) {
        final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
        final boolean hybridWouldDismiss = hybridEnabled
                && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
        final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
        return (isPowerSaver && !hybridEnabled)
                || plugged
                || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
                        || hybridWouldDismiss));
    }

    private boolean isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver,
            int batteryStatus) {
        if (plugged || batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
    boolean shouldShowHybridWarning(BatteryStateSnapshot snapshot) {
        if (snapshot.getPlugged()
                || snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN) {
            Slog.d(TAG, "can't show warning due to - plugged: " + snapshot.getPlugged()
                    + " status unknown: "
                    + (snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN));
            return false;
        }
        int warnLevel = mLowBatteryReminderLevels[0];
        int critLevel = mLowBatteryReminderLevels[1];

        // Only show the low warning once per charge cycle & no battery saver
        final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !isPowerSaver
                && (timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
                || mBatteryLevel <= warnLevel);
        final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver()
                && (snapshot.getTimeRemainingMillis() < snapshot.getLowThresholdMillis()
                || snapshot.getBatteryLevel() <= snapshot.getLowLevelThreshold());

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

        final boolean canShow = canShowWarning || canShowSevereWarning;
        if (DEBUG) {
            Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith values: "
            Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith battery snapshot:"
                    + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle
                    + " mSevereWarningShownThisChargeCycle: " + mSevereWarningShownThisChargeCycle
                    + " mEnhancedEstimates.timeremaining: " + timeRemaining
                    + " mBatteryLevel: " + mBatteryLevel
                    + " canShowWarning: " + canShowWarning
                    + " canShowSevereWarning: " + canShowSevereWarning
                    + " plugged: " + plugged
                    + " batteryStatus: " + batteryStatus
                    + " isPowerSaver: " + isPowerSaver);
                    + "\n" + snapshot.toString());
        }
        return canShow;
    }

    @VisibleForTesting
    boolean shouldDismissHybridWarning(BatteryStateSnapshot snapshot) {
        return snapshot.getPlugged()
                || snapshot.getTimeRemainingMillis() > snapshot.getLowThresholdMillis();
    }

    protected void maybeShowBatteryWarning(
            BatteryStateSnapshot currentSnapshot,
            BatteryStateSnapshot lastSnapshot) {
        final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
                || lastSnapshot.getPlugged();

        if (shouldShowLowBatteryWarning(currentSnapshot, lastSnapshot)) {
            mWarnings.showLowBatteryWarning(playSound);
        } else if (shouldDismissLowBatteryWarning(currentSnapshot, lastSnapshot)) {
            mWarnings.dismissLowBatteryWarning();
        } else {
            mWarnings.updateLowBatteryWarning();
        }
        return canShowWarning || canShowSevereWarning;
    }

    @VisibleForTesting
    boolean shouldShowLowBatteryWarning(
            BatteryStateSnapshot currentSnapshot,
            BatteryStateSnapshot lastSnapshot) {
        return !currentSnapshot.getPlugged()
                && !currentSnapshot.isPowerSaver()
                && (((currentSnapshot.getBucket() < lastSnapshot.getBucket()
                        || lastSnapshot.getPlugged())
                && currentSnapshot.getBucket() < 0))
                && currentSnapshot.getBatteryStatus() != BatteryManager.BATTERY_STATUS_UNKNOWN;
    }

    @VisibleForTesting
    boolean shouldDismissLowBatteryWarning(
            BatteryStateSnapshot currentSnapshot,
            BatteryStateSnapshot lastSnapshot) {
        return currentSnapshot.isPowerSaver()
                || currentSnapshot.getPlugged()
                || (currentSnapshot.getBucket() > lastSnapshot.getBucket()
                        && currentSnapshot.getBucket() > 0);
    }

    private void initTemperature() {
@@ -453,12 +520,20 @@ public class PowerUI extends SystemUI {
        mWarnings.dump(pw);
    }

    /**
     * The interface to allow PowerUI to communicate with whatever implementation of WarningsUI
     * is being used by the system.
     */
    public interface WarningsUI {
        void update(int batteryLevel, int bucket, long screenOffTime);

        void updateEstimate(Estimate estimate);

        void updateThresholds(long lowThreshold, long severeThreshold);
        /**
         * Updates battery and screen info for determining whether to trigger battery warnings or
         * not.
         * @param batteryLevel The current battery level
         * @param bucket The current battery bucket
         * @param screenOffTime How long the screen has been off in millis
         */
        void update(int batteryLevel, int bucket, long screenOffTime);

        void dismissLowBatteryWarning();

@@ -486,6 +561,12 @@ public class PowerUI extends SystemUI {
        void dump(PrintWriter pw);

        void userSwitched();

        /**
         * Updates the snapshot of battery state used for evaluating battery warnings
         * @param snapshot object containing relevant values for making battery warning decisions.
         */
        void updateSnapshot(BatteryStateSnapshot snapshot);
    }

    // Thermal event received from thermal service manager subsystem
Loading