Merge "Update triggering logic for hybrid notification" into pi-dev
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index f08219a..c409f73 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -52,6 +52,7 @@
 
 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 @@
     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 @@
     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 @@
     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 @@
 
                 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 @@
         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 @@
                 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,13 +283,15 @@
                 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()) {
-                mSevereWarningShownThisChargeCycle = true;
-            } else {
-                mLowWarningShownThisChargeCycle = true;
+            // 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)) {
@@ -295,12 +303,16 @@
 
     @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 @@
                         || 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;
     }
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 4b455ba..149f2de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -58,6 +58,7 @@
     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 @@
         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 @@
         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 @@
    }
 
     @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 @@
 
         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,