Support battery usage chart start index label is the detailed last full charge time

https://screenshot.googleplex.com/5dYp7kJpNPMU6Jx.png
https://screenshot.googleplex.com/7ZSbWyCGRFSym6A.png
https://screenshot.googleplex.com/9GsPTa9zd2XttDX.png

Bug: 271214926
Fix: 271214926
Test: manual
Change-Id: Ic869653f66866964f34e111dd2ee8c052313e119
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index e9d10f2..67d3969 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -251,8 +251,7 @@
                     hourlyBatteryLevelsPerDay.getLevels(),
                     hourlyBatteryLevelsPerDay.getTimestamps(),
                     BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
-                    mHourlyChartLabelTextGenerator.setLatestTimestamp(getLast(getLast(
-                            batteryLevelData.getHourlyBatteryLevelsPerDay()).getTimestamps()))));
+                    mHourlyChartLabelTextGenerator.updateSpecialCaseContext(batteryLevelData)));
         }
         refreshUi();
     }
@@ -640,7 +639,11 @@
 
     private final class HourlyChartLabelTextGenerator implements
             BatteryChartViewModel.LabelTextGenerator {
-        private Long mLatestTimestamp;
+        private static final int FULL_CHARGE_BATTERY_LEVEL = 100;
+
+        private boolean mIsFromFullCharge;
+        private long mFistTimestamp;
+        private long mLatestTimestamp;
 
         @Override
         public String generateText(List<Long> timestamps, int index) {
@@ -648,24 +651,37 @@
                 // Replaces the latest timestamp text to "now".
                 return mContext.getString(R.string.battery_usage_chart_label_now);
             }
-            return ConvertUtils.utcToLocalTimeHour(mContext, timestamps.get(index),
-                    mIs24HourFormat);
+            long timestamp = timestamps.get(index);
+            boolean showMinute = false;
+            if (Objects.equal(timestamp, mFistTimestamp)) {
+                if (mIsFromFullCharge) {
+                    showMinute = true;
+                } else {
+                    // starts from 7 days ago
+                    timestamp = TimestampUtils.getLastEvenHourTimestamp(timestamp);
+                }
+            }
+            return ConvertUtils.utcToLocalTimeHour(
+                    mContext, timestamp, mIs24HourFormat, showMinute);
         }
 
         @Override
         public String generateFullText(List<Long> timestamps, int index) {
-            if (Objects.equal(timestamps.get(index), mLatestTimestamp)) {
-                // Replaces the latest timestamp text to "now".
-                return mContext.getString(R.string.battery_usage_chart_label_now);
-            }
             return index == timestamps.size() - 1
                     ? generateText(timestamps, index)
                     : mContext.getString(R.string.battery_usage_timestamps_hyphen,
                             generateText(timestamps, index), generateText(timestamps, index + 1));
         }
 
-        public HourlyChartLabelTextGenerator setLatestTimestamp(Long latestTimestamp) {
-            this.mLatestTimestamp = latestTimestamp;
+        HourlyChartLabelTextGenerator updateSpecialCaseContext(
+                @NonNull final BatteryLevelData batteryLevelData) {
+            BatteryLevelData.PeriodBatteryLevelData firstDayLevelData =
+                    batteryLevelData.getHourlyBatteryLevelsPerDay().get(0);
+            this.mIsFromFullCharge =
+                    firstDayLevelData.getLevels().get(0) == FULL_CHARGE_BATTERY_LEVEL;
+            this.mFistTimestamp = firstDayLevelData.getTimestamps().get(0);
+            this.mLatestTimestamp = getLast(getLast(
+                    batteryLevelData.getHourlyBatteryLevelsPerDay()).getTimestamps());
             return this;
         }
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index c6c548f..2c98c4b 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -280,12 +280,12 @@
     }
 
     /** Converts UTC timestamp to local time hour data. */
