diff options
8 files changed, 575 insertions, 459 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt new file mode 100644 index 000000000000..d7a2d9acf3b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt @@ -0,0 +1,55 @@ +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 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java deleted file mode 100644 index 12a8f0a435b4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/power/Estimate.java +++ /dev/null @@ -1,11 +0,0 @@ -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; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.kt b/packages/SystemUI/src/com/android/systemui/power/Estimate.kt new file mode 100644 index 000000000000..dca0d45c1c9f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.kt @@ -0,0 +1,3 @@ +package com.android.systemui.power + +data class Estimate(val estimateMillis: Long, val isBasedOnUsage: Boolean)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index fdb0b36ee51e..41bcab53f8e9 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -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)); } @@ -324,10 +315,10 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private String getHybridContentString(String percentage) { return PowerUtil.getBatteryRemainingStringFormatted( - mContext, - mEstimate.estimateMillis, - percentage, - mEstimate.isBasedOnUsage); + mContext, + mCurrentBatterySnapshot.getTimeRemainingMillis(), + percentage, + mCurrentBatterySnapshot.isBasedOnUsage()); } private PendingIntent pendingBroadcast(String action) { diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index e27c25efd88f..18638606a251 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -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; + if (DEBUG) { + Slog.d(TAG, "using hybrid"); } - // 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 - // notification again - if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET - && mTimeRemaining > SIX_HOURS_MILLIS) { - mLowWarningShownThisChargeCycle = false; - mSevereWarningShownThisChargeCycle = false; - } + 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]); } - if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, - mTimeRemaining, isPowerSaver, mBatteryStatus)) { - mWarnings.showLowBatteryWarning(playSound); + 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 (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"); + } + } + + 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]) { - mSevereWarningShownThisChargeCycle = true; - mLowWarningShownThisChargeCycle = true; - } else { - mLowWarningShownThisChargeCycle = true; + 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 { + if (DEBUG) { + Slog.d(TAG, "Updating warning"); + } mWarnings.updateLowBatteryWarning(); } } @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); - } - // 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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index af3c96f73642..3fa3e1a6e6d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -226,7 +226,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0); return PowerUtil.getBatteryRemainingShortStringFormatted( - mContext, mEstimate.estimateMillis); + mContext, mEstimate.getEstimateMillis()); } private void updateEstimateInBackground() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index 5876ae1910be..58c931190c83 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.NotificationManager; +import android.os.BatteryManager; import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -57,6 +58,9 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { // Test Instance. mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager); mPowerNotificationWarnings = new PowerNotificationWarnings(mContext); + BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1, + BatteryManager.BATTERY_HEALTH_GOOD, 5, 15); + mPowerNotificationWarnings.updateSnapshot(snapshot); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index 0aed63d25112..f51e4731a390 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -17,8 +17,7 @@ package com.android.systemui.power; import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING; import static android.provider.Settings.Global.SHOW_USB_TEMPERATURE_ALARM; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.anyObject; @@ -29,7 +28,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.Intent; import android.os.BatteryManager; import android.os.IThermalEventListener; import android.os.IThermalService; @@ -42,22 +40,20 @@ import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; -import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.power.PowerUI.WarningsUI; import com.android.systemui.statusbar.phone.StatusBar; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.time.Duration; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -75,6 +71,7 @@ public class PowerUITest extends SysuiTestCase { private static final int OLD_BATTERY_LEVEL_NINE = 9; private static final int OLD_BATTERY_LEVEL_10 = 10; private static final long VERY_BELOW_SEVERE_HYBRID_THRESHOLD = TimeUnit.MINUTES.toMillis(15); + public static final int BATTERY_LEVEL_10 = 10; private WarningsUI mMockWarnings; private PowerUI mPowerUI; private EnhancedEstimates mEnhancedEstimates; @@ -176,368 +173,333 @@ public class PowerUITest extends SysuiTestCase { } @Test - public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() { - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()) - .thenReturn(Duration.ofHours(1).toMillis()); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + public void testMaybeShowHybridWarning() { mPowerUI.start(); - // unplugged device that would not show the non-hybrid notification but would show the - // hybrid but the threshold has been overriden to be too low - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertFalse(shouldShow); - } + // verify low warning shown this cycle noticed + BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper(); + BatteryStateSnapshot lastState = state.get(); + state.mTimeRemainingMillis = Duration.ofHours(2).toMillis(); + state.mBatteryLevel = 15; - @Test - public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsShow() { - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()) - .thenReturn(Duration.ofHours(5).toMillis()); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - mPowerUI.start(); + mPowerUI.maybeShowHybridWarning(state.get(), lastState); - // unplugged device that would not show the non-hybrid notification but would show the - // hybrid since the threshold has been overriden to be much higher - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertTrue(shouldShow); - } + assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue(); + assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse(); - @Test - public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() { - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - mPowerUI.start(); + // verify severe warning noticed this cycle + lastState = state.get(); + state.mBatteryLevel = 1; + state.mTimeRemainingMillis = Duration.ofMinutes(10).toMillis(); - // unplugged device that would not show the non-hybrid notification but would show the - // hybrid - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertTrue(shouldShow); - } + mPowerUI.maybeShowHybridWarning(state.get(), lastState); - @Test - public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() { - 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(); + assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isTrue(); + assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isTrue(); - // unplugged device that would show the non-hybrid notification and the hybrid - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertTrue(shouldShow); - } + // verify getting past threshold resets values + lastState = state.get(); + state.mBatteryLevel = 100; + state.mTimeRemainingMillis = Duration.ofDays(1).toMillis(); - @Test - public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() { - 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(); + mPowerUI.maybeShowHybridWarning(state.get(), lastState); - // unplugged device that would show the non-hybrid but not the hybrid - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertTrue(shouldShow); + assertThat(mPowerUI.mLowWarningShownThisChargeCycle).isFalse(); + assertThat(mPowerUI.mSevereWarningShownThisChargeCycle).isFalse(); } @Test - public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() { - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + public void testShouldShowHybridWarning_lowLevelWarning() { mPowerUI.start(); - - // unplugged device that would show the neither due to battery level being good - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertFalse(shouldShow); + mPowerUI.mLowWarningShownThisChargeCycle = false; + mPowerUI.mSevereWarningShownThisChargeCycle = false; + BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper(); + + // sanity check to make sure we can show for a valid config + state.mBatteryLevel = 10; + state.mTimeRemainingMillis = Duration.ofHours(2).toMillis(); + boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // Shouldn't show if plugged in + state.mPlugged = true; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + // Shouldn't show if battery is unknown + state.mPlugged = false; + state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; + // Already shown both warnings + mPowerUI.mLowWarningShownThisChargeCycle = true; + mPowerUI.mSevereWarningShownThisChargeCycle = true; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + // Can show low warning + mPowerUI.mLowWarningShownThisChargeCycle = false; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // Can't show if above the threshold for time & battery + state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis(); + state.mBatteryLevel = 100; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + // Battery under low percentage threshold but not time + state.mBatteryLevel = 10; + state.mLowLevelThreshold = 50; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // Should also trigger if both level and time remaining under low threshold + state.mTimeRemainingMillis = Duration.ofHours(2).toMillis(); + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // battery saver should block the low level warning though + state.mIsPowerSaver = true; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); } @Test - public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() { - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - mPowerUI.start(); - - // plugged device that would show the neither due to being plugged - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertFalse(shouldShow); - } - - @Test - 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); + public void testShouldShowHybridWarning_severeLevelWarning() { mPowerUI.start(); - - // 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, - !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN); - assertFalse(shouldShow); + mPowerUI.mLowWarningShownThisChargeCycle = false; + mPowerUI.mSevereWarningShownThisChargeCycle = false; + BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper(); + + // sanity check to make sure we can show for a valid config + state.mBatteryLevel = 1; + state.mTimeRemainingMillis = Duration.ofMinutes(1).toMillis(); + boolean shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // Shouldn't show if plugged in + state.mPlugged = true; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + // Shouldn't show if battery is unknown + state.mPlugged = false; + state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; + // Already shown both warnings + mPowerUI.mLowWarningShownThisChargeCycle = true; + mPowerUI.mSevereWarningShownThisChargeCycle = true; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + // Can show severe warning + mPowerUI.mSevereWarningShownThisChargeCycle = false; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // Can't show if above the threshold for time & battery + state.mTimeRemainingMillis = Duration.ofHours(1000).toMillis(); + state.mBatteryLevel = 100; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isFalse(); + + // Battery under low percentage threshold but not time + state.mBatteryLevel = 1; + state.mSevereLevelThreshold = 5; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // Should also trigger if both level and time remaining under low threshold + state.mTimeRemainingMillis = Duration.ofHours(2).toMillis(); + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); + + // battery saver should not block the severe level warning though + state.mIsPowerSaver = true; + shouldShow = mPowerUI.shouldShowHybridWarning(state.get()); + assertThat(shouldShow).isTrue(); } @Test - public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() { - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); + public void testShouldDismissHybridWarning() { mPowerUI.start(); - - // BatterySaverEnabled device that would show the neither due to battery saver - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, - !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertFalse(shouldShow); + BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper(); + + // We should dismiss if the device is plugged in + state.mPlugged = true; + state.mTimeRemainingMillis = Duration.ofHours(1).toMillis(); + state.mLowThresholdMillis = Duration.ofHours(2).toMillis(); + boolean shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get()); + assertThat(shouldDismiss).isTrue(); + + // If not plugged in and below the threshold we should not dismiss + state.mPlugged = false; + shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get()); + assertThat(shouldDismiss).isFalse(); + + // If we go over the low warning threshold we should dismiss + state.mTimeRemainingMillis = Duration.ofHours(3).toMillis(); + shouldDismiss = mPowerUI.shouldDismissHybridWarning(state.get()); + assertThat(shouldDismiss).isTrue(); } @Test - public void testShouldShowLowBatteryWarning_onlyShowsOncePerChargeCycle() { + public void testRefreshEstimateIfNeeded_onlyQueriesEstimateOnBatteryLevelChangeOrNull() { mPowerUI.start(); + Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true); when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - when(mEnhancedEstimates.getEstimate()) - .thenReturn(new Estimate(BELOW_HYBRID_THRESHOLD, true)); - mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; - - mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED, - ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET); - - // reduce battery level to handle time based trigger -> level trigger interactions + when(mEnhancedEstimates.getEstimate()).thenReturn(estimate); mPowerUI.mBatteryLevel = 10; - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertFalse(shouldShow); - } - - @Test - public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabledLegacy() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - // device that gets power saver turned on should dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, - BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF); - assertTrue(shouldDismiss); - } - @Test - public void testShouldNotDismissLowBatteryWarning_dismissWhenPowerSaverEnabledHybrid() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - // device that gets power saver turned on should dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, - BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF); - assertFalse(shouldDismiss); - } - - @Test - public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - // device that gets plugged in should dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET, - BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); - assertTrue(shouldDismiss); - } - - @Test - public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - // would dismiss hybrid but not non-hybrid should not dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, - BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); - assertFalse(shouldDismiss); - } - - @Test - public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - // would dismiss non-hybrid but not hybrid should not dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF); - assertFalse(shouldDismiss); - } - - @Test - public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - // should not dismiss when both would not dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, - BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF); - assertFalse(shouldDismiss); - } - - @Test - public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - //should dismiss if both would dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); - assertTrue(shouldDismiss); - } - - @Test - public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() { - mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - - // would dismiss non-hybrid with hybrid disabled should dismiss - boolean shouldDismiss = - mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); - assertTrue(shouldDismiss); - } - - @Test - public void testShouldDismissLowBatteryWarning_powerSaverModeEnabled() - throws InterruptedException { - when(mPowerManager.isPowerSaveMode()).thenReturn(true); - - mPowerUI.start(); - mPowerUI.mReceiver.onReceive(mContext, - new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); - - CountDownLatch latch = new CountDownLatch(1); - ThreadUtils.postOnBackgroundThread(() -> latch.countDown()); - latch.await(5, TimeUnit.SECONDS); - - verify(mMockWarnings).dismissLowBatteryWarning(); - } - - @Test - public void testShouldNotDismissLowBatteryWarning_powerSaverModeDisabled() - throws InterruptedException { - when(mPowerManager.isPowerSaveMode()).thenReturn(false); - - mPowerUI.start(); - mPowerUI.mReceiver.onReceive(mContext, - new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); - - CountDownLatch latch = new CountDownLatch(1); - ThreadUtils.postOnBackgroundThread(() -> latch.countDown()); - latch.await(5, TimeUnit.SECONDS); + // we expect that the first time it will query since there is no last battery snapshot. + // However an invalid estimate (-1) is returned. + Estimate refreshedEstimate = mPowerUI.refreshEstimateIfNeeded(); + assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD); + BatteryStateSnapshot snapshot = new BatteryStateSnapshot( + BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, + 0, 0, -1, 0, 0, false); + mPowerUI.mLastBatteryStateSnapshot = snapshot; + + // query again since the estimate was -1 + estimate = new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true); + when(mEnhancedEstimates.getEstimate()).thenReturn(estimate); + refreshedEstimate = mPowerUI.refreshEstimateIfNeeded(); + assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD); + snapshot = new BatteryStateSnapshot( + BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, 0, + 0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, false); + mPowerUI.mLastBatteryStateSnapshot = snapshot; + + // Battery level hasn't changed, so we don't query again + estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true); + when(mEnhancedEstimates.getEstimate()).thenReturn(estimate); + refreshedEstimate = mPowerUI.refreshEstimateIfNeeded(); + assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD); - verify(mMockWarnings, never()).dismissLowBatteryWarning(); + // Battery level changes so we update again + mPowerUI.mBatteryLevel = 9; + refreshedEstimate = mPowerUI.refreshEstimateIfNeeded(); + assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD); } @Test - public void testSevereWarning_countsAsLowAndSevere_WarningOnlyShownOnce() { + public void testShouldShowStandardWarning() { mPowerUI.start(); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - when(mEnhancedEstimates.getEstimate()) - .thenReturn(new Estimate(BELOW_SEVERE_HYBRID_THRESHOLD, true)); - mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; - - // reduce battery level to handle time based trigger -> level trigger interactions - mPowerUI.mBatteryLevel = 5; - boolean shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, BELOW_SEVERE_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertTrue(shouldShow); - - // actually run the end to end since it handles changing the internal state. - mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED, - ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET); - - shouldShow = - mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, - ABOVE_WARNING_BUCKET, VERY_BELOW_SEVERE_HYBRID_THRESHOLD, - POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); - assertFalse(shouldShow); + BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper(); + state.mIsHybrid = false; + BatteryStateSnapshot lastState = state.get(); + + // sanity check to make sure we can show for a valid config + state.mBatteryLevel = 10; + state.mBucket = -1; + boolean shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isTrue(); + lastState = state.get(); + + // Shouldn't show if plugged in + state.mPlugged = true; + shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isFalse(); + + state.mPlugged = false; + // Shouldn't show if battery saver + state.mIsPowerSaver = true; + shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isFalse(); + + state.mIsPowerSaver = false; + // Shouldn't show if battery is unknown + state.mPlugged = false; + state.mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; + shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isFalse(); + + state.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; + // show if plugged -> unplugged, bucket -1 -> -1 + state.mPlugged = true; + state.mBucket = -1; + lastState = state.get(); + state.mPlugged = false; + state.mBucket = -1; + shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isTrue(); + + // don't show if plugged -> unplugged, bucket 0 -> 0 + state.mPlugged = true; + state.mBucket = 0; + lastState = state.get(); + state.mPlugged = false; + state.mBucket = 0; + shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isFalse(); + + // show if unplugged -> unplugged, bucket 0 -> -1 + state.mPlugged = false; + state.mBucket = 0; + lastState = state.get(); + state.mPlugged = false; + state.mBucket = -1; + shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isTrue(); + + // don't show if unplugged -> unplugged, bucket -1 -> 1 + state.mPlugged = false; + state.mBucket = -1; + lastState = state.get(); + state.mPlugged = false; + state.mBucket = 1; + shouldShow = mPowerUI.shouldShowLowBatteryWarning(state.get(), lastState); + assertThat(shouldShow).isFalse(); } @Test - public void testMaybeShowBatteryWarning_onlyQueriesEstimateOnBatteryLevelChangeOrNull() { + public void testShouldDismissStandardWarning() { mPowerUI.start(); - Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true); - when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true); - when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS); - when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS); - when(mEnhancedEstimates.getEstimate()).thenReturn(estimate); - mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; - - // we expect that the first time it will query even if the level is the same - mPowerUI.mBatteryLevel = 9; - mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED, - ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET); - verify(mEnhancedEstimates, times(1)).getEstimate(); - - // We should NOT query again if the battery level hasn't changed - mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED, - ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET); - verify(mEnhancedEstimates, times(1)).getEstimate(); - - // Battery level has changed, so we should query again - mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED, - ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET); - verify(mEnhancedEstimates, times(2)).getEstimate(); + BatteryStateSnapshotWrapper state = new BatteryStateSnapshotWrapper(); + state.mIsHybrid = false; + BatteryStateSnapshot lastState = state.get(); + + // should dismiss if battery saver + state.mIsPowerSaver = true; + boolean shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState); + assertThat(shouldDismiss).isTrue(); + + state.mIsPowerSaver = false; + // should dismiss if plugged + state.mPlugged = true; + shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState); + assertThat(shouldDismiss).isTrue(); + + state.mPlugged = false; + // should dismiss if bucket 0 -> 1 + state.mBucket = 0; + lastState = state.get(); + state.mBucket = 1; + shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState); + assertThat(shouldDismiss).isTrue(); + + // shouldn't dismiss if bucket -1 -> 0 + state.mBucket = -1; + lastState = state.get(); + state.mBucket = 0; + shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState); + assertThat(shouldDismiss).isFalse(); + + // should dismiss if powersaver & bucket 0 -> 1 + state.mIsPowerSaver = true; + state.mBucket = 0; + lastState = state.get(); + state.mBucket = 1; + shouldDismiss = mPowerUI.shouldDismissLowBatteryWarning(state.get(), lastState); + assertThat(shouldDismiss).isTrue(); } private Temperature getEmergencyStatusTemp(int type, String name) { @@ -556,4 +518,35 @@ public class PowerUITest extends SysuiTestCase { mPowerUI.mComponents = mContext.getComponents(); mPowerUI.mThermalService = mThermalServiceMock; } + + /** + * A simple wrapper class that sets values by default and makes them not final to improve + * test clarity. + */ + private class BatteryStateSnapshotWrapper { + public int mBatteryLevel = 100; + public boolean mIsPowerSaver = false; + public boolean mPlugged = false; + public long mSevereThresholdMillis = Duration.ofHours(1).toMillis(); + public long mLowThresholdMillis = Duration.ofHours(3).toMillis(); + public int mSevereLevelThreshold = 5; + public int mLowLevelThreshold = 15; + public int mBucket = 1; + public int mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD; + public long mTimeRemainingMillis = Duration.ofHours(24).toMillis(); + public boolean mIsBasedOnUsage = true; + public boolean mIsHybrid = true; + + public BatteryStateSnapshot get() { + if (mIsHybrid) { + return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket, + mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold, + mTimeRemainingMillis, mSevereThresholdMillis, mLowThresholdMillis, + mIsBasedOnUsage); + } else { + return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket, + mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold); + } + } + } } |