Merge "Implements the buttons layout for the extra defend" into tm-qpr-dev
diff --git a/res/layout/battery_chart_graph.xml b/res/layout/battery_chart_graph.xml
index df48144..b95c660 100644
--- a/res/layout/battery_chart_graph.xml
+++ b/res/layout/battery_chart_graph.xml
@@ -29,14 +29,24 @@
         android:layout_marginVertical="16dp"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textColor="?android:attr/textColorSecondary"
-        android:text="@string/battery_usage_chart_graph_hint" />
+        android:text="@string/battery_usage_chart_graph_hint_last_full_charge" />
 
     <com.android.settings.fuelgauge.batteryusage.BatteryChartView
-        android:id="@+id/battery_chart"
+        android:id="@+id/daily_battery_chart"
         android:layout_width="match_parent"
         android:layout_height="170dp"
-        android:layout_marginBottom="6dp"
-        android:visibility="invisible"
+        android:layout_marginBottom="16dp"
+        android:visibility="gone"
+        android:contentDescription="@string/battery_usage_chart"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        settings:textColor="?android:attr/textColorSecondary" />
+
+    <com.android.settings.fuelgauge.batteryusage.BatteryChartView
+        android:id="@+id/hourly_battery_chart"
+        android:layout_width="match_parent"
+        android:layout_height="170dp"
+        android:layout_marginBottom="16dp"
+        android:visibility="visible"
         android:contentDescription="@string/battery_usage_chart"
         android:textAppearance="?android:attr/textAppearanceSmall"
         settings:textColor="?android:attr/textColorSecondary" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8fbe72c..406c4a2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6753,10 +6753,16 @@
     <!-- [CHAR_LIMIT=NONE] Battery percentage: Description for preference -->
     <string name="battery_percentage_description">Show battery percentage in status bar</string>
 
+    <!-- [CHAR_LIMIT=NONE] Battery usage main screen chart graph hint since last full charge -->
+    <string name="battery_usage_chart_graph_hint_last_full_charge">Battery level since last full charge</string>
     <!-- [CHAR_LIMIT=NONE] Battery usage main screen chart graph hint -->
     <string name="battery_usage_chart_graph_hint">Battery level for past 24 hr</string>
+    <!-- [CHAR_LIMIT=NONE] Battery app usage section header since last full charge -->
+    <string name="battery_app_usage">App usage since last full charge</string>
     <!-- [CHAR_LIMIT=NONE] Battery app usage section header for past 24 hour -->
     <string name="battery_app_usage_for_past_24">App usage for past 24 hr</string>
+    <!-- [CHAR_LIMIT=NONE] Battery system usage section header since last full charge -->
+    <string name="battery_system_usage">System usage since last full charge</string>
     <!-- [CHAR_LIMIT=NONE] Battery system usage section header for past 24 hour -->
     <string name="battery_system_usage_for_past_24">System usage for past 24 hr</string>
     <!-- [CHAR_LIMIT=NONE] Battery system usage section header -->
diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
index 99c630d..732163b 100644
--- a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
@@ -179,7 +179,7 @@
                     return null;
                 }
                 final BatteryDiffEntry entry =
