Refine the interpolation rule and clip the usage time data

Bug: 184807417
Test: make SettingsRoboTests
Change-Id: I6c115beed34abd393e5c615cc59d3c4c323dfa0b
diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
index c4efef8..f4c9b0c 100644
--- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
@@ -223,7 +223,7 @@
             ConvertUtils.getIndexedUsageMap(
                 mPrefContext, /*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
                 mBatteryHistoryKeys, batteryHistoryMap,
-                /*purgeLowPercentageData=*/ true);
+                /*purgeLowPercentageAndFakeData=*/ true);
         forceRefreshUi();
 
         Log.d(TAG, String.format(
diff --git a/src/com/android/settings/fuelgauge/ConvertUtils.java b/src/com/android/settings/fuelgauge/ConvertUtils.java
index 1f6600b..fc58e50 100644
--- a/src/com/android/settings/fuelgauge/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/ConvertUtils.java
@@ -18,12 +18,15 @@
 import android.content.Context;
 import android.os.BatteryUsageStats;
 import android.os.UserHandle;
+import android.text.format.DateUtils;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
@@ -41,6 +44,8 @@
     private static final Map<String, BatteryHistEntry> EMPTY_BATTERY_MAP = new HashMap<>();
     private static final BatteryHistEntry EMPTY_BATTERY_HIST_ENTRY =
         new BatteryHistEntry(new ContentValues());
+    // Maximum total time value for each slot cumulative data at most 2 hours.
+    private static final float TOTAL_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2;
 
     @VisibleForTesting
     static double PERCENTAGE_OF_TOTAL_THRESHOLD = 1f;
@@ -145,7 +150,10 @@
             final int timeSlotSize,
             final long[] batteryHistoryKeys,
             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
-            final boolean purgeLowPercentageData) {
+            final boolean purgeLowPercentageAndFakeData) {
+        if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
+            return new HashMap<>();
+        }
         final Map<Integer, List<BatteryDiffEntry>> resultMap = new HashMap<>();
         // Each time slot usage diff data =
         //     Math.abs(timestamp[i+2] data - timestamp[i+1] data) +
@@ -155,16 +163,10 @@
         for (int index = 0; index < timeSlotSize; index++) {
             final Long currentTimestamp =
                 Long.valueOf(batteryHistoryKeys[index * timestampStride]);
-            // Uses empty list if the timestamp is default value.
-            if (currentTimestamp == 0) {
-                resultMap.put(Integer.valueOf(index), new ArrayList<BatteryDiffEntry>());
-                continue;
-            }
             final Long nextTimestamp =
                 Long.valueOf(batteryHistoryKeys[index * timestampStride + 1]);
             final Long nextTwoTimestamp =
                 Long.valueOf(batteryHistoryKeys[index * timestampStride + 2]);
-
             // Fetches BatteryHistEntry data from corresponding time slot.
             final Map<String, BatteryHistEntry> currentBatteryHistMap =
                 batteryHistoryMap.getOrDefault(currentTimestamp, EMPTY_BATTERY_MAP);
@@ -172,8 +174,17 @@
                 batteryHistoryMap.getOrDefault(nextTimestamp, EMPTY_BATTERY_MAP);
             final Map<String, BatteryHistEntry> nextTwoBatteryHistMap =
                 batteryHistoryMap.getOrDefault(nextTwoTimestamp, EMPTY_BATTERY_MAP);
+            // We should not get the empty list since we have at least one fake data to record
+            // the battery level and status in each time slot, the empty list is used to
+            // represent there is no enough data to apply interpolation arithmetic.
+            if (currentBatteryHistMap.isEmpty()
+                    || nextBatteryHistMap.isEmpty()
+                    || nextTwoBatteryHistMap.isEmpty()) {
+                resultMap.put(Integer.valueOf(index), new ArrayList<BatteryDiffEntry>());
+                continue;
+            }
 
-            // Collects all keys in these three time slot records as population.
+            // Collects all keys in these three time slot records as all populations.
             final Set<String> allBatteryHistEntryKeys = new HashSet<>();
             allBatteryHistEntryKeys.addAll(currentBatteryHistMap.keySet());
             allBatteryHistEntryKeys.addAll(nextBatteryHistMap.keySet());
@@ -193,12 +204,12 @@
                 final BatteryHistEntry nextTwoEntry =
                     nextTwoBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
                 // Cumulative values is a specific time slot for a specific app.
-                final long foregroundUsageTimeInMs =
+                long foregroundUsageTimeInMs =
                     getDiffValue(
                         currentEntry.mForegroundUsageTimeInMs,
                         nextEntry.mForegroundUsageTimeInMs,
                         nextTwoEntry.mForegroundUsageTimeInMs);
-                final long backgroundUsageTimeInMs =
+                long backgroundUsageTimeInMs =
                     getDiffValue(
                         currentEntry.mBackgroundUsageTimeInMs,
                         nextEntry.mBackgroundUsageTimeInMs,
@@ -212,8 +223,8 @@
 
                 // Excludes entry since we don't have enough data to calculate.
                 if (foregroundUsageTimeInMs == 0
-                    && backgroundUsageTimeInMs == 0
-                    && consumePower == 0) {
+                        && backgroundUsageTimeInMs == 0
+                        && consumePower == 0) {
                     continue;
                 }
                 final BatteryHistEntry selectedBatteryEntry =
@@ -221,6 +232,21 @@
                 if (selectedBatteryEntry == null) {
                     continue;
                 }
+                // Force refine the cumulative value since it may introduce deviation
+                // error since we will apply the interpolation arithmetic.
+                final float totalUsageTimeInMs =
+                    foregroundUsageTimeInMs + backgroundUsageTimeInMs;
+                if (totalUsageTimeInMs > TOTAL_TIME_THRESHOLD) {
+                    final float ratio = TOTAL_TIME_THRESHOLD / totalUsageTimeInMs;
+                    Log.w(TAG, String.format("abnormal usage time %d|%d for:\n%s",
+                          Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(),
+                          Duration.ofMillis(backgroundUsageTimeInMs).getSeconds(),
+                          currentEntry));
+                    foregroundUsageTimeInMs =
+                        Math.round(foregroundUsageTimeInMs * ratio);
+                    backgroundUsageTimeInMs =
+                        Math.round(backgroundUsageTimeInMs * ratio);
+                }
                 batteryDiffEntryList.add(
                     new BatteryDiffEntry(
                         context,
@@ -234,10 +260,10 @@
                 diffEntry.setTotalConsumePower(totalConsumePower);
             }
         }
-        insert24HoursData(BatteryChartView.SELECTED_INDEX_ALL, resultMap);
-        if (purgeLowPercentageData) {
-            purgeLowPercentageData(resultMap);
+        if (purgeLowPercentageAndFakeData) {
+            purgeLowPercentageAndFakeData(resultMap);
         }
+        insert24HoursData(BatteryChartView.SELECTED_INDEX_ALL, resultMap);
         return resultMap;
     }
 
@@ -273,13 +299,15 @@
         indexedUsageMap.put(Integer.valueOf(desiredIndex), resultList);
     }
 
-    private static void purgeLowPercentageData(
+    // Removes low percentage data and fake usage data, which will be zero value.
+    private static void purgeLowPercentageAndFakeData(
             final Map<Integer, List<BatteryDiffEntry>> indexedUsageMap) {
         for (List<BatteryDiffEntry> entries : indexedUsageMap.values()) {
             final Iterator<BatteryDiffEntry> iterator = entries.iterator();
             while (iterator.hasNext()) {
                 final BatteryDiffEntry entry = iterator.next();
-                if (entry.getPercentOfTotal() < PERCENTAGE_OF_TOTAL_THRESHOLD) {
+                if (entry.getPercentOfTotal() < PERCENTAGE_OF_TOTAL_THRESHOLD
+                        || FAKE_PACKAGE_NAME.equals(entry.getPackageName())) {
                     iterator.remove();
                 }
             }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java
index 30df466..c4341ef 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/ConvertUtilsTest.java
@@ -141,27 +141,46 @@
     }
 
     @Test
+    public void testGetIndexedUsageMap_nullOrEmptyHistoryMap_returnEmptyCollection() {
+        final int timeSlotSize = 2;
+        final long[] batteryHistoryKeys = new long[] {101L, 102L, 103L, 104L, 105L};
+
+        assertThat(ConvertUtils.getIndexedUsageMap(
+                mContext, timeSlotSize, batteryHistoryKeys,
+                /*batteryHistoryMap=*/ null, /*purgeLowPercentageAndFakeData=*/ true))
+            .isEmpty();
+        assertThat(ConvertUtils.getIndexedUsageMap(
+                mContext, timeSlotSize, batteryHistoryKeys,
+                new HashMap<Long, Map<String, BatteryHistEntry>>(),
+                /*purgeLowPercentageAndFakeData=*/ true))
+            .isEmpty();
+    }
+    @Test
     public void testGetIndexedUsageMap_returnsExpectedResult() {
         // Creates the fake testing data.
         final int timeSlotSize = 2;
         final long[] batteryHistoryKeys = new long[] {101L, 102L, 103L, 104L, 105L};
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
             new HashMap<>();
+        final BatteryHistEntry fakeEntry = createBatteryHistEntry(
+            ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", 0, 0L, 0L, 0L);
         // Adds the index = 0 data.
         Map<String, BatteryHistEntry> entryMap = new HashMap<>();
         BatteryHistEntry entry = createBatteryHistEntry(
             "package1", "label1", 5.0, 1L, 10L, 20L);
         entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[0]), entryMap);
         // Adds the index = 1 data.
-        batteryHistoryMap.put(
-            Long.valueOf(batteryHistoryKeys[1]),
-            new HashMap<String, BatteryHistEntry>());
+        entryMap = new HashMap<>();
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[1]), entryMap);
         // Adds the index = 2 data.
         entryMap = new HashMap<>();
         entry = createBatteryHistEntry(
             "package2", "label2", 10.0, 2L, 15L, 25L);
         entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[2]), entryMap);
         // Adds the index = 3 data.
         entryMap = new HashMap<>();
@@ -171,6 +190,7 @@
         entry = createBatteryHistEntry(
             "package3", "label3", 5.0, 3L, 5L, 5L);
         entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[3]), entryMap);
         // Adds the index = 4 data.
         entryMap = new HashMap<>();
@@ -183,12 +203,13 @@
         entry = createBatteryHistEntry(
             "package3", "label3", 5.0, 3L, 5L, 5L);
         entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
         batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[4]), entryMap);
 
         final Map<Integer, List<BatteryDiffEntry>> resultMap =
             ConvertUtils.getIndexedUsageMap(
                 mContext, timeSlotSize, batteryHistoryKeys, batteryHistoryMap,
-                /*purgeLowPercentageData=*/ false);
+                /*purgeLowPercentageAndFakeData=*/ false);
 
         assertThat(resultMap).hasSize(3);
         // Verifies the first timestamp result.
@@ -213,7 +234,7 @@
         final Map<Integer, List<BatteryDiffEntry>> purgedResultMap =
             ConvertUtils.getIndexedUsageMap(
                 mContext, timeSlotSize, batteryHistoryKeys, batteryHistoryMap,
-                 /*purgeLowPercentageData=*/ true);
+                 /*purgeLowPercentageAndFakeData=*/ true);
 
         assertThat(purgedResultMap).hasSize(3);
         // Verifies the first timestamp result.
@@ -225,8 +246,49 @@
         assertBatteryDiffEntry(entryList.get(0), 75, 40L, 50L);
         // Verifies the last 24 hours aggregate result.
         entryList = purgedResultMap.get(Integer.valueOf(-1));
+        assertThat(entryList).hasSize(2);
+        // Verifies the fake data is cleared out.
+        assertThat(entryList.get(0).getPackageName())
+            .isNotEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
+        assertThat(entryList.get(1).getPackageName())
+            .isNotEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
+    }
+
+    @Test
+    public void testGetIndexedUsageMap_usageTimeExceed_returnsExpectedResult() {
+        final int timeSlotSize = 1;
+        final long[] batteryHistoryKeys = new long[] {101L, 102L, 103L};
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+            new HashMap<>();
+        final BatteryHistEntry fakeEntry = createBatteryHistEntry(
+            ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", 0, 0L, 0L, 0L);
+        // Adds the index = 0 data.
+        Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[0]), entryMap);
+        // Adds the index = 1 data.
+        entryMap = new HashMap<>();
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[1]), entryMap);
+        // Adds the index = 2 data.
+        entryMap = new HashMap<>();
+        final BatteryHistEntry entry = createBatteryHistEntry(
+            "package3", "label3", 500, 5L, 3600000L, 7200000L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(Long.valueOf(batteryHistoryKeys[2]), entryMap);
+
+        final Map<Integer, List<BatteryDiffEntry>> purgedResultMap =
+            ConvertUtils.getIndexedUsageMap(
+                mContext, timeSlotSize, batteryHistoryKeys, batteryHistoryMap,
+                 /*purgeLowPercentageAndFakeData=*/ true);
+
+        assertThat(purgedResultMap).hasSize(2);
+        final List<BatteryDiffEntry> entryList = purgedResultMap.get(0);
         assertThat(entryList).hasSize(1);
-        assertBatteryDiffEntry(entryList.get(0), 68, 40L, 50L);
+        // Verifies the clipped usage time.
+        final BatteryDiffEntry resultEntry = entryList.get(0);
+        assertThat(resultEntry.mForegroundUsageTimeInMs).isEqualTo(2400000);
+        assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(4800000);
     }
 
     @Test