Show app list in Battery Usage page when there is no battery level data.

https://drive.google.com/file/d/1mZ2Sn3dmjQcCxnhKqDPxfLJDyiTe6mEk/view?usp=sharing

Bug: 246233366
Test: make RunSettingsRoboTests + manually
Change-Id: If536c93652506c8009f5cabf3d0ae373b6825bfc
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
index 79f0880..69b19d3 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -111,6 +112,7 @@
     private View mCategoryTitleView;
     private PreferenceScreen mPreferenceScreen;
     private FooterPreference mFooterPreference;
+    private TextView mChartSummaryTextView;
     private BatteryChartViewModel mDailyViewModel;
     private List<BatteryChartViewModel> mHourlyViewModels;
 
@@ -121,9 +123,9 @@
     private final MetricsFeatureProvider mMetricsFeatureProvider;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final AnimatorListenerAdapter mHourlyChartFadeInAdapter =
-            createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ true);
+            createHourlyChartAnimatorListenerAdapter(/*visible=*/ true);
     private final AnimatorListenerAdapter mHourlyChartFadeOutAdapter =
-            createHourlyChartAnimatorListenerAdapter(/*isToShow=*/ false);
+            createHourlyChartAnimatorListenerAdapter(/*visible=*/ false);
 
     @VisibleForTesting
     final DailyChartLabelTextGenerator mDailyChartLabelTextGenerator =
@@ -289,6 +291,8 @@
                 getTotalHours(batteryLevelData));
 
         if (batteryLevelData == null) {
+            mDailyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
+            mHourlyChartIndex = BatteryChartViewModel.SELECTED_INDEX_ALL;
             mDailyViewModel = null;
             mHourlyViewModels = null;
             refreshUi();
@@ -321,6 +325,11 @@
             mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
             animateBatteryChartViewGroup();
         }
+        if (mBatteryChartViewGroup != null) {
+            final View grandparentView = (View) mBatteryChartViewGroup.getParent();
+            mChartSummaryTextView = grandparentView != null
+                    ? grandparentView.findViewById(R.id.chart_summary) : null;
+        }
     }
 
     private void setBatteryChartViewInner(@NonNull final BatteryChartView dailyChartView,
@@ -367,8 +376,45 @@
             // Chart views are not initialized.
             return false;
         }
-        if (mDailyViewModel == null || mHourlyViewModels == null) {
-            // Fail to get battery level data, show an empty hourly chart view.
+
+        // When mDailyViewModel or mHourlyViewModels is null, there is no battery level data.
+        // This is mainly in 2 cases:
+        // 1) battery data is within 2 hours
+        // 2) no battery data in the latest 7 days (power off >= 7 days)
+        final boolean refreshUiResult = mDailyViewModel == null || mHourlyViewModels == null
+                ? refreshUiWithNoLevelDataCase()
+                : refreshUiWithLevelDataCase();
+
+        if (!refreshUiResult) {
+            return false;
+        }
+
+        mHandler.post(() -> {
+            final long start = System.currentTimeMillis();
+            removeAndCacheAllPrefs();
+            addAllPreferences();
+            refreshCategoryTitle();
+            Log.d(TAG, String.format("refreshUi is finished in %d/ms",
+                    (System.currentTimeMillis() - start)));
+        });
+        return true;
+    }
+
+    private boolean refreshUiWithNoLevelDataCase() {
+        setChartSummaryVisible(false);
+        if (mBatteryUsageMap == null) {
+            // There is no battery level data and battery usage data is not ready, wait for data
+            // ready to refresh UI. Show nothing temporarily.
+            mDailyChartView.setVisibility(View.GONE);
+            mHourlyChartView.setVisibility(View.GONE);
+            mDailyChartView.setViewModel(null);
+            mHourlyChartView.setViewModel(null);
+            return false;
+        } else if (mBatteryUsageMap
+                .get(BatteryChartViewModel.SELECTED_INDEX_ALL)
+                .get(BatteryChartViewModel.SELECTED_INDEX_ALL) == null) {
+            // There is no battery level data and battery usage data, show an empty hourly chart
+            // view.
             mDailyChartView.setVisibility(View.GONE);
             mHourlyChartView.setVisibility(View.VISIBLE);
             mHourlyChartView.setViewModel(null);
@@ -376,7 +422,12 @@
             addFooterPreferenceIfNeeded(false);
             return false;
         }
+        return true;
+    }
 