-                        BatteryChartPreferenceController.getBatteryLast24HrUsageData(
+                        BatteryChartPreferenceController.getAppBatteryUsageData(
                                 mContext, mPackageName, mUserId);
                 Log.d(TAG, "loadBatteryDiffEntries():\n" + entry);
                 return entry;
@@ -200,10 +200,10 @@
                 mBatteryPercent = Utils.formatPercentage(
                         mBatteryDiffEntry.getPercentOfTotal(), /* round */ true);
                 mPreference.setSummary(mContext.getString(
-                        R.string.battery_summary_24hr, mBatteryPercent));
+                        R.string.battery_summary, mBatteryPercent));
             } else {
                 mPreference.setSummary(
-                        mContext.getString(R.string.no_battery_summary_24hr));
+                        mContext.getString(R.string.no_battery_summary));
             }
         }
 
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index d096d49..db98a4c 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -539,16 +539,13 @@
             return null;
         }
         if (totalTimeMs == 0) {
-            final int batteryWithoutUsageTime = consumedPower > 0
-                    ? R.string.battery_usage_without_time : R.string.battery_not_usage_24hr;
-            usageTimeSummary = getText(isChartGraphEnabled
-                    ? batteryWithoutUsageTime : R.string.battery_not_usage);
+            usageTimeSummary = getText(
+                    isChartGraphEnabled && consumedPower > 0 ? R.string.battery_usage_without_time
+                            : R.string.battery_not_usage);
         } else if (slotTime == null) {
-            // Shows summary text with past 24 hr or full charge if slot time is null.
-            usageTimeSummary = isChartGraphEnabled
-                    ? getAppPast24HrActiveSummary(foregroundTimeMs, backgroundTimeMs, totalTimeMs)
-                    : getAppFullChargeActiveSummary(
-                            foregroundTimeMs, backgroundTimeMs, totalTimeMs);
+            // Shows summary text with last full charge if slot time is null.
+            usageTimeSummary = getAppFullChargeActiveSummary(
+                    foregroundTimeMs, backgroundTimeMs, totalTimeMs);
         } else {
             // Shows summary text with slot time.
             usageTimeSummary = getAppActiveSummaryWithSlotTime(
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index d363308..88bec0d 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -28,7 +27,9 @@
 import android.text.format.DateFormat;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceGroup;
@@ -53,8 +54,6 @@
 import com.android.settingslib.widget.FooterPreference;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -62,27 +61,23 @@
 /** Controls the update for chart graph and the list items. */
 public class BatteryChartPreferenceController extends AbstractPreferenceController
         implements PreferenceControllerMixin, LifecycleObserver, OnCreate, OnDestroy,
-        OnSaveInstanceState, BatteryChartView.OnSelectListener, OnResume,
-        ExpandDividerPreference.OnExpandListener {
+        OnSaveInstanceState, OnResume, ExpandDividerPreference.OnExpandListener {
     private static final String TAG = "BatteryChartPreferenceController";
     private static final String KEY_FOOTER_PREF = "battery_graph_footer";
     private static final String PACKAGE_NAME_NONE = "none";
 
-    /** Desired battery history size for timestamp slots. */
-    public static final int DESIRED_HISTORY_SIZE = 25;
-    private static final int CHART_LEVEL_ARRAY_SIZE = 13;
-    private static final int CHART_KEY_ARRAY_SIZE = DESIRED_HISTORY_SIZE;
     private static final long VALID_USAGE_TIME_DURATION = DateUtils.HOUR_IN_MILLIS * 2;
     private static final long VALID_DIFF_DURATION = DateUtils.MINUTE_IN_MILLIS * 3;
 
     // Keys for bundle instance to restore configurations.
     private static final String KEY_EXPAND_SYSTEM_INFO = "expand_system_info";
-    private static final String KEY_CURRENT_TIME_SLOT = "current_time_slot";
+    private static final String KEY_DAILY_CHART_INDEX = "daily_chart_index";
+    private static final String KEY_HOURLY_CHART_INDEX = "hourly_chart_index";
 
     private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
 
     @VisibleForTesting
-    Map<Integer, List<BatteryDiffEntry>> mBatteryIndexedMap;
+    Map<Integer, Map<Integer, BatteryDiffData>> mBatteryUsageMap;
 
     @VisibleForTesting
     Context mPrefContext;
@@ -91,28 +86,34 @@
     @VisibleForTesting
     PreferenceGroup mAppListPrefGroup;
     @VisibleForTesting
-    BatteryChartView mBatteryChartView;
-    @VisibleForTesting
     ExpandDividerPreference mExpandDividerPreference;
-
     @VisibleForTesting
     boolean mIsExpanded = false;
-    @VisibleForTesting
-    int[] mBatteryHistoryLevels;
-    @VisibleForTesting
-    long[] mBatteryHistoryKeys;
-    @VisibleForTesting
-    int mTrapezoidIndex = BatteryChartView.SELECTED_INDEX_INVALID;
 
-    private boolean mIs24HourFormat = false;
+    @VisibleForTesting
+    BatteryChartView mDailyChartView;
+    @VisibleForTesting
+    BatteryChartView mHourlyChartView;
+
+    @VisibleForTesting
+    int mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+    @VisibleForTesting
+    int mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+
+    private boolean mIs24HourFormat;
     private boolean mIsFooterPrefAdded = false;
     private PreferenceScreen mPreferenceScreen;
     private FooterPreference mFooterPreference;
+    // Daily view model only saves abbreviated day of week texts (e.g. MON). This field saves the
+    // full day of week texts (e.g. Monday), which is used in category title and battery detail
+    // page.
+    private List<String> mDailyTimestampFullTexts;
+    private BatteryChartViewModel mDailyViewModel;
+    private List<BatteryChartViewModel> mHourlyViewModels;
 
     private final String mPreferenceKey;
     private final SettingsActivity mActivity;
     private final InstrumentedPreferenceFragment mFragment;
-    private final CharSequence[] mNotAllowShowEntryPackages;
     private final CharSequence[] mNotAllowShowSummaryPackages;
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -120,8 +121,6 @@
     // Preference cache to avoid create new instance each time.
     @VisibleForTesting
     final Map<String, Preference> mPreferenceCache = new HashMap<>();
-    @VisibleForTesting
-    final List<BatteryDiffEntry> mSystemEntries = new ArrayList<>();
 
     public BatteryChartPreferenceController(
             Context context, String preferenceKey,
@@ -134,10 +133,6 @@
         mIs24HourFormat = DateFormat.is24HourFormat(context);
         mMetricsFeatureProvider =
                 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider();
-        mNotAllowShowEntryPackages =
-                FeatureFactory.getFactory(context)
-                        .getPowerUsageFeatureProvider(context)
-                        .getHideApplicationEntries(context);
         mNotAllowShowSummaryPackages =
                 FeatureFactory.getFactory(context)
                         .getPowerUsageFeatureProvider(context)
@@ -152,12 +147,14 @@
         if (savedInstanceState == null) {
             return;
         }
-        mTrapezoidIndex =
-                savedInstanceState.getInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex);
+        mDailyChartIndex =
+                savedInstanceState.getInt(KEY_DAILY_CHART_INDEX, mDailyChartIndex);
+        mHourlyChartIndex =
+                savedInstanceState.getInt(KEY_HOURLY_CHART_INDEX, mHourlyChartIndex);
         mIsExpanded =
                 savedInstanceState.getBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
-        Log.d(TAG, String.format("onCreate() slotIndex=%d isExpanded=%b",
-                mTrapezoidIndex, mIsExpanded));
+        Log.d(TAG, String.format("onCreate() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
+                mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
     }
 
     @Override
@@ -179,10 +176,11 @@
         if (savedInstance == null) {
             return;
         }
-        savedInstance.putInt(KEY_CURRENT_TIME_SLOT, mTrapezoidIndex);
+        savedInstance.putInt(KEY_DAILY_CHART_INDEX, mDailyChartIndex);
+        savedInstance.putInt(KEY_HOURLY_CHART_INDEX, mHourlyChartIndex);
         savedInstance.putBoolean(KEY_EXPAND_SYSTEM_INFO, mIsExpanded);
-        Log.d(TAG, String.format("onSaveInstanceState() slotIndex=%d isExpanded=%b",
-                mTrapezoidIndex, mIsExpanded));
+        Log.d(TAG, String.format("onSaveInstanceState() dailyIndex=%d hourlyIndex=%d isExpanded=%b",
+                mDailyChartIndex, mHourlyChartIndex, mIsExpanded));
     }
 
     @Override
@@ -204,8 +202,7 @@
         mPrefContext = screen.getContext();
         mAppListPrefGroup = screen.findPreference(mPreferenceKey);
         mAppListPrefGroup.setOrderingAsAdded(false);
-        mAppListPrefGroup.setTitle(
-                mPrefContext.getString(R.string.battery_app_usage_for_past_24));
+        mAppListPrefGroup.setTitle(mPrefContext.getString(R.string.battery_app_usage));
         mFooterPreference = screen.findPreference(KEY_FOOTER_PREF);
         // Removes footer first until usage data is loaded to avoid flashing.
         if (mFooterPreference != null) {
@@ -250,17 +247,6 @@
     }
 
     @Override
-    public void onSelect(int trapezoidIndex) {
-        Log.d(TAG, "onChartSelect:" + trapezoidIndex);
-        refreshUi(trapezoidIndex, /*isForce=*/ false);
-        mMetricsFeatureProvider.action(
-                mPrefContext,
-                trapezoidIndex == BatteryChartView.SELECTED_INDEX_ALL
-                        ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
-                        : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
-    }
-
-    @Override
     public void onExpand(boolean isExpanded) {
         mIsExpanded = isExpanded;
         mMetricsFeatureProvider.action(
@@ -272,81 +258,120 @@
 
     void setBatteryHistoryMap(
             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
-        // Resets all battery history data relative variables.
-        if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
-            mBatteryIndexedMap = null;
-            mBatteryHistoryKeys = null;
-            mBatteryHistoryLevels = null;
-            addFooterPreferenceIfNeeded(false);
+        Log.d(TAG, "setBatteryHistoryMap() " + (batteryHistoryMap == null ? "null"
+                : ("size=" + batteryHistoryMap.size())));
+        final BatteryLevelData batteryLevelData =
+                DataProcessor.getBatteryLevelData(mContext, mHandler, batteryHistoryMap,
+                        batteryUsageMap -> {
+                            mBatteryUsageMap = batteryUsageMap;
+                            refreshUi();
+                        });
+        Log.d(TAG, "getBatteryLevelData: " + batteryLevelData);
+        if (batteryLevelData == null) {
+            mDailyTimestampFullTexts = null;
+            mDailyViewModel = null;
+            mHourlyViewModels = null;
+            refreshUi();
             return;
         }
-        mBatteryHistoryKeys = getBatteryHistoryKeys(batteryHistoryMap);
-        mBatteryHistoryLevels = new int[CHART_LEVEL_ARRAY_SIZE];
-        for (int index = 0; index < CHART_LEVEL_ARRAY_SIZE; index++) {
-            final long timestamp = mBatteryHistoryKeys[index * 2];
-            final Map<String, BatteryHistEntry> entryMap = batteryHistoryMap.get(timestamp);
-            if (entryMap == null || entryMap.isEmpty()) {
-                Log.e(TAG, "abnormal entry list in the timestamp:"
-                        + ConvertUtils.utcToLocalTime(mPrefContext, timestamp));
-                continue;
+        mDailyTimestampFullTexts = generateTimestampDayOfWeekTexts(
+                mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
+                /* isAbbreviation= */ false);
+        mDailyViewModel = new BatteryChartViewModel(
+                batteryLevelData.getDailyBatteryLevels().getLevels(),
+                generateTimestampDayOfWeekTexts(
+                        mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(),
+                        /* isAbbreviation= */ true),
+                BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
+        mHourlyViewModels = new ArrayList<>();
+        for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay :
+                batteryLevelData.getHourlyBatteryLevelsPerDay()) {
+            mHourlyViewModels.add(new BatteryChartViewModel(
+                    hourlyBatteryLevelsPerDay.getLevels(),
+                    generateTimestampHourTexts(
+                            mContext, hourlyBatteryLevelsPerDay.getTimestamps()),
+                    BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
+        }
+        refreshUi();
+    }
+
+    void setBatteryChartView(@NonNull final BatteryChartView dailyChartView,
+            @NonNull final BatteryChartView hourlyChartView) {
+        if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) {
+            mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
+        }
+    }
+
+    private void setBatteryChartViewInner(@NonNull final BatteryChartView dailyChartView,
+            @NonNull final BatteryChartView hourlyChartView) {
+        mDailyChartView = dailyChartView;
+        mDailyChartView.setOnSelectListener(trapezoidIndex -> {
+            if (mDailyChartIndex == trapezoidIndex) {
+                return;
             }
-            // Averages the battery level in each time slot to avoid corner conditions.
-            float batteryLevelCounter = 0;
-            for (BatteryHistEntry entry : entryMap.values()) {
-                batteryLevelCounter += entry.mBatteryLevel;
+            Log.d(TAG, "onDailyChartSelect:" + trapezoidIndex);
+            mDailyChartIndex = trapezoidIndex;
+            mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+            refreshUi();
+            // TODO: Change to log daily data.
+        });
+        mHourlyChartView = hourlyChartView;
+        mHourlyChartView.setOnSelectListener(trapezoidIndex -> {
+            if (mHourlyChartIndex == trapezoidIndex) {
+                return;
             }
-            mBatteryHistoryLevels[index] =
-                    Math.round(batteryLevelCounter / entryMap.size());
-        }
-        forceRefreshUi();
-        Log.d(TAG, String.format(
-                "setBatteryHistoryMap() size=%d key=%s\nlevels=%s",
-                batteryHistoryMap.size(),
-                ConvertUtils.utcToLocalTime(mPrefContext,
-                        mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1]),
-                Arrays.toString(mBatteryHistoryLevels)));
-
-        // Loads item icon and label in the background.
-        new LoadAllItemsInfoTask(batteryHistoryMap).execute();
-    }
-
-    void setBatteryChartView(final BatteryChartView batteryChartView) {
-        if (mBatteryChartView != batteryChartView) {
-            mHandler.post(() -> setBatteryChartViewInner(batteryChartView));
-        }
-    }
-
-    private void setBatteryChartViewInner(final BatteryChartView batteryChartView) {
-        mBatteryChartView = batteryChartView;
-        mBatteryChartView.setOnSelectListener(this);
-        forceRefreshUi();
-    }
-
-    private void forceRefreshUi() {
-        final int refreshIndex =
-                mTrapezoidIndex == BatteryChartView.SELECTED_INDEX_INVALID
-                        ? BatteryChartView.SELECTED_INDEX_ALL
-                        : mTrapezoidIndex;
-        if (mBatteryChartView != null) {
-            mBatteryChartView.setLevels(mBatteryHistoryLevels);
-            mBatteryChartView.setSelectedIndex(refreshIndex);
-            setTimestampLabel();
-        }
-        refreshUi(refreshIndex, /*isForce=*/ true);
+            Log.d(TAG, "onHourlyChartSelect:" + trapezoidIndex);
+            mHourlyChartIndex = trapezoidIndex;
+            refreshUi();
+            mMetricsFeatureProvider.action(
+                    mPrefContext,
+                    trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_ALL
+                            ? SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL
+                            : SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
+        });
+        refreshUi();
     }
 
     @VisibleForTesting
-    boolean refreshUi(int trapezoidIndex, boolean isForce) {
-        // Invalid refresh condition.
-        if (mBatteryIndexedMap == null
-                || mBatteryChartView == null
-                || (mTrapezoidIndex == trapezoidIndex && !isForce)) {
+    boolean refreshUi() {
+        if (mDailyChartView == null || mHourlyChartView == null) {
+            // Chart views are not initialized.
             return false;
         }
-        Log.d(TAG, String.format("refreshUi: index=%d size=%d isForce:%b",
-                trapezoidIndex, mBatteryIndexedMap.size(), isForce));
+        if (mDailyViewModel == null || mHourlyViewModels == null) {
+            // Fail to get battery level data, show an empty hourly chart view.
+            mDailyChartView.setVisibility(View.GONE);
+            mHourlyChartView.setVisibility(View.VISIBLE);
+            mHourlyChartView.setViewModel(null);
+            removeAndCacheAllPrefs();
+            addFooterPreferenceIfNeeded(false);
+            return false;
+        }
+        if (mBatteryUsageMap == null) {
+            // Battery usage data is not ready, wait for data ready to refresh UI.
+            return false;
+        }
 
-        mTrapezoidIndex = trapezoidIndex;
+        if (isBatteryLevelDataInOneDay()) {
+            // Only 1 day data, hide the daily chart view.
+            mDailyChartView.setVisibility(View.GONE);
+            mDailyChartIndex = 0;
+        } else {
+            mDailyChartView.setVisibility(View.VISIBLE);
+            mDailyViewModel.setSelectedIndex(mDailyChartIndex);
+            mDailyChartView.setViewModel(mDailyViewModel);
+        }
+
+        if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
+            // Multiple days are selected, hide the hourly chart view.
+            mHourlyChartView.setVisibility(View.GONE);
+        } else {
+            mHourlyChartView.setVisibility(View.VISIBLE);
+            final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
+            hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
+            mHourlyChartView.setViewModel(hourlyViewModel);
+        }
+
         mHandler.post(() -> {
             final long start = System.currentTimeMillis();
             removeAndCacheAllPrefs();
@@ -359,43 +384,22 @@
     }
 
     private void addAllPreferences() {
-        final List<BatteryDiffEntry> entries =
-                mBatteryIndexedMap.get(Integer.valueOf(mTrapezoidIndex));
-        addFooterPreferenceIfNeeded(entries != null && !entries.isEmpty());
-        if (entries == null) {
-            Log.w(TAG, "cannot find BatteryDiffEntry for:" + mTrapezoidIndex);
+        final BatteryDiffData batteryDiffData =
+                mBatteryUsageMap.get(mDailyChartIndex).get(mHourlyChartIndex);
+        addFooterPreferenceIfNeeded(batteryDiffData != null
+                && (!batteryDiffData.getAppDiffEntryList().isEmpty()
+                || !batteryDiffData.getSystemDiffEntryList().isEmpty()));
+        if (batteryDiffData == null) {
+            Log.w(TAG, "cannot find BatteryDiffEntry for daily_index: " + mDailyChartIndex
+                    + " hourly_index: " + mHourlyChartIndex);
             return;
         }
-        // Separates data into two groups and sort them individually.
-        final List<BatteryDiffEntry> appEntries = new ArrayList<>();
-        mSystemEntries.clear();
-        entries.forEach(entry -> {
-            final String packageName = entry.getPackageName();
-            if (!isValidToShowEntry(packageName)) {
-                Log.w(TAG, "ignore showing item:" + packageName);
-                return;
-            }
-            if (entry.isSystemEntry()) {
-                mSystemEntries.add(entry);
-            } else {
-                appEntries.add(entry);
-            }
-            // Validates the usage time if users click a specific slot.
-            if (mTrapezoidIndex >= 0) {
-                validateUsageTime(entry);
-            }
-        });
-        Collections.sort(appEntries, BatteryDiffEntry.COMPARATOR);
-        Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
-        Log.d(TAG, String.format("addAllPreferences() app=%d system=%d",
-                appEntries.size(), mSystemEntries.size()));
-
         // Adds app entries to the list if it is not empty.
-        if (!appEntries.isEmpty()) {
-            addPreferenceToScreen(appEntries);
+        if (!batteryDiffData.getAppDiffEntryList().isEmpty()) {
+            addPreferenceToScreen(batteryDiffData.getAppDiffEntryList());
         }
         // Adds the expabable divider if we have system entries data.
-        if (!mSystemEntries.isEmpty()) {
+        if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) {
             if (mExpandDividerPreference == null) {
                 mExpandDividerPreference = new ExpandDividerPreference(mPrefContext);
                 mExpandDividerPreference.setOnExpandListener(this);
@@ -469,11 +473,13 @@
     }
 
     private void refreshExpandUi() {
+        final List<BatteryDiffEntry> systemEntries = mBatteryUsageMap.get(mDailyChartIndex).get(
+                mHourlyChartIndex).getSystemDiffEntryList();
         if (mIsExpanded) {
-            addPreferenceToScreen(mSystemEntries);
+            addPreferenceToScreen(systemEntries);
         } else {
             // Removes and recycles all system entries to hide all of them.
-            for (BatteryDiffEntry entry : mSystemEntries) {
+            for (BatteryDiffEntry entry : systemEntries) {
                 final String prefKey = entry.mBatteryHistEntry.getKey();
                 final Preference pref = mAppListPrefGroup.findPreference(prefKey);
                 if (pref != null) {
@@ -499,11 +505,12 @@
     }
 
     private String getSlotInformation(boolean isApp, String slotInformation) {
+        // TODO: Updates the right slot information from daily and hourly chart selection.
         // Null means we show all information without a specific time slot.
         if (slotInformation == null) {
             return isApp
-                    ? mPrefContext.getString(R.string.battery_app_usage_for_past_24)
-                    : mPrefContext.getString(R.string.battery_system_usage_for_past_24);
+                    ? mPrefContext.getString(R.string.battery_app_usage)
+                    : mPrefContext.getString(R.string.battery_system_usage);
         } else {
             return isApp
                     ? mPrefContext.getString(R.string.battery_app_usage_for, slotInformation)
@@ -511,17 +518,33 @@
         }
     }
 
-    private String getSlotInformation() {
-        if (mTrapezoidIndex < 0) {
+    @VisibleForTesting
+    String getSlotInformation() {
+        if (mDailyTimestampFullTexts == null || mDailyViewModel == null
+                || mHourlyViewModels == null) {
+            // No data
             return null;
         }
-        final String fromHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
-                mBatteryHistoryKeys[mTrapezoidIndex * 2], mIs24HourFormat);
-        final String toHour = ConvertUtils.utcToLocalTimeHour(mPrefContext,
-                mBatteryHistoryKeys[(mTrapezoidIndex + 1) * 2], mIs24HourFormat);
-        return mIs24HourFormat
-                ? String.format("%s–%s", fromHour, toHour)
-                : String.format("%s – %s", fromHour, toHour);
+        if (isAllSelected()) {
+            return null;
+        }
+
+        final String selectedDayText = mDailyTimestampFullTexts.get(mDailyChartIndex);
+        if (mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
+            return selectedDayText;
+        }
+
+        final String fromHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
+                mHourlyChartIndex);
+        final String toHourText = mHourlyViewModels.get(mDailyChartIndex).texts().get(
+                mHourlyChartIndex + 1);
+        final String selectedHourText =
+                String.format("%s%s%s", fromHourText, mIs24HourFormat ? "-" : " - ", toHourText);
+        if (isBatteryLevelDataInOneDay()) {
+            return selectedHourText;
+        }
+
+        return String.format("%s %s", selectedDayText, selectedHourText);
     }
 
     @VisibleForTesting
@@ -575,22 +598,7 @@
 
     @VisibleForTesting
     boolean isValidToShowSummary(String packageName) {
-        return !contains(packageName, mNotAllowShowSummaryPackages);
-    }
-
-    @VisibleForTesting
-    boolean isValidToShowEntry(String packageName) {
-        return !contains(packageName, mNotAllowShowEntryPackages);
-    }
-
-    @VisibleForTesting
-    void setTimestampLabel() {
-        if (mBatteryChartView == null || mBatteryHistoryKeys == null) {
-            return;
-        }
-        final long latestTimestamp =
-                mBatteryHistoryKeys[mBatteryHistoryKeys.length - 1];
-        mBatteryChartView.setLatestTimestamp(latestTimestamp);
+        return !DataProcessor.contains(packageName, mNotAllowShowSummaryPackages);
     }
 
     private void addFooterPreferenceIfNeeded(boolean containAppItems) {
@@ -605,60 +613,65 @@
         mHandler.post(() -> mPreferenceScreen.addPreference(mFooterPreference));
     }
 
-    private static boolean contains(String target, CharSequence[] packageNames) {
-        if (target != null && packageNames != null) {
-            for (CharSequence packageName : packageNames) {
-                if (TextUtils.equals(target, packageName)) {
-                    return true;
-                }
-            }
-        }
-        return false;
+    private boolean isBatteryLevelDataInOneDay() {
+        return mHourlyViewModels != null && mHourlyViewModels.size() == 1;
     }
 
-    @VisibleForTesting
-    static boolean validateUsageTime(BatteryDiffEntry entry) {
-        final long foregroundUsageTimeInMs = entry.mForegroundUsageTimeInMs;
-        final long backgroundUsageTimeInMs = entry.mBackgroundUsageTimeInMs;
-        final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
-        if (foregroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
-                || backgroundUsageTimeInMs > VALID_USAGE_TIME_DURATION
-                || totalUsageTimeInMs > VALID_USAGE_TIME_DURATION) {
-            Log.e(TAG, "validateUsageTime() fail for\n" + entry);
-            return false;
+    private boolean isAllSelected() {
+        return (isBatteryLevelDataInOneDay()
+                || mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL)
+                && mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL;
+    }
+
+    private static List<String> generateTimestampDayOfWeekTexts(@NonNull final Context context,
+            @NonNull final List<Long> timestamps, final boolean isAbbreviation) {
+        final ArrayList<String> texts = new ArrayList<>();
+        for (Long timestamp : timestamps) {
+            texts.add(ConvertUtils.utcToLocalTimeDayOfWeek(context, timestamp, isAbbreviation));
         }
-        return true;
+        return texts;
+    }
+
+    private static List<String> generateTimestampHourTexts(
+            @NonNull final Context context, @NonNull final List<Long> timestamps) {
+        final boolean is24HourFormat = DateFormat.is24HourFormat(context);
+        final ArrayList<String> texts = new ArrayList<>();
+        for (Long timestamp : timestamps) {
+            texts.add(ConvertUtils.utcToLocalTimeHour(context, timestamp, is24HourFormat));
+        }
+        return texts;
     }
 
     /** Used for {@link AppBatteryPreferenceController}. */
-    public static List<BatteryDiffEntry> getBatteryLast24HrUsageData(Context context) {
+    public static List<BatteryDiffEntry> getAppBatteryUsageData(Context context) {
         final long start = System.currentTimeMillis();
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
                 FeatureFactory.getFactory(context)
                         .getPowerUsageFeatureProvider(context)
-                        .getBatteryHistory(context);
+                        .getBatteryHistorySinceLastFullCharge(context);
         if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
             return null;
         }
-        Log.d(TAG, String.format("getBatteryLast24HrData() size=%d time=&d/ms",
+        Log.d(TAG, String.format("getBatterySinceLastFullChargeUsageData() size=%d time=%d/ms",
                 batteryHistoryMap.size(), (System.currentTimeMillis() - start)));
-        final Map<Integer, List<BatteryDiffEntry>> batteryIndexedMap =
-                ConvertUtils.getIndexedUsageMap(
-                        context,
-                        /*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
-                        getBatteryHistoryKeys(batteryHistoryMap),
-                        batteryHistoryMap,
-                        /*purgeLowPercentageAndFakeData=*/ true);
-        return batteryIndexedMap.get(BatteryChartView.SELECTED_INDEX_ALL);
+
+        final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageData =
+                DataProcessor.getBatteryUsageData(context, batteryHistoryMap);
+        return batteryUsageData == null
+                ? null
+                : batteryUsageData
+                        .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
+                        .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
+                        .getAppDiffEntryList();
     }
 
     /** Used for {@link AppBatteryPreferenceController}. */
-    public static BatteryDiffEntry getBatteryLast24HrUsageData(
+    public static BatteryDiffEntry getAppBatteryUsageData(
             Context context, String packageName, int userId) {
         if (packageName == null) {
             return null;
         }
-        final List<BatteryDiffEntry> entries = getBatteryLast24HrUsageData(context);
+        final List<BatteryDiffEntry> entries = getAppBatteryUsageData(context);
         if (entries == null) {
             return null;
         }
@@ -673,65 +686,4 @@
         }
         return null;
     }
-
-    private static long[] getBatteryHistoryKeys(
-            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
-        final List<Long> batteryHistoryKeyList =
-                new ArrayList<>(batteryHistoryMap.keySet());
-        Collections.sort(batteryHistoryKeyList);
-        final long[] batteryHistoryKeys = new long[CHART_KEY_ARRAY_SIZE];
-        for (int index = 0; index < CHART_KEY_ARRAY_SIZE; index++) {
-            batteryHistoryKeys[index] = batteryHistoryKeyList.get(index);
-        }
-        return batteryHistoryKeys;
-    }
-
-    // Loads all items icon and label in the background.
-    private final class LoadAllItemsInfoTask
-            extends AsyncTask<Void, Void, Map<Integer, List<BatteryDiffEntry>>> {
-
-        private long[] mBatteryHistoryKeysCache;
-        private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
-
-        private LoadAllItemsInfoTask(
-                Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
-            this.mBatteryHistoryMap = batteryHistoryMap;
-            this.mBatteryHistoryKeysCache = mBatteryHistoryKeys;
-        }
-
-        @Override
-        protected Map<Integer, List<BatteryDiffEntry>> doInBackground(Void... voids) {
-            if (mPrefContext == null || mBatteryHistoryKeysCache == null) {
-                return null;
-            }
-            final long startTime = System.currentTimeMillis();
-            final Map<Integer, List<BatteryDiffEntry>> indexedUsageMap =
-                    ConvertUtils.getIndexedUsageMap(
-                            mPrefContext, /*timeSlotSize=*/ CHART_LEVEL_ARRAY_SIZE - 1,
-                            mBatteryHistoryKeysCache, mBatteryHistoryMap,
-                            /*purgeLowPercentageAndFakeData=*/ true);
-            // Pre-loads each BatteryDiffEntry relative icon and label for all slots.
-            for (List<BatteryDiffEntry> entries : indexedUsageMap.values()) {
-                entries.forEach(entry -> entry.loadLabelAndIcon());
-            }
-            Log.d(TAG, String.format("execute LoadAllItemsInfoTask in %d/ms",
-                    (System.currentTimeMillis() - startTime)));
-            return indexedUsageMap;
-        }
-
-        @Override
-        protected void onPostExecute(
-                Map<Integer, List<BatteryDiffEntry>> indexedUsageMap) {
-            mBatteryHistoryMap = null;
-            mBatteryHistoryKeysCache = null;
-            if (indexedUsageMap == null) {
-                return;
-            }
-            // Posts results back to main thread to refresh UI.
-            mHandler.post(() -> {
-                mBatteryIndexedMap = indexedUsageMap;
-                forceRefreshUi();
-            });
-        }
-    }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
index 427388b..e668b37 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java
@@ -18,6 +18,7 @@
 import static com.android.settings.Utils.formatPercentage;
 
 import static java.lang.Math.round;
+import static java.util.Objects.requireNonNull;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
@@ -29,8 +30,6 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.os.Handler;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
@@ -39,6 +38,7 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.widget.AppCompatImageView;
 
@@ -46,7 +46,7 @@
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.Utils;
 
-import java.time.Clock;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
@@ -58,36 +58,26 @@
     private static final List<String> ACCESSIBILITY_SERVICE_NAMES =
             Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService");
 
-    private static final int DEFAULT_TRAPEZOID_COUNT = 12;
-    private static final int DEFAULT_TIMESTAMP_COUNT = 4;
-    private static final int TIMESTAMP_GAPS_COUNT = DEFAULT_TIMESTAMP_COUNT - 1;
     private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5");
     private static final long UPDATE_STATE_DELAYED_TIME = 500L;
 
-    /** Selects all trapezoid shapes. */
-    public static final int SELECTED_INDEX_ALL = -1;
-    public static final int SELECTED_INDEX_INVALID = -2;
-
     /** A callback listener for selected group index is updated. */
     public interface OnSelectListener {
         /** The callback function for selected group index is updated. */
         void onSelect(int trapezoidIndex);
     }
 
+    private BatteryChartViewModel mViewModel;
+
     private int mDividerWidth;
     private int mDividerHeight;
-    private int mTrapezoidCount;
     private float mTrapezoidVOffset;
     private float mTrapezoidHOffset;
     private boolean mIsSlotsClickabled;
     private String[] mPercentages = getPercentages();
 
     @VisibleForTesting
-    int mHoveredIndex = SELECTED_INDEX_INVALID;
-    @VisibleForTesting
-    int mSelectedIndex = SELECTED_INDEX_INVALID;
-    @VisibleForTesting
-    String[] mTimestamps;
+    int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID;
 
     // Colors for drawing the trapezoid shape and dividers.
     private int mTrapezoidColor;
@@ -98,25 +88,26 @@
     private final Rect mIndent = new Rect();
     private final Rect[] mPercentageBounds =
             new Rect[]{new Rect(), new Rect(), new Rect()};
-    // For drawing the timestamp information.
-    private final Rect[] mTimestampsBounds =
-            new Rect[]{new Rect(), new Rect(), new Rect(), new Rect()};
+    // For drawing the axis label information.
+    private final List<Rect> mAxisLabelsBounds = new ArrayList<>();
+
 
     @VisibleForTesting
     Handler mHandler = new Handler();
     @VisibleForTesting
     final Runnable mUpdateClickableStateRun = () -> updateClickableState();
 
-    private int[] mLevels;
     private Paint mTextPaint;
     private Paint mDividerPaint;
     private Paint mTrapezoidPaint;
 
     @VisibleForTesting
     Paint mTrapezoidCurvePaint = null;
-    private TrapezoidSlot[] mTrapezoidSlots;
+    @VisibleForTesting
+    TrapezoidSlot[] mTrapezoidSlots;
     // Records the location to calculate selected index.
-    private float mTouchUpEventX = Float.MIN_VALUE;
+    @VisibleForTesting
+    float mTouchUpEventX = Float.MIN_VALUE;
     private BatteryChartView.OnSelectListener mOnSelectListener;
 
     public BatteryChartView(Context context) {
@@ -128,57 +119,25 @@
         initializeColors(context);
         // Registers the click event listener.
         setOnClickListener(this);
-        setSelectedIndex(SELECTED_INDEX_ALL);
-        setTrapezoidCount(DEFAULT_TRAPEZOID_COUNT);
         setClickable(false);
-        setLatestTimestamp(0);
+        requestLayout();
     }
 
-    /** Sets the total trapezoid count for drawing. */
-    public void setTrapezoidCount(int trapezoidCount) {
-        Log.i(TAG, "trapezoidCount:" + trapezoidCount);
-        mTrapezoidCount = trapezoidCount;
-        mTrapezoidSlots = new TrapezoidSlot[trapezoidCount];
-        // Allocates the trapezoid slot array.
-        for (int index = 0; index < trapezoidCount; index++) {
-            mTrapezoidSlots[index] = new TrapezoidSlot();
-        }
-        invalidate();
-    }
-
-    /** Sets all levels value to draw the trapezoid shape */
-    public void setLevels(int[] levels) {
-        Log.d(TAG, "setLevels() " + (levels == null ? "null" : levels.length));
-        if (levels == null) {
-            mLevels = null;
-            return;
-        }
-        // We should provide trapezoid count + 1 data to draw all trapezoids.
-        mLevels = levels.length == mTrapezoidCount + 1 ? levels : null;
-        setClickable(false);
-        invalidate();
-        if (mLevels == null) {
-            return;
-        }
-        // Sets the chart is clickable if there is at least one valid item in it.
-        for (int index = 0; index < mLevels.length - 1; index++) {
-            if (mLevels[index] != 0 && mLevels[index + 1] != 0) {
-                setClickable(true);
-                break;
-            }
-        }
-    }
-
-    /** Sets the selected group index to draw highlight effect. */
-    public void setSelectedIndex(int index) {
-        if (mSelectedIndex != index) {
-            mSelectedIndex = index;
+    /** Sets the data model of this view. */
+    public void setViewModel(BatteryChartViewModel viewModel) {
+        if (viewModel == null) {
+            mViewModel = null;
             invalidate();
-            // Callbacks to the listener if we have.
-            if (mOnSelectListener != null) {
-                mOnSelectListener.onSelect(mSelectedIndex);
-            }
+            return;
         }
+
+        Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.",
+                viewModel.size(), viewModel.selectedIndex()));
+        mViewModel = viewModel;
+        initializeAxisLabelsBounds();
+        initializeTrapezoidSlots(viewModel.size() - 1);
+        setClickable(hasAnyValidTrapezoid(viewModel));
+        requestLayout();
     }
 
     /** Sets the callback to monitor the selected group index. */
@@ -195,29 +154,6 @@
         } else {
             mTextPaint = null;
         }
-        setVisibility(View.VISIBLE);
-        requestLayout();
-    }
-
-    /** Sets the latest timestamp for drawing into x-axis information. */
-    public void setLatestTimestamp(long latestTimestamp) {
-        if (latestTimestamp == 0) {
-            latestTimestamp = Clock.systemUTC().millis();
-        }
-        if (mTimestamps == null) {
-            mTimestamps = new String[DEFAULT_TIMESTAMP_COUNT];
-        }
-        final long timeSlotOffset =
-                DateUtils.HOUR_IN_MILLIS * (/*total 24 hours*/ 24 / TIMESTAMP_GAPS_COUNT);
-        final boolean is24HourFormat = DateFormat.is24HourFormat(getContext());
-        for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
-            mTimestamps[index] =
-                    ConvertUtils.utcToLocalTimeHour(
-                            getContext(),
-                            latestTimestamp - (TIMESTAMP_GAPS_COUNT - index)
-                                    * timeSlotOffset,
-                            is24HourFormat);
-        }
         requestLayout();
     }
 
@@ -226,6 +162,7 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         // Measures text bounds and updates indent configuration.
         if (mTextPaint != null) {
+            mTextPaint.setTextAlign(Paint.Align.LEFT);
             for (int index = 0; index < mPercentages.length; index++) {
                 mTextPaint.getTextBounds(
                         mPercentages[index], 0, mPercentages[index].length(),
@@ -235,15 +172,14 @@
             mIndent.top = mPercentageBounds[0].height();
             mIndent.right = mPercentageBounds[0].width() + mTextPadding;
 
-            if (mTimestamps != null) {
-                int maxHeight = 0;
-                for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
-                    mTextPaint.getTextBounds(
-                            mTimestamps[index], 0, mTimestamps[index].length(),
-                            mTimestampsBounds[index]);
-                    maxHeight = Math.max(maxHeight, mTimestampsBounds[index].height());
+            if (mViewModel != null) {
+                int maxTop = 0;
+                for (int index = 0; index < mViewModel.size(); index++) {
+                    final String text = mViewModel.texts().get(index);
+                    mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index));
+                    maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top);
                 }
-                mIndent.bottom = maxHeight + round(mTextPadding * 1.5f);
+                mIndent.bottom = maxTop + round(mTextPadding * 2f);
             }
             Log.d(TAG, "setIndent:" + mPercentageBounds[0]);
         } else {
@@ -254,7 +190,12 @@
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
+        // Before mLevels initialized, the count of trapezoids is unknown. Only draws the
+        // horizontal percentages and dividers.
         drawHorizontalDividers(canvas);
+        if (mViewModel == null) {
+            return;
+        }
         drawVerticalDividers(canvas);
         drawTrapezoids(canvas);
     }
@@ -294,7 +235,7 @@
     public void onHoverChanged(boolean hovered) {
         super.onHoverChanged(hovered);
         if (!hovered) {
-            mHoveredIndex = SELECTED_INDEX_INVALID; // reset
+            mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; // reset
             invalidate();
         }
     }
@@ -307,15 +248,15 @@
         }
         final int trapezoidIndex = getTrapezoidIndex(mTouchUpEventX);
         // Ignores the click event if the level is zero.
-        if (trapezoidIndex == SELECTED_INDEX_INVALID
-                || !isValidToDraw(trapezoidIndex)) {
+        if (trapezoidIndex == BatteryChartViewModel.SELECTED_INDEX_INVALID
+                || !isValidToDraw(mViewModel, trapezoidIndex)) {
             return;
         }
-        // Selects all if users click the same trapezoid item two times.
-        if (trapezoidIndex == mSelectedIndex) {
-            setSelectedIndex(SELECTED_INDEX_ALL);
-        } else {
-            setSelectedIndex(trapezoidIndex);
+        if (mOnSelectListener != null) {
+            // Selects all if users click the same trapezoid item two times.
+            mOnSelectListener.onSelect(
+                    trapezoidIndex == mViewModel.selectedIndex()
+                            ? BatteryChartViewModel.SELECTED_INDEX_ALL : trapezoidIndex);
         }
         view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
     }
@@ -364,8 +305,8 @@
             mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2);
         } else if (mIsSlotsClickabled) {
             mTrapezoidCurvePaint = null;
-            // Sets levels again to force update the click state.
-            setLevels(mLevels);
+            // Sets view model again to force update the click state.
+            setViewModel(mViewModel);
         }
         invalidate();
     }
@@ -380,6 +321,13 @@
         super.setClickable(clickable);
     }
 
+    private void initializeTrapezoidSlots(int count) {
+        mTrapezoidSlots = new TrapezoidSlot[count];
+        for (int index = 0; index < mTrapezoidSlots.length; index++) {
+            mTrapezoidSlots[index] = new TrapezoidSlot();
+        }
+    }
+
     private void initializeColors(Context context) {
         setBackgroundColor(Color.TRANSPARENT);
         mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
@@ -434,10 +382,10 @@
 
     private void drawPercentage(Canvas canvas, int index, float offsetY) {
         if (mTextPaint != null) {
+            mTextPaint.setTextAlign(Paint.Align.RIGHT);
             canvas.drawText(
                     mPercentages[index],
-                    getWidth() - mPercentageBounds[index].width()
-                            - mPercentageBounds[index].left,
+                    getWidth(),
                     offsetY + mPercentageBounds[index].height() * .5f,
                     mTextPaint);
         }
@@ -445,9 +393,9 @@
 
     private void drawVerticalDividers(Canvas canvas) {
         final int width = getWidth() - mIndent.right;
-        final int dividerCount = mTrapezoidCount + 1;
+        final int dividerCount = mTrapezoidSlots.length + 1;
         final float dividerSpace = dividerCount * mDividerWidth;
-        final float unitWidth = (width - dividerSpace) / (float) mTrapezoidCount;
+        final float unitWidth = (width - dividerSpace) / (float) mTrapezoidSlots.length;
         final float bottomY = getHeight() - mIndent.bottom;
         final float startY = bottomY - mDividerHeight;
         final float trapezoidSlotOffset = mTrapezoidHOffset + mDividerWidth * .5f;
@@ -463,66 +411,140 @@
             }
             startX = nextX;
         }
-        // Draws the timestamp slot information.
-        if (mTimestamps != null) {
-            final float[] xOffsets = new float[DEFAULT_TIMESTAMP_COUNT];
-            final float baselineX = mDividerWidth * .5f;
-            final float offsetX = mDividerWidth + unitWidth;
-            final int slotBarOffset = (/*total 12 bars*/ 12) / TIMESTAMP_GAPS_COUNT;
-            for (int index = 0; index < DEFAULT_TIMESTAMP_COUNT; index++) {
-                xOffsets[index] = baselineX + index * offsetX * slotBarOffset;
+        // Draws the axis label slot information.
+        if (mViewModel != null) {
+            final float baselineY = getHeight() - mTextPadding * 1.5f;
+            Rect[] axisLabelDisplayAreas;
+            switch (mViewModel.axisLabelPosition()) {
+                case CENTER_OF_TRAPEZOIDS:
+                    axisLabelDisplayAreas = getAxisLabelDisplayAreas(
+                            /* size= */ mViewModel.size() - 1,
+                            /* baselineX= */ mDividerWidth + unitWidth * .5f,
+                            /* offsetX= */ mDividerWidth + unitWidth,
+                            baselineY,
+                            /* shiftFirstAndLast= */ false);
+                    break;
+                case BETWEEN_TRAPEZOIDS:
+                default:
+                    axisLabelDisplayAreas = getAxisLabelDisplayAreas(
+                            /* size= */ mViewModel.size(),
+                            /* baselineX= */ mDividerWidth * .5f,
+                            /* offsetX= */ mDividerWidth + unitWidth,
+                            baselineY,
+                            /* shiftFirstAndLast= */ true);
+                    break;
             }
-            drawTimestamp(canvas, xOffsets);
+            drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY);
         }
     }
 
-    private void drawTimestamp(Canvas canvas, float[] xOffsets) {
-        // Draws the 1st timestamp info.
-        canvas.drawText(
-                mTimestamps[0],
-                xOffsets[0] - mTimestampsBounds[0].left,
-                getTimestampY(0), mTextPaint);
-        final int latestIndex = DEFAULT_TIMESTAMP_COUNT - 1;
-        // Draws the last timestamp info.
-        canvas.drawText(
-                mTimestamps[latestIndex],
-                xOffsets[latestIndex] - mTimestampsBounds[latestIndex].width()
-                        - mTimestampsBounds[latestIndex].left,
-                getTimestampY(latestIndex), mTextPaint);
-        // Draws the rest of timestamp info since it is located in the center.
-        for (int index = 1; index <= DEFAULT_TIMESTAMP_COUNT - 2; index++) {
-            canvas.drawText(
-                    mTimestamps[index],
-                    xOffsets[index]
-                            - (mTimestampsBounds[index].width() - mTimestampsBounds[index].left)
-                            * .5f,
-                    getTimestampY(index), mTextPaint);
+    /** Gets all the axis label texts displaying area positions if they are shown. */
+    private Rect[] getAxisLabelDisplayAreas(final int size, final float baselineX,
+            final float offsetX, final float baselineY, final boolean shiftFirstAndLast) {
+        final Rect[] result = new Rect[size];
+        for (int index = 0; index < result.length; index++) {
+            final float width = mAxisLabelsBounds.get(index).width();
+            float middle = baselineX + index * offsetX;
+            if (shiftFirstAndLast) {
+                if (index == 0) {
+                    middle += width * .5f;
+                }
+                if (index == size - 1) {
+                    middle -= width * .5f;
+                }
+            }
+            final float left = middle - width * .5f;
+            final float right = left + width;
+            final float top = baselineY + mAxisLabelsBounds.get(index).top;
+            final float bottom = top + mAxisLabelsBounds.get(index).height();
+            result[index] = new Rect(round(left), round(top), round(right), round(bottom));
+        }
+        return result;
+    }
 
+    private void drawAxisLabels(Canvas canvas, final Rect[] displayAreas, final float baselineY) {
+        final int lastIndex = displayAreas.length - 1;
+        // Suppose first and last labels are always able to draw.
+        drawAxisLabelText(canvas, 0, displayAreas[0], baselineY);
+        drawAxisLabelText(canvas, lastIndex, displayAreas[lastIndex], baselineY);
+        drawAxisLabelsBetweenStartIndexAndEndIndex(canvas, displayAreas, 0, lastIndex, baselineY);
+    }
+
+    /**
+     * Recursively draws axis labels between the start index and the end index. If the inner number
+     * can be exactly divided into 2 parts, check and draw the middle index label and then
+     * recursively draw the 2 parts. Otherwise, divide into 3 parts. Check and draw the middle two
+     * labels and then recursively draw the 3 parts. If there are any overlaps, skip drawing and go
+     * back to the uplevel of the recursion.
+     */
+    private void drawAxisLabelsBetweenStartIndexAndEndIndex(Canvas canvas,
+            final Rect[] displayAreas, final int startIndex, final int endIndex,
+            final float baselineY) {
+        if (endIndex - startIndex <= 1) {
+            return;
+        }
+        if ((endIndex - startIndex) % 2 == 0) {
+            int middleIndex = (startIndex + endIndex) / 2;
+            if (hasOverlap(displayAreas, startIndex, middleIndex)
+                    || hasOverlap(displayAreas, middleIndex, endIndex)) {
+                return;
+            }
+            drawAxisLabelText(canvas, middleIndex, displayAreas[middleIndex], baselineY);
+            drawAxisLabelsBetweenStartIndexAndEndIndex(
+                    canvas, displayAreas, startIndex, middleIndex, baselineY);
+            drawAxisLabelsBetweenStartIndexAndEndIndex(
+                    canvas, displayAreas, middleIndex, endIndex, baselineY);
+        } else {
+            int middleIndex1 = startIndex + round((endIndex - startIndex) / 3f);
+            int middleIndex2 = startIndex + round((endIndex - startIndex) * 2 / 3f);
+            if (hasOverlap(displayAreas, startIndex, middleIndex1)
+                    || hasOverlap(displayAreas, middleIndex1, middleIndex2)
+                    || hasOverlap(displayAreas, middleIndex2, endIndex)) {
+                return;
+            }
+            drawAxisLabelText(canvas, middleIndex1, displayAreas[middleIndex1], baselineY);
+            drawAxisLabelText(canvas, middleIndex2, displayAreas[middleIndex2], baselineY);
+            drawAxisLabelsBetweenStartIndexAndEndIndex(
+                    canvas, displayAreas, startIndex, middleIndex1, baselineY);
+            drawAxisLabelsBetweenStartIndexAndEndIndex(
+                    canvas, displayAreas, middleIndex1, middleIndex2, baselineY);
+            drawAxisLabelsBetweenStartIndexAndEndIndex(
+                    canvas, displayAreas, middleIndex2, endIndex, baselineY);
         }
     }
 
-    private int getTimestampY(int index) {
-        return getHeight() - mTimestampsBounds[index].height()
-                + (mTimestampsBounds[index].height() + mTimestampsBounds[index].top)
-                + round(mTextPadding * 1.5f);
+    private boolean hasOverlap(
+            final Rect[] displayAreas, final int leftIndex, final int rightIndex) {
+        return displayAreas[leftIndex].right + mTextPadding * 2f > displayAreas[rightIndex].left;
+    }
+
+    private void drawAxisLabelText(
+            Canvas canvas, final int index, final Rect displayArea, final float baselineY) {
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+        canvas.drawText(
+                mViewModel.texts().get(index),
+                displayArea.centerX(),
+                baselineY,
+                mTextPaint);
     }
 
     private void drawTrapezoids(Canvas canvas) {
         // Ignores invalid trapezoid data.
-        if (mLevels == null) {
+        if (mViewModel == null) {
             return;
         }
         final float trapezoidBottom =
                 getHeight() - mIndent.bottom - mDividerHeight - mDividerWidth
                         - mTrapezoidVOffset;
-        final float availableSpace = trapezoidBottom - mDividerWidth * .5f - mIndent.top;
+        final float availableSpace =
+                trapezoidBottom - mDividerWidth * .5f - mIndent.top - mTrapezoidVOffset;
         final float unitHeight = availableSpace / 100f;
         // Draws all trapezoid shapes into the canvas.
         final Path trapezoidPath = new Path();
         Path trapezoidCurvePath = null;
-        for (int index = 0; index < mTrapezoidCount; index++) {
+        for (int index = 0; index < mTrapezoidSlots.length; index++) {
             // Not draws the trapezoid for corner or not initialization cases.
-            if (!isValidToDraw(index)) {
+            if (!isValidToDraw(mViewModel, index)) {
                 if (mTrapezoidCurvePaint != null && trapezoidCurvePath != null) {
                     canvas.drawPath(trapezoidCurvePath, mTrapezoidCurvePaint);
                     trapezoidCurvePath = null;
@@ -530,17 +552,18 @@
                 continue;
             }
             // Configures the trapezoid paint color.
-            final int trapezoidColor =
-                    !mIsSlotsClickabled
-                            ? mTrapezoidColor
-                            : mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL
-                                    ? mTrapezoidSolidColor : mTrapezoidColor;
+            final int trapezoidColor = mIsSlotsClickabled && (mViewModel.selectedIndex() == index
+                    || mViewModel.selectedIndex() == BatteryChartViewModel.SELECTED_INDEX_ALL)
+                    ? mTrapezoidSolidColor : mTrapezoidColor;
             final boolean isHoverState =
-                    mIsSlotsClickabled && mHoveredIndex == index && isValidToDraw(mHoveredIndex);
+                    mIsSlotsClickabled && mHoveredIndex == index
+                            && isValidToDraw(mViewModel, mHoveredIndex);
             mTrapezoidPaint.setColor(isHoverState ? mTrapezoidHoverColor : trapezoidColor);
 
-            final float leftTop = round(trapezoidBottom - mLevels[index] * unitHeight);
-            final float rightTop = round(trapezoidBottom - mLevels[index + 1] * unitHeight);
+            final float leftTop = round(
+                    trapezoidBottom - requireNonNull(mViewModel.levels().get(index)) * unitHeight);
+            final float rightTop = round(trapezoidBottom
+                    - requireNonNull(mViewModel.levels().get(index + 1)) * unitHeight);
             trapezoidPath.reset();
             trapezoidPath.moveTo(mTrapezoidSlots[index].mLeft, trapezoidBottom);
             trapezoidPath.lineTo(mTrapezoidSlots[index].mLeft, leftTop);
@@ -579,15 +602,37 @@
                 return index;
             }
         }
-        return SELECTED_INDEX_INVALID;
+        return BatteryChartViewModel.SELECTED_INDEX_INVALID;
     }
 
-    private boolean isValidToDraw(int trapezoidIndex) {
-        return mLevels != null
+    private void initializeAxisLabelsBounds() {
+        mAxisLabelsBounds.clear();
+        for (int i = 0; i < mViewModel.size(); i++) {
+            mAxisLabelsBounds.add(new Rect());
+        }
+    }
+
+    private static boolean isTrapezoidValid(
+            @NonNull BatteryChartViewModel viewModel, int trapezoidIndex) {
+        return viewModel.levels().get(trapezoidIndex) != null
+                && viewModel.levels().get(trapezoidIndex + 1) != null;
+    }
+
+    private static boolean isValidToDraw(BatteryChartViewModel viewModel, int trapezoidIndex) {
+        return viewModel != null
                 && trapezoidIndex >= 0
-                && trapezoidIndex < mLevels.length - 1
-                && mLevels[trapezoidIndex] != 0
-                && mLevels[trapezoidIndex + 1] != 0;
+                && trapezoidIndex < viewModel.size() - 1
+                && isTrapezoidValid(viewModel, trapezoidIndex);
+    }
+
+    private static boolean hasAnyValidTrapezoid(@NonNull BatteryChartViewModel viewModel) {
+        // Sets the chart is clickable if there is at least one valid item in it.
+        for (int trapezoidIndex = 0; trapezoidIndex < viewModel.size() - 1; trapezoidIndex++) {
+            if (isTrapezoidValid(viewModel, trapezoidIndex)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private static String[] getPercentages() {
@@ -621,7 +666,8 @@
     }
 
     // A container class for each trapezoid left and right location.
-    private static final class TrapezoidSlot {
+    @VisibleForTesting
+    static final class TrapezoidSlot {
         public float mLeft;
         public float mRight;
 
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java
new file mode 100644
index 0000000..ac01bfd
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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 androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+/** The view model of {@code BatteryChartView} */
+class BatteryChartViewModel {
+    private static final String TAG = "BatteryChartViewModel";
+
+    public static final int SELECTED_INDEX_ALL = -1;
+    public static final int SELECTED_INDEX_INVALID = -2;
+
+    // We need at least 2 levels to draw a trapezoid.
+    private static final int MIN_LEVELS_DATA_SIZE = 2;
+
+    enum AxisLabelPosition {
+        BETWEEN_TRAPEZOIDS,
+        CENTER_OF_TRAPEZOIDS,
+    }
+
+    private final List<Integer> mLevels;
+    private final List<String> mTexts;
+    private final AxisLabelPosition mAxisLabelPosition;
+    private int mSelectedIndex = SELECTED_INDEX_ALL;
+
+    BatteryChartViewModel(
+            @NonNull List<Integer> levels, @NonNull List<String> texts,
+            @NonNull AxisLabelPosition axisLabelPosition) {
+        Preconditions.checkArgument(
+                levels.size() == texts.size() && levels.size() >= MIN_LEVELS_DATA_SIZE,
+                String.format(Locale.ENGLISH,
+                        "Invalid BatteryChartViewModel levels.size: %d, texts.size: %d.",
+                        levels.size(), texts.size()));
+        mLevels = levels;
+        mTexts = texts;
+        mAxisLabelPosition = axisLabelPosition;
+    }
+
+    public int size() {
+        return mLevels.size();
+    }
+
+    public List<Integer> levels() {
+        return mLevels;
+    }
+
+    public List<String> texts() {
+        return mTexts;
+    }
+
+    public AxisLabelPosition axisLabelPosition() {
+        return mAxisLabelPosition;
+    }
+
+    public int selectedIndex() {
+        return mSelectedIndex;
+    }
+
+    public void setSelectedIndex(int index) {
+        mSelectedIndex = index;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mLevels, mTexts, mSelectedIndex, mAxisLabelPosition);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (!(other instanceof BatteryChartViewModel)) {
+            return false;
+        }
+        final BatteryChartViewModel batteryChartViewModel = (BatteryChartViewModel) other;
+        return Objects.equals(mLevels, batteryChartViewModel.mLevels)
+                && Objects.equals(mTexts, batteryChartViewModel.mTexts)
+                && mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition
+                && mSelectedIndex == batteryChartViewModel.mSelectedIndex;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.ENGLISH,
+                "levels: %s,\ntexts: %s,\naxisLabelPosition: %s, selectedIndex: %d",
+                Objects.toString(mLevels), Objects.toString(mTexts), mAxisLabelPosition,
+                mSelectedIndex);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
new file mode 100644
index 0000000..b5d4dde
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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 androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.List;
+
+/** Wraps the battery usage diff data for each entry used for battery usage app list. */
+public class BatteryDiffData {
+    private final List<BatteryDiffEntry> mAppEntries;
+    private final List<BatteryDiffEntry> mSystemEntries;
+
+    /** Constructor for the diff entries which already have totalConsumePower value. */
+    public BatteryDiffData(
+            @NonNull List<BatteryDiffEntry> appDiffEntries,
+            @NonNull List<BatteryDiffEntry> systemDiffEntries) {
+        mAppEntries = appDiffEntries;
+        mSystemEntries = systemDiffEntries;
+        sortEntries();
+    }
+
+    /** Constructor for the diff entries which have not set totalConsumePower value. */
+    public BatteryDiffData(
+            @NonNull List<BatteryDiffEntry> appDiffEntries,
+            @NonNull List<BatteryDiffEntry> systemDiffEntries,
+            final double totalConsumePower) {
+        mAppEntries = appDiffEntries;
+        mSystemEntries = systemDiffEntries;
+        setTotalConsumePowerForAllEntries(totalConsumePower);
+        sortEntries();
+    }
+
+    public List<BatteryDiffEntry> getAppDiffEntryList() {
+        return mAppEntries;
+    }
+
+    public List<BatteryDiffEntry> getSystemDiffEntryList() {
+        return mSystemEntries;
+    }
+
+    // Sets total consume power for each entry.
+    private void setTotalConsumePowerForAllEntries(final double totalConsumePower) {
+        mAppEntries.forEach(diffEntry -> diffEntry.setTotalConsumePower(totalConsumePower));
+        mSystemEntries.forEach(diffEntry -> diffEntry.setTotalConsumePower(totalConsumePower));
+    }
+
+    // Sorts entries based on consumed percentage.
+    private void sortEntries() {
+        Collections.sort(mAppEntries, BatteryDiffEntry.COMPARATOR);
+        Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java
index 34606a5..83b2615 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoader.java
@@ -43,6 +43,6 @@
     public Map<Long, Map<String, BatteryHistEntry>> loadInBackground() {
         final PowerUsageFeatureProvider powerUsageFeatureProvider =
                 FeatureFactory.getFactory(mContext).getPowerUsageFeatureProvider(mContext);
-        return powerUsageFeatureProvider.getBatteryHistory(mContext);
+        return powerUsageFeatureProvider.getBatteryHistorySinceLastFullCharge(mContext);
     }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java
index e125d17..71fd26c 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryPreference.java
@@ -49,7 +49,8 @@
 
     private TextView mSummaryView;
     private CharSequence mSummaryContent;
-    private BatteryChartView mBatteryChartView;
+    private BatteryChartView mDailyChartView;
+    private BatteryChartView mHourlyChartView;
     private BatteryChartPreferenceController mChartPreferenceController;
 
     public BatteryHistoryPreference(Context context, AttributeSet attrs) {
@@ -92,8 +93,8 @@
 
     void setChartPreferenceController(BatteryChartPreferenceController controller) {
         mChartPreferenceController = controller;
-        if (mBatteryChartView != null) {
-            mChartPreferenceController.setBatteryChartView(mBatteryChartView);
+        if (mDailyChartView != null && mHourlyChartView != null) {
+            mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
         }
     }
 
@@ -105,11 +106,14 @@
             return;
         }
         if (mIsChartGraphEnabled) {
-            mBatteryChartView = (BatteryChartView) view.findViewById(R.id.battery_chart);
-            mBatteryChartView.setCompanionTextView(
+            mDailyChartView = (BatteryChartView) view.findViewById(R.id.daily_battery_chart);
+            mDailyChartView.setCompanionTextView(
+                    (TextView) view.findViewById(R.id.companion_text));
+            mHourlyChartView = (BatteryChartView) view.findViewById(R.id.hourly_battery_chart);
+            mHourlyChartView.setCompanionTextView(
                     (TextView) view.findViewById(R.id.companion_text));
             if (mChartPreferenceController != null) {
-                mChartPreferenceController.setBatteryChartView(mBatteryChartView);
+                mChartPreferenceController.setBatteryChartView(mDailyChartView, mHourlyChartView);
             }
         } else {
             final TextView chargeView = (TextView) view.findViewById(R.id.charge);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java
new file mode 100644
index 0000000..4ff9eeb
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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 androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+/** Wraps the battery timestamp and level data used for battery usage chart. */
+public final class BatteryLevelData {
+    /** A container for the battery timestamp and level data. */
+    public static final class PeriodBatteryLevelData {
+        // The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when
+        // there is no level data for the corresponding timestamp.
+        private final List<Long> mTimestamps;
+        private final List<Integer> mLevels;
+
+        public PeriodBatteryLevelData(
+                @NonNull List<Long> timestamps, @NonNull List<Integer> levels) {
+            Preconditions.checkArgument(timestamps.size() == levels.size(),
+                    /* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: "
+                            + levels.size());
+            mTimestamps = timestamps;
+            mLevels = levels;
+        }
+
+        public List<Long> getTimestamps() {
+            return mTimestamps;
+        }
+
+        public List<Integer> getLevels() {
+            return mLevels;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(Locale.ENGLISH, "timestamps: %s; levels: %s",
+                    Objects.toString(mTimestamps), Objects.toString(mLevels));
+        }
+    }
+
+    /**
+     * There could be 2 cases for the daily battery levels:
+     * 1) length is 2: The usage data is within 1 day. Only contains start and end data, such as
+     *    data of 2022-01-01 06:00 and 2022-01-01 16:00.
+     * 2) length > 2: The usage data is more than 1 days. The data should be the start, end and 0am
+     *    data of every day between the start and end, such as data of 2022-01-01 06:00,
+     *    2022-01-02 00:00, 2022-01-03 00:00 and 2022-01-03 08:00.
+     */
+    private final PeriodBatteryLevelData mDailyBatteryLevels;
+    // The size of hourly data must be the size of daily data - 1.
+    private final List<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
+
+    public BatteryLevelData(
+            @NonNull PeriodBatteryLevelData dailyBatteryLevels,
+            @NonNull List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
+        final long dailySize = dailyBatteryLevels.getTimestamps().size();
+        final long hourlySize = hourlyBatteryLevelsPerDay.size();
+        Preconditions.checkArgument(hourlySize == dailySize - 1,
+                /* errorMessage= */ "DailySize: " + dailySize + ", HourlySize: " + hourlySize);
+        mDailyBatteryLevels = dailyBatteryLevels;
+        mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
+    }
+
+    public PeriodBatteryLevelData getDailyBatteryLevels() {
+        return mDailyBatteryLevels;
+    }
+
+    public List<PeriodBatteryLevelData> getHourlyBatteryLevelsPerDay() {
+        return mHourlyBatteryLevelsPerDay;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(Locale.ENGLISH,
+                "dailyBatteryLevels: %s; hourlyBatteryLevelsPerDay: %s",
+                Objects.toString(mDailyBatteryLevels),
+                Objects.toString(mHourlyBatteryLevelsPerDay));
+    }
+}
+
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index 168fe0f..f04658d 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -140,7 +140,7 @@
 
     /** Converts UTC timestamp to local time hour data. */
     public static String utcToLocalTimeHour(
-            Context context, long timestamp, boolean is24HourFormat) {
+            final Context context, final long timestamp, final boolean is24HourFormat) {
         final Locale locale = getLocale(context);
         // e.g. for 12-hour format: 9 pm
         // e.g. for 24-hour format: 09:00
@@ -149,6 +149,15 @@
         return DateFormat.format(pattern, timestamp).toString().toLowerCase(locale);
     }
 
+    /** Converts UTC timestamp to local time day of week data. */
+    public static String utcToLocalTimeDayOfWeek(
+            final Context context, final long timestamp, final boolean isAbbreviation) {
+        final Locale locale = getLocale(context);
+        final String pattern = DateFormat.getBestDateTimePattern(locale,
+                isAbbreviation ? "E" : "EEEE");
+        return DateFormat.format(pattern, timestamp).toString();
+    }
+
     /** Gets indexed battery usage data for each corresponding time slot. */
     public static Map<Integer, List<BatteryDiffEntry>> getIndexedUsageMap(
             final Context context,
@@ -267,7 +276,7 @@
                 diffEntry.setTotalConsumePower(totalConsumePower);
             }
         }
-        insert24HoursData(BatteryChartView.SELECTED_INDEX_ALL, resultMap);
+        insert24HoursData(BatteryChartViewModel.SELECTED_INDEX_ALL, resultMap);
         resolveMultiUsersData(context, resultMap);
         if (purgeLowPercentageAndFakeData) {
             purgeLowPercentageAndFakeData(context, resultMap);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
new file mode 100644
index 0000000..125f879
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -0,0 +1,1058 @@
+/*
+ * Copyright (C) 2022 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.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTime;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.Utils;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.fuelgauge.BatteryStatus;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A utility class to process data loaded from database and make the data easy to use for battery
+ * usage UI.
+ */
+public final class DataProcessor {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "DataProcessor";
+    private static final int MIN_DAILY_DATA_SIZE = 2;
+    private static final int MIN_TIMESTAMP_DATA_SIZE = 2;
+    private static final int MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP = 5;
+    // Maximum total time value for each hourly slot cumulative data at most 2 hours.
+    private static final float TOTAL_HOURLY_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2;
+    private static final Map<String, BatteryHistEntry> EMPTY_BATTERY_MAP = new HashMap<>();
+    private static final BatteryHistEntry EMPTY_BATTERY_HIST_ENTRY =
+            new BatteryHistEntry(new ContentValues());
+
+    @VisibleForTesting
+    static final double PERCENTAGE_OF_TOTAL_THRESHOLD = 1f;
+    @VisibleForTesting
+    static final int SELECTED_INDEX_ALL = BatteryChartViewModel.SELECTED_INDEX_ALL;
+
+    /** A fake package name to represent no BatteryEntry data. */
+    public static final String FAKE_PACKAGE_NAME = "fake_package";
+
+    /** A callback listener when battery usage loading async task is executed. */
+    public interface UsageMapAsyncResponse {
+        /** The callback function when batteryUsageMap is loaded. */
+        void onBatteryUsageMapLoaded(
+                Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap);
+    }
+
+    private DataProcessor() {
+    }
+
+    /**
+     * @return Returns battery level data and start async task to compute battery diff usage data
+     * and load app labels + icons.
+     * Returns null if the input is invalid or not having at least 2 hours data.
+     */
+    @Nullable
+    public static BatteryLevelData getBatteryLevelData(
+            Context context,
+            @Nullable Handler handler,
+            @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
+            final UsageMapAsyncResponse asyncResponseDelegate) {
+        if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
+            Log.d(TAG, "getBatteryLevelData() returns null");
+            return null;
+        }
+        handler = handler != null ? handler : new Handler(Looper.getMainLooper());
+        // Process raw history map data into hourly timestamps.
+        final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap =
+                getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap);
+        // Wrap and processed history map into easy-to-use format for UI rendering.
+        final BatteryLevelData batteryLevelData =
+                getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap);
+
+        // Start the async task to compute diff usage data and load labels and icons.
+        if (batteryLevelData != null) {
+            new ComputeUsageMapAndLoadItemsTask(
+                    context,
+                    handler,
+                    asyncResponseDelegate,
+                    batteryLevelData.getHourlyBatteryLevelsPerDay(),
+                    processedBatteryHistoryMap).execute();
+        }
+
+        return batteryLevelData;
+    }
+
+    /**
+     * @return Returns battery usage data of different entries.
+     * Returns null if the input is invalid or there is no enough data.
+     */
+    @Nullable
+    public static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageData(
+            Context context,
+            @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
+        if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
+            Log.d(TAG, "getBatteryLevelData() returns null");
+            return null;
+        }
+        // Process raw history map data into hourly timestamps.
+        final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap =
+                getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap);
+        // Wrap and processed history map into easy-to-use format for UI rendering.
+        final BatteryLevelData batteryLevelData =
+                getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap);
+        return batteryLevelData == null
+                ? null
+                : getBatteryUsageMap(
+                        context,
+                        batteryLevelData.getHourlyBatteryLevelsPerDay(),
+                        processedBatteryHistoryMap);
+    }
+
+    /**
+     * @return Returns whether the target is in the CharSequence array.
+     */
+    public static boolean contains(String target, CharSequence[] packageNames) {
+        if (target != null && packageNames != null) {
+            for (CharSequence packageName : packageNames) {
+                if (TextUtils.equals(target, packageName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return Returns the processed history map which has interpolated to every hour data.
+     * The start and end timestamp must be the even hours.
+     * The keys of processed history map should contain every hour between the start and end
+     * timestamp. If there's no data in some key, the value will be the empty hashmap.
+     */
+    @VisibleForTesting
+    static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapWithExpectedTimestamps(
+            Context context,
+            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
+        final long startTime = System.currentTimeMillis();
+        final List<Long> rawTimestampList = new ArrayList<>(batteryHistoryMap.keySet());
+        final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
+        if (rawTimestampList.isEmpty()) {
+            Log.d(TAG, "empty batteryHistoryMap in getHistoryMapWithExpectedTimestamps()");
+            return resultMap;
+        }
+        Collections.sort(rawTimestampList);
+        final List<Long> expectedTimestampList = getTimestampSlots(rawTimestampList);
+        final boolean isFromFullCharge =
+                isFromFullCharge(batteryHistoryMap.get(rawTimestampList.get(0)));
+        interpolateHistory(
+                context, rawTimestampList, expectedTimestampList, isFromFullCharge,
+                batteryHistoryMap, resultMap);
+        Log.d(TAG, String.format("getHistoryMapWithExpectedTimestamps() size=%d in %d/ms",
+                resultMap.size(), (System.currentTimeMillis() - startTime)));
+        return resultMap;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    static BatteryLevelData getLevelDataThroughProcessedHistoryMap(
+            Context context,
+            final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap) {
+        final List<Long> timestampList = new ArrayList<>(processedBatteryHistoryMap.keySet());
+        Collections.sort(timestampList);
+        final List<Long> dailyTimestamps = getDailyTimestamps(timestampList);
+        // There should be at least the start and end timestamps. Otherwise, return null to not show
+        // data in usage chart.
+        if (dailyTimestamps.size() < MIN_DAILY_DATA_SIZE) {
+            return null;
+        }
+
+        final List<List<Long>> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps);
+        final BatteryLevelData.PeriodBatteryLevelData dailyLevelData =
+                getPeriodBatteryLevelData(context, processedBatteryHistoryMap, dailyTimestamps);
+        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyLevelData =
+                getHourlyPeriodBatteryLevelData(
+                        context, processedBatteryHistoryMap, hourlyTimestamps);
+        return new BatteryLevelData(dailyLevelData, hourlyLevelData);
+    }
+
+    /**
+     * Computes expected timestamp slots for last full charge, which will return hourly timestamps
+     * between start and end two even hour values.
+     */
+    @VisibleForTesting
+    static List<Long> getTimestampSlots(final List<Long> rawTimestampList) {
+        final List<Long> timestampSlots = new ArrayList<>();
+        final int rawTimestampListSize = rawTimestampList.size();
+        // If timestamp number is smaller than 2, the following computation is not necessary.
+        if (rawTimestampListSize < MIN_TIMESTAMP_DATA_SIZE) {
+            return timestampSlots;
+        }
+        final long rawStartTimestamp = rawTimestampList.get(0);
+        final long rawEndTimestamp = rawTimestampList.get(rawTimestampListSize - 1);
+        // 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 even hour before the raw end timestamp as the end.
+        final long endTimestamp = getLastEvenHourBeforeTimestamp(rawEndTimestamp);
+        // 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(timestamp);
+        }
+        return timestampSlots;
+    }
+
+    /**
+     * Computes expected daily timestamp slots.
+     *
+     * The valid result should be composed of 3 parts:
+     * 1) start timestamp
+     * 2) every 00:00 timestamp (default timezone) between the start and end
+     * 3) end timestamp
+     * Otherwise, returns an empty list.
+     */
+    @VisibleForTesting
+    static List<Long> getDailyTimestamps(final List<Long> timestampList) {
+        final List<Long> dailyTimestampList = new ArrayList<>();
+        // If timestamp number is smaller than 2, the following computation is not necessary.
+        if (timestampList.size() < MIN_TIMESTAMP_DATA_SIZE) {
+            return dailyTimestampList;
+        }
+        final long startTime = timestampList.get(0);
+        final long endTime = timestampList.get(timestampList.size() - 1);
+        long nextDay = getTimestampOfNextDay(startTime);
+        dailyTimestampList.add(startTime);
+        while (nextDay < endTime) {
+            dailyTimestampList.add(nextDay);
+            nextDay += DateUtils.DAY_IN_MILLIS;
+        }
+        dailyTimestampList.add(endTime);
+        return dailyTimestampList;
+    }
+
+    @VisibleForTesting
+    static boolean isFromFullCharge(@Nullable final Map<String, BatteryHistEntry> entryList) {
+        if (entryList == null) {
+            Log.d(TAG, "entryList is null in isFromFullCharge()");
+            return false;
+        }
+        final List<String> entryKeys = new ArrayList<>(entryList.keySet());
+        if (entryKeys.isEmpty()) {
+            Log.d(TAG, "empty entryList in isFromFullCharge()");
+            return false;
+        }
+        // The hist entries in the same timestamp should have same battery status and level.
+        // Checking the first one should be enough.
+        final BatteryHistEntry firstHistEntry = entryList.get(entryKeys.get(0));
+        return BatteryStatus.isCharged(firstHistEntry.mBatteryStatus, firstHistEntry.mBatteryLevel);
+    }
+
+    @VisibleForTesting
+    static long[] findNearestTimestamp(final List<Long> timestamps, final long target) {
+        final long[] results = new long[] {Long.MIN_VALUE, Long.MAX_VALUE};
+        // Searches the nearest lower and upper timestamp value.
+        timestamps.forEach(timestamp -> {
+            if (timestamp <= target && timestamp > results[0]) {
+                results[0] = timestamp;
+            }
+            if (timestamp >= target && timestamp < results[1]) {
+                results[1] = timestamp;
+            }
+        });
+        // Uses zero value to represent invalid searching result.
+        results[0] = results[0] == Long.MIN_VALUE ? 0 : results[0];
+        results[1] = results[1] == Long.MAX_VALUE ? 0 : results[1];
+        return results;
+    }
+
+    /**
+     * @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.
+     *
+     * There could be 2 cases of the returned value:
+     * 1) null: empty or invalid data.
+     * 2) non-null: must be a 2d map and composed by 3 parts:
+     *    1 - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
+     *    2 - [0][SELECTED_INDEX_ALL] ~ [maxDailyIndex][SELECTED_INDEX_ALL]
+     *    3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]
+     */
+    @VisibleForTesting
+    @Nullable
+    static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMap(
+            final Context context,
+            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
+            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
+        if (batteryHistoryMap.isEmpty()) {
+            return null;
+        }
+        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>();
+        // Insert diff data from [0][0] to [maxDailyIndex][maxHourlyIndex].
+        insertHourlyUsageDiffData(
+                context, hourlyBatteryLevelsPerDay, batteryHistoryMap, resultMap);
+        // Insert diff data from [0][SELECTED_INDEX_ALL] to [maxDailyIndex][SELECTED_INDEX_ALL].
+        insertDailyUsageDiffData(hourlyBatteryLevelsPerDay, resultMap);
+        // Insert diff data [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL].
+        insertAllUsageDiffData(resultMap);
+        purgeLowPercentageAndFakeData(context, resultMap);
+        if (!isUsageMapValid(resultMap, hourlyBatteryLevelsPerDay)) {
+            return null;
+        }
+        return resultMap;
+    }
+
+    /**
+     * Interpolates history map based on expected timestamp slots and processes the corner case when
+     * the expected start timestamp is earlier than what we have.
+     */
+    private static void interpolateHistory(
+            Context context,
+            final List<Long> rawTimestampList,
+            final List<Long> expectedTimestampSlots,
+            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);
+            final boolean isStartOrEnd = index == 0 || index == expectedTimestampSlotsSize - 1;
+            interpolateHistoryForSlot(
+                    context, currentSlot, rawTimestampList, batteryHistoryMap, resultMap,
+                    isStartOrEnd);
+        }
+    }
+
+    private static void interpolateHistoryForSlot(
+            Context context,
+            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 long[] nearestTimestamps = findNearestTimestamp(rawTimestampList, currentSlot);
+        final long lowerTimestamp = nearestTimestamps[0];
+        final long upperTimestamp = nearestTimestamps[1];
+        // Case 1: upper timestamp is zero since scheduler is delayed!
+        if (upperTimestamp == 0) {
+            log(context, "job scheduler is delayed", currentSlot, null);
+            resultMap.put(currentSlot, new HashMap<>());
+            return;
+        }
+        // Case 2: upper timestamp is closed to the current timestamp.
+        if ((upperTimestamp - currentSlot)
+                < MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP * DateUtils.SECOND_IN_MILLIS) {
+            log(context, "force align into the nearest slot", currentSlot, null);
+            resultMap.put(currentSlot, batteryHistoryMap.get(upperTimestamp));
+            return;
+        }
+        // Case 3: lower timestamp is zero before starting to collect data.
+        if (lowerTimestamp == 0) {
+            log(context, "no lower timestamp slot data", currentSlot, null);
+            resultMap.put(currentSlot, new HashMap<>());
+            return;
+        }
+        interpolateHistoryForSlot(context,
+                currentSlot, lowerTimestamp, upperTimestamp, batteryHistoryMap, resultMap,
+                isStartOrEnd);
+    }
+
+    private static void interpolateHistoryForSlot(
+            Context context,
+            final long currentSlot,
+            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<String, BatteryHistEntry> lowerEntryDataMap =
+                batteryHistoryMap.get(lowerTimestamp);
+        final Map<String, BatteryHistEntry> upperEntryDataMap =
+                batteryHistoryMap.get(upperTimestamp);
+        // Verifies whether the lower data is valid to use or not by checking boot time.
+        final BatteryHistEntry upperEntryDataFirstEntry =
+                upperEntryDataMap.values().stream().findFirst().get();
+        final long upperEntryDataBootTimestamp =
+                upperEntryDataFirstEntry.mTimestamp - upperEntryDataFirstEntry.mBootTimestamp;
+        // Lower data is captured before upper data corresponding device is booting.
+        // Skips the booting-specific logics and always does interpolation for daily chart level
+        // data.
+        if (lowerTimestamp < upperEntryDataBootTimestamp
+                && !isForDailyChart(isStartOrEnd, 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);
+                resultMap.put(currentSlot, upperEntryDataMap);
+            } else {
+                log(context, "in the different booting section", currentSlot, null);
+                resultMap.put(currentSlot, new HashMap<>());
+            }
+            return;
+        }
+        log(context, "apply interpolation arithmetic", currentSlot, null);
+        final Map<String, BatteryHistEntry> newHistEntryMap = new HashMap<>();
+        final double timestampLength = upperTimestamp - lowerTimestamp;
+        final double timestampDiff = currentSlot - lowerTimestamp;
+        // Applies interpolation arithmetic for each BatteryHistEntry.
+        for (String entryKey : upperEntryDataMap.keySet()) {
+            final BatteryHistEntry lowerEntry = lowerEntryDataMap.get(entryKey);
+            final BatteryHistEntry upperEntry = upperEntryDataMap.get(entryKey);
+            // Checks whether there is any abnormal battery reset conditions.
+            if (lowerEntry != null) {
+                final boolean invalidForegroundUsageTime =
+                        lowerEntry.mForegroundUsageTimeInMs > upperEntry.mForegroundUsageTimeInMs;
+                final boolean invalidBackgroundUsageTime =
+                        lowerEntry.mBackgroundUsageTimeInMs > upperEntry.mBackgroundUsageTimeInMs;
+                if (invalidForegroundUsageTime || invalidBackgroundUsageTime) {
+                    newHistEntryMap.put(entryKey, upperEntry);
+                    log(context, "abnormal reset condition is found", currentSlot, upperEntry);
+                    continue;
+                }
+            }
+            final BatteryHistEntry newEntry =
+                    BatteryHistEntry.interpolate(
+                            currentSlot,
+                            upperTimestamp,
+                            /*ratio=*/ timestampDiff / timestampLength,
+                            lowerEntry,
+                            upperEntry);
+            newHistEntryMap.put(entryKey, newEntry);
+            if (lowerEntry == null) {
+                log(context, "cannot find lower entry data", currentSlot, upperEntry);
+                continue;
+            }
+        }
+        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 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 += 2 * DateUtils.HOUR_IN_MILLIS;
+            }
+            hourlyTimestamps.add(hourlyTimestampsPerDay);
+        }
+        return hourlyTimestamps;
+    }
+
+    private static List<BatteryLevelData.PeriodBatteryLevelData> getHourlyPeriodBatteryLevelData(
+            Context context,
+            final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
+            final List<List<Long>> timestamps) {
+        final List<BatteryLevelData.PeriodBatteryLevelData> levelData = new ArrayList<>();
+        timestamps.forEach(
+                timestampList -> levelData.add(
+                        getPeriodBatteryLevelData(
+                                context, processedBatteryHistoryMap, timestampList)));
+        return levelData;
+    }
+
+    private static BatteryLevelData.PeriodBatteryLevelData getPeriodBatteryLevelData(
+            Context context,
+            final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
+            final List<Long> timestamps) {
+        final List<Integer> levels = new ArrayList<>();
+        timestamps.forEach(
+                timestamp -> levels.add(getLevel(context, processedBatteryHistoryMap, timestamp)));
+        return new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels);
+    }
+
+    private static Integer getLevel(
+            Context context,
+            final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
+            final long timestamp) {
+        final Map<String, BatteryHistEntry> entryMap = processedBatteryHistoryMap.get(timestamp);
+        if (entryMap == null || entryMap.isEmpty()) {
+            Log.e(TAG, "abnormal entry list in the timestamp:"
+                    + utcToLocalTime(context, timestamp));
+            return null;
+        }
+        // Averages the battery level in each time slot to avoid corner conditions.
+        float batteryLevelCounter = 0;
+        for (BatteryHistEntry entry : entryMap.values()) {
+            batteryLevelCounter += entry.mBatteryLevel;
+        }
+        return Math.round(batteryLevelCounter / entryMap.size());
+    }
+
+    private static void insertHourlyUsageDiffData(
+            Context context,
+            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
+            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
+            final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
+        final int currentUserId = context.getUserId();
+        final UserHandle userHandle =
+                Utils.getManagedProfile(context.getSystemService(UserManager.class));
+        final int workProfileUserId =
+                userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
+        // Each time slot usage diff data =
+        //     Math.abs(timestamp[i+2] data - timestamp[i+1] data) +
+        //     Math.abs(timestamp[i+1] data - timestamp[i] data);
+        // since we want to aggregate every two hours data into a single time slot.
+        for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
+            final Map<Integer, BatteryDiffData> dailyDiffMap = new HashMap<>();
+            resultMap.put(dailyIndex, dailyDiffMap);
+            if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
+                continue;
+            }
+            final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
+            for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
+                final BatteryDiffData hourlyBatteryDiffData =
+                        insertHourlyUsageDiffDataPerSlot(
+                                context,
+                                currentUserId,
+                                workProfileUserId,
+                                hourlyIndex,
+                                timestamps,
+                                batteryHistoryMap);
+                dailyDiffMap.put(hourlyIndex, hourlyBatteryDiffData);
+            }
+        }
+    }
+
+    private static void insertDailyUsageDiffData(
+            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
+            final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
+        for (int index = 0; index < hourlyBatteryLevelsPerDay.size(); index++) {
+            Map<Integer, BatteryDiffData> dailyUsageMap = resultMap.get(index);
+            if (dailyUsageMap == null) {
+                dailyUsageMap = new HashMap<>();
+                resultMap.put(index, dailyUsageMap);
+            }
+            dailyUsageMap.put(
+                    SELECTED_INDEX_ALL,
+                    getAccumulatedUsageDiffData(dailyUsageMap.values()));
+        }
+    }
+
+    private static void insertAllUsageDiffData(
+            final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
+        final List<BatteryDiffData> diffDataList = new ArrayList<>();
+        resultMap.keySet().forEach(
+                key -> diffDataList.add(resultMap.get(key).get(SELECTED_INDEX_ALL)));
+        final Map<Integer, BatteryDiffData> allUsageMap = new HashMap<>();
+        allUsageMap.put(SELECTED_INDEX_ALL, getAccumulatedUsageDiffData(diffDataList));
+        resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
+    }
+
+    @Nullable
+    private static BatteryDiffData insertHourlyUsageDiffDataPerSlot(
+            Context context,
+            final int currentUserId,
+            final int workProfileUserId,
+            final int currentIndex,
+            final List<Long> timestamps,
+            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
+        final List<BatteryDiffEntry> appEntries = new ArrayList<>();
+        final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
+
+        final Long currentTimestamp = timestamps.get(currentIndex);
+        final Long nextTimestamp = currentTimestamp + DateUtils.HOUR_IN_MILLIS;
+        final Long nextTwoTimestamp = nextTimestamp + DateUtils.HOUR_IN_MILLIS;
+        // Fetches BatteryHistEntry data from corresponding time slot.
+        final Map<String, BatteryHistEntry> currentBatteryHistMap =
+                batteryHistoryMap.getOrDefault(currentTimestamp, EMPTY_BATTERY_MAP);
+        final Map<String, BatteryHistEntry> nextBatteryHistMap =
+                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()) {
+            return null;
+        }
+
+        // Collects all keys in these three time slot records as all populations.
+        final Set<String> allBatteryHistEntryKeys = new ArraySet<>();
+        allBatteryHistEntryKeys.addAll(currentBatteryHistMap.keySet());
+        allBatteryHistEntryKeys.addAll(nextBatteryHistMap.keySet());
+        allBatteryHistEntryKeys.addAll(nextTwoBatteryHistMap.keySet());
+
+        double totalConsumePower = 0.0;
+        double consumePowerFromOtherUsers = 0f;
+        // Calculates all packages diff usage data in a specific time slot.
+        for (String key : allBatteryHistEntryKeys) {
+            final BatteryHistEntry currentEntry =
+                    currentBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
+            final BatteryHistEntry nextEntry =
+                    nextBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
+            final BatteryHistEntry nextTwoEntry =
+                    nextTwoBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
+            // Cumulative values is a specific time slot for a specific app.
+            long foregroundUsageTimeInMs =
+                    getDiffValue(
+                            currentEntry.mForegroundUsageTimeInMs,
+                            nextEntry.mForegroundUsageTimeInMs,
+                            nextTwoEntry.mForegroundUsageTimeInMs);
+            long backgroundUsageTimeInMs =
+                    getDiffValue(
+                            currentEntry.mBackgroundUsageTimeInMs,
+                            nextEntry.mBackgroundUsageTimeInMs,
+                            nextTwoEntry.mBackgroundUsageTimeInMs);
+            double consumePower =
+                    getDiffValue(
+                            currentEntry.mConsumePower,
+                            nextEntry.mConsumePower,
+                            nextTwoEntry.mConsumePower);
+            // Excludes entry since we don't have enough data to calculate.
+            if (foregroundUsageTimeInMs == 0
+                    && backgroundUsageTimeInMs == 0
+                    && consumePower == 0) {
+                continue;
+            }
+            final BatteryHistEntry selectedBatteryEntry =
+                    selectBatteryHistEntry(currentEntry, nextEntry, nextTwoEntry);
+            if (selectedBatteryEntry == null) {
+                continue;
+            }
+            // Forces 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_HOURLY_TIME_THRESHOLD) {
+                final float ratio = TOTAL_HOURLY_TIME_THRESHOLD / totalUsageTimeInMs;
+                if (DEBUG) {
+                    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);
+                consumePower = consumePower * ratio;
+            }
+            totalConsumePower += consumePower;
+
+            final boolean isFromOtherUsers = isConsumedFromOtherUsers(
+                    currentUserId, workProfileUserId, selectedBatteryEntry);
+            if (isFromOtherUsers) {
+                consumePowerFromOtherUsers += consumePower;
+            } else {
+                final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
+                        context,
+                        foregroundUsageTimeInMs,
+                        backgroundUsageTimeInMs,
+                        consumePower,
+                        selectedBatteryEntry);
+                if (currentBatteryDiffEntry.isSystemEntry()) {
+                    systemEntries.add(currentBatteryDiffEntry);
+                } else {
+                    appEntries.add(currentBatteryDiffEntry);
+                }
+            }
+        }
+        if (consumePowerFromOtherUsers != 0) {
+            systemEntries.add(createOtherUsersEntry(context, consumePowerFromOtherUsers));
+        }
+
+        // If there is no data, return null instead of empty item.
+        if (appEntries.isEmpty() && systemEntries.isEmpty()) {
+            return null;
+        }
+
+        final BatteryDiffData resultDiffData =
+                new BatteryDiffData(appEntries, systemEntries, totalConsumePower);
+        return resultDiffData;
+    }
+
+    private static boolean isConsumedFromOtherUsers(
+            final int currentUserId,
+            final int workProfileUserId,
+            final BatteryHistEntry batteryHistEntry) {
+        return batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
+                && batteryHistEntry.mUserId != currentUserId
+                && batteryHistEntry.mUserId != workProfileUserId;
+    }
+
+    @Nullable
+    private static BatteryDiffData getAccumulatedUsageDiffData(
+            final Collection<BatteryDiffData> diffEntryListData) {
+        double totalConsumePower = 0f;
+        final Map<String, BatteryDiffEntry> diffEntryMap = new HashMap<>();
+        final List<BatteryDiffEntry> appEntries = new ArrayList<>();
+        final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
+
+        for (BatteryDiffData diffEntryList : diffEntryListData) {
+            if (diffEntryList == null) {
+                continue;
+            }
+            for (BatteryDiffEntry entry : diffEntryList.getAppDiffEntryList()) {
+                computeUsageDiffDataPerEntry(entry, diffEntryMap);
+                totalConsumePower += entry.mConsumePower;
+            }
+            for (BatteryDiffEntry entry : diffEntryList.getSystemDiffEntryList()) {
+                computeUsageDiffDataPerEntry(entry, diffEntryMap);
+                totalConsumePower += entry.mConsumePower;
+            }
+        }
+
+        final Collection<BatteryDiffEntry> diffEntryList = diffEntryMap.values();
+        for (BatteryDiffEntry entry : diffEntryList) {
+            // Sets total daily consume power data into all BatteryDiffEntry.
+            entry.setTotalConsumePower(totalConsumePower);
+            if (entry.isSystemEntry()) {
+                systemEntries.add(entry);
+            } else {
+                appEntries.add(entry);
+            }
+        }
+
+        return diffEntryList.isEmpty() ? null : new BatteryDiffData(appEntries, systemEntries);
+    }
+
+    private static void computeUsageDiffDataPerEntry(
+            final BatteryDiffEntry entry,
+            final Map<String, BatteryDiffEntry> diffEntryMap) {
+        final String key = entry.mBatteryHistEntry.getKey();
+        final BatteryDiffEntry oldBatteryDiffEntry = diffEntryMap.get(key);
+        // Creates new BatteryDiffEntry if we don't have it.
+        if (oldBatteryDiffEntry == null) {
+            diffEntryMap.put(key, entry.clone());
+        } else {
+            // Sums up some field data into the existing one.
+            oldBatteryDiffEntry.mForegroundUsageTimeInMs +=
+                    entry.mForegroundUsageTimeInMs;
+            oldBatteryDiffEntry.mBackgroundUsageTimeInMs +=
+                    entry.mBackgroundUsageTimeInMs;
+            oldBatteryDiffEntry.mConsumePower += entry.mConsumePower;
+        }
+    }
+
+    // Removes low percentage data and fake usage data, which will be zero value.
+    private static void purgeLowPercentageAndFakeData(
+            final Context context,
+            final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
+        final Set<CharSequence> backgroundUsageTimeHideList =
+                FeatureFactory.getFactory(context)
+                        .getPowerUsageFeatureProvider(context)
+                        .getHideBackgroundUsageTimeSet(context);
+        final CharSequence[] notAllowShowEntryPackages =
+                FeatureFactory.getFactory(context)
+                        .getPowerUsageFeatureProvider(context)
+                        .getHideApplicationEntries(context);
+        resultMap.keySet().forEach(dailyKey -> {
+            final Map<Integer, BatteryDiffData> dailyUsageMap = resultMap.get(dailyKey);
+            dailyUsageMap.values().forEach(diffEntryLists -> {
+                if (diffEntryLists == null) {
+                    return;
+                }
+                purgeLowPercentageAndFakeData(
+                        diffEntryLists.getAppDiffEntryList(), backgroundUsageTimeHideList,
+                        notAllowShowEntryPackages);
+                purgeLowPercentageAndFakeData(
+                        diffEntryLists.getSystemDiffEntryList(), backgroundUsageTimeHideList,
+                        notAllowShowEntryPackages);
+            });
+        });
+    }
+
+    private static void purgeLowPercentageAndFakeData(
+            final List<BatteryDiffEntry> entries,
+            final Set<CharSequence> backgroundUsageTimeHideList,
+            final CharSequence[] notAllowShowEntryPackages) {
+        final Iterator<BatteryDiffEntry> iterator = entries.iterator();
+        while (iterator.hasNext()) {
+            final BatteryDiffEntry entry = iterator.next();
+            final String packageName = entry.getPackageName();
+            if (entry.getPercentOfTotal() < PERCENTAGE_OF_TOTAL_THRESHOLD
+                    || FAKE_PACKAGE_NAME.equals(packageName)
+                    || contains(packageName, notAllowShowEntryPackages)) {
+                iterator.remove();
+            }
+            if (packageName != null
+                    && !backgroundUsageTimeHideList.isEmpty()
+                    && contains(packageName, backgroundUsageTimeHideList)) {
+                entry.mBackgroundUsageTimeInMs = 0;
+            }
+        }
+    }
+
+    private static boolean isUsageMapValid(
+            final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap,
+            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
+        if (batteryUsageMap.get(SELECTED_INDEX_ALL) == null
+                || !batteryUsageMap.get(SELECTED_INDEX_ALL).containsKey(SELECTED_INDEX_ALL)) {
+            Log.e(TAG, "no [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL] in batteryUsageMap");
+            return false;
+        }
+        for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
+            if (batteryUsageMap.get(dailyIndex) == null
+                    || !batteryUsageMap.get(dailyIndex).containsKey(SELECTED_INDEX_ALL)) {
+                Log.e(TAG, "no [" + dailyIndex + "][SELECTED_INDEX_ALL] in batteryUsageMap, "
+                        + "daily size is: " + hourlyBatteryLevelsPerDay.size());
+                return false;
+            }
+            if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
+                continue;
+            }
+            final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
+            // Length of hourly usage map should be the length of hourly level data - 1.
+            for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
+                if (!batteryUsageMap.get(dailyIndex).containsKey(hourlyIndex)) {
+                    Log.e(TAG, "no [" + dailyIndex + "][" + hourlyIndex + "] in batteryUsageMap, "
+                            + "hourly size is: " + (timestamps.size() - 1));
+                    return false;
+                }
+            }
+        }
+        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 boolean contains(String target, Set<CharSequence> packageNames) {
+        if (target != null && packageNames != null) {
+            for (CharSequence packageName : packageNames) {
+                if (TextUtils.equals(target, packageName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static long getDiffValue(long v1, long v2, long v3) {
+        return (v2 > v1 ? v2 - v1 : 0) + (v3 > v2 ? v3 - v2 : 0);
+    }
+
+    private static double getDiffValue(double v1, double v2, double v3) {
+        return (v2 > v1 ? v2 - v1 : 0) + (v3 > v2 ? v3 - v2 : 0);
+    }
+
+    @Nullable
+    private static BatteryHistEntry selectBatteryHistEntry(
+            final BatteryHistEntry... batteryHistEntries) {
+        for (BatteryHistEntry entry : batteryHistEntries) {
+            if (entry != null && entry != EMPTY_BATTERY_HIST_ENTRY) {
+                return entry;
+            }
+        }
+        return null;
+    }
+
+    private static BatteryDiffEntry createOtherUsersEntry(
+            Context context, final double consumePower) {
+        final ContentValues values = new ContentValues();
+        values.put(BatteryHistEntry.KEY_UID, BatteryUtils.UID_OTHER_USERS);
+        values.put(BatteryHistEntry.KEY_USER_ID, BatteryUtils.UID_OTHER_USERS);
+        values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
+        // We will show the percentage for the "other users" item only, the aggregated
+        // running time information is useless for users to identify individual apps.
+        final BatteryDiffEntry batteryDiffEntry = new BatteryDiffEntry(
+                context,
+                /*foregroundUsageTimeInMs=*/ 0,
+                /*backgroundUsageTimeInMs=*/ 0,
+                consumePower,
+                new BatteryHistEntry(values));
+        return batteryDiffEntry;
+    }
+
+    private static void log(Context context, final String content, final long timestamp,
+            final BatteryHistEntry entry) {
+        if (DEBUG) {
+            Log.d(TAG, String.format(entry != null ? "%s %s:\n%s" : "%s %s:%s",
+                    utcToLocalTime(context, timestamp), content, entry));
+        }
+    }
+
+    // Compute diff map and loads all items (icon and label) in the background.
+    private static final class ComputeUsageMapAndLoadItemsTask
+            extends AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>> {
+
+        private Context mApplicationContext;
+        private Handler mHandler;
+        private UsageMapAsyncResponse mAsyncResponseDelegate;
+        private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
+        private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
+
+        private ComputeUsageMapAndLoadItemsTask(
+                Context context,
+                Handler handler,
+                final UsageMapAsyncResponse asyncResponseDelegate,
+                final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
+                final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
+            mApplicationContext = context.getApplicationContext();
+            mHandler = handler;
+            mAsyncResponseDelegate = asyncResponseDelegate;
+            mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
+            mBatteryHistoryMap = batteryHistoryMap;
+        }
+
+        @Override
+        protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
+            if (mApplicationContext == null
+                    || mHandler == null
+                    || mAsyncResponseDelegate == null
+                    || mBatteryHistoryMap == null
+                    || mHourlyBatteryLevelsPerDay == null) {
+                Log.e(TAG, "invalid input for ComputeUsageMapAndLoadItemsTask()");
+                return null;
+            }
+            final long startTime = System.currentTimeMillis();
+            final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
+                    getBatteryUsageMap(
+                            mApplicationContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap);
+            if (batteryUsageMap != null) {
+                // Pre-loads each BatteryDiffEntry relative icon and label for all slots.
+                final BatteryDiffData batteryUsageMapForAll =
+                        batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL);
+                if (batteryUsageMapForAll != null) {
+                    batteryUsageMapForAll.getAppDiffEntryList().forEach(
+                            entry -> entry.loadLabelAndIcon());
+                    batteryUsageMapForAll.getSystemDiffEntryList().forEach(
+                            entry -> entry.loadLabelAndIcon());
+                }
+            }
+            Log.d(TAG, String.format("execute ComputeUsageMapAndLoadItemsTask in %d/ms",
+                    (System.currentTimeMillis() - startTime)));
+            return batteryUsageMap;
+        }
+
+        @Override
+        protected void onPostExecute(
+                final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
+            mApplicationContext = null;
+            mHourlyBatteryLevelsPerDay = null;
+            mBatteryHistoryMap = null;
+            // Post results back to main thread to refresh UI.
+            if (mHandler != null && mAsyncResponseDelegate != null) {
+                mHandler.post(() -> {
+                    mAsyncResponseDelegate.onBatteryUsageMapLoaded(batteryUsageMap);
+                });
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java
index 405d855..bca32a7 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummary.java
@@ -259,10 +259,7 @@
     @VisibleForTesting
     void initPreference() {
         mBatteryUsagePreference = findPreference(KEY_BATTERY_USAGE);
-        mBatteryUsagePreference.setSummary(
-                mPowerFeatureProvider.isChartGraphEnabled(getContext())
-                        ? getString(R.string.advanced_battery_preference_summary_with_hours)
-                        : getString(R.string.advanced_battery_preference_summary));
+        mBatteryUsagePreference.setSummary(getString(R.string.advanced_battery_preference_summary));
 
         mHelpPreference = findPreference(KEY_BATTERY_ERROR);
         mHelpPreference.setVisible(false);
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
index ba7628e..f7fc07a 100644
--- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -19,6 +19,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -49,7 +50,6 @@
 import android.provider.Telephony.CarrierId;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
@@ -711,27 +711,17 @@
         // Checks if the SIM subscription is active.
         final List<SubscriptionInfo> activeSubscriptionInfos = mContext
                 .getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList();
-        final int defaultDataSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId();
         if (activeSubscriptionInfos != null) {
-            for (SubscriptionInfo subscriptionInfo : activeSubscriptionInfos) {
-                final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
-                        subscriptionInfo, mContext);
-                if (config.carrierId == subscriptionInfo.getCarrierId()) {
-                    mEapSimSubscriptionPref.setSummary(displayName);
-                    return;
-                }
-
-                // When it's UNKNOWN_CARRIER_ID, devices connects it with the SIM subscription of
-                // defaultDataSubscriptionId.
-                if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID
-                        && defaultDataSubscriptionId == subscriptionInfo.getSubscriptionId()) {
-                    mEapSimSubscriptionPref.setSummary(displayName);
-                    return;
-                }
+            SubscriptionInfo info = fineSubscriptionInfo(config.carrierId, activeSubscriptionInfos,
+                    SubscriptionManager.getDefaultDataSubscriptionId());
+            if (info != null) {
+                mEapSimSubscriptionPref.setSummary(
+                        SubscriptionUtil.getUniqueSubscriptionDisplayName(info, mContext));
+                return;
             }
         }
 
-        if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
+        if (config.carrierId == UNKNOWN_CARRIER_ID) {
             mEapSimSubscriptionPref.setSummary(R.string.wifi_no_related_sim_card);
             return;
         }
@@ -750,6 +740,25 @@
                 null /* orderBy */);
     }
 
+    @VisibleForTesting
+    SubscriptionInfo fineSubscriptionInfo(int carrierId,
+            List<SubscriptionInfo> activeSubscriptionInfos, int defaultDataSubscriptionId) {
+        SubscriptionInfo firstMatchedInfo = null;
+        for (SubscriptionInfo info : activeSubscriptionInfos) {
+            // When it's UNKNOWN_CARRIER_ID or matched with configured CarrierId,
+            // devices connects it with the SIM subscription of defaultDataSubscriptionId.
+            if (defaultDataSubscriptionId == info.getSubscriptionId()
+                    && (carrierId == info.getCarrierId() || carrierId == UNKNOWN_CARRIER_ID)) {
+                return info;
+            }
+
+            if (firstMatchedInfo == null && carrierId == info.getCarrierId()) {
+                firstMatchedInfo = info;
+            }
+        }
+        return firstMatchedInfo;
+    }
+
     private void refreshMacAddress() {
         final String macAddress = mWifiEntry.getMacAddress();
         if (TextUtils.isEmpty(macAddress)) {
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java
index a75663b..c95a509 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java
@@ -162,7 +162,8 @@
 
         mController.updateBatteryWithDiffEntry();
 
-        assertThat(mBatteryPreference.getSummary()).isEqualTo("No battery use for past 24 hours");
+        assertThat(mBatteryPreference.getSummary().toString()).isEqualTo(
+                "No battery use since last full charge");
     }
 
     @Test
@@ -175,7 +176,8 @@
 
         mController.updateBatteryWithDiffEntry();
 
-        assertThat(mBatteryPreference.getSummary()).isEqualTo("60% use for past 24 hours");
+        assertThat(mBatteryPreference.getSummary().toString()).isEqualTo(
+                "60% use since last full charge");
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
index b06932a..7c56ab3 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
@@ -232,7 +232,7 @@
     }
 
     @Test
-    public void testGetPreferenceScreenResId_returnNewLayout() {
+    public void setPreferenceScreenResId_returnNewLayout() {
         assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(R.xml.power_usage_detail);
     }
 
@@ -252,7 +252,7 @@
     }
 
     @Test
-    public void testInitHeader_HasAppEntry_BuildByAppEntry() {
+    public void initHeader_HasAppEntry_BuildByAppEntry() {
         ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
                 new InstantAppDataProvider() {
                     @Override
@@ -269,7 +269,7 @@
     }
 
     @Test
-    public void testInitHeader_HasAppEntry_InstantApp() {
+    public void initHeader_HasAppEntry_InstantApp() {
         ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
                 new InstantAppDataProvider() {
                     @Override
@@ -286,7 +286,7 @@
     }
 
     @Test
-    public void testInitHeader_noUsageTimeAndGraphDisabled_hasCorrectSummary() {
+    public void initHeader_noUsageTimeAndGraphDisabled_hasCorrectSummary() {
         when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
                 .thenReturn(false);
 
@@ -304,7 +304,7 @@
     }
 
     @Test
-    public void testInitHeader_bgTwoMinFgZeroAndGraphDisabled_hasCorrectSummary() {
+    public void initHeader_bgTwoMinFgZeroAndGraphDisabled_hasCorrectSummary() {
         when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
                 .thenReturn(false);
 
@@ -324,7 +324,7 @@
     }
 
     @Test
-    public void testInitHeader_bgLessThanAMinFgZeroAndGraphDisabled_hasCorrectSummary() {
+    public void initHeader_bgLessThanAMinFgZeroAndGraphDisabled_hasCorrectSummary() {
         when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
                 .thenReturn(false);
 
@@ -345,7 +345,7 @@
     }
 
     @Test
-    public void testInitHeader_totalUsageLessThanAMinAndGraphDisabled_hasCorrectSummary() {
+    public void initHeader_totalUsageLessThanAMinAndGraphDisabled_hasCorrectSummary() {
         when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
                 .thenReturn(false);
 
@@ -367,7 +367,7 @@
     }
 
     @Test
-    public void testInitHeader_TotalAMinutesBgLessThanAMinAndGraphDisabled_hasCorrectSummary() {
+    public void initHeader_TotalAMinutesBgLessThanAMinAndGraphDisabled_hasCorrectSummary() {
         when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
                 .thenReturn(false);
 
@@ -387,7 +387,7 @@
     }
 
     @Test
-    public void testInitHeader_TotalAMinBackgroundZeroAndGraphDisabled_hasCorrectSummary() {
+    public void initHeader_TotalAMinBackgroundZeroAndGraphDisabled_hasCorrectSummary() {
         when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
                 .thenReturn(false);
         final long backgroundTimeZero = 0;
@@ -406,7 +406,7 @@
     }
 
     @Test
-    public void testInitHeader_fgTwoMinBgFourMinAndGraphDisabled_hasCorrectSummary() {
+    public void initHeader_fgTwoMinBgFourMinAndGraphDisabled_hasCorrectSummary() {
         when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mContext))
                 .thenReturn(false);
         final long backgroundTimeFourMinute = 240000;
@@ -424,7 +424,7 @@
     }
 
     @Test
-    public void testInitHeader_noUsageTime_hasCorrectSummary() {
+    public void initHeader_noUsageTime_hasCorrectSummary() {
         Bundle bundle = new Bundle(2);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, /* value */ 0);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, /* value */ 0);
@@ -435,11 +435,11 @@
         ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
         verify(mEntityHeaderController).setSummary(captor.capture());
         assertThat(captor.getValue().toString())
-                .isEqualTo("No usage for past 24 hr");
+                .isEqualTo("No usage from last full charge");
     }
 
     @Test
-    public void testInitHeader_noUsageTimeButConsumedPower_hasEmptySummary() {
+    public void initHeader_noUsageTimeButConsumedPower_hasEmptySummary() {
         Bundle bundle = new Bundle(3);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, /* value */ 0);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, /* value */ 0);
@@ -454,7 +454,7 @@
     }
 
     @Test
-    public void testInitHeader_backgroundTwoMinForegroundZero_hasCorrectSummary() {
+    public void initHeader_backgroundTwoMinForegroundZero_hasCorrectSummary() {
         final long backgroundTimeTwoMinutes = 120000;
         final long foregroundTimeZero = 0;
         Bundle bundle = new Bundle(2);
@@ -467,11 +467,11 @@
         ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
         verify(mEntityHeaderController).setSummary(captor.capture());
         assertThat(captor.getValue().toString())
-                .isEqualTo("2 min background for past 24 hr");
+                .isEqualTo("2 min background from last full charge");
     }
 
     @Test
-    public void testInitHeader_backgroundLessThanAMinForegroundZero_hasCorrectSummary() {
+    public void initHeader_backgroundLessThanAMinForegroundZero_hasCorrectSummary() {
         final long backgroundTimeLessThanAMinute = 59999;
         final long foregroundTimeZero = 0;
         Bundle bundle = new Bundle(2);
@@ -485,11 +485,11 @@
         ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
         verify(mEntityHeaderController).setSummary(captor.capture());
         assertThat(captor.getValue().toString())
-                .isEqualTo("Background less than a minute for past 24 hr");
+                .isEqualTo("Background less than a minute from last full charge");
     }
 
     @Test
-    public void testInitHeader_totalUsageLessThanAMin_hasCorrectSummary() {
+    public void initHeader_totalUsageLessThanAMin_hasCorrectSummary() {
         final long backgroundTimeLessThanHalfMinute = 20000;
         final long foregroundTimeLessThanHalfMinute = 20000;
         Bundle bundle = new Bundle(2);
@@ -504,11 +504,11 @@
         ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
         verify(mEntityHeaderController).setSummary(captor.capture());
         assertThat(captor.getValue().toString())
-                .isEqualTo("Total less than a minute for past 24 hr");
+                .isEqualTo("Total less than a minute from last full charge");
     }
 
     @Test
-    public void testInitHeader_TotalAMinutesBackgroundLessThanAMin_hasCorrectSummary() {
+    public void initHeader_TotalAMinutesBackgroundLessThanAMin_hasCorrectSummary() {
         final long backgroundTimeZero = 59999;
         final long foregroundTimeTwoMinutes = 1;
         Bundle bundle = new Bundle(2);
@@ -521,11 +521,11 @@
         ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
         verify(mEntityHeaderController).setSummary(captor.capture());
         assertThat(captor.getValue().toString())
-                .isEqualTo("1 min total • background less than a minute\nfor past 24 hr");
+                .isEqualTo("1 min total • background less than a minute\nfrom last full charge");
     }
 
     @Test
-    public void testInitHeader_TotalAMinBackgroundZero_hasCorrectSummary() {
+    public void initHeader_TotalAMinBackgroundZero_hasCorrectSummary() {
         final long backgroundTimeZero = 0;
         final long foregroundTimeAMinutes = 60000;
         Bundle bundle = new Bundle(2);
@@ -538,11 +538,11 @@
         ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
         verify(mEntityHeaderController).setSummary(captor.capture());
         assertThat(captor.getValue().toString())
-                .isEqualTo("1 min total for past 24 hr");
+                .isEqualTo("1 min total from last full charge");
     }
 
     @Test
-    public void testInitHeader_foregroundTwoMinBackgroundFourMin_hasCorrectSummary() {
+    public void initHeader_foregroundTwoMinBackgroundFourMin_hasCorrectSummary() {
         final long backgroundTimeFourMinute = 240000;
         final long foregroundTimeTwoMinutes = 120000;
         Bundle bundle = new Bundle(2);
@@ -555,11 +555,11 @@
         ArgumentCaptor<CharSequence> captor = ArgumentCaptor.forClass(CharSequence.class);
         verify(mEntityHeaderController).setSummary(captor.capture());
         assertThat(captor.getValue().toString())
-                .isEqualTo("6 min total • 4 min background\nfor past 24 hr");
+                .isEqualTo("6 min total • 4 min background\nfrom last full charge");
     }
 
     @Test
-    public void testInitHeader_totalUsageLessThanAMinWithSlotTime_hasCorrectSummary() {
+    public void initHeader_totalUsageLessThanAMinWithSlotTime_hasCorrectSummary() {
         final long backgroundTimeLessThanHalfMinute = 20000;
         final long foregroundTimeLessThanHalfMinute = 20000;
         Bundle bundle = new Bundle(3);
@@ -579,7 +579,7 @@
     }
 
     @Test
-    public void testInitHeader_TotalAMinBackgroundLessThanAMinWithSlotTime_hasCorrectSummary() {
+    public void initHeader_TotalAMinBackgroundLessThanAMinWithSlotTime_hasCorrectSummary() {
         final long backgroundTimeZero = 59999;
         final long foregroundTimeTwoMinutes = 1;
         Bundle bundle = new Bundle(3);
@@ -597,7 +597,7 @@
     }
 
     @Test
-    public void testInitHeader_TotalAMinBackgroundZeroWithSlotTime_hasCorrectSummary() {
+    public void initHeader_TotalAMinBackgroundZeroWithSlotTime_hasCorrectSummary() {
         final long backgroundTimeZero = 0;
         final long foregroundTimeAMinutes = 60000;
         Bundle bundle = new Bundle(3);
@@ -615,7 +615,7 @@
     }
 
     @Test
-    public void testInitHeader_foregroundTwoMinBackgroundFourMinWithSlotTime_hasCorrectSummary() {
+    public void initHeader_foregroundTwoMinBackgroundFourMinWithSlotTime_hasCorrectSummary() {
         final long backgroundTimeFourMinute = 240000;
         final long foregroundTimeTwoMinutes = 120000;
         Bundle bundle = new Bundle(3);
@@ -633,7 +633,7 @@
     }
 
     @Test
-    public void testInitHeader_systemUidWithChartIsDisabled_nullSummary() {
+    public void initHeader_systemUidWithChartIsDisabled_nullSummary() {
         Bundle bundle = new Bundle(3);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, 240000);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, 120000);
@@ -650,7 +650,7 @@
     }
 
     @Test
-    public void testInitHeader_systemUidWithChartIsEnabled_notNullSummary() {
+    public void initHeader_systemUidWithChartIsEnabled_notNullSummary() {
         Bundle bundle = new Bundle(3);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, 240000);
         bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, 120000);
@@ -665,21 +665,21 @@
     }
 
     @Test
-    public void testStartBatteryDetailPage_hasBasicData() {
+    public void startBatteryDetailPage_hasBasicData() {
         AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
                 mBatteryEntry, USAGE_PERCENT, /*isValidToShowSummary=*/ true);
 
         assertThat(mBundle.getInt(AdvancedPowerUsageDetail.EXTRA_UID)).isEqualTo(UID);
         assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME))
-            .isEqualTo(BACKGROUND_TIME_MS);
+                .isEqualTo(BACKGROUND_TIME_MS);
         assertThat(mBundle.getLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME))
-            .isEqualTo(FOREGROUND_TIME_MS);
+                .isEqualTo(FOREGROUND_TIME_MS);
         assertThat(mBundle.getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT))
-            .isEqualTo(USAGE_PERCENT);
+                .isEqualTo(USAGE_PERCENT);
     }
 
     @Test
-    public void testStartBatteryDetailPage_invalidToShowSummary_noFGBDData() {
+    public void startBatteryDetailPage_invalidToShowSummary_noFGBDData() {
         AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
                 mBatteryEntry, USAGE_PERCENT, /*isValidToShowSummary=*/ false);
 
@@ -693,7 +693,7 @@
     }
 
     @Test
-    public void testStartBatteryDetailPage_NormalApp() {
+    public void startBatteryDetailPage_NormalApp() {
         when(mBatteryEntry.getDefaultPackageName()).thenReturn(PACKAGE_NAME[0]);
 
         AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
@@ -704,7 +704,7 @@
     }
 
     @Test
-    public void testStartBatteryDetailPage_SystemApp() {
+    public void startBatteryDetailPage_SystemApp() {
         when(mBatteryEntry.getDefaultPackageName()).thenReturn(null);
 
         AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment,
@@ -716,7 +716,7 @@
     }
 
     @Test
-    public void testStartBatteryDetailPage_WorkApp() {
+    public void startBatteryDetailPage_WorkApp() {
         final int appUid = 1010019;
         doReturn(appUid).when(mBatteryEntry).getUid();
 
@@ -727,7 +727,7 @@
     }
 
     @Test
-    public void testStartBatteryDetailPage_typeUser_startByCurrentUser() {
+    public void startBatteryDetailPage_typeUser_startByCurrentUser() {
         when(mBatteryEntry.isUserEntry()).thenReturn(true);
 
         final int currentUser = 20;
@@ -739,7 +739,7 @@
     }
 
     @Test
-    public void testStartBatteryDetailPage_noBatteryUsage_hasBasicData() {
+    public void startBatteryDetailPage_noBatteryUsage_hasBasicData() {
         final ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
 
         AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, mFragment, PACKAGE_NAME[0]);
@@ -747,16 +747,16 @@
         verify(mActivity).startActivity(captor.capture());
 
         assertThat(captor.getValue().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS)
-            .getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME))
-            .isEqualTo(PACKAGE_NAME[0]);
+                .getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME))
+                .isEqualTo(PACKAGE_NAME[0]);
 
         assertThat(captor.getValue().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS)
-            .getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT))
-            .isEqualTo("0%");
+                .getString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT))
+                .isEqualTo("0%");
     }
 
     @Test
-    public void testStartBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName() throws
+    public void startBatteryDetailPage_batteryEntryNotExisted_extractUidFromPackageName() throws
             PackageManager.NameNotFoundException {
         doReturn(UID).when(mPackageManager).getPackageUid(PACKAGE_NAME[0], 0 /* no flag */);
 
@@ -796,7 +796,7 @@
     }
 
     @Test
-    public void testInitPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() {
+    public void initPreferenceForTriState_isSystemOrDefaultApp_hasCorrectString() {
         when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
         when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true);
 
@@ -807,7 +807,7 @@
     }
 
     @Test
-    public void testInitPreferenceForTriState_hasCorrectString() {
+    public void initPreferenceForTriState_hasCorrectString() {
         when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true);
         when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(false);
 
@@ -818,7 +818,7 @@
     }
 
     @Test
-    public void testOnRadioButtonClicked_clickOptimizePref_optimizePreferenceChecked() {
+    public void onRadioButtonClicked_clickOptimizePref_optimizePreferenceChecked() {
         mOptimizePreference.setKey(KEY_PREF_OPTIMIZED);
         mRestrictedPreference.setKey(KEY_PREF_RESTRICTED);
         mUnrestrictedPreference.setKey(KEY_PREF_UNRESTRICTED);
@@ -830,7 +830,7 @@
     }
 
     @Test
-    public void testOnPause_optimizationModeChanged_logPreference() {
+    public void onPause_optimizationModeChanged_logPreference() {
         final int mode = BatteryOptimizeUtils.MODE_RESTRICTED;
         mFragment.mOptimizationMode = mode;
         when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode);
@@ -849,7 +849,7 @@
     }
 
     @Test
-    public void testOnPause_optimizationModeIsNotChanged_notInvokeLogging() {
+    public void onPause_optimizationModeIsNotChanged_notInvokeLogging() {
         final int mode = BatteryOptimizeUtils.MODE_OPTIMIZED;
         mFragment.mOptimizationMode = mode;
         when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode);
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 ec98226..016287e 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java
@@ -18,23 +18,24 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.settings.SettingsEnums;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.LocaleList;
 import android.text.format.DateUtils;
+import android.view.View;
+import android.widget.LinearLayout;
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
@@ -58,15 +59,15 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TimeZone;
 
 @RunWith(RobolectricTestRunner.class)
 public final class BatteryChartPreferenceControllerTest {
     private static final String PREF_KEY = "pref_key";
     private static final String PREF_SUMMARY = "fake preference summary";
-    private static final int DESIRED_HISTORY_SIZE =
-            BatteryChartPreferenceController.DESIRED_HISTORY_SIZE;
 
     @Mock
     private InstrumentedPreferenceFragment mFragment;
@@ -79,11 +80,15 @@
     @Mock
     private BatteryHistEntry mBatteryHistEntry;
     @Mock
-    private BatteryChartView mBatteryChartView;
+    private BatteryChartView mDailyChartView;
+    @Mock
+    private BatteryChartView mHourlyChartView;
     @Mock
     private PowerGaugePreference mPowerGaugePreference;
     @Mock
     private BatteryUtils mBatteryUtils;
+    @Mock
+    private LinearLayout.LayoutParams mLayoutParams;
 
     private Context mContext;
     private FakeFeatureFactory mFeatureFactory;
@@ -96,6 +101,7 @@
         MockitoAnnotations.initMocks(this);
         Locale.setDefault(new Locale("en_US"));
         org.robolectric.shadows.ShadowSettings.set24HourTimeFormat(false);
+        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
         mFeatureFactory = FakeFeatureFactory.setupForTest();
         mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
         mContext = spy(RuntimeEnvironment.application);
@@ -108,10 +114,12 @@
         doReturn(new String[]{"com.android.gms.persistent"})
                 .when(mFeatureFactory.powerUsageFeatureProvider)
                 .getHideApplicationEntries(mContext);
+        doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
         mBatteryChartPreferenceController = createController();
         mBatteryChartPreferenceController.mPrefContext = mContext;
         mBatteryChartPreferenceController.mAppListPrefGroup = mAppListGroup;
-        mBatteryChartPreferenceController.mBatteryChartView = mBatteryChartView;
+        mBatteryChartPreferenceController.mDailyChartView = mDailyChartView;
+        mBatteryChartPreferenceController.mHourlyChartView = mHourlyChartView;
         mBatteryDiffEntry = new BatteryDiffEntry(
                 mContext,
                 /*foregroundUsageTimeInMs=*/ 1,
@@ -123,12 +131,10 @@
         BatteryDiffEntry.sResourceCache.put(
                 "fakeBatteryDiffEntryKey",
                 new BatteryEntry.NameAndIcon("fakeName", /*icon=*/ null, /*iconId=*/ 1));
-        mBatteryChartPreferenceController.setBatteryHistoryMap(
-                createBatteryHistoryMap());
     }
 
     @Test
-    public void testOnDestroy_activityIsChanging_clearBatteryEntryCache() {
+    public void onDestroy_activityIsChanging_clearBatteryEntryCache() {
         doReturn(true).when(mSettingsActivity).isChangingConfigurations();
         // Ensures the testing environment is correct.
         assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
@@ -138,7 +144,7 @@
     }
 
     @Test
-    public void testOnDestroy_activityIsNotChanging_notClearBatteryEntryCache() {
+    public void onDestroy_activityIsNotChanging_notClearBatteryEntryCache() {
         doReturn(false).when(mSettingsActivity).isChangingConfigurations();
         // Ensures the testing environment is correct.
         assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
@@ -148,7 +154,7 @@
     }
 
     @Test
-    public void testOnDestroy_clearPreferenceCache() {
+    public void onDestroy_clearPreferenceCache() {
         // Ensures the testing environment is correct.
         mBatteryChartPreferenceController.mPreferenceCache.put(
                 PREF_KEY, mPowerGaugePreference);
@@ -160,113 +166,135 @@
     }
 
     @Test
-    public void testOnDestroy_removeAllPreferenceFromPreferenceGroup() {
+    public void onDestroy_removeAllPreferenceFromPreferenceGroup() {
         mBatteryChartPreferenceController.onDestroy();
         verify(mAppListGroup).removeAll();
     }
 
     @Test
-    public void testSetBatteryHistoryMap_createExpectedKeysAndLevels() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(
-                createBatteryHistoryMap());
+    public void setBatteryChartViewModel_6Hours() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
 
-        // Verifies the created battery keys array.
-        for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
-            assertThat(mBatteryChartPreferenceController.mBatteryHistoryKeys[index])
-                    // These values is are calculated by hand from createBatteryHistoryMap().
-                    .isEqualTo(index + 1);
-        }
-        // Verifies the created battery levels array.
-        for (int index = 0; index < 13; index++) {
-            assertThat(mBatteryChartPreferenceController.mBatteryHistoryLevels[index])
-                    // These values is are calculated by hand from createBatteryHistoryMap().
-                    .isEqualTo(100 - index * 2);
-        }
-        assertThat(mBatteryChartPreferenceController.mBatteryIndexedMap).hasSize(13);
+        verify(mDailyChartView, atLeastOnce()).setVisibility(View.GONE);
+        verify(mHourlyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
+        verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
+                List.of(100, 97, 95),
+                List.of("8 am", "10 am", "12 pm"),
+                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
     }
 
     @Test
-    public void testSetBatteryHistoryMap_largeSize_createExpectedKeysAndLevels() {
-        mBatteryChartPreferenceController.setBatteryHistoryMap(
-                createBatteryHistoryMap());
+    public void setBatteryChartViewModel_60Hours() {
+        BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
+                List.of(100, 83, 59, 41),
+                List.of("Sat", "Sun", "Mon", "Mon"),
+                BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS);
 
-        // Verifies the created battery keys array.
-        for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
-            assertThat(mBatteryChartPreferenceController.mBatteryHistoryKeys[index])
-                    // These values is are calculated by hand from createBatteryHistoryMap().
-                    .isEqualTo(index + 1);
-        }
-        // Verifies the created battery levels array.
-        for (int index = 0; index < 13; index++) {
-            assertThat(mBatteryChartPreferenceController.mBatteryHistoryLevels[index])
-                    // These values is are calculated by hand from createBatteryHistoryMap().
-                    .isEqualTo(100 - index * 2);
-        }
-        assertThat(mBatteryChartPreferenceController.mBatteryIndexedMap).hasSize(13);
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
+
+        verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE);
+        verify(mHourlyChartView, atLeastOnce()).setVisibility(View.GONE);
+        verify(mDailyChartView).setViewModel(expectedDailyViewModel);
+
+        reset(mDailyChartView);
+        reset(mHourlyChartView);
+        doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
+        mBatteryChartPreferenceController.mDailyChartIndex = 0;
+        mBatteryChartPreferenceController.refreshUi();
+        verify(mDailyChartView).setVisibility(View.VISIBLE);
+        verify(mHourlyChartView).setVisibility(View.VISIBLE);
+
+        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("8 am", "10 am", "12 pm", "2 pm", "4 pm", "6 pm", "8 pm", "10 pm",
+                        "12 am"),
+                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
+
+        reset(mDailyChartView);
+        reset(mHourlyChartView);
+        doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
+        mBatteryChartPreferenceController.mDailyChartIndex = 1;
+        mBatteryChartPreferenceController.mHourlyChartIndex = 6;
+        mBatteryChartPreferenceController.refreshUi();
+        verify(mDailyChartView).setVisibility(View.VISIBLE);
+        verify(mHourlyChartView).setVisibility(View.VISIBLE);
+        expectedDailyViewModel.setSelectedIndex(1);
+        verify(mDailyChartView).setViewModel(expectedDailyViewModel);
+        BatteryChartViewModel expectedHourlyViewModel = new BatteryChartViewModel(
+                List.of(83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59),
+                List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm",
+                        "4 pm", "6 pm", "8 pm", "10 pm", "12 am"),
+                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
+        expectedHourlyViewModel.setSelectedIndex(6);
+        verify(mHourlyChartView).setViewModel(expectedHourlyViewModel);
+
+        reset(mDailyChartView);
+        reset(mHourlyChartView);
+        doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
+        mBatteryChartPreferenceController.mDailyChartIndex = 2;
+        mBatteryChartPreferenceController.mHourlyChartIndex =
+                BatteryChartViewModel.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.refreshUi();
+        verify(mDailyChartView).setVisibility(View.VISIBLE);
+        verify(mHourlyChartView).setVisibility(View.VISIBLE);
+        expectedDailyViewModel.setSelectedIndex(2);
+        verify(mDailyChartView).setViewModel(expectedDailyViewModel);
+        verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
+                List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41),
+                List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm",
+                        "4 pm", "6 pm"),
+                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
     }
 
     @Test
-    public void testRefreshUi_batteryIndexedMapIsNull_ignoreRefresh() {
+    public void refreshUi_normalCase_returnTrue() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        assertThat(mBatteryChartPreferenceController.refreshUi()).isTrue();
+    }
+
+    @Test
+    public void refreshUi_batteryIndexedMapIsNull_ignoreRefresh() {
         mBatteryChartPreferenceController.setBatteryHistoryMap(null);
-        assertThat(mBatteryChartPreferenceController.refreshUi(
-                /*trapezoidIndex=*/ 1, /*isForce=*/ false)).isFalse();
+        assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
     }
 
     @Test
-    public void testRefreshUi_batteryChartViewIsNull_ignoreRefresh() {
-        mBatteryChartPreferenceController.mBatteryChartView = null;
-        assertThat(mBatteryChartPreferenceController.refreshUi(
-                /*trapezoidIndex=*/ 1, /*isForce=*/ false)).isFalse();
+    public void refreshUi_dailyChartViewIsNull_ignoreRefresh() {
+        mBatteryChartPreferenceController.mDailyChartView = null;
+        assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
     }
 
     @Test
-    public void testRefreshUi_trapezoidIndexIsNotChanged_ignoreRefresh() {
-        final int trapezoidIndex = 1;
-        mBatteryChartPreferenceController.mTrapezoidIndex = trapezoidIndex;
-        assertThat(mBatteryChartPreferenceController.refreshUi(
-                trapezoidIndex, /*isForce=*/ false)).isFalse();
+    public void refreshUi_hourlyChartViewIsNull_ignoreRefresh() {
+        mBatteryChartPreferenceController.mHourlyChartView = null;
+        assertThat(mBatteryChartPreferenceController.refreshUi()).isFalse();
     }
 
     @Test
-    public void testRefreshUi_forceUpdate_refreshUi() {
-        final int trapezoidIndex = 1;
-        mBatteryChartPreferenceController.mTrapezoidIndex = trapezoidIndex;
-        assertThat(mBatteryChartPreferenceController.refreshUi(
-                trapezoidIndex, /*isForce=*/ true)).isTrue();
-    }
-
-    @Test
-    public void testForceRefreshUi_updateTrapezoidIndexIntoSelectAll() {
-        mBatteryChartPreferenceController.mTrapezoidIndex =
-                BatteryChartView.SELECTED_INDEX_INVALID;
-        mBatteryChartPreferenceController.setBatteryHistoryMap(
-                createBatteryHistoryMap());
-
-        assertThat(mBatteryChartPreferenceController.mTrapezoidIndex)
-                .isEqualTo(BatteryChartView.SELECTED_INDEX_ALL);
-    }
-
-    @Test
-    public void testRemoveAndCacheAllPrefs_emptyContent_ignoreRemoveAll() {
-        final int trapezoidIndex = 1;
+    public void removeAndCacheAllPrefs_emptyContent_ignoreRemoveAll() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
         doReturn(0).when(mAppListGroup).getPreferenceCount();
 
-        mBatteryChartPreferenceController.refreshUi(
-                trapezoidIndex, /*isForce=*/ true);
+        mBatteryChartPreferenceController.refreshUi();
         verify(mAppListGroup, never()).removeAll();
     }
 
     @Test
-    public void testRemoveAndCacheAllPrefs_buildCacheAndRemoveAllPreference() {
-        final int trapezoidIndex = 1;
+    public void removeAndCacheAllPrefs_buildCacheAndRemoveAllPreference() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
         doReturn(1).when(mAppListGroup).getPreferenceCount();
         doReturn(mPowerGaugePreference).when(mAppListGroup).getPreference(0);
+        doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
         doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
+        doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY);
         // Ensures the testing data is correct.
         assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty();
 
-        mBatteryChartPreferenceController.refreshUi(
-                trapezoidIndex, /*isForce=*/ true);
+        mBatteryChartPreferenceController.refreshUi();
 
         assertThat(mBatteryChartPreferenceController.mPreferenceCache.get(PREF_KEY))
                 .isEqualTo(mPowerGaugePreference);
@@ -274,14 +302,14 @@
     }
 
     @Test
-    public void testAddPreferenceToScreen_emptyContent_ignoreAddPreference() {
+    public void addPreferenceToScreen_emptyContent_ignoreAddPreference() {
         mBatteryChartPreferenceController.addPreferenceToScreen(
                 new ArrayList<BatteryDiffEntry>());
         verify(mAppListGroup, never()).addPreference(any());
     }
 
     @Test
-    public void testAddPreferenceToScreen_addPreferenceIntoScreen() {
+    public void addPreferenceToScreen_addPreferenceIntoScreen() {
         final String appLabel = "fake app label";
         doReturn(1).when(mAppListGroup).getPreferenceCount();
         doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
@@ -310,7 +338,7 @@
     }
 
     @Test
-    public void testAddPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() {
+    public void addPreferenceToScreen_alreadyInScreen_notAddPreferenceAgain() {
         final String appLabel = "fake app label";
         doReturn(1).when(mAppListGroup).getPreferenceCount();
         doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
@@ -325,7 +353,7 @@
     }
 
     @Test
-    public void testHandlePreferenceTreeiClick_notPowerGaugePreference_returnFalse() {
+    public void handlePreferenceTreeClick_notPowerGaugePreference_returnFalse() {
         assertThat(mBatteryChartPreferenceController.handlePreferenceTreeClick(mAppListGroup))
                 .isFalse();
 
@@ -336,7 +364,7 @@
     }
 
     @Test
-    public void testHandlePreferenceTreeClick_forAppEntry_returnTrue() {
+    public void handlePreferenceTreeClick_forAppEntry_returnTrue() {
         doReturn(false).when(mBatteryHistEntry).isAppEntry();
         doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
 
@@ -352,7 +380,7 @@
     }
 
     @Test
-    public void testHandlePreferenceTreeClick_forSystemEntry_returnTrue() {
+    public void handlePreferenceTreeClick_forSystemEntry_returnTrue() {
         mBatteryChartPreferenceController.mBatteryUtils = mBatteryUtils;
         doReturn(true).when(mBatteryHistEntry).isAppEntry();
         doReturn(mBatteryDiffEntry).when(mPowerGaugePreference).getBatteryDiffEntry();
@@ -369,7 +397,7 @@
     }
 
     @Test
-    public void testSetPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() {
+    public void setPreferenceSummary_setNullContentIfTotalUsageTimeIsZero() {
         final PowerGaugePreference pref = new PowerGaugePreference(mContext);
         pref.setSummary(PREF_SUMMARY);
 
@@ -381,7 +409,7 @@
     }
 
     @Test
-    public void testSetPreferenceSummary_setBackgroundUsageTimeOnly() {
+    public void setPreferenceSummary_setBackgroundUsageTimeOnly() {
         final PowerGaugePreference pref = new PowerGaugePreference(mContext);
         pref.setSummary(PREF_SUMMARY);
 
@@ -393,7 +421,7 @@
     }
 
     @Test
-    public void testSetPreferenceSummary_setTotalUsageTimeLessThanAMinute() {
+    public void setPreferenceSummary_setTotalUsageTimeLessThanAMinute() {
         final PowerGaugePreference pref = new PowerGaugePreference(mContext);
         pref.setSummary(PREF_SUMMARY);
 
@@ -405,7 +433,7 @@
     }
 
     @Test
-    public void testSetPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() {
+    public void setPreferenceSummary_setTotalTimeIfBackgroundTimeLessThanAMinute() {
         final PowerGaugePreference pref = new PowerGaugePreference(mContext);
         pref.setSummary(PREF_SUMMARY);
 
@@ -418,7 +446,7 @@
     }
 
     @Test
-    public void testSetPreferenceSummary_setTotalAndBackgroundUsageTime() {
+    public void setPreferenceSummary_setTotalAndBackgroundUsageTime() {
         final PowerGaugePreference pref = new PowerGaugePreference(mContext);
         pref.setSummary(PREF_SUMMARY);
 
@@ -430,7 +458,7 @@
     }
 
     @Test
-    public void testSetPreferenceSummary_notAllowShownPackage_setSummayAsNull() {
+    public void setPreferenceSummary_notAllowShownPackage_setSummayAsNull() {
         final PowerGaugePreference pref = new PowerGaugePreference(mContext);
         pref.setSummary(PREF_SUMMARY);
         final BatteryDiffEntry batteryDiffEntry =
@@ -445,36 +473,9 @@
     }
 
     @Test
-    public void testValidateUsageTime_returnTrueIfBatteryDiffEntryIsValid() {
-        assertThat(BatteryChartPreferenceController.validateUsageTime(
-                createBatteryDiffEntry(
-                        /*foregroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS,
-                        /*backgroundUsageTimeInMs=*/ DateUtils.MINUTE_IN_MILLIS)))
-                .isTrue();
-    }
-
-    @Test
-    public void testValidateUsageTime_foregroundTimeExceedThreshold_returnFalse() {
-        assertThat(BatteryChartPreferenceController.validateUsageTime(
-                createBatteryDiffEntry(
-                        /*foregroundUsageTimeInMs=*/ DateUtils.HOUR_IN_MILLIS * 3,
-                        /*backgroundUsageTimeInMs=*/ 0)))
-                .isFalse();
-    }
-
-    @Test
-    public void testValidateUsageTime_backgroundTimeExceedThreshold_returnFalse() {
-        assertThat(BatteryChartPreferenceController.validateUsageTime(
-                createBatteryDiffEntry(
-                        /*foregroundUsageTimeInMs=*/ 0,
-                        /*backgroundUsageTimeInMs=*/ DateUtils.HOUR_IN_MILLIS * 3)))
-                .isFalse();
-    }
-
-    @Test
-    public void testOnExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() {
+    public void onExpand_expandedIsTrue_addSystemEntriesToPreferenceGroup() {
         doReturn(1).when(mAppListGroup).getPreferenceCount();
-        mBatteryChartPreferenceController.mSystemEntries.add(mBatteryDiffEntry);
+        mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
         doReturn("label").when(mBatteryDiffEntry).getAppLabel();
         doReturn(mDrawable).when(mBatteryDiffEntry).getAppIcon();
         doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
@@ -493,10 +494,10 @@
     }
 
     @Test
-    public void testOnExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() {
+    public void onExpand_expandedIsFalse_removeSystemEntriesFromPreferenceGroup() {
         doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
         doReturn(mPowerGaugePreference).when(mAppListGroup).findPreference(PREF_KEY);
-        mBatteryChartPreferenceController.mSystemEntries.add(mBatteryDiffEntry);
+        mBatteryChartPreferenceController.mBatteryUsageMap = createBatteryUsageMap();
         // Verifies the cache is empty first.
         assertThat(mBatteryChartPreferenceController.mPreferenceCache).isEmpty();
 
@@ -513,57 +514,17 @@
     }
 
     @Test
-    public void testOnSelect_selectSpecificTimeSlot_logMetric() {
-        mBatteryChartPreferenceController.onSelect(1 /*slot index*/);
-
-        verify(mMetricsFeatureProvider)
-                .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_TIME_SLOT);
-    }
-
-    @Test
-    public void testOnSelect_selectAll_logMetric() {
-        mBatteryChartPreferenceController.onSelect(
-                BatteryChartView.SELECTED_INDEX_ALL /*slot index*/);
-
-        verify(mMetricsFeatureProvider)
-                .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_SHOW_ALL);
-    }
-
-    @Test
-    public void testRefreshCategoryTitle_setHourIntoBothTitleTextView() {
-        mBatteryChartPreferenceController = createController();
-        setUpBatteryHistoryKeys();
-        mBatteryChartPreferenceController.mAppListPrefGroup =
-                spy(new PreferenceCategory(mContext));
-        mBatteryChartPreferenceController.mExpandDividerPreference =
-                spy(new ExpandDividerPreference(mContext));
-        // Simulates select the first slot.
-        mBatteryChartPreferenceController.mTrapezoidIndex = 0;
-
-        mBatteryChartPreferenceController.refreshCategoryTitle();
-
-        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
-        // Verifies the title in the preference group.
-        verify(mBatteryChartPreferenceController.mAppListPrefGroup)
-                .setTitle(captor.capture());
-        assertThat(captor.getValue()).isNotEqualTo("App usage for past 24 hr");
-        // Verifies the title in the expandable divider.
-        captor = ArgumentCaptor.forClass(String.class);
-        verify(mBatteryChartPreferenceController.mExpandDividerPreference)
-                .setTitle(captor.capture());
-        assertThat(captor.getValue()).isNotEqualTo("System usage for past 24 hr");
-    }
-
-    @Test
-    public void testRefreshCategoryTitle_setLast24HrIntoBothTitleTextView() {
+    public void refreshCategoryTitle_setLastFullChargeIntoBothTitleTextView() {
         mBatteryChartPreferenceController = createController();
         mBatteryChartPreferenceController.mAppListPrefGroup =
                 spy(new PreferenceCategory(mContext));
         mBatteryChartPreferenceController.mExpandDividerPreference =
                 spy(new ExpandDividerPreference(mContext));
         // Simulates select all condition.
-        mBatteryChartPreferenceController.mTrapezoidIndex =
-                BatteryChartView.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.mDailyChartIndex =
+                BatteryChartViewModel.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.mHourlyChartIndex =
+                BatteryChartViewModel.SELECTED_INDEX_ALL;
 
         mBatteryChartPreferenceController.refreshCategoryTitle();
 
@@ -572,76 +533,93 @@
         verify(mBatteryChartPreferenceController.mAppListPrefGroup)
                 .setTitle(captor.capture());
         assertThat(captor.getValue())
-                .isEqualTo("App usage for past 24 hr");
+                .isEqualTo("App usage since last full charge");
         // Verifies the title in the expandable divider.
         captor = ArgumentCaptor.forClass(String.class);
         verify(mBatteryChartPreferenceController.mExpandDividerPreference)
                 .setTitle(captor.capture());
         assertThat(captor.getValue())
-                .isEqualTo("System usage for past 24 hr");
+                .isEqualTo("System usage since last full charge");
     }
 
     @Test
-    public void testSetTimestampLabel_nullBatteryHistoryKeys_ignore() {
-        mBatteryChartPreferenceController = createController();
-        mBatteryChartPreferenceController.mBatteryHistoryKeys = null;
-        mBatteryChartPreferenceController.mBatteryChartView =
-                spy(new BatteryChartView(mContext));
-        mBatteryChartPreferenceController.setTimestampLabel();
+    public void selectedSlotText_selectAllDaysAllHours_returnNull() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
+        mBatteryChartPreferenceController.mDailyChartIndex =
+                BatteryChartViewModel.SELECTED_INDEX_ALL;
+        mBatteryChartPreferenceController.mHourlyChartIndex =
+                BatteryChartViewModel.SELECTED_INDEX_ALL;
 
-        verify(mBatteryChartPreferenceController.mBatteryChartView, never())
-                .setLatestTimestamp(anyLong());
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
     }
 
     @Test
-    public void testSetTimestampLabel_setExpectedTimestampData() {
-        mBatteryChartPreferenceController = createController();
-        mBatteryChartPreferenceController.mBatteryChartView =
-                spy(new BatteryChartView(mContext));
-        setUpBatteryHistoryKeys();
+    public void selectedSlotText_onlyOneDayDataSelectAllHours_returnNull() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.mDailyChartIndex = 0;
+        mBatteryChartPreferenceController.mHourlyChartIndex =
+                BatteryChartViewModel.SELECTED_INDEX_ALL;
 
-        mBatteryChartPreferenceController.setTimestampLabel();
-
-        verify(mBatteryChartPreferenceController.mBatteryChartView)
-                .setLatestTimestamp(1619247636826L);
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null);
     }
 
     @Test
-    public void testSetTimestampLabel_withoutValidTimestamp_setExpectedTimestampData() {
-        mBatteryChartPreferenceController = createController();
-        mBatteryChartPreferenceController.mBatteryChartView =
-                spy(new BatteryChartView(mContext));
-        mBatteryChartPreferenceController.mBatteryHistoryKeys = new long[]{0L};
+    public void selectedSlotText_selectADayAllHours_onlyDayText() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
+        mBatteryChartPreferenceController.mDailyChartIndex = 1;
+        mBatteryChartPreferenceController.mHourlyChartIndex =
+                BatteryChartViewModel.SELECTED_INDEX_ALL;
 
-        mBatteryChartPreferenceController.setTimestampLabel();
-
-        verify(mBatteryChartPreferenceController.mBatteryChartView)
-                .setLatestTimestamp(anyLong());
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("Sunday");
     }
 
     @Test
-    public void testOnSaveInstanceState_restoreSelectedIndexAndExpandState() {
-        final int expectedIndex = 1;
+    public void selectedSlotText_onlyOneDayDataSelectAnHour_onlyHourText() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(6));
+        mBatteryChartPreferenceController.mDailyChartIndex = 0;
+        mBatteryChartPreferenceController.mHourlyChartIndex = 1;
+
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
+                "10 am - 12 pm");
+    }
+
+    @Test
+    public void selectedSlotText_SelectADayAnHour_dayAndHourText() {
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60));
+        mBatteryChartPreferenceController.mDailyChartIndex = 1;
+        mBatteryChartPreferenceController.mHourlyChartIndex = 8;
+
+        assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(
+                "Sunday 4 pm - 6 pm");
+    }
+
+    @Test
+    public void onSaveInstanceState_restoreSelectedIndexAndExpandState() {
+        final int expectedDailyIndex = 1;
+        final int expectedHourlyIndex = 2;
         final boolean isExpanded = true;
         final Bundle bundle = new Bundle();
-        mBatteryChartPreferenceController.mTrapezoidIndex = expectedIndex;
+        mBatteryChartPreferenceController.mDailyChartIndex = expectedDailyIndex;
+        mBatteryChartPreferenceController.mHourlyChartIndex = expectedHourlyIndex;
         mBatteryChartPreferenceController.mIsExpanded = isExpanded;
         mBatteryChartPreferenceController.onSaveInstanceState(bundle);
         // Replaces the original controller with other values.
-        mBatteryChartPreferenceController.mTrapezoidIndex = -1;
+        mBatteryChartPreferenceController.mDailyChartIndex = -1;
+        mBatteryChartPreferenceController.mHourlyChartIndex = -1;
         mBatteryChartPreferenceController.mIsExpanded = false;
 
         mBatteryChartPreferenceController.onCreate(bundle);
-        mBatteryChartPreferenceController.setBatteryHistoryMap(
-                createBatteryHistoryMap());
+        mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(25));
 
-        assertThat(mBatteryChartPreferenceController.mTrapezoidIndex)
-                .isEqualTo(expectedIndex);
+        assertThat(mBatteryChartPreferenceController.mDailyChartIndex)
+                .isEqualTo(expectedDailyIndex);
+        assertThat(mBatteryChartPreferenceController.mHourlyChartIndex)
+                .isEqualTo(expectedHourlyIndex);
         assertThat(mBatteryChartPreferenceController.mIsExpanded).isTrue();
     }
 
     @Test
-    public void testIsValidToShowSummary_returnExpectedResult() {
+    public void isValidToShowSummary_returnExpectedResult() {
         assertThat(mBatteryChartPreferenceController
                 .isValidToShowSummary("com.google.android.apps.scone"))
                 .isTrue();
@@ -652,31 +630,42 @@
                 .isFalse();
     }
 
-    @Test
-    public void testIsValidToShowEntry_returnExpectedResult() {
-        assertThat(mBatteryChartPreferenceController
-                .isValidToShowEntry("com.google.android.apps.scone"))
-                .isTrue();
-
-        // Verifies the items which are defined in the array list.
-        assertThat(mBatteryChartPreferenceController
-                .isValidToShowEntry("com.android.gms.persistent"))
-                .isFalse();
+    private static Long generateTimestamp(int index) {
+        // "2021-04-23 07:00:00 UTC" + index hours
+        return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS;
     }
 
-    private static Map<Long, Map<String, BatteryHistEntry>> createBatteryHistoryMap() {
+    private static Map<Long, Map<String, BatteryHistEntry>> createBatteryHistoryMap(
+            int numOfHours) {
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
-        for (int index = 0; index < DESIRED_HISTORY_SIZE; index++) {
+        for (int index = 0; index < numOfHours; index++) {
             final ContentValues values = new ContentValues();
             values.put("batteryLevel", Integer.valueOf(100 - index));
+            values.put("consumePower", Integer.valueOf(100 - index));
             final BatteryHistEntry entry = new BatteryHistEntry(values);
             final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
             entryMap.put("fake_entry_key" + index, entry);
-            batteryHistoryMap.put(Long.valueOf(index + 1), entryMap);
+            batteryHistoryMap.put(generateTimestamp(index), entryMap);
         }
         return batteryHistoryMap;
     }
 
+    private Map<Integer, Map<Integer, BatteryDiffData>> createBatteryUsageMap() {
+        final int selectedAll = BatteryChartViewModel.SELECTED_INDEX_ALL;
+        return Map.of(
+                selectedAll, Map.of(
+                        selectedAll, new BatteryDiffData(
+                                Arrays.asList(mBatteryDiffEntry),
+                                Arrays.asList(mBatteryDiffEntry))),
+                0, Map.of(
+                        selectedAll, new BatteryDiffData(
+                                Arrays.asList(mBatteryDiffEntry),
+                                Arrays.asList(mBatteryDiffEntry)),
+                        0, new BatteryDiffData(
+                                Arrays.asList(mBatteryDiffEntry),
+                                Arrays.asList(mBatteryDiffEntry))));
+    }
+
     private BatteryDiffEntry createBatteryDiffEntry(
             long foregroundUsageTimeInMs, long backgroundUsageTimeInMs) {
         return new BatteryDiffEntry(
@@ -684,13 +673,6 @@
                 /*consumePower=*/ 0, mBatteryHistEntry);
     }
 
-    private void setUpBatteryHistoryKeys() {
-        mBatteryChartPreferenceController.mBatteryHistoryKeys =
-                new long[]{1619196786769L, 0L, 1619247636826L};
-        ConvertUtils.utcToLocalTimeHour(
-                mContext, /*timestamp=*/ 0, /*is24HourFormat=*/ false);
-    }
-
     private BatteryChartPreferenceController createController() {
         final BatteryChartPreferenceController controller =
                 new BatteryChartPreferenceController(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java
index a2d8ca9..8a43087 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewTest.java
@@ -26,6 +26,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
 import android.os.LocaleList;
+import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -41,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 @RunWith(RobolectricTestRunner.class)
@@ -55,6 +57,8 @@
     private AccessibilityServiceInfo mMockAccessibilityServiceInfo;
     @Mock
     private AccessibilityManager mMockAccessibilityManager;
+    @Mock
+    private View mMockView;
 
     @Before
     public void setUp() {
@@ -74,13 +78,13 @@
     }
 
     @Test
-    public void testIsAccessibilityEnabled_disable_returnFalse() {
+    public void isAccessibilityEnabled_disable_returnFalse() {
         doReturn(false).when(mMockAccessibilityManager).isEnabled();
         assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse();
     }
 
     @Test
-    public void testIsAccessibilityEnabled_emptyInfo_returnFalse() {
+    public void isAccessibilityEnabled_emptyInfo_returnFalse() {
         doReturn(true).when(mMockAccessibilityManager).isEnabled();
         doReturn(new ArrayList<AccessibilityServiceInfo>())
                 .when(mMockAccessibilityManager)
@@ -90,68 +94,70 @@
     }
 
     @Test
-    public void testIsAccessibilityEnabled_validServiceId_returnTrue() {
+    public void isAccessibilityEnabled_validServiceId_returnTrue() {
         doReturn(true).when(mMockAccessibilityManager).isEnabled();
         assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isTrue();
     }
 
     @Test
-    public void testSetSelectedIndex_invokesCallback() {
+    public void onClick_invokesCallback() {
+        final int originalSelectedIndex = 2;
+        BatteryChartViewModel batteryChartViewModel = new BatteryChartViewModel(
+                List.of(90, 80, 70, 60), List.of("", "", "", ""),
+                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS);
+        batteryChartViewModel.setSelectedIndex(originalSelectedIndex);
+        mBatteryChartView.setViewModel(batteryChartViewModel);
+        for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) {
+            mBatteryChartView.mTrapezoidSlots[i] = new BatteryChartView.TrapezoidSlot();
+            mBatteryChartView.mTrapezoidSlots[i].mLeft = i;
+            mBatteryChartView.mTrapezoidSlots[i].mRight = i + 0.5f;
+        }
         final int[] selectedIndex = new int[1];
-        final int expectedIndex = 2;
-        mBatteryChartView.mSelectedIndex = 1;
         mBatteryChartView.setOnSelectListener(
                 trapezoidIndex -> {
                     selectedIndex[0] = trapezoidIndex;
                 });
 
-        mBatteryChartView.setSelectedIndex(expectedIndex);
+        // Verify onClick() a different index 1.
+        mBatteryChartView.mTouchUpEventX = 1;
+        selectedIndex[0] = Integer.MIN_VALUE;
+        mBatteryChartView.onClick(mMockView);
+        assertThat(selectedIndex[0]).isEqualTo(1);
 
-        assertThat(mBatteryChartView.mSelectedIndex)
-                .isEqualTo(expectedIndex);
-        assertThat(selectedIndex[0]).isEqualTo(expectedIndex);
+        // Verify onClick() the same index 2.
+        mBatteryChartView.mTouchUpEventX = 2;
+        selectedIndex[0] = Integer.MIN_VALUE;
+        mBatteryChartView.onClick(mMockView);
+        assertThat(selectedIndex[0]).isEqualTo(BatteryChartViewModel.SELECTED_INDEX_ALL);
     }
 
     @Test
-    public void testSetSelectedIndex_sameIndex_notInvokesCallback() {
-        final int[] selectedIndex = new int[1];
-        final int expectedIndex = 1;
-        mBatteryChartView.mSelectedIndex = expectedIndex;
-        mBatteryChartView.setOnSelectListener(
-                trapezoidIndex -> {
-                    selectedIndex[0] = trapezoidIndex;
-                });
-
-        mBatteryChartView.setSelectedIndex(expectedIndex);
-
-        assertThat(selectedIndex[0]).isNotEqualTo(expectedIndex);
-    }
-
-    @Test
-    public void testClickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
+    public void clickable_isChartGraphSlotsEnabledIsFalse_notClickable() {
         mBatteryChartView.setClickableForce(true);
         when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
                 .thenReturn(false);
 
         mBatteryChartView.onAttachedToWindow();
+
         assertThat(mBatteryChartView.isClickable()).isFalse();
         assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
     }
 
     @Test
-    public void testClickable_accessibilityIsDisabled_clickable() {
+    public void clickable_accessibilityIsDisabled_clickable() {
         mBatteryChartView.setClickableForce(true);
         when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
                 .thenReturn(true);
         doReturn(false).when(mMockAccessibilityManager).isEnabled();
 
         mBatteryChartView.onAttachedToWindow();
+
         assertThat(mBatteryChartView.isClickable()).isTrue();
         assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
     }
 
     @Test
-    public void testClickable_accessibilityIsEnabledWithoutValidId_clickable() {
+    public void clickable_accessibilityIsEnabledWithoutValidId_clickable() {
         mBatteryChartView.setClickableForce(true);
         when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
                 .thenReturn(true);
@@ -161,30 +167,34 @@
                 .getEnabledAccessibilityServiceList(anyInt());
 
         mBatteryChartView.onAttachedToWindow();
+
         assertThat(mBatteryChartView.isClickable()).isTrue();
         assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNull();
     }
 
     @Test
-    public void testClickable_accessibilityIsEnabledWithValidId_notClickable() {
+    public void clickable_accessibilityIsEnabledWithValidId_notClickable() {
         mBatteryChartView.setClickableForce(true);
         when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
                 .thenReturn(true);
         doReturn(true).when(mMockAccessibilityManager).isEnabled();
 
         mBatteryChartView.onAttachedToWindow();
+
         assertThat(mBatteryChartView.isClickable()).isFalse();
         assertThat(mBatteryChartView.mTrapezoidCurvePaint).isNotNull();
     }
 
     @Test
-    public void testClickable_restoreFromNonClickableState() {
-        final int[] levels = new int[13];
-        for (int index = 0; index < levels.length; index++) {
-            levels[index] = index + 1;
+    public void clickable_restoreFromNonClickableState() {
+        final List<Integer> levels = new ArrayList<Integer>();
+        final List<String> texts = new ArrayList<String>();
+        for (int index = 0; index < 13; index++) {
+            levels.add(index + 1);
+            texts.add("");
         }
-        mBatteryChartView.setTrapezoidCount(12);
-        mBatteryChartView.setLevels(levels);
+        mBatteryChartView.setViewModel(new BatteryChartViewModel(levels, texts,
+                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS));
         mBatteryChartView.setClickableForce(true);
         when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext))
                 .thenReturn(true);
@@ -201,14 +211,14 @@
     }
 
     @Test
-    public void testOnAttachedToWindow_addAccessibilityStateChangeListener() {
+    public void onAttachedToWindow_addAccessibilityStateChangeListener() {
         mBatteryChartView.onAttachedToWindow();
         verify(mMockAccessibilityManager)
                 .addAccessibilityStateChangeListener(mBatteryChartView);
     }
 
     @Test
-    public void testOnDetachedFromWindow_removeAccessibilityStateChangeListener() {
+    public void onDetachedFromWindow_removeAccessibilityStateChangeListener() {
         mBatteryChartView.onAttachedToWindow();
         mBatteryChartView.mHandler.postDelayed(
                 mBatteryChartView.mUpdateClickableStateRun, 1000);
@@ -223,7 +233,7 @@
     }
 
     @Test
-    public void testOnAccessibilityStateChanged_postUpdateStateRunnable() {
+    public void onAccessibilityStateChanged_postUpdateStateRunnable() {
         mBatteryChartView.mHandler = spy(mBatteryChartView.mHandler);
         mBatteryChartView.onAccessibilityStateChanged(/*enabled=*/ true);
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoaderTest.java
index 98a44de..5717857 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistoryLoaderTest.java
@@ -53,7 +53,7 @@
     public void testLoadIBackground_returnsMapFromPowerFeatureProvider() {
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
         doReturn(batteryHistoryMap).when(mFeatureFactory.powerUsageFeatureProvider)
-                .getBatteryHistory(mContext);
+                .getBatteryHistorySinceLastFullCharge(mContext);
 
         assertThat(mBatteryHistoryLoader.loadInBackground())
                 .isSameInstanceAs(batteryHistoryMap);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
index c1f9815..c9bac03 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -26,6 +26,7 @@
 import android.os.BatteryUsageStats;
 import android.os.LocaleList;
 import android.os.UserHandle;
+import android.text.format.DateUtils;
 
 import com.android.settings.fuelgauge.BatteryUtils;
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
@@ -39,8 +40,8 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -173,7 +174,8 @@
     public void getIndexedUsageMap_returnsExpectedResult() {
         // Creates the fake testing data.
         final int timeSlotSize = 2;
-        final long[] batteryHistoryKeys = new long[]{101L, 102L, 103L, 104L, 105L};
+        final long[] batteryHistoryKeys = new long[]{generateTimestamp(0), generateTimestamp(1),
+                generateTimestamp(2), generateTimestamp(3), generateTimestamp(4)};
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
                 new HashMap<>();
         final BatteryHistEntry fakeEntry = createBatteryHistEntry(
@@ -270,11 +272,11 @@
         for (int index = 0; index < remainingSize; index++) {
             batteryHistoryMap.put(105L + index + 1, new HashMap<>());
         }
-        when(mPowerUsageFeatureProvider.getBatteryHistory(mContext))
+        when(mPowerUsageFeatureProvider.getBatteryHistorySinceLastFullCharge(mContext))
                 .thenReturn(batteryHistoryMap);
 
         final List<BatteryDiffEntry> batteryDiffEntryList =
-                BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext);
+                BatteryChartPreferenceController.getAppBatteryUsageData(mContext);
 
         assertThat(batteryDiffEntryList).isNotEmpty();
         final BatteryDiffEntry resultEntry = batteryDiffEntryList.get(0);
@@ -472,4 +474,9 @@
         assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
         assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
     }
+
+    private static Long generateTimestamp(int index) {
+        // "2021-04-23 07:00:00 UTC" + index hours
+        return 1619247600000L + index * DateUtils.HOUR_IN_MILLIS;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
new file mode 100644
index 0000000..883b0e7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -0,0 +1,950 @@
+/*
+ * Copyright (C) 2022 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 static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+@RunWith(RobolectricTestRunner.class)
+public class DataProcessorTest {
+    private static final String FAKE_ENTRY_KEY = "fake_entry_key";
+
+    private Context mContext;
+
+    private FakeFeatureFactory mFeatureFactory;
+    private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
+
+        mContext = spy(RuntimeEnvironment.application);
+        mFeatureFactory = FakeFeatureFactory.setupForTest();
+        mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
+    }
+
+    @Test
+    public void getBatteryLevelData_emptyHistoryMap_returnNull() {
+        assertThat(DataProcessor.getBatteryLevelData(
+                mContext,
+                /*handler=*/ null,
+                /*batteryHistoryMap=*/ null,
+                /*asyncResponseDelegate=*/ null))
+                .isNull();
+        assertThat(DataProcessor.getBatteryLevelData(
+                mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null))
+                .isNull();
+    }
+
+    @Test
+    public void getBatteryLevelData_notEnoughData_returnNull() {
+        // The timestamps are within 1 hour.
+        final long[] timestamps = {1000000L, 2000000L, 3000000L};
+        final int[] levels = {100, 99, 98};
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+                createHistoryMap(timestamps, levels);
+
+        assertThat(DataProcessor.getBatteryLevelData(
+                mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null))
+                .isNull();
+    }
+
+    @Test
+    public void getBatteryLevelData_returnExpectedResult() {
+        // Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00, 2022-01-01 02:00:00
+        final long[] timestamps = {1640966400000L, 1640970000000L, 1640973600000L};
+        final int[] levels = {100, 99, 98};
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+                createHistoryMap(timestamps, levels);
+
+        final BatteryLevelData resultData =
+                DataProcessor.getBatteryLevelData(
+                        mContext,
+                        /*handler=*/ null,
+                        batteryHistoryMap,
+                        /*asyncResponseDelegate=*/ null);
+
+        final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[2]);
+        final List<Integer> expectedDailyLevels = List.of(levels[0], levels[2]);
+        final List<List<Long>> expectedHourlyTimestamps = List.of(expectedDailyTimestamps);
+        final List<List<Integer>> expectedHourlyLevels = List.of(expectedDailyLevels);
+        verifyExpectedBatteryLevelData(
+                resultData,
+                expectedDailyTimestamps,
+                expectedDailyLevels,
+                expectedHourlyTimestamps,
+                expectedHourlyLevels);
+    }
+
+    @Test
+    public void getHistoryMapWithExpectedTimestamps_emptyHistoryMap_returnEmptyMap() {
+        assertThat(DataProcessor
+                .getHistoryMapWithExpectedTimestamps(mContext, new HashMap<>()))
+                .isEmpty();
+    }
+
+    @Test
+    public void getHistoryMapWithExpectedTimestamps_returnExpectedMap() {
+        // Timezone GMT+8
+        final long[] timestamps = {
+                1640966700000L, // 2022-01-01 00:05:00
+                1640970180000L, // 2022-01-01 01:03:00
+                1640973840000L, // 2022-01-01 02:04:00
+                1640978100000L, // 2022-01-01 03:15:00
+                1640981400000L  // 2022-01-01 04:10:00
+        };
+        final int[] levels = {100, 94, 90, 82, 50};
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+                createHistoryMap(timestamps, levels);
+
+        final Map<Long, Map<String, BatteryHistEntry>> resultMap =
+                DataProcessor.getHistoryMapWithExpectedTimestamps(mContext, batteryHistoryMap);
+
+        // Timezone GMT+8
+        final long[] expectedTimestamps = {
+                1640966400000L, // 2022-01-01 00:00: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
+        };
+        final int[] expectedLevels = {100, 94, 90, 84, 56};
+        assertThat(resultMap).hasSize(expectedLevels.length);
+        for (int index = 0; index < expectedLevels.length; index++) {
+            assertThat(resultMap.get(expectedTimestamps[index]).get(FAKE_ENTRY_KEY).mBatteryLevel)
+                    .isEqualTo(expectedLevels[index]);
+        }
+    }
+
+    @Test
+    public void getLevelDataThroughProcessedHistoryMap_notEnoughData_returnNull() {
+        final long[] timestamps = {100L};
+        final int[] levels = {100};
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+                createHistoryMap(timestamps, levels);
+
+        assertThat(
+                DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap))
+                .isNull();
+    }
+
+    @Test
+    public void getLevelDataThroughProcessedHistoryMap_OneDayData_returnExpectedResult() {
+        // Timezone GMT+8
+        final long[] timestamps = {
+                1640966400000L, // 2022-01-01 00:00: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
+        };
+        final int[] levels = {100, 94, 90, 82, 50};
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+                createHistoryMap(timestamps, levels);
+
+        final BatteryLevelData resultData =
+                DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap);
+
+        final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[4]);
+        final List<Integer> expectedDailyLevels = List.of(levels[0], levels[4]);
+        final List<List<Long>> expectedHourlyTimestamps = List.of(
+                List.of(timestamps[0], timestamps[2], timestamps[4])
+        );
+        final List<List<Integer>> expectedHourlyLevels = List.of(
+                List.of(levels[0], levels[2], levels[4])
+        );
+        verifyExpectedBatteryLevelData(
+                resultData,
+                expectedDailyTimestamps,
+                expectedDailyLevels,
+                expectedHourlyTimestamps,
+                expectedHourlyLevels);
+    }
+
+    @Test
+    public void getLevelDataThroughProcessedHistoryMap_MultipleDaysData_returnExpectedResult() {
+        // Timezone GMT+8
+        final long[] timestamps = {
+                1641038400000L, // 2022-01-01 20:00:00
+                1641060000000L, // 2022-01-02 02:00:00
+                1641067200000L, // 2022-01-02 04:00:00
+                1641081600000L, // 2022-01-02 08:00:00
+        };
+        final int[] levels = {100, 94, 90, 82};
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
+                createHistoryMap(timestamps, levels);
+
+        final BatteryLevelData resultData =
+                DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap);
+
+        final List<Long> expectedDailyTimestamps = List.of(
+                1641038400000L, // 2022-01-01 20:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641081600000L  // 2022-01-02 08:00:00
+        );
+        final List<Integer> expectedDailyLevels = new ArrayList<>();
+        expectedDailyLevels.add(100);
+        expectedDailyLevels.add(null);
+        expectedDailyLevels.add(82);
+        final List<List<Long>> expectedHourlyTimestamps = List.of(
+                List.of(
+                        1641038400000L, // 2022-01-01 20:00:00
+                        1641045600000L, // 2022-01-01 22:00:00
+                        1641052800000L  // 2022-01-02 00:00:00
+                ),
+                List.of(
+                        1641052800000L, // 2022-01-02 00:00:00
+                        1641060000000L, // 2022-01-02 02:00:00
+                        1641067200000L, // 2022-01-02 04:00:00
+                        1641074400000L, // 2022-01-02 06:00:00
+                        1641081600000L  // 2022-01-02 08:00:00
+                )
+        );
+        final List<Integer> expectedHourlyLevels1 = new ArrayList<>();
+        expectedHourlyLevels1.add(100);
+        expectedHourlyLevels1.add(null);
+        expectedHourlyLevels1.add(null);
+        final List<Integer> expectedHourlyLevels2 = new ArrayList<>();
+        expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(94);
+        expectedHourlyLevels2.add(90);
+        expectedHourlyLevels2.add(null);
+        expectedHourlyLevels2.add(82);
+        final List<List<Integer>> expectedHourlyLevels = List.of(
+                expectedHourlyLevels1,
+                expectedHourlyLevels2
+        );
+        verifyExpectedBatteryLevelData(
+                resultData,
+                expectedDailyTimestamps,
+                expectedDailyLevels,
+                expectedHourlyTimestamps,
+                expectedHourlyLevels);
+    }
+
+    @Test
+    public void getTimestampSlots_emptyRawList_returnEmptyList() {
+        final List<Long> resultList =
+                DataProcessor.getTimestampSlots(new ArrayList<>());
+        assertThat(resultList).isEmpty();
+    }
+
+    @Test
+    public void getTimestampSlots_startWithEvenHour_returnExpectedResult() {
+        final Calendar startCalendar = Calendar.getInstance();
+        startCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
+        final Calendar endCalendar = Calendar.getInstance();
+        endCalendar.set(2022, 6, 5, 22, 30, 50); // 2022-07-05 22:30:50
+
+        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, 5, 22, 0, 0); // 2022-07-05 22:00:00
+        verifyExpectedTimestampSlots(
+                startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
+    }
+
+    @Test
+    public void getTimestampSlots_startWithOddHour_returnExpectedResult() {
+        final Calendar startCalendar = Calendar.getInstance();
+        startCalendar.set(2022, 6, 5, 5, 0, 50); // 2022-07-05 05:00:50
+        final Calendar endCalendar = Calendar.getInstance();
+        endCalendar.set(2022, 6, 6, 21, 00, 50); // 2022-07-06 21:00:50
+
+        final Calendar expectedStartCalendar = Calendar.getInstance();
+        expectedStartCalendar.set(2022, 6, 5, 6, 00, 00); // 2022-07-05 06:00:00
+        final Calendar expectedEndCalendar = Calendar.getInstance();
+        expectedEndCalendar.set(2022, 6, 6, 20, 00, 00); // 2022-07-06 20:00:00
+        verifyExpectedTimestampSlots(
+                startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar);
+    }
+
+    @Test
+    public void getDailyTimestamps_notEnoughData_returnEmptyList() {
+        assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty();
+        assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty();
+    }
+
+    @Test
+    public void getDailyTimestamps_OneDayData_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1640966400000L, // 2022-01-01 00:00: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
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                1640966400000L, // 2022-01-01 00:00:00
+                1640980800000L  // 2022-01-01 04:00:00
+        );
+        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void getDailyTimestamps_MultipleDaysData_returnExpectedList() {
+        // Timezone GMT+8
+        final List<Long> timestamps = List.of(
+                1640988000000L, // 2022-01-01 06:00:00
+                1641060000000L, // 2022-01-02 02:00:00
+                1641160800000L, // 2022-01-03 06:00:00
+                1641254400000L  // 2022-01-04 08:00:00
+        );
+
+        final List<Long> expectedTimestamps = List.of(
+                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
+                1641254400000L  // 2022-01-04 08:00:00
+        );
+        assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps);
+    }
+
+    @Test
+    public void isFromFullCharge_emptyData_returnFalse() {
+        assertThat(DataProcessor.isFromFullCharge(null)).isFalse();
+        assertThat(DataProcessor.isFromFullCharge(new HashMap<>())).isFalse();
+    }
+
+    @Test
+    public void isFromFullCharge_notChargedData_returnFalse() {
+        final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        final ContentValues values = new ContentValues();
+        values.put("batteryLevel", 98);
+        final BatteryHistEntry entry = new BatteryHistEntry(values);
+        entryMap.put(FAKE_ENTRY_KEY, entry);
+
+        assertThat(DataProcessor.isFromFullCharge(entryMap)).isFalse();
+    }
+
+    @Test
+    public void isFromFullCharge_chargedData_returnTrue() {
+        final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        final ContentValues values = new ContentValues();
+        values.put("batteryLevel", 100);
+        final BatteryHistEntry entry = new BatteryHistEntry(values);
+        entryMap.put(FAKE_ENTRY_KEY, entry);
+
+        assertThat(DataProcessor.isFromFullCharge(entryMap)).isTrue();
+    }
+
+    @Test
+    public void findNearestTimestamp_returnExpectedResult() {
+        long[] results = DataProcessor.findNearestTimestamp(
+                Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 15L);
+        assertThat(results).isEqualTo(new long[] {10L, 20L});
+
+        results = DataProcessor.findNearestTimestamp(
+                Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 10L);
+        assertThat(results).isEqualTo(new long[] {10L, 10L});
+
+        results = DataProcessor.findNearestTimestamp(
+                Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 5L);
+        assertThat(results).isEqualTo(new long[] {0L, 10L});
+
+        results = DataProcessor.findNearestTimestamp(
+                Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 50L);
+        assertThat(results).isEqualTo(new long[] {40L, 0L});
+    }
+
+    @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<>();
+        hourlyBatteryLevelsPerDay.add(
+                new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
+
+        assertThat(DataProcessor.getBatteryUsageMap(
+                mContext, hourlyBatteryLevelsPerDay, new HashMap<>())).isNull();
+    }
+
+    @Test
+    public void getBatteryUsageMap_returnsExpectedResult() {
+        final long[] batteryHistoryKeys = new long[]{
+                1641045600000L, // 2022-01-01 22:00:00
+                1641049200000L, // 2022-01-01 23:00:00
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L, // 2022-01-02 01:00:00
+                1641060000000L, // 2022-01-02 02:00:00
+        };
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
+        final int currentUserId = mContext.getUserId();
+        final BatteryHistEntry fakeEntry = createBatteryHistEntry(
+                ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0, /*uid=*/ 0L,
+                currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
+                /*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L);
+        // Adds the index = 0 data.
+        Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        BatteryHistEntry entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 5.0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
+        // Adds the index = 1 data.
+        entryMap = new HashMap<>();
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
+        // Adds the index = 2 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 20.0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 15L,
+                25L);
+        entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
+        // Adds the index = 3 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 40.0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 25L,
+                /*backgroundUsageTimeInMs=*/ 35L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 3L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*foregroundUsageTimeInMs=*/ 40L,
+                /*backgroundUsageTimeInMs=*/ 50L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package3", "label3", /*consumePower=*/ 15.0, /*uid=*/ 4L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 5L,
+                /*backgroundUsageTimeInMs=*/ 5L);
+        entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(batteryHistoryKeys[3], entryMap);
+        // Adds the index = 4 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 40.0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
+                /*backgroundUsageTimeInMs=*/ 40L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 20.0, /*uid=*/ 3L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*foregroundUsageTimeInMs=*/ 50L,
+                /*backgroundUsageTimeInMs=*/ 60L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package3", "label3", /*consumePower=*/ 40.0, /*uid=*/ 4L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 5L,
+                /*backgroundUsageTimeInMs=*/ 5L);
+        entryMap.put(entry.getKey(), entry);
+        entryMap.put(fakeEntry.getKey(), fakeEntry);
+        batteryHistoryMap.put(batteryHistoryKeys[4], entryMap);
+        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
+                new ArrayList<>();
+        // Adds the day 1 data.
+        List<Long> timestamps =
+                List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
+        final List<Integer> levels = List.of(100, 100);
+        hourlyBatteryLevelsPerDay.add(
+                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+        // Adds the day 2 data.
+        timestamps = List.of(batteryHistoryKeys[2], batteryHistoryKeys[4]);
+        hourlyBatteryLevelsPerDay.add(
+                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+
+        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
+                DataProcessor.getBatteryUsageMap(
+                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
+
+        BatteryDiffData resultDiffData =
+                resultMap
+                        .get(DataProcessor.SELECTED_INDEX_ALL)
+                        .get(DataProcessor.SELECTED_INDEX_ALL);
+        assertBatteryDiffEntry(
+                resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 40.0,
+                /*foregroundUsageTimeInMs=*/ 30, /*backgroundUsageTimeInMs=*/ 40);
+        assertBatteryDiffEntry(
+                resultDiffData.getAppDiffEntryList().get(1), currentUserId, /*uid=*/ 4L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 40.0,
+                /*foregroundUsageTimeInMs=*/ 5, /*backgroundUsageTimeInMs=*/ 5);
+        assertBatteryDiffEntry(
+                resultDiffData.getSystemDiffEntryList().get(0), currentUserId, /*uid=*/ 3L,
+                ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 20.0,
+                /*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60);
+        resultDiffData = resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL);
+        assertBatteryDiffEntry(
+                resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 100.0,
+                /*foregroundUsageTimeInMs=*/ 15, /*backgroundUsageTimeInMs=*/ 25);
+        resultDiffData = resultMap.get(1).get(DataProcessor.SELECTED_INDEX_ALL);
+        assertBatteryDiffEntry(
+                resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 4L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
+                /*foregroundUsageTimeInMs=*/ 5, /*backgroundUsageTimeInMs=*/ 5);
+        assertBatteryDiffEntry(
+                resultDiffData.getAppDiffEntryList().get(1), currentUserId, /*uid=*/ 2L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 25.0,
+                /*foregroundUsageTimeInMs=*/ 15, /*backgroundUsageTimeInMs=*/ 15);
+        assertBatteryDiffEntry(
+                resultDiffData.getSystemDiffEntryList().get(0), currentUserId, /*uid=*/ 3L,
+                ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 25.0,
+                /*foregroundUsageTimeInMs=*/ 50, /*backgroundUsageTimeInMs=*/ 60);
+    }
+
+    @Test
+    public void getBatteryUsageMap_multipleUsers_returnsExpectedResult() {
+        final long[] batteryHistoryKeys = new long[]{
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L, // 2022-01-02 01:00:00
+                1641060000000L  // 2022-01-02 02:00:00
+        };
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
+        final int currentUserId = mContext.getUserId();
+        // Adds the index = 0 data.
+        Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        BatteryHistEntry entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 5.0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId + 1,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 5.0, /*uid=*/ 3L, currentUserId + 2,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
+                /*backgroundUsageTimeInMs=*/ 30L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
+        // Adds the index = 1 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 15.0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
+                /*backgroundUsageTimeInMs=*/ 30L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 30.0, /*uid=*/ 2L, currentUserId + 1,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 15.0, /*uid=*/ 3L, currentUserId + 2,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
+                /*backgroundUsageTimeInMs=*/ 30L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
+        // Adds the index = 2 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 25.0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
+                /*backgroundUsageTimeInMs=*/ 30L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 50.0, /*uid=*/ 2L, currentUserId + 1,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 20L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 25.0, /*uid=*/ 3L, currentUserId + 2,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 30L,
+                /*backgroundUsageTimeInMs=*/ 30L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
+        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
+                new ArrayList<>();
+        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
+        final List<Integer> levels = List.of(100, 100);
+        hourlyBatteryLevelsPerDay.add(
+                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+
+        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
+                DataProcessor.getBatteryUsageMap(
+                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
+
+        final BatteryDiffData resultDiffData =
+                resultMap
+                        .get(DataProcessor.SELECTED_INDEX_ALL)
+                        .get(DataProcessor.SELECTED_INDEX_ALL);
+        assertBatteryDiffEntry(
+                resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 1L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 25.0,
+                /*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 10);
+        assertBatteryDiffEntry(
+                resultDiffData.getSystemDiffEntryList().get(0), BatteryUtils.UID_OTHER_USERS,
+                /*uid=*/ BatteryUtils.UID_OTHER_USERS, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
+                /*consumePercentage=*/ 75.0, /*foregroundUsageTimeInMs=*/ 0,
+                /*backgroundUsageTimeInMs=*/ 0);
+        assertThat(resultMap.get(0).get(0)).isNotNull();
+        assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
+    }
+
+    @Test
+    public void getBatteryUsageMap_usageTimeExceed_returnsExpectedResult() {
+        final long[] batteryHistoryKeys = new long[]{
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L, // 2022-01-02 01:00:00
+                1641060000000L  // 2022-01-02 02:00:00
+        };
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
+        final int currentUserId = mContext.getUserId();
+        // Adds the index = 0 data.
+        Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        BatteryHistEntry entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
+        // Adds the index = 1 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
+        // Adds the index = 2 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 500.0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 3600000L,
+                /*backgroundUsageTimeInMs=*/ 7200000L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
+        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
+                new ArrayList<>();
+        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
+        final List<Integer> levels = List.of(100, 100);
+        hourlyBatteryLevelsPerDay.add(
+                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+
+        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
+                DataProcessor.getBatteryUsageMap(
+                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
+
+        final BatteryDiffData resultDiffData =
+                resultMap
+                        .get(DataProcessor.SELECTED_INDEX_ALL)
+                        .get(DataProcessor.SELECTED_INDEX_ALL);
+        // Verifies the clipped usage time.
+        final float ratio = (float) (7200) / (float) (3600 + 7200);
+        final BatteryDiffEntry resultEntry = resultDiffData.getAppDiffEntryList().get(0);
+        assertThat(resultEntry.mForegroundUsageTimeInMs)
+                .isEqualTo(Math.round(entry.mForegroundUsageTimeInMs * ratio));
+        assertThat(resultEntry.mBackgroundUsageTimeInMs)
+                .isEqualTo(Math.round(entry.mBackgroundUsageTimeInMs * ratio));
+        assertThat(resultEntry.mConsumePower)
+                .isEqualTo(entry.mConsumePower * ratio);
+        assertThat(resultMap.get(0).get(0)).isNotNull();
+        assertThat(resultMap.get(0).get(DataProcessor.SELECTED_INDEX_ALL)).isNotNull();
+    }
+
+    @Test
+    public void getBatteryUsageMap_hideApplicationEntries_returnsExpectedResult() {
+        final long[] batteryHistoryKeys = new long[]{
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L, // 2022-01-02 01:00:00
+                1641060000000L  // 2022-01-02 02:00:00
+        };
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
+        final int currentUserId = mContext.getUserId();
+        // Adds the index = 0 data.
+        Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        BatteryHistEntry entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
+        // Adds the index = 1 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
+        // Adds the index = 2 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
+        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
+                new ArrayList<>();
+        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
+        final List<Integer> levels = List.of(100, 100);
+        hourlyBatteryLevelsPerDay.add(
+                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+        when(mPowerUsageFeatureProvider.getHideApplicationEntries(mContext))
+                .thenReturn(new CharSequence[]{"package1"});
+
+        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
+                DataProcessor.getBatteryUsageMap(
+                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
+
+        final BatteryDiffData resultDiffData =
+                resultMap
+                        .get(DataProcessor.SELECTED_INDEX_ALL)
+                        .get(DataProcessor.SELECTED_INDEX_ALL);
+        assertBatteryDiffEntry(
+                resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
+                /*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 20);
+    }
+
+    @Test
+    public void getBatteryUsageMap_hideBackgroundUsageTime_returnsExpectedResult() {
+        final long[] batteryHistoryKeys = new long[]{
+                1641052800000L, // 2022-01-02 00:00:00
+                1641056400000L, // 2022-01-02 01:00:00
+                1641060000000L  // 2022-01-02 02:00:00
+        };
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
+        final int currentUserId = mContext.getUserId();
+        // Adds the index = 0 data.
+        Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+        BatteryHistEntry entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[0], entryMap);
+        // Adds the index = 1 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 0L,
+                /*backgroundUsageTimeInMs=*/ 0L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[1], entryMap);
+        // Adds the index = 2 data.
+        entryMap = new HashMap<>();
+        entry = createBatteryHistEntry(
+                "package1", "label1", /*consumePower=*/ 10.0, /*uid=*/ 1L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        entry = createBatteryHistEntry(
+                "package2", "label2", /*consumePower=*/ 10.0, /*uid=*/ 2L, currentUserId,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*foregroundUsageTimeInMs=*/ 10L,
+                /*backgroundUsageTimeInMs=*/ 20L);
+        entryMap.put(entry.getKey(), entry);
+        batteryHistoryMap.put(batteryHistoryKeys[2], entryMap);
+        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
+                new ArrayList<>();
+        List<Long> timestamps = List.of(batteryHistoryKeys[0], batteryHistoryKeys[2]);
+        final List<Integer> levels = List.of(100, 100);
+        hourlyBatteryLevelsPerDay.add(
+                new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
+        when(mPowerUsageFeatureProvider.getHideBackgroundUsageTimeSet(mContext))
+                .thenReturn(new HashSet(Arrays.asList((CharSequence) "package2")));
+
+        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap =
+                DataProcessor.getBatteryUsageMap(
+                        mContext, hourlyBatteryLevelsPerDay, batteryHistoryMap);
+
+        final BatteryDiffData resultDiffData =
+                resultMap
+                        .get(DataProcessor.SELECTED_INDEX_ALL)
+                        .get(DataProcessor.SELECTED_INDEX_ALL);
+        BatteryDiffEntry resultEntry = resultDiffData.getAppDiffEntryList().get(0);
+        assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(20);
+        resultEntry = resultDiffData.getAppDiffEntryList().get(1);
+        assertThat(resultEntry.mBackgroundUsageTimeInMs).isEqualTo(0);
+    }
+
+    private static Map<Long, Map<String, BatteryHistEntry>> createHistoryMap(
+            final long[] timestamps, final int[] levels) {
+        final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();
+        for (int index = 0; index < timestamps.length; index++) {
+            final Map<String, BatteryHistEntry> entryMap = new HashMap<>();
+            final ContentValues values = new ContentValues();
+            values.put(BatteryHistEntry.KEY_BATTERY_LEVEL, levels[index]);
+            final BatteryHistEntry entry = new BatteryHistEntry(values);
+            entryMap.put(FAKE_ENTRY_KEY, entry);
+            batteryHistoryMap.put(timestamps[index], entryMap);
+        }
+        return batteryHistoryMap;
+    }
+
+    private static BatteryHistEntry createBatteryHistEntry(
+            final String packageName, final String appLabel, final double consumePower,
+            final long uid, final long userId, final int consumerType,
+            final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs) {
+        // Only insert required fields.
+        final ContentValues values = new ContentValues();
+        values.put(BatteryHistEntry.KEY_PACKAGE_NAME, packageName);
+        values.put(BatteryHistEntry.KEY_APP_LABEL, appLabel);
+        values.put(BatteryHistEntry.KEY_UID, uid);
+        values.put(BatteryHistEntry.KEY_USER_ID, userId);
+        values.put(BatteryHistEntry.KEY_CONSUMER_TYPE, consumerType);
+        values.put(BatteryHistEntry.KEY_CONSUME_POWER, consumePower);
+        values.put(BatteryHistEntry.KEY_FOREGROUND_USAGE_TIME, foregroundUsageTimeInMs);
+        values.put(BatteryHistEntry.KEY_BACKGROUND_USAGE_TIME, backgroundUsageTimeInMs);
+        return new BatteryHistEntry(values);
+    }
+
+    private static void verifyExpectedBatteryLevelData(
+            final BatteryLevelData resultData,
+            final List<Long> expectedDailyTimestamps,
+            final List<Integer> expectedDailyLevels,
+            final List<List<Long>> expectedHourlyTimestamps,
+            final List<List<Integer>> expectedHourlyLevels) {
+        final BatteryLevelData.PeriodBatteryLevelData dailyResultData =
+                resultData.getDailyBatteryLevels();
+        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData =
+                resultData.getHourlyBatteryLevelsPerDay();
+        verifyExpectedDailyBatteryLevelData(
+                dailyResultData, expectedDailyTimestamps, expectedDailyLevels);
+        verifyExpectedHourlyBatteryLevelData(
+                hourlyResultData, expectedHourlyTimestamps, expectedHourlyLevels);
+    }
+
+    private static void verifyExpectedDailyBatteryLevelData(
+            final BatteryLevelData.PeriodBatteryLevelData dailyResultData,
+            final List<Long> expectedDailyTimestamps,
+            final List<Integer> expectedDailyLevels) {
+        assertThat(dailyResultData.getTimestamps()).isEqualTo(expectedDailyTimestamps);
+        assertThat(dailyResultData.getLevels()).isEqualTo(expectedDailyLevels);
+    }
+
+    private static void verifyExpectedHourlyBatteryLevelData(
+            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData,
+            final List<List<Long>> expectedHourlyTimestamps,
+            final List<List<Integer>> expectedHourlyLevels) {
+        final int expectedHourlySize = expectedHourlyTimestamps.size();
+        assertThat(hourlyResultData).hasSize(expectedHourlySize);
+        for (int dailyIndex = 0; dailyIndex < expectedHourlySize; dailyIndex++) {
+            assertThat(hourlyResultData.get(dailyIndex).getTimestamps())
+                    .isEqualTo(expectedHourlyTimestamps.get(dailyIndex));
+            assertThat(hourlyResultData.get(dailyIndex).getLevels())
+                    .isEqualTo(expectedHourlyLevels.get(dailyIndex));
+        }
+    }
+
+    private static void verifyExpectedTimestampSlots(
+            final Calendar start,
+            final Calendar end,
+            final Calendar expectedStart,
+            final Calendar expectedEnd) {
+        expectedStart.set(Calendar.MILLISECOND, 0);
+        expectedEnd.set(Calendar.MILLISECOND, 0);
+        final ArrayList<Long> timestampSlots = new ArrayList<>();
+        timestampSlots.add(start.getTimeInMillis());
+        timestampSlots.add(end.getTimeInMillis());
+        final List<Long> resultList =
+                DataProcessor.getTimestampSlots(timestampSlots);
+
+        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());
+    }
+
+    private static void assertBatteryDiffEntry(
+            final BatteryDiffEntry entry, final long userId, final long uid,
+            final int consumerType, final double consumePercentage,
+            final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs) {
+        assertThat(entry.mBatteryHistEntry.mUserId).isEqualTo(userId);
+        assertThat(entry.mBatteryHistEntry.mUid).isEqualTo(uid);
+        assertThat(entry.mBatteryHistEntry.mConsumerType).isEqualTo(consumerType);
+        assertThat(entry.getPercentOfTotal()).isEqualTo(consumePercentage);
+        assertThat(entry.mForegroundUsageTimeInMs).isEqualTo(foregroundUsageTimeInMs);
+        assertThat(entry.mBackgroundUsageTimeInMs).isEqualTo(backgroundUsageTimeInMs);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummaryTest.java
index 81b574a..c049497 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PowerUsageSummaryTest.java
@@ -139,17 +139,7 @@
     }
 
     @Test
-    public void initPreference_chartGraphEnabled_hasCorrectSummary() {
-        mFragment.initPreference();
-
-        verify(mBatteryUsagePreference).setSummary("View usage for past 24 hours");
-    }
-
-    @Test
-    public void initPreference_chartGraphDisabled_hasCorrectSummary() {
-        when(mFeatureFactory.powerUsageFeatureProvider.isChartGraphEnabled(mRealContext))
-                .thenReturn(false);
-
+    public void initPreference_hasCorrectSummary() {
         mFragment.initPreference();
 
         verify(mBatteryUsagePreference).setSummary("View usage from last full charge");
diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
index 08bcd2a..274ac16 100644
--- a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
+++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
@@ -58,6 +58,7 @@
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.provider.Settings;
+import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -108,6 +109,7 @@
 import java.time.format.DateTimeFormatter;
 import java.time.format.FormatStyle;
 import java.util.Arrays;
+import java.util.List;
 import java.util.stream.Collectors;
 
 // TODO(b/143326832): Should add test cases for connect button.
@@ -1791,4 +1793,74 @@
 
         return pref;
     }
+
+    @Test
+    public void fineSubscriptionInfo_noMatchedCarrierId_returnNull() {
+        setUpSpyController();
+        SubscriptionInfo sub1 = mockSubscriptionInfo(1, "sim1", 1111);
+        SubscriptionInfo sub2 = mockSubscriptionInfo(2, "sim2", 2222);
+        List<SubscriptionInfo> activeSubInfos = Arrays.asList(sub1, sub2);
+
+        SubscriptionInfo info = mController.fineSubscriptionInfo(3333, activeSubInfos, 1);
+
+        assertThat(info).isNull();
+
+        info = mController.fineSubscriptionInfo(3333, activeSubInfos, 2);
+
+        assertThat(info).isNull();
+    }
+
+    @Test
+    public void fineSubscriptionInfo_diffCarrierId_returnMatchedOne() {
+        setUpSpyController();
+        SubscriptionInfo sub1 = mockSubscriptionInfo(1, "sim1", 1111);
+        SubscriptionInfo sub2 = mockSubscriptionInfo(2, "sim2", 2222);
+        List<SubscriptionInfo> activeSubInfos = Arrays.asList(sub1, sub2);
+
+        SubscriptionInfo info = mController.fineSubscriptionInfo(1111, activeSubInfos, 1);
+
+        assertThat(info).isNotNull();
+        assertThat(info.getDisplayName().toString()).isEqualTo("sim1");
+
+        info = mController.fineSubscriptionInfo(1111, activeSubInfos, 2);
+
+        assertThat(info).isNotNull();
+        assertThat(info.getDisplayName().toString()).isEqualTo("sim1");
+
+        info = mController.fineSubscriptionInfo(2222, activeSubInfos, 1);
+
+        assertThat(info).isNotNull();
+        assertThat(info.getDisplayName().toString()).isEqualTo("sim2");
+
+        info = mController.fineSubscriptionInfo(2222, activeSubInfos, 2);
+
+        assertThat(info).isNotNull();
+        assertThat(info.getDisplayName().toString()).isEqualTo("sim2");
+    }
+
+    @Test
+    public void fineSubscriptionInfo_sameCarrierId_returnDefaultDataOne() {
+        setUpSpyController();
+        SubscriptionInfo sub1 = mockSubscriptionInfo(1, "sim1", 1111);
+        SubscriptionInfo sub2 = mockSubscriptionInfo(2, "sim2", 1111);
+        List<SubscriptionInfo> activeSubInfos = Arrays.asList(sub1, sub2);
+
+        SubscriptionInfo info = mController.fineSubscriptionInfo(1111, activeSubInfos, 1);
+
+        assertThat(info).isNotNull();
+        assertThat(info.getDisplayName().toString()).isEqualTo("sim1");
+
+        info = mController.fineSubscriptionInfo(1111, activeSubInfos, 2);
+
+        assertThat(info).isNotNull();
+        assertThat(info.getDisplayName().toString()).isEqualTo("sim2");
+    }
+
+    private SubscriptionInfo mockSubscriptionInfo(int subId, String displayName, int carrierId) {
+        SubscriptionInfo info = mock(SubscriptionInfo.class);
+        when(info.getSubscriptionId()).thenReturn(subId);
+        when(info.getDisplayName()).thenReturn(displayName);
+        when(info.getCarrierId()).thenReturn(carrierId);
+        return info;
+    }
 }
diff --git a/tests/unit/src/com/android/settings/security/SecuritySettingsTest.java b/tests/unit/src/com/android/settings/security/SecuritySettingsTest.java
index c50d993..272c840 100644
--- a/tests/unit/src/com/android/settings/security/SecuritySettingsTest.java
+++ b/tests/unit/src/com/android/settings/security/SecuritySettingsTest.java
@@ -16,26 +16,42 @@
 
 package com.android.settings.security;
 
+import static android.content.Context.FACE_SERVICE;
+import static android.content.Context.FINGERPRINT_SERVICE;
+import static android.content.pm.PackageManager.FEATURE_FACE;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
 import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY;
 import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_INCLUDE_PREF_SCREEN;
 import static com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.annotation.XmlRes;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
+import android.os.Looper;
 import android.provider.SearchIndexableResource;
 
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.settings.biometrics.combination.CombinedBiometricStatusPreferenceController;
+import com.android.settings.biometrics.face.FaceStatusPreferenceController;
+import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceController;
 import com.android.settings.core.PreferenceXmlParserUtils;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.security.trustagent.TrustAgentManager;
 import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.core.AbstractPreferenceController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,26 +68,44 @@
 
     private Context mContext;
     private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider;
+    private SecuritySettings mSecuritySettings;
 
     @Mock
     private TrustAgentManager mTrustAgentManager;
+    @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private PackageManager mPackageManager;
 
     @Before
+    @UiThreadTest
     public void setup() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
         MockitoAnnotations.initMocks(this);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(FEATURE_FACE)).thenReturn(true);
+        when(mPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
+        doReturn(mFaceManager).when(mContext).getSystemService(FACE_SERVICE);
+        doReturn(mFingerprintManager).when(mContext).getSystemService(FINGERPRINT_SERVICE);
         FakeFeatureFactory mFeatureFactory = FakeFeatureFactory.setupForTest();
-        mSecuritySettingsFeatureProvider = mFeatureFactory.getSecuritySettingsFeatureProvider();
         SecurityFeatureProvider mSecurityFeatureProvider =
                 mFeatureFactory.getSecurityFeatureProvider();
 
         when(mSecurityFeatureProvider.getTrustAgentManager()).thenReturn(mTrustAgentManager);
+        mSecuritySettingsFeatureProvider = mFeatureFactory.getSecuritySettingsFeatureProvider();
+        mSecuritySettings = new SecuritySettings();
     }
 
     @Test
     public void noAlternativeFragmentAvailable_pageIndexIncluded() throws Exception {
-        when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment())
-                .thenReturn(false);
+        when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()).thenReturn(
+                false);
         BaseSearchIndexProvider indexProvider = SecuritySettings.SEARCH_INDEX_DATA_PROVIDER;
 
         List<String> allXmlKeys = getAllXmlKeys(indexProvider);
@@ -83,8 +117,8 @@
 
     @Test
     public void alternativeFragmentAvailable_pageIndexExcluded() throws Exception {
-        when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment())
-                .thenReturn(true);
+        when(mSecuritySettingsFeatureProvider.hasAlternativeSecuritySettingsFragment()).thenReturn(
+                true);
         BaseSearchIndexProvider indexProvider = SecuritySettings.SEARCH_INDEX_DATA_PROVIDER;
 
         List<String> allXmlKeys = getAllXmlKeys(indexProvider);
@@ -94,6 +128,45 @@
         assertThat(allXmlKeys).isEmpty();
     }
 
+    @Test
+    @UiThreadTest
+    public void preferenceController_containsFaceWhenAvailable() {
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        final List<AbstractPreferenceController> controllers =
+                mSecuritySettings.createPreferenceControllers(mContext);
+
+        assertThat(isFacePrefAvailable(controllers)).isTrue();
+        assertThat(isFingerprintPrefAvailable(controllers)).isFalse();
+        assertThat(isCombinedPrefAvailable(controllers)).isFalse();
+    }
+
+    @Test
+    @UiThreadTest
+    public void preferenceController_containsFingerprintWhenAvailable() {
+        when(mFaceManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        final List<AbstractPreferenceController> controllers =
+                mSecuritySettings.createPreferenceControllers(mContext);
+
+        assertThat(isFacePrefAvailable(controllers)).isFalse();
+        assertThat(isFingerprintPrefAvailable(controllers)).isTrue();
+        assertThat(isCombinedPrefAvailable(controllers)).isFalse();
+    }
+
+    @Test
+    @UiThreadTest
+    public void preferenceController_containsCombinedBiometricWhenAvailable() {
+        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        final List<AbstractPreferenceController> controllers =
+                mSecuritySettings.createPreferenceControllers(mContext);
+
+        assertThat(isFacePrefAvailable(controllers)).isFalse();
+        assertThat(isFingerprintPrefAvailable(controllers)).isFalse();
+        assertThat(isCombinedPrefAvailable(controllers)).isTrue();
+    }
+
     private List<String> getAllXmlKeys(BaseSearchIndexProvider indexProvider) throws Exception {
         final List<SearchIndexableResource> resources = indexProvider.getXmlResourcesToIndex(
                 mContext, true /* not used*/);
@@ -109,11 +182,29 @@
 
     private List<String> getKeysFromXml(@XmlRes int xmlResId) throws Exception {
         final List<String> keys = new ArrayList<>();
-        final List<Bundle> metadata = PreferenceXmlParserUtils
-                .extractMetadata(mContext, xmlResId, FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN);
+        final List<Bundle> metadata = PreferenceXmlParserUtils.extractMetadata(mContext, xmlResId,
+                FLAG_NEED_KEY | FLAG_INCLUDE_PREF_SCREEN);
         for (Bundle bundle : metadata) {
             keys.add(bundle.getString(METADATA_KEY));
         }
         return keys;
     }
+
+    boolean isFacePrefAvailable(List<AbstractPreferenceController> controllers) {
+        return controllers.stream().filter(
+                controller -> controller instanceof FaceStatusPreferenceController
+                        && controller.isAvailable()).count() == 1;
+    }
+
+    boolean isFingerprintPrefAvailable(List<AbstractPreferenceController> controllers) {
+        return controllers.stream().filter(
+                controller -> controller instanceof FingerprintStatusPreferenceController
+                        && controller.isAvailable()).count() == 1;
+    }
+
+    boolean isCombinedPrefAvailable(List<AbstractPreferenceController> controllers) {
+        return controllers.stream().filter(
+                controller -> controller instanceof CombinedBiometricStatusPreferenceController
+                        && controller.isAvailable()).count() == 1;
+    }
 }