-    public static String utcToLocalTimeHour(
-            final Context context, final long timestamp, final boolean is24HourFormat) {
+    public static String utcToLocalTimeHour(final Context context, final long timestamp,
+            final boolean is24HourFormat, final boolean showMinute) {
         final Locale locale = getLocale(context);
         // e.g. for 12-hour format: 9 PM
         // e.g. for 24-hour format: 09:00
-        final String skeleton = is24HourFormat ? "HHm" : "ha";
+        final String skeleton = is24HourFormat ? "HHm" : (showMinute ? "hma" : "ha");
         final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
         return DateFormat.format(pattern, timestamp).toString();
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 24571cd..9aee2f7 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -285,7 +285,6 @@
         final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> resultMap =
                 new ArrayMap<>();
 
-        final long dailySize = hourlyBatteryLevelsPerDay.size();
         for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
             final Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>> dailyMap =
                     new ArrayMap<>();
@@ -294,21 +293,9 @@
                 continue;
             }
             final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
-            final long hourlySize = timestamps.size() - 1;
             for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
-                // The start slot timestape is the near hour timestamp instead of the last full
-                // charge time. So use rawStartTimestamp instead of reading the timestamp from
-                // hourlyBatteryLevelsPerDay here.
-                final long startTimestamp =
-                        dailyIndex == 0 && hourlyIndex == 0 && !sDebug
-                                ? rawStartTimestamp : timestamps.get(hourlyIndex);
-                // The final slot is to show the data from last even hour until now but the
-                // timestamp in hourlyBatteryLevelsPerDay is not the real value. So use current
-                // timestamp instead of reading the timestamp from hourlyBatteryLevelsPerDay here.
-                final long endTimestamp =
-                        dailyIndex == dailySize - 1 && hourlyIndex == hourlySize - 1 && !sDebug
-                                ? System.currentTimeMillis() : timestamps.get(hourlyIndex + 1);
-
+                final long startTimestamp = timestamps.get(hourlyIndex);
+                final long endTimestamp = timestamps.get(hourlyIndex + 1);
                 // Gets the app usage event list for this hourly slot first.
                 final List<AppUsageEvent> hourlyAppUsageEventList =
                         getAppUsageEventListWithinTimeRangeWithBuffer(
@@ -463,11 +450,8 @@
         Collections.sort(rawTimestampList);
         final long currentTime = getCurrentTimeMillis();
         final List<Long> expectedTimestampList = getTimestampSlots(rawTimestampList, currentTime);
-        final boolean isFromFullCharge =
-                isFromFullCharge(batteryHistoryMap.get(rawTimestampList.get(0)));
         interpolateHistory(
-                context, rawTimestampList, expectedTimestampList, currentTime, isFromFullCharge,
-                batteryHistoryMap, resultMap);
+                context, rawTimestampList, expectedTimestampList, batteryHistoryMap, resultMap);
         Log.d(TAG, String.format("getHistoryMapWithExpectedTimestamps() size=%d in %d/ms",
                 resultMap.size(), (System.currentTimeMillis() - startTime)));
         return resultMap;
@@ -496,8 +480,9 @@
     }
 
     /**
-     * Computes expected timestamp slots for last full charge, which will return hourly timestamps
-     * between start and end two even hour values.
+     * Computes expected timestamp slots. The start timestamp is the last full charge time.
+     * The end timestamp is current time. The middle timestamps are the sharp hour timestamps
+     * between the start and end timestamps.
      */
     @VisibleForTesting
     static List<Long> getTimestampSlots(final List<Long> rawTimestampList, final long currentTime) {
@@ -505,19 +490,18 @@
         if (rawTimestampList.isEmpty()) {
             return timestampSlots;
         }
-        final long rawStartTimestamp = rawTimestampList.get(0);
-        // No matter the start is from last full charge or 6 days ago, use the nearest even hour.
-        final long startTimestamp = getNearestEvenHourTimestamp(rawStartTimestamp);
-        // Use the first even hour after the current time as the end.
-        final long endTimestamp = getFirstEvenHourAfterTimestamp(currentTime);
+        final long startTimestamp = rawTimestampList.get(0);
+        final long endTimestamp = currentTime;
         // If the start timestamp is later or equal the end one, return the empty list.
         if (startTimestamp >= endTimestamp) {
             return timestampSlots;
         }
-        for (long timestamp = startTimestamp; timestamp <= endTimestamp;
-                timestamp += DateUtils.HOUR_IN_MILLIS) {
+        timestampSlots.add(startTimestamp);
+        for (long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
+                timestamp < endTimestamp; timestamp += DateUtils.HOUR_IN_MILLIS) {
             timestampSlots.add(timestamp);
         }
+        timestampSlots.add(endTimestamp);
         return timestampSlots;
     }
 
@@ -539,35 +523,39 @@
         }
         final long startTime = timestampList.get(0);
         final long endTime = timestampList.get(timestampList.size() - 1);
-        // If the timestamp diff is smaller than MIN_TIME_SLOT, returns the empty list directly.
-        if (endTime - startTime < MIN_TIME_SLOT) {
-            return dailyTimestampList;
+        for (long timestamp = startTime; timestamp < endTime;
+                timestamp = TimestampUtils.getNextDayTimestamp(timestamp)) {
+            dailyTimestampList.add(timestamp);
         }
-        long nextDay = getTimestampOfNextDay(startTime);
-        // Only if the timestamp diff in the first day is bigger than MIN_TIME_SLOT, start from the
-        // first day. Otherwise, start from the second day.
-        if (nextDay - startTime >= MIN_TIME_SLOT) {
-            dailyTimestampList.add(startTime);
-        }
-        while (nextDay < endTime) {
-            dailyTimestampList.add(nextDay);
-            nextDay = getTimestampOfNextDay(nextDay);
-        }
-        final long lastDailyTimestamp = dailyTimestampList.get(dailyTimestampList.size() - 1);
-        // Only if the timestamp diff in the last day is bigger than MIN_TIME_SLOT, add the
-        // last day.
-        if (endTime - lastDailyTimestamp >= MIN_TIME_SLOT) {
-            dailyTimestampList.add(endTime);
-        }
-        // The dailyTimestampList must have the start and end timestamp, otherwise, return an empty
-        // list.
-        if (dailyTimestampList.size() < MIN_TIMESTAMP_DATA_SIZE) {
-            return new ArrayList<>();
-        }
+        dailyTimestampList.add(endTime);
         return dailyTimestampList;
     }
 
     @VisibleForTesting
+    static List<List<Long>> getHourlyTimestamps(final List<Long> dailyTimestamps) {
+        final List<List<Long>> hourlyTimestamps = new ArrayList<>();
+        if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) {
+            return hourlyTimestamps;
+        }
+
+        for (int dailyIndex = 0; dailyIndex < dailyTimestamps.size() - 1; dailyIndex++) {
+            final List<Long> hourlyTimestampsPerDay = new ArrayList<>();
+            final long startTime = dailyTimestamps.get(dailyIndex);
+            final long endTime = dailyTimestamps.get(dailyIndex + 1);
+
+            hourlyTimestampsPerDay.add(startTime);
+            for (long timestamp = TimestampUtils.getNextEvenHourTimestamp(startTime);
+                    timestamp < endTime; timestamp += MIN_TIME_SLOT) {
+                hourlyTimestampsPerDay.add(timestamp);
+            }
+            hourlyTimestampsPerDay.add(endTime);
+
+            hourlyTimestamps.add(hourlyTimestampsPerDay);
+        }
+        return hourlyTimestamps;
+    }
+
+    @VisibleForTesting
     static boolean isFromFullCharge(@Nullable final Map<String, BatteryHistEntry> entryList) {
         if (entryList == null) {
             Log.d(TAG, "entryList is null in isFromFullCharge()");
@@ -603,30 +591,6 @@
     }
 
     /**
-     * @return Returns the timestamp for 00:00 1 day after the given timestamp based on local
-     * timezone.
-     */
-    @VisibleForTesting
-    static long getTimestampOfNextDay(long timestamp) {
-        return getTimestampWithDayDiff(timestamp, /*dayDiff=*/ 1);
-    }
-
-    /**
-     * Returns whether currentSlot will be used in daily chart.
-     */
-    @VisibleForTesting
-    static boolean isForDailyChart(final boolean isStartOrEnd, final long currentSlot) {
-        // The start and end timestamps will always be used in daily chart.
-        if (isStartOrEnd) {
-            return true;
-        }
-
-        // The timestamps for 00:00 will be used in daily chart.
-        final long startOfTheDay = getTimestampWithDayDiff(currentSlot, /*dayDiff=*/ 0);
-        return currentSlot == startOfTheDay;
-    }
-
-    /**
      * @return Returns the indexed battery usage data for each corresponding time slot.
      *
      * <p>There could be 2 cases of the returned value:</p>
@@ -1184,43 +1148,22 @@
             Context context,
             final List<Long> rawTimestampList,
             final List<Long> expectedTimestampSlots,
-            final long currentTime,
-            final boolean isFromFullCharge,
             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
             final Map<Long, Map<String, BatteryHistEntry>> resultMap) {
         if (rawTimestampList.isEmpty() || expectedTimestampSlots.isEmpty()) {
             return;
         }
-        final long expectedStartTimestamp = expectedTimestampSlots.get(0);
-        final long rawStartTimestamp = rawTimestampList.get(0);
-        int startIndex = 0;
-        // If the expected start timestamp is full charge or earlier than what we have, use the
-        // first data of what we have directly. This should be OK because the expected start
-        // timestamp is the nearest even hour of the raw start timestamp, their time diff is no
-        // more than 1 hour.
-        if (isFromFullCharge || expectedStartTimestamp < rawStartTimestamp) {
-            startIndex = 1;
-            resultMap.put(expectedStartTimestamp, batteryHistoryMap.get(rawStartTimestamp));
-        }
         final int expectedTimestampSlotsSize = expectedTimestampSlots.size();
-        for (int index = startIndex; index < expectedTimestampSlotsSize; index++) {
-            final long currentSlot = expectedTimestampSlots.get(index);
-            if (currentSlot > currentTime) {
-                // The slot timestamp is greater than the current time. Puts a placeholder first,
-                // then in the async task, loads the real time battery usage data from the battery
-                // stats service.
-                // If current time is odd hour, one placeholder is added. If the current hour is
-                // even hour, two placeholders are added. This is because the method
-                // insertHourlyUsageDiffDataPerSlot() requires continuing three hours data.
-                resultMap.put(currentSlot,
-                        Map.of(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER, EMPTY_BATTERY_HIST_ENTRY));
-                continue;
-            }
-            final boolean isStartOrEnd = index == 0 || index == expectedTimestampSlotsSize - 1;
-            interpolateHistoryForSlot(
-                    context, currentSlot, rawTimestampList, batteryHistoryMap, resultMap,
-                    isStartOrEnd);
+        final long startTimestamp = expectedTimestampSlots.get(0);
+        final long endTimestamp = expectedTimestampSlots.get(expectedTimestampSlotsSize - 1);
+
+        resultMap.put(startTimestamp, batteryHistoryMap.get(startTimestamp));
+        for (int index = 1; index < expectedTimestampSlotsSize - 1; index++) {
+            interpolateHistoryForSlot(context, expectedTimestampSlots.get(index), rawTimestampList,
+                    batteryHistoryMap, resultMap);
         }
+        resultMap.put(endTimestamp,
+                Map.of(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER, EMPTY_BATTERY_HIST_ENTRY));
     }
 
     private static void interpolateHistoryForSlot(
@@ -1228,8 +1171,7 @@
             final long currentSlot,
             final List<Long> rawTimestampList,
             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
-            final Map<Long, Map<String, BatteryHistEntry>> resultMap,
-            final boolean isStartOrEnd) {
+            final Map<Long, Map<String, BatteryHistEntry>> resultMap) {
         final long[] nearestTimestamps = findNearestTimestamp(rawTimestampList, currentSlot);
         final long lowerTimestamp = nearestTimestamps[0];
         final long upperTimestamp = nearestTimestamps[1];
@@ -1252,9 +1194,8 @@
             resultMap.put(currentSlot, new ArrayMap<>());
             return;
         }
-        interpolateHistoryForSlot(context,
-                currentSlot, lowerTimestamp, upperTimestamp, batteryHistoryMap, resultMap,
-                isStartOrEnd);
+        interpolateHistoryForSlot(
+                context, currentSlot, lowerTimestamp, upperTimestamp, batteryHistoryMap, resultMap);
     }
 
     private static void interpolateHistoryForSlot(
@@ -1263,8 +1204,7 @@
             final long lowerTimestamp,
             final long upperTimestamp,
             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
-            final Map<Long, Map<String, BatteryHistEntry>> resultMap,
-            final boolean isStartOrEnd) {
+            final Map<Long, Map<String, BatteryHistEntry>> resultMap) {
         final Map<String, BatteryHistEntry> lowerEntryDataMap =
                 batteryHistoryMap.get(lowerTimestamp);
         final Map<String, BatteryHistEntry> upperEntryDataMap =
@@ -1278,7 +1218,7 @@
         // Skips the booting-specific logics and always does interpolation for daily chart level
         // data.
         if (lowerTimestamp < upperEntryDataBootTimestamp
-                && !isForDailyChart(isStartOrEnd, currentSlot)) {
+                && !TimestampUtils.isMidnight(currentSlot)) {
             // Provides an opportunity to force align the slot directly.
             if ((upperTimestamp - currentSlot) < 10 * DateUtils.MINUTE_IN_MILLIS) {
                 log(context, "force align into the nearest slot", currentSlot, null);
@@ -1325,70 +1265,6 @@
         resultMap.put(currentSlot, newHistEntryMap);
     }
 
-    /**
-     * @return Returns the nearest even hour timestamp of the given timestamp.
-     */
-    private static long getNearestEvenHourTimestamp(long rawTimestamp) {
-        // If raw hour is even, the nearest even hour should be the even hour before raw
-        // start. The hour doesn't need to change and just set the minutes and seconds to 0.
-        // Otherwise, the nearest even hour should be raw hour + 1.
-        // For example, the nearest hour of 14:30:50 should be 14:00:00. While the nearest
-        // hour of 15:30:50 should be 16:00:00.
-        return getEvenHourTimestamp(rawTimestamp, /*addHourOfDay*/ 1);
-    }
-
-    /**
-     * @return Returns the fist even hour timestamp after the given timestamp.
-     */
-    private static long getFirstEvenHourAfterTimestamp(long rawTimestamp) {
-        return getLastEvenHourBeforeTimestamp(rawTimestamp + DateUtils.HOUR_IN_MILLIS * 2);
-    }
-
-    /**
-     * @return Returns the last even hour timestamp before the given timestamp.
-     */
-    private static long getLastEvenHourBeforeTimestamp(long rawTimestamp) {
-        // If raw hour is even, the hour doesn't need to change as well.
-        // Otherwise, the even hour before raw end should be raw hour - 1.
-        // For example, the even hour before 14:30:50 should be 14:00:00. While the even
-        // hour before 15:30:50 should be 14:00:00.
-        return getEvenHourTimestamp(rawTimestamp, /*addHourOfDay*/ -1);
-    }
-
-    private static long getEvenHourTimestamp(long rawTimestamp, int addHourOfDay) {
-        final Calendar evenHourCalendar = Calendar.getInstance();
-        evenHourCalendar.setTimeInMillis(rawTimestamp);
-        // Before computing the evenHourCalendar, record raw hour based on local timezone.
-        final int rawHour = evenHourCalendar.get(Calendar.HOUR_OF_DAY);
-        if (rawHour % 2 != 0) {
-            evenHourCalendar.add(Calendar.HOUR_OF_DAY, addHourOfDay);
-        }
-        evenHourCalendar.set(Calendar.MINUTE, 0);
-        evenHourCalendar.set(Calendar.SECOND, 0);
-        evenHourCalendar.set(Calendar.MILLISECOND, 0);
-        return evenHourCalendar.getTimeInMillis();
-    }
-
-    private static List<List<Long>> getHourlyTimestamps(final List<Long> dailyTimestamps) {
-        final List<List<Long>> hourlyTimestamps = new ArrayList<>();
-        if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) {
-            return hourlyTimestamps;
-        }
-
-        for (int dailyStartIndex = 0; dailyStartIndex < dailyTimestamps.size() - 1;
-                dailyStartIndex++) {
-            long currentTimestamp = dailyTimestamps.get(dailyStartIndex);
-            final long dailyEndTimestamp = dailyTimestamps.get(dailyStartIndex + 1);
-            final List<Long> hourlyTimestampsPerDay = new ArrayList<>();
-            while (currentTimestamp <= dailyEndTimestamp) {
-                hourlyTimestampsPerDay.add(currentTimestamp);
-                currentTimestamp += MIN_TIME_SLOT;
-            }
-            hourlyTimestamps.add(hourlyTimestampsPerDay);
-        }
-        return hourlyTimestamps;
-    }
-
     private static List<BatteryLevelData.PeriodBatteryLevelData> getHourlyPeriodBatteryLevelData(
             Context context,
             final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
@@ -1465,11 +1341,16 @@
                 final Long endTimestamp = hourlyTimestamps.get(hourlyIndex + 1);
                 final long slotDuration = endTimestamp - startTimestamp;
                 List<Map<String, BatteryHistEntry>> slotBatteryHistoryList = new ArrayList<>();
-                for (Long timestamp = startTimestamp; timestamp <= endTimestamp;
-                        timestamp += DateUtils.HOUR_IN_MILLIS) {
+                slotBatteryHistoryList.add(
+                        batteryHistoryMap.getOrDefault(startTimestamp, EMPTY_BATTERY_MAP));
+                for (Long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
+                        timestamp < endTimestamp; timestamp += DateUtils.HOUR_IN_MILLIS) {
                     slotBatteryHistoryList.add(
                             batteryHistoryMap.getOrDefault(timestamp, EMPTY_BATTERY_MAP));
                 }
+                slotBatteryHistoryList.add(
+                        batteryHistoryMap.getOrDefault(endTimestamp, EMPTY_BATTERY_MAP));
+
                 final BatteryDiffData hourlyBatteryDiffData =
                         insertHourlyUsageDiffDataPerSlot(
                                 context,
@@ -1942,16 +1823,6 @@
         return true;
     }
 
-    private static long getTimestampWithDayDiff(final long timestamp, final int dayDiff) {
-        final Calendar calendar = Calendar.getInstance();
-        calendar.setTimeInMillis(timestamp);
-        calendar.add(Calendar.DAY_OF_YEAR, dayDiff);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        return calendar.getTimeInMillis();
-    }
-
     private static long getDiffValue(long v1, long v2) {
         return v2 > v1 ? v2 - v1 : 0;
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/TimestampUtils.java b/src/com/android/settings/fuelgauge/batteryusage/TimestampUtils.java
new file mode 100644
index 0000000..594a0ef
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/TimestampUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import java.util.Calendar;
+
+/** A utility class for timestamp operations. */
+final class TimestampUtils {
+
+    static long getNextHourTimestamp(final long timestamp) {
+        final Calendar calendar = getSharpHourCalendar(timestamp);
+        calendar.add(Calendar.HOUR_OF_DAY, 1);
+        return calendar.getTimeInMillis();
+    }
+
+    static long getNextEvenHourTimestamp(final long timestamp) {
+        final Calendar calendar = getSharpHourCalendar(timestamp);
+        final int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        calendar.add(Calendar.HOUR_OF_DAY, hour % 2 == 0 ? 2 : 1);
+        return calendar.getTimeInMillis();
+    }
+
+    static long getLastEvenHourTimestamp(final long timestamp) {
+        final Calendar calendar = getSharpHourCalendar(timestamp);
+        final int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        calendar.add(Calendar.HOUR_OF_DAY, hour % 2 == 0 ? 0 : -1);
+        return calendar.getTimeInMillis();
+    }
+
+    static long getNextDayTimestamp(final long timestamp) {
+        final Calendar calendar = getSharpHourCalendar(timestamp);
+        calendar.add(Calendar.DAY_OF_YEAR, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        return calendar.getTimeInMillis();
+    }
+
+    static boolean isMidnight(final long timestamp) {
+        final Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(timestamp);
+        return calendar.get(Calendar.HOUR_OF_DAY) == 0
+                && calendar.get(Calendar.MINUTE) == 0
+                && calendar.get(Calendar.SECOND) == 0
+                && calendar.get(Calendar.MILLISECOND) == 0;
+    }
+
+    private static Calendar getSharpHourCalendar(final long timestamp) {
+        final Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(timestamp);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
index f18228b..9cebd19 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
@@ -150,11 +150,12 @@
         // Ignore fast refresh ui from the data processor callback.
         verify(mHourlyChartView, atLeast(0)).setViewModel(null);
         verify(mHourlyChartView, atLeastOnce()).setViewModel(new BatteryChartViewModel(
-                List.of(100, 97, 95, 66),
-                List.of(1619251200000L /* 8 AM */,
+                List.of(100, 99, 97, 95, 66),
+                List.of(1619247660000L /* 7:01 AM */,
+                        1619251200000L /* 8 AM */,
                         1619258400000L /* 10 AM */,
                         1619265600000L /* 12 PM */,
-                        1619272800000L /* 2 PM */),
+                        1619265720000L /* now (12:02 PM) */),
                 BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
                 mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));
     }
@@ -168,10 +169,10 @@
         BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
                 List.of(100, 83, 59, 66),
                 // "Sat", "Sun", "Mon", "Mon"
-                List.of(1619251200000L /* Sat */,
+                List.of(1619247660000L /* Sat */,
                         1619308800000L /* Sun */,
                         1619395200000L /* Mon */,
-                        1619467200000L /* Mon */),
+                        1619460120000L /* Mon */),
                 BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS,
                 mBatteryChartPreferenceController.mDailyChartLabelTextGenerator);
 
@@ -194,8 +195,9 @@
         expectedDailyViewModel.setSelectedIndex(0);
         verify(mDailyChartView).setViewModel(expectedDailyViewModel);
         verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
-                List.of(100, 97, 95, 93, 91, 89, 87, 85, 83),
-                List.of(1619251200000L /* 8 AM */,
+                List.of(100, 99, 97, 95, 93, 91, 89, 87, 85, 83),
+                List.of(1619247660000L /* 7:01 AM */,
+                        1619251200000L /* 8 AM */,
                         1619258400000L /* 10 AM */,
                         1619265600000L /* 12 PM */,
                         1619272800000L /* 2 PM */,
@@ -262,7 +264,7 @@
                         1619445600000L /* 2 PM */,
                         1619452800000L /* 4 PM */,
                         1619460000000L /* 6 PM */,
-                        1619467200000L /* 8 PM */),
+                        1619460120000L /* now (6:02 PM) */),
                 BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
                 mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));
 
@@ -327,7 +329,7 @@
     public void selectedSlotText_onlyOneDayDataSelectAnHour_onlyHourText() {
         mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
         mBatteryChartPreferenceController.mDailyChartIndex = 0;
-        mBatteryChartPreferenceController.mHourlyChartIndex = 1;
+        mBatteryChartPreferenceController.mHourlyChartIndex = 2;
 
         assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
                 "10 AM - 12 PM");
@@ -344,6 +346,36 @@
     }
 
     @Test
+    public void selectedSlotText_selectFirstSlot_withMinuteText() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.mDailyChartIndex = 0;
+        mBatteryChartPreferenceController.mHourlyChartIndex = 0;
+
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
+                "7:01 AM - 8 AM");
+    }
+
+    @Test
+    public void selectedSlotText_selectLastSlot_withNowText() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.mDailyChartIndex = 0;
+        mBatteryChartPreferenceController.mHourlyChartIndex = 3;
+
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
+                "12 PM - now");
+    }
+
+    @Test
+    public void selectedSlotText_selectOnlySlot_withMinuteAndNowText() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(1));
+        mBatteryChartPreferenceController.mDailyChartIndex = 0;
+        mBatteryChartPreferenceController.mHourlyChartIndex = 0;
+
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
+                "7:01 AM - now");
+    }
+
+    @Test
     public void onSaveInstanceState_restoreSelectedIndexAndExpandState() {
         final int expectedDailyIndex = 1;
         final int expectedHourlyIndex = 2;
@@ -373,7 +405,7 @@
         final int totalHour = BatteryChartPreferenceController.getTotalHours(batteryLevelData);
 
         // Only calculate the even hours.
-        assertThat(totalHour).isEqualTo(60);
+        assertThat(totalHour).isEqualTo(59);
     }
 
     private static Long generateTimestamp(int index) {
@@ -403,10 +435,14 @@
             final BatteryHistEntry entry = new BatteryHistEntry(values);
             final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
             entryMap.put("fake_entry_key" + index, entry);
-            batteryHistoryMap.put(generateTimestamp(index), entryMap);
+            long timestamp = generateTimestamp(index);
+            if (index == 0) {
+                timestamp += DateUtils.MINUTE_IN_MILLIS;
+            }
+            batteryHistoryMap.put(timestamp, entryMap);
         }
         DataProcessor.sTestCurrentTimeMillis =
-                generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS;
+                generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS * 2;
         return batteryHistoryMap;
     }
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
index 2a00116..b610cfb 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
@@ -263,7 +263,7 @@
     }
 
     @Test
-    public void getBatteryLevelData_notEnoughData_returnNull() {
+    public void getBatteryLevelData_allDataInOneHour_returnExpectedResult() {
         // The timestamps and the current time are within half hour before an even hour.
         final long[] timestamps = {
                 DateUtils.HOUR_IN_MILLIS * 2 - 300L,
@@ -274,9 +274,26 @@
                 createHistoryMap(timestamps, levels);
         DataProcessor.sTestCurrentTimeMillis = timestamps[timestamps.length - 1];
 
-        assertThat(DataProcessManager.getBatteryLevelData(
-                mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null))
-                .isNull();
+        final BatteryLevelData resultData =
+                DataProcessManager.getBatteryLevelData(
+                        mContext,
+                        /*handler=*/ null,
+                        batteryHistoryMap,
+                        /*asyncResponseDelegate=*/ null);
+
+
+        final List<Long> expectedDailyTimestamps = List.of(
+                DateUtils.HOUR_IN_MILLIS * 2 - 300L,
+                DateUtils.HOUR_IN_MILLIS * 2 - 100L);
+        final List<Integer> expectedDailyLevels = List.of(100, 66);
+        final List<List<Long>> expectedHourlyTimestamps = List.of(expectedDailyTimestamps);
+        final List<List<Integer>> expectedHourlyLevels = List.of(expectedDailyLevels);
+        verifyExpectedBatteryLevelData(
+                resultData,
+                expectedDailyTimestamps,
+                expectedDailyLevels,
+                expectedHourlyTimestamps,
+                expectedHourlyLevels);
     }
 
     @Test
@@ -297,7 +314,7 @@
 
         final List<Long> expectedDailyTimestamps = List.of(
                 1640966400000L,  // 2022-01-01 00:00:00
-                1640973600000L); // 2022-01-01 02:00:00
+                1640970000000L); // 2022-01-01 01:00:00
         final List<Integer> expectedDailyLevels = List.of(100, 66);
         final List<List<Long>> expectedHourlyTimestamps = List.of(expectedDailyTimestamps);
         final List<List<Integer>> expectedHourlyLevels = List.of(expectedDailyLevels);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
index 66e48c0..ea2db86 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -42,7 +42,6 @@
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.UserManager;
-import android.text.format.DateUtils;
 
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -409,24 +408,21 @@
 
         // Timezone GMT+8
         final long[] expectedTimestamps = {
-                1640966400000L, // 2022-01-01 00:00:00
+                1640966700000L, // 2022-01-01 00:05:00
                 1640970000000L, // 2022-01-01 01:00:00
                 1640973600000L, // 2022-01-01 02:00:00
                 1640977200000L, // 2022-01-01 03:00:00
                 1640980800000L, // 2022-01-01 04:00:00
-                1640984400000L, // 2022-01-01 05:00:00
-                1640988000000L  // 2022-01-01 06:00:00
+                1640981400000L  // 2022-01-01 04:10:00
         };
-        final int[] expectedLevels = {100, 94, 90, 84, 56, 98, 98};
+        final int[] expectedLevels = {100, 94, 90, 84, 56, 98};
         assertThat(resultMap).hasSize(expectedLevels.length);
-        for (int index = 0; index < 5; index++) {
+        for (int index = 0; index < expectedLevels.length - 1; index++) {
             assertThat(resultMap.get(expectedTimestamps[index]).get(FAKE_ENTRY_KEY).mBatteryLevel)
                     .isEqualTo(expectedLevels[index]);
         }
-        for (int index = 5; index < 7; index++) {
-            assertThat(resultMap.get(expectedTimestamps[index]).containsKey(
-                    DataProcessor.CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)).isTrue();
-        }
+        assertThat(resultMap.get(expectedTimestamps[expectedLevels.length - 1]).containsKey(
+                DataProcessor.CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)).isTrue();
     }
 
     @Test
@@ -589,7 +585,8 @@
                         1667782800000L, // 2022-11-06 17:00:00
                         1667790000000L, // 2022-11-06 19:00:00
                         1667797200000L, // 2022-11-06 21:00:00
-                        1667804400000L  // 2022-11-06 23:00:00
+                        1667804400000L, // 2022-11-06 23:00:00
+                        1667808000000L  // 2022-11-07 00:00:00
                 ),
                 List.of(
                         1667808000000L, // 2022-11-07 00:00:00
@@ -621,6 +618,7 @@
         expectedHourlyLevels2.add(null);
         expectedHourlyLevels2.add(null);
         expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(null);
         final List<Integer> expectedHourlyLevels3 = new ArrayList<>();
         expectedHourlyLevels3.add(null);
         expectedHourlyLevels3.add(null);
@@ -683,7 +681,8 @@
                         1647216000000L, // 2022-03-13 17:00:00
                         1647223200000L, // 2022-03-13 19:00:00
                         1647230400000L, // 2022-03-13 21:00:00
-                        1647237600000L  // 2022-03-13 23:00:00
+                        1647237600000L, // 2022-03-13 23:00:00
+                        1647241200000L  // 2022-03-14 00:00:00
                 ),
                 List.of(
                         1647241200000L, // 2022-03-14 00:00:00
@@ -708,6 +707,7 @@
         expectedHourlyLevels2.add(null);
         expectedHourlyLevels2.add(null);
         expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(null);
         final List<Integer> expectedHourlyLevels3 = new ArrayList<>();
         expectedHourlyLevels3.add(null);
         expectedHourlyLevels3.add(null);
@@ -736,49 +736,95 @@
     @Test
     public void getTimestampSlots_startWithEvenHour_returnExpectedResult() {
         final Calendar startCalendar = Calendar.getInstance();
+        startCalendar.clear();
         startCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
+        final long startTimestamp = startCalendar.getTimeInMillis();
         final Calendar endCalendar = Calendar.getInstance();
+        endCalendar.clear();
         endCalendar.set(2022, 6, 5, 22, 30, 50); // 2022-07-05 22:30:50
+        final long endTimestamp = endCalendar.getTimeInMillis();
 
-        final Calendar expectedStartCalendar = Calendar.getInstance();
-        expectedStartCalendar.set(2022, 6, 5, 6, 0, 0); // 2022-07-05 06:00:00
-        final Calendar expectedEndCalendar = Calendar.getInstance();
-        expectedEndCalendar.set(2022, 6, 6, 0, 0, 0); // 2022-07-05 22:00:00
-        verifyExpectedTimestampSlots(
-                startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
+        final Calendar calendar = Calendar.getInstance();
+        List<Long> expectedTimestamps = new ArrayList<>();
+        calendar.clear();
+        calendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
+        expectedTimestamps.add(calendar.getTimeInMillis());
+        for (int hour = 7; hour <= 22; hour++) {
+            calendar.clear();
+            calendar.set(2022, 6, 5, hour, 0, 0); // 2022-07-05 <hour>:00:00
+            expectedTimestamps.add(calendar.getTimeInMillis());
+        }
+        calendar.clear();
+        calendar.set(2022, 6, 5, 22, 30, 50); // 2022-07-05 22:30:50
+        expectedTimestamps.add(calendar.getTimeInMillis());
+
+        verifyExpectedTimestampSlots(startTimestamp, endTimestamp, expectedTimestamps);
     }
 
     @Test
     public void getTimestampSlots_startWithOddHour_returnExpectedResult() {
         final Calendar startCalendar = Calendar.getInstance();
+        startCalendar.clear();
         startCalendar.set(2022, 6, 5, 5, 0, 50); // 2022-07-05 05:00:50
+        final long startTimestamp = startCalendar.getTimeInMillis();
         final Calendar endCalendar = Calendar.getInstance();
-        endCalendar.set(2022, 6, 6, 21, 0, 50); // 2022-07-06 21:00:50
+        endCalendar.clear();
+        endCalendar.set(2022, 6, 5, 21, 0, 50); // 2022-07-05 21:00:50
+        final long endTimestamp = endCalendar.getTimeInMillis();
 
-        final Calendar expectedStartCalendar = Calendar.getInstance();
-        expectedStartCalendar.set(2022, 6, 5, 6, 0, 0); // 2022-07-05 06:00:00
-        final Calendar expectedEndCalendar = Calendar.getInstance();
-        expectedEndCalendar.set(2022, 6, 6, 22, 0, 0); // 2022-07-06 20:00:00
-        verifyExpectedTimestampSlots(
-                startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
+        final Calendar calendar = Calendar.getInstance();
+        List<Long> expectedTimestamps = new ArrayList<>();
+        calendar.clear();
+        calendar.set(2022, 6, 5, 5, 0, 50); // 2022-07-05 05:00:50
+        expectedTimestamps.add(calendar.getTimeInMillis());
+        for (int hour = 6; hour <= 21; hour++) {
+            calendar.clear();
+            calendar.set(2022, 6, 5, hour, 0, 0); // 2022-07-05 <hour>:00:00
+            expectedTimestamps.add(calendar.getTimeInMillis());
+        }
+        calendar.clear();
+        calendar.set(2022, 6, 5, 21, 0, 50); // 2022-07-05 21:00:50
+        expectedTimestamps.add(calendar.getTimeInMillis());
+
+        verifyExpectedTimestampSlots(startTimestamp, endTimestamp, expectedTimestamps);
     }
 
     @Test
     public void getDailyTimestamps_notEnoughData_returnEmptyList() {
         assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty();
         assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty();
-        assertThat(DataProcessor.getDailyTimestamps(List.of(100L, 5400000L))).isEmpty();
     }
 
     @Test
-    public void getDailyTimestamps_OneHourDataPerDay_returnEmptyList() {
+    public void getDailyTimestamps_allDataInOneHour_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1640970006000L, // 2022-01-01 01:00:06
+                1640973608000L  // 2022-01-01 01:00:08
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1640970006000L, // 2022-01-01 01:00:06
+                1640973608000L  // 2022-01-01 01:00:08
+        );
+        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_OneHourDataPerDay_returnExpectedList() {
         // Timezone GMT+8
         final List<Long> timestamps = List.of(
                 1641049200000L, // 2022-01-01 23:00:00
                 1641052800000L, // 2022-01-02 00:00:00
                 1641056400000L  // 2022-01-02 01:00:00
         );
-        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEmpty();
+
+        final List<Long> expectedTimestamps = List.of(
+                1641049200000L, // 2022-01-01 23:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L  // 2022-01-02 01:00:00
+        );
+        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
     }
 
     @Test
@@ -830,6 +876,7 @@
         );
 
         final List<Long> expectedTimestamps = List.of(
+                1641049200000L, // 2022-01-01 23:00:00
                 1641052800000L, // 2022-01-02 00:00:00
                 1641139200000L, // 2022-01-03 00:00:00
                 1641225600000L, // 2022-01-04 00:00:00
@@ -871,7 +918,8 @@
                 1640988000000L, // 2022-01-01 06:00:00
                 1641052800000L, // 2022-01-02 00:00:00
                 1641139200000L, // 2022-01-03 00:00:00
-                1641225600000L  // 2022-01-04 00:00:00
+                1641225600000L, // 2022-01-04 00:00:00
+                1641229200000L  // 2022-01-04 01:00:00
         );
         assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
     }
@@ -923,27 +971,6 @@
     }
 
     @Test
-    public void getTimestampOfNextDay_returnExpectedResult() {
-        // 2021-02-28 06:00:00 => 2021-03-01 00:00:00
-        assertThat(DataProcessor.getTimestampOfNextDay(1614463200000L))
-                .isEqualTo(1614528000000L);
-        // 2021-12-31 16:00:00 => 2022-01-01 00:00:00
-        assertThat(DataProcessor.getTimestampOfNextDay(1640937600000L))
-                .isEqualTo(1640966400000L);
-    }
-
-    @Test
-    public void isForDailyChart_returnExpectedResult() {
-        assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ true, 0L)).isTrue();
-        // 2022-01-01 00:00:00
-        assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ false, 1640966400000L))
-                .isTrue();
-        // 2022-01-01 01:00:05
-        assertThat(DataProcessor.isForDailyChart(/*isStartOrEnd=*/ false, 1640970005000L))
-                .isFalse();
-    }
-
-    @Test
     public void getBatteryUsageMap_emptyHistoryMap_returnNull() {
         final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
                 new ArrayList<>();
@@ -2046,24 +2073,16 @@
     }
 
     private static void verifyExpectedTimestampSlots(
-            final Calendar start,
-            final Calendar current,
-            final Calendar expectedStart,
-            final Calendar expectedEnd) {
-        expectedStart.set(Calendar.MILLISECOND, 0);
-        expectedEnd.set(Calendar.MILLISECOND, 0);
+            final long startTimestamp,
+            final long currentTimestamp,
+            final List<Long> expectedTimestamps) {
         final ArrayList<Long> timestampSlots = new ArrayList<>();
-        timestampSlots.add(start.getTimeInMillis());
-        final List<Long> resultList =
-                DataProcessor.getTimestampSlots(timestampSlots, current.getTimeInMillis());
+        timestampSlots.add(startTimestamp);
 
-        for (int index = 0; index < resultList.size(); index++) {
-            final long expectedTimestamp =
-                    expectedStart.getTimeInMillis() + index * DateUtils.HOUR_IN_MILLIS;
-            assertThat(resultList.get(index)).isEqualTo(expectedTimestamp);
-        }
-        assertThat(resultList.get(resultList.size() - 1))
-                .isEqualTo(expectedEnd.getTimeInMillis());
+        final List<Long> resultList =
+                DataProcessor.getTimestampSlots(timestampSlots, currentTimestamp);
+
+        assertThat(resultList).isEqualTo(expectedTimestamps);
     }
 
     private static void assertBatteryDiffEntry(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/TimestampUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/TimestampUtilsTest.java
new file mode 100644
index 0000000..23787c7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/TimestampUtilsTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.TimeZone;
+
+@RunWith(RobolectricTestRunner.class)
+public class TimestampUtilsTest {
+
+    @Before
+    public void setUp() {
+        TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
+    }
+
+    @Test
+    public void getNextHourTimestamp_returnExpectedResult() {
+        // 2021-02-28 06:00:00 => 2021-02-28 07:00:00
+        assertThat(TimestampUtils.getNextHourTimestamp(1614463200000L))
+                .isEqualTo(1614466800000L);
+        // 2021-12-31 23:59:59 => 2022-01-01 00:00:00
+        assertThat(TimestampUtils.getNextHourTimestamp(16409663999999L))
+                .isEqualTo(16409664000000L);
+    }
+
+    @Test
+    public void getNextEvenHourTimestamp_returnExpectedResult() {
+        // 2021-02-28 06:00:00 => 2021-02-28 08:00:00
+        assertThat(TimestampUtils.getNextEvenHourTimestamp(1614463200000L))
+                .isEqualTo(1614470400000L);
+        // 2021-12-31 23:59:59 => 2022-01-01 00:00:00
+        assertThat(TimestampUtils.getNextEvenHourTimestamp(16409663999999L))
+                .isEqualTo(16409664000000L);
+    }
+
+    @Test
+    public void getLastEvenHourTimestamp_returnExpectedResult() {
+        // 2021-02-28 06:00:06 => 2021-02-28 06:00:00
+        assertThat(TimestampUtils.getLastEvenHourTimestamp(1614463206000L))
+                .isEqualTo(1614463200000L);
+        // 2021-12-31 23:59:59 => 2021-12-31 22:00:00
+        assertThat(TimestampUtils.getLastEvenHourTimestamp(16409663999999L))
+                .isEqualTo(16409656800000L);
+    }
+
+    @Test
+    public void getTimestampOfNextDay_returnExpectedResult() {
+        // 2021-02-28 06:00:00 => 2021-03-01 00:00:00
+        assertThat(TimestampUtils.getNextDayTimestamp(1614463200000L))
+                .isEqualTo(1614528000000L);
+        // 2021-12-31 16:00:00 => 2022-01-01 00:00:00
+        assertThat(TimestampUtils.getNextDayTimestamp(1640937600000L))
+                .isEqualTo(1640966400000L);
+    }
+
+    @Test
+    public void isMidnight_returnExpectedResult() {
+        // 2022-01-01 00:00:00
+        assertThat(TimestampUtils.isMidnight(1640966400000L)).isTrue();
+        // 2022-01-01 01:00:05
+        assertThat(TimestampUtils.isMidnight(1640970005000L)).isFalse();
+    }
+}