+    private boolean refreshUiWithLevelDataCase() {
+        setChartSummaryVisible(true);
+        // Gets valid battery level data.
         if (isBatteryLevelDataInOneDay()) {
             // Only 1 day data, hide the daily chart view.
             mDailyChartView.setVisibility(View.GONE);
@@ -389,10 +440,11 @@
 
         if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) {
             // Multiple days are selected, hide the hourly chart view.
-            animateBatteryHourlyChartView(/*isToShow=*/ false);
+            animateBatteryHourlyChartView(/*visible=*/ false);
         } else {
-            animateBatteryHourlyChartView(/*isToShow=*/ true);
-            final BatteryChartViewModel hourlyViewModel = mHourlyViewModels.get(mDailyChartIndex);
+            animateBatteryHourlyChartView(/*visible=*/ true);
+            final BatteryChartViewModel hourlyViewModel =
+                    mHourlyViewModels.get(mDailyChartIndex);
             hourlyViewModel.setSelectedIndex(mHourlyChartIndex);
             mHourlyChartView.setViewModel(hourlyViewModel);
         }
@@ -401,14 +453,6 @@
             // Battery usage data is not ready, wait for data ready to refresh UI.
             return false;
         }
-        mHandler.post(() -> {
-            final long start = System.currentTimeMillis();
-            removeAndCacheAllPrefs();
-            addAllPreferences();
-            refreshCategoryTitle();
-            Log.d(TAG, String.format("refreshUi is finished in %d/ms",
-                    (System.currentTimeMillis() - start)));
-        });
         return true;
     }
 
@@ -427,7 +471,7 @@
         if (!batteryDiffData.getAppDiffEntryList().isEmpty()) {
             addPreferenceToScreen(batteryDiffData.getAppDiffEntryList());
         }
-        // Adds the expabable divider if we have system entries data.
+        // Adds the expandable divider if we have system entries data.
         if (!batteryDiffData.getSystemDiffEntryList().isEmpty()) {
             if (mExpandDividerPreference == null) {
                 mExpandDividerPreference = new ExpandDividerPreference(mPrefContext);
@@ -645,12 +689,12 @@
         }
     }
 
-    private void animateBatteryHourlyChartView(final boolean isToShow) {
+    private void animateBatteryHourlyChartView(final boolean visible) {
         if (mHourlyChartView == null) {
             return;
         }
 
-        if (isToShow) {
+        if (visible) {
             mHourlyChartView.setAlpha(0f);
             mHourlyChartView.setVisibility(View.VISIBLE);
             mHourlyChartView.animate()
@@ -667,9 +711,15 @@
         }
     }
 
+    private void setChartSummaryVisible(final boolean visible) {
+        if (mChartSummaryTextView != null) {
+            mChartSummaryTextView.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
+
     private AnimatorListenerAdapter createHourlyChartAnimatorListenerAdapter(
-            final boolean isToShow) {
-        final int visibility = isToShow ? View.VISIBLE : View.GONE;
+            final boolean visible) {
+        final int visibility = visible ? View.VISIBLE : View.GONE;
 
         return new AnimatorListenerAdapter() {
             @Override
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index 68f0dc7..1303573 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -55,11 +55,6 @@
     // Maximum total time value for each slot cumulative data at most 2 hours.
     private static final float TOTAL_TIME_THRESHOLD = DateUtils.HOUR_IN_MILLIS * 2;
 
-    // Keys for metric metadata.
-    static final int METRIC_KEY_PACKAGE = 1;
-    static final int METRIC_KEY_BATTERY_LEVEL = 2;
-    static final int METRIC_KEY_BATTERY_USAGE = 3;
-
     @VisibleForTesting
     static double PERCENTAGE_OF_TOTAL_THRESHOLD = 1f;
 
@@ -87,7 +82,7 @@
     }
 
     /** Converts to content values */
-    public static ContentValues convert(
+    public static ContentValues convertToContentValues(
             BatteryEntry entry,
             BatteryUsageStats batteryUsageStats,
             int batteryLevel,
@@ -130,6 +125,21 @@
         return values;
     }
 
+    /** Converts to {@link BatteryHistEntry} */
+    public static BatteryHistEntry convertToBatteryHistEntry(
+            BatteryEntry entry,
+            BatteryUsageStats batteryUsageStats) {
+        return new BatteryHistEntry(
+                convertToContentValues(
+                        entry,
+                        batteryUsageStats,
+                        /*batteryLevel=*/ 0,
+                        /*batteryStatus=*/ 0,
+                        /*batteryHealth=*/ 0,
+                        /*bootTimestamp=*/ 0,
+                        /*timestamp=*/ 0));
+    }
+
     /** Converts UTC timestamp to human readable local time string. */
     public static String utcToLocalTime(Context context, long timestamp) {
         final Locale locale = getLocale(context);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index f493ece..9c2c4d5 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -22,6 +22,9 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.os.AsyncTask;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -50,6 +53,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * A utility class to process data loaded from database and make the data easy to use for battery
@@ -97,7 +101,9 @@
             @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
             final UsageMapAsyncResponse asyncResponseDelegate) {
         if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
-            Log.d(TAG, "getBatteryLevelData() returns null");
+            Log.d(TAG, "batteryHistoryMap is null in getBatteryLevelData()");
+            loadBatteryUsageDataFromBatteryStatsService(
+                    context, handler, asyncResponseDelegate);
             return null;
         }
         handler = handler != null ? handler : new Handler(Looper.getMainLooper());
@@ -107,16 +113,20 @@
         // Wrap and processed history map into easy-to-use format for UI rendering.
         final BatteryLevelData batteryLevelData =
                 getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap);
+        if (batteryLevelData == null) {
+            loadBatteryUsageDataFromBatteryStatsService(
+                    context, handler, asyncResponseDelegate);
+            Log.d(TAG, "getBatteryLevelData() returns null");
+            return null;
+        }
 
         // 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();
-        }
+        new ComputeUsageMapAndLoadItemsTask(
+                context,
+                handler,
+                asyncResponseDelegate,
+                batteryLevelData.getHourlyBatteryLevelsPerDay(),
+                processedBatteryHistoryMap).execute();
 
         return batteryLevelData;
     }
@@ -365,19 +375,165 @@
             return null;
         }
 
-        final MetricsFeatureProvider metricsFeatureProvider =
-                FeatureFactory.getFactory(context).getMetricsFeatureProvider();
-        metricsFeatureProvider.action(
-                context,
-                SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT,
-                countOfAppAfterPurge);
-        metricsFeatureProvider.action(
-                context,
-                SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT,
-                countOfAppBeforePurge - countOfAppAfterPurge);
+        logAppCountMetrics(context, countOfAppBeforePurge, countOfAppAfterPurge);
         return resultMap;
     }
 
+    @VisibleForTesting
+    @Nullable
+    static BatteryDiffData generateBatteryDiffData(
+            final Context context,
+            @Nullable final List<BatteryEntry> batteryEntryList,
+            final BatteryUsageStats batteryUsageStats) {
+        final List<BatteryHistEntry> batteryHistEntryList =
+                convertToBatteryHistEntry(batteryEntryList, batteryUsageStats);
+        if (batteryHistEntryList == null || batteryHistEntryList.isEmpty()) {
+            Log.w(TAG, "batteryHistEntryList is null or empty in generateBatteryDiffData()");
+            return null;
+        }
+        final int currentUserId = context.getUserId();
+        final UserHandle userHandle =
+                Utils.getManagedProfile(context.getSystemService(UserManager.class));
+        final int workProfileUserId =
+                userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
+        final List<BatteryDiffEntry> appEntries = new ArrayList<>();
+        final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
+        double totalConsumePower = 0f;
+        double consumePowerFromOtherUsers = 0f;
+
+        for (BatteryHistEntry entry : batteryHistEntryList) {
+            final boolean isFromOtherUsers = isConsumedFromOtherUsers(
+                    currentUserId, workProfileUserId, entry);
+            totalConsumePower += entry.mConsumePower;
+            if (isFromOtherUsers) {
+                consumePowerFromOtherUsers += entry.mConsumePower;
+            } else {
+                final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
+                        context,
+                        entry.mForegroundUsageTimeInMs,
+                        entry.mBackgroundUsageTimeInMs,
+                        entry.mConsumePower,
+                        entry);
+                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;
+        }
+
+        return new BatteryDiffData(appEntries, systemEntries, totalConsumePower);
+    }
+
+    /**
+     * Starts the async task to load battery diff usage data and load app labels + icons.
+     */
+    private static void loadBatteryUsageDataFromBatteryStatsService(
+            Context context,
+            @Nullable Handler handler,
+            final UsageMapAsyncResponse asyncResponseDelegate) {
+        new LoadUsageMapFromBatteryStatsServiceTask(
+                context,
+                handler,
+                asyncResponseDelegate).execute();
+    }
+
+    /**
+     * @return Returns the overall battery usage data from battery stats service directly.
+     *
+     * The returned value should be always a 2d map and composed by only 1 part:
+     * - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
+     */
+    @Nullable
+    private static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService(
+            final Context context) {
+        final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>();
+        final Map<Integer, BatteryDiffData> allUsageMap = new HashMap<>();
+        // Always construct the map whether the value is null or not.
+        allUsageMap.put(SELECTED_INDEX_ALL,
+                getBatteryDiffDataFromBatteryStatsService(context));
+        resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
+
+        // Compute the apps number before purge. Must put before purgeLowPercentageAndFakeData.
+        final int countOfAppBeforePurge = getCountOfApps(resultMap);
+        purgeLowPercentageAndFakeData(context, resultMap);
+        // Compute the apps number after purge. Must put after purgeLowPercentageAndFakeData.
+        final int countOfAppAfterPurge = getCountOfApps(resultMap);
+
+        logAppCountMetrics(context, countOfAppBeforePurge, countOfAppAfterPurge);
+        return resultMap;
+    }
+
+    @Nullable
+    private static BatteryDiffData getBatteryDiffDataFromBatteryStatsService(
+            final Context context) {
+        BatteryDiffData batteryDiffData = null;
+        try {
+            final BatteryUsageStatsQuery batteryUsageStatsQuery =
+                    new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build();
+            final BatteryUsageStats batteryUsageStats =
+                    context.getSystemService(BatteryStatsManager.class)
+                            .getBatteryUsageStats(batteryUsageStatsQuery);
+
+            if (batteryUsageStats == null) {
+                Log.w(TAG, "batteryUsageStats is null content");
+                return null;
+            }
+
+            final List<BatteryEntry> batteryEntryList =
+                    generateBatteryEntryListFromBatteryUsageStats(context, batteryUsageStats);
+            batteryDiffData = generateBatteryDiffData(context, batteryEntryList, batteryUsageStats);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "load batteryUsageStats:" + e);
+        }
+
+        return batteryDiffData;
+    }
+
+    @Nullable
+    private static List<BatteryEntry> generateBatteryEntryListFromBatteryUsageStats(
+            final Context context, final BatteryUsageStats batteryUsageStats) {
+        // Loads the battery consuming data.
+        final BatteryAppListPreferenceController controller =
+                new BatteryAppListPreferenceController(
+                        context,
+                        /*preferenceKey=*/ null,
+                        /*lifecycle=*/ null,
+                        /*activity*=*/ null,
+                        /*fragment=*/ null);
+        return controller.getBatteryEntryList(batteryUsageStats, /*showAllApps=*/ true);
+    }
+
+    @Nullable
+    private static List<BatteryHistEntry> convertToBatteryHistEntry(
+            @Nullable final List<BatteryEntry> batteryEntryList,
+            final BatteryUsageStats batteryUsageStats) {
+        if (batteryEntryList == null || batteryEntryList.isEmpty()) {
+            Log.w(TAG, "batteryEntryList is null or empty in convertToBatteryHistEntry()");
+            return null;
+        }
+        return batteryEntryList.stream()
+                .filter(entry -> {
+                    final long foregroundMs = entry.getTimeInForegroundMs();
+                    final long backgroundMs = entry.getTimeInBackgroundMs();
+                    return entry.getConsumedPower() > 0
+                            || (entry.getConsumedPower() == 0
+                            && (foregroundMs != 0 || backgroundMs != 0));
+                })
+                .map(entry -> ConvertUtils.convertToBatteryHistEntry(
+                                entry,
+                                batteryUsageStats))
+                .collect(Collectors.toList());
+    }
+
     /**
      * Interpolates history map based on expected timestamp slots and processes the corner case when
      * the expected start timestamp is earlier than what we have.
@@ -940,6 +1096,22 @@
         return true;
     }
 
+    private static void loadLabelAndIcon(
+            @Nullable final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
+        if (batteryUsageMap == null) {
+            return;
+        }
+        // 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());
+        }
+    }
+
     private static long getTimestampWithDayDiff(final long timestamp, final int dayDiff) {
         final Calendar calendar = Calendar.getInstance();
         calendar.setTimeInMillis(timestamp);
@@ -1006,6 +1178,21 @@
         return batteryDiffEntry;
     }
 
+    private static void logAppCountMetrics(
+            Context context, final int countOfAppBeforePurge, final int countOfAppAfterPurge) {
+        context = context.getApplicationContext();
+        final MetricsFeatureProvider metricsFeatureProvider =
+                FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+        metricsFeatureProvider.action(
+                context,
+                SettingsEnums.ACTION_BATTERY_USAGE_SHOWN_APP_COUNT,
+                countOfAppAfterPurge);
+        metricsFeatureProvider.action(
+                context,
+                SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT,
+                countOfAppBeforePurge - countOfAppAfterPurge);
+    }
+
     private static void log(Context context, final String content, final long timestamp,
             final BatteryHistEntry entry) {
         if (DEBUG) {
@@ -1015,12 +1202,12 @@
     }
 
     // Compute diff map and loads all items (icon and label) in the background.
-    private static final class ComputeUsageMapAndLoadItemsTask
+    private static class ComputeUsageMapAndLoadItemsTask
             extends AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>> {
 
-        private Context mApplicationContext;
-        private Handler mHandler;
-        private UsageMapAsyncResponse mAsyncResponseDelegate;
+        Context mApplicationContext;
+        final Handler mHandler;
+        final UsageMapAsyncResponse mAsyncResponseDelegate;
         private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
         private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
 
@@ -1051,17 +1238,7 @@
             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());
-                }
-            }
+            loadLabelAndIcon(batteryUsageMap);
             Log.d(TAG, String.format("execute ComputeUsageMapAndLoadItemsTask in %d/ms",
                     (System.currentTimeMillis() - startTime)));
             return batteryUsageMap;
@@ -1081,4 +1258,35 @@
             }
         }
     }
+
+    // Loads battery usage data from battery stats service directly and loads all items (icon and
+    // label) in the background.
+    private static final class LoadUsageMapFromBatteryStatsServiceTask
+            extends ComputeUsageMapAndLoadItemsTask {
+
+        private LoadUsageMapFromBatteryStatsServiceTask(
+                Context context,
+                Handler handler,
+                final UsageMapAsyncResponse asyncResponseDelegate) {
+            super(context, handler, asyncResponseDelegate, /*hourlyBatteryLevelsPerDay=*/ null,
+                    /*batteryHistoryMap=*/ null);
+        }
+
+        @Override
+        protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
+            if (mApplicationContext == null
+                    || mHandler == null
+                    || mAsyncResponseDelegate == null) {
+                Log.e(TAG, "invalid input for ComputeUsageMapAndLoadItemsTask()");
+                return null;
+            }
+            final long startTime = System.currentTimeMillis();
+            final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
+                    getBatteryUsageMapFromStatsService(mApplicationContext);
+            loadLabelAndIcon(batteryUsageMap);
+            Log.d(TAG, String.format("execute LoadUsageMapFromBatteryStatsServiceTask in %d/ms",
+                    (System.currentTimeMillis() - startTime)));
+            return batteryUsageMap;
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
index 553c089..5dfdd39 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
@@ -64,7 +64,7 @@
         when(mMockBatteryEntry.getConsumerType())
                 .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
         final ContentValues values =
-                ConvertUtils.convert(
+                ConvertUtils.convertToContentValues(
                         mMockBatteryEntry,
                         mBatteryUsageStats,
                         /*batteryLevel=*/ 12,
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 c9bac03..2ae73b1 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -71,7 +71,7 @@
     }
 
     @Test
-    public void convert_returnsExpectedContentValues() {
+    public void convertToContentValues_returnsExpectedContentValues() {
         final int expectedType = 3;
         when(mMockBatteryEntry.getUid()).thenReturn(1001);
         when(mMockBatteryEntry.getLabel()).thenReturn("Settings");
@@ -88,7 +88,7 @@
                 .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
 
         final ContentValues values =
-                ConvertUtils.convert(
+                ConvertUtils.convertToContentValues(
                         mMockBatteryEntry,
                         mBatteryUsageStats,
                         /*batteryLevel=*/ 12,
@@ -128,9 +128,9 @@
     }
 
     @Test
-    public void convert_nullBatteryEntry_returnsExpectedContentValues() {
+    public void convertToContentValues_nullBatteryEntry_returnsExpectedContentValues() {
         final ContentValues values =
-                ConvertUtils.convert(
+                ConvertUtils.convertToContentValues(
                         /*entry=*/ null,
                         /*batteryUsageStats=*/ null,
                         /*batteryLevel=*/ 12,
@@ -155,6 +155,76 @@
     }
 
     @Test
+    public void convertToBatteryHistEntry_returnsExpectedResult() {
+        final int expectedType = 3;
+        when(mMockBatteryEntry.getUid()).thenReturn(1001);
+        when(mMockBatteryEntry.getLabel()).thenReturn("Settings");
+        when(mMockBatteryEntry.getDefaultPackageName())
+                .thenReturn("com.android.settings.battery");
+        when(mMockBatteryEntry.isHidden()).thenReturn(true);
+        when(mBatteryUsageStats.getConsumedPower()).thenReturn(5.1);
+        when(mMockBatteryEntry.getConsumedPower()).thenReturn(1.1);
+        mMockBatteryEntry.mPercent = 0.3;
+        when(mMockBatteryEntry.getTimeInForegroundMs()).thenReturn(1234L);
+        when(mMockBatteryEntry.getTimeInBackgroundMs()).thenReturn(5689L);
+        when(mMockBatteryEntry.getPowerComponentId()).thenReturn(expectedType);
+        when(mMockBatteryEntry.getConsumerType())
+                .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
+
+        final BatteryHistEntry batteryHistEntry =
+                ConvertUtils.convertToBatteryHistEntry(
+                        mMockBatteryEntry,
+                        mBatteryUsageStats);
+
+        assertThat(batteryHistEntry.mUid).isEqualTo(1001L);
+        assertThat(batteryHistEntry.mUserId)
+                .isEqualTo(UserHandle.getUserId(1001));
+        assertThat(batteryHistEntry.mAppLabel)
+                .isEqualTo("Settings");
+        assertThat(batteryHistEntry.mPackageName)
+                .isEqualTo("com.android.settings.battery");
+        assertThat(batteryHistEntry.mIsHidden).isTrue();
+        assertThat(batteryHistEntry.mBootTimestamp)
+                .isEqualTo(0L);
+        assertThat(batteryHistEntry.mTimestamp).isEqualTo(0L);
+        assertThat(batteryHistEntry.mZoneId)
+                .isEqualTo(TimeZone.getDefault().getID());
+        assertThat(batteryHistEntry.mTotalPower).isEqualTo(5.1);
+        assertThat(batteryHistEntry.mConsumePower).isEqualTo(1.1);
+        assertThat(batteryHistEntry.mPercentOfTotal).isEqualTo(0.3);
+        assertThat(batteryHistEntry.mForegroundUsageTimeInMs)
+                .isEqualTo(1234L);
+        assertThat(batteryHistEntry.mBackgroundUsageTimeInMs)
+                .isEqualTo(5689L);
+        assertThat(batteryHistEntry.mDrainType).isEqualTo(expectedType);
+        assertThat(batteryHistEntry.mConsumerType)
+                .isEqualTo(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
+        assertThat(batteryHistEntry.mBatteryLevel).isEqualTo(0);
+        assertThat(batteryHistEntry.mBatteryStatus).isEqualTo(0);
+        assertThat(batteryHistEntry.mBatteryHealth).isEqualTo(0);
+    }
+
+    @Test
+    public void convertToBatteryHistEntry_nullBatteryEntry_returnsExpectedResult() {
+        final BatteryHistEntry batteryHistEntry =
+                ConvertUtils.convertToBatteryHistEntry(
+                        /*entry=*/ null,
+                        /*batteryUsageStats=*/ null);
+
+        assertThat(batteryHistEntry.mBootTimestamp)
+                .isEqualTo(0L);
+        assertThat(batteryHistEntry.mTimestamp)
+                .isEqualTo(0);
+        assertThat(batteryHistEntry.mZoneId)
+                .isEqualTo(TimeZone.getDefault().getID());
+        assertThat(batteryHistEntry.mBatteryLevel).isEqualTo(0);
+        assertThat(batteryHistEntry.mBatteryStatus).isEqualTo(0);
+        assertThat(batteryHistEntry.mBatteryHealth).isEqualTo(0);
+        assertThat(batteryHistEntry.mPackageName)
+                .isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
+    }
+
+    @Test
     public void getIndexedUsageMap_nullOrEmptyHistoryMap_returnEmptyCollection() {
         final int timeSlotSize = 2;
         final long[] batteryHistoryKeys = new long[]{101L, 102L, 103L, 104L, 105L};
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
index 84f9310..c483204 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -26,6 +27,8 @@
 import android.app.settings.SettingsEnums;
 import android.content.ContentValues;
 import android.content.Context;
+import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
 import android.text.format.DateUtils;
 
 import com.android.settings.fuelgauge.BatteryUtils;
@@ -36,6 +39,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
@@ -59,6 +63,13 @@
     private MetricsFeatureProvider mMetricsFeatureProvider;
     private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
 
+    @Mock private BatteryUsageStats mBatteryUsageStats;
+    @Mock private BatteryEntry mMockBatteryEntry1;
+    @Mock private BatteryEntry mMockBatteryEntry2;
+    @Mock private BatteryEntry mMockBatteryEntry3;
+    @Mock private BatteryEntry mMockBatteryEntry4;
+
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -883,6 +894,60 @@
                 .action(mContext, SettingsEnums.ACTION_BATTERY_USAGE_HIDDEN_APP_COUNT, 0);
     }
 
+    @Test
+    public void generateBatteryDiffData_emptyBatteryEntryList_returnNull() {
+        assertThat(DataProcessor.generateBatteryDiffData(
+                mContext, null, mBatteryUsageStats)).isNull();
+    }
+
+    @Test
+    public void generateBatteryDiffData_returnsExpectedResult() {
+        final List<BatteryEntry> batteryEntryList = new ArrayList<>();
+        batteryEntryList.add(mMockBatteryEntry1);
+        batteryEntryList.add(mMockBatteryEntry2);
+        batteryEntryList.add(mMockBatteryEntry3);
+        batteryEntryList.add(mMockBatteryEntry4);
+        doReturn(0.0).when(mMockBatteryEntry1).getConsumedPower();
+        doReturn(30L).when(mMockBatteryEntry1).getTimeInForegroundMs();
+        doReturn(40L).when(mMockBatteryEntry1).getTimeInBackgroundMs();
+        doReturn(1).when(mMockBatteryEntry1).getUid();
+        doReturn(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).when(mMockBatteryEntry1).getConsumerType();
+        doReturn(0.5).when(mMockBatteryEntry2).getConsumedPower();
+        doReturn(20L).when(mMockBatteryEntry2).getTimeInForegroundMs();
+        doReturn(20L).when(mMockBatteryEntry2).getTimeInBackgroundMs();
+        doReturn(2).when(mMockBatteryEntry2).getUid();
+        doReturn(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).when(mMockBatteryEntry2).getConsumerType();
+        doReturn(0.0).when(mMockBatteryEntry3).getConsumedPower();
+        doReturn(0L).when(mMockBatteryEntry3).getTimeInForegroundMs();
+        doReturn(0L).when(mMockBatteryEntry3).getTimeInBackgroundMs();
+        doReturn(3).when(mMockBatteryEntry3).getUid();
+        doReturn(ConvertUtils.CONSUMER_TYPE_UID_BATTERY).when(mMockBatteryEntry3).getConsumerType();
+        doReturn(1.5).when(mMockBatteryEntry4).getConsumedPower();
+        doReturn(10L).when(mMockBatteryEntry4).getTimeInForegroundMs();
+        doReturn(10L).when(mMockBatteryEntry4).getTimeInBackgroundMs();
+        doReturn(4).when(mMockBatteryEntry4).getUid();
+        doReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY)
+                .when(mMockBatteryEntry4).getConsumerType();
+        doReturn(BatteryConsumer.POWER_COMPONENT_CAMERA)
+                .when(mMockBatteryEntry4).getPowerComponentId();
+
+        final BatteryDiffData batteryDiffData = DataProcessor.generateBatteryDiffData(
+                mContext, batteryEntryList, mBatteryUsageStats);
+
+        assertBatteryDiffEntry(
+                batteryDiffData.getAppDiffEntryList().get(0), 0, /*uid=*/ 2L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 25.0,
+                /*foregroundUsageTimeInMs=*/ 20, /*backgroundUsageTimeInMs=*/ 20);
+        assertBatteryDiffEntry(
+                batteryDiffData.getAppDiffEntryList().get(1), 0, /*uid=*/ 1L,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 0.0,
+                /*foregroundUsageTimeInMs=*/ 30, /*backgroundUsageTimeInMs=*/ 40);
+        assertBatteryDiffEntry(
+                batteryDiffData.getSystemDiffEntryList().get(0), 0, /*uid=*/ 4L,
+                ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY, /*consumePercentage=*/ 75.0,
+                /*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 10);
+    }
+
     private static Map<Long, Map<String, BatteryHistEntry>> createHistoryMap(
             final long[] timestamps, final int[] levels) {
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>();