Add the async task to compute diff usage data and load labels and icons.

Bug: 236101687
Test: make RunSettingsRoboTests
Change-Id: Ie24ea89fa6cfd351c73e64de40e2c9315867af9a
This commit is contained in:
Kuan Wang
2022-07-26 13:22:39 +08:00
parent a0d8b1fbc6
commit 0dc8d58de5
4 changed files with 1072 additions and 19 deletions

View File

@@ -18,21 +18,36 @@ 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
@@ -43,10 +58,27 @@ public final class DataProcessor {
private static final String TAG = "DataProcessor";
private static final int MIN_DAILY_DATA_SIZE = 2;
private static final int MIN_TIMESTAMP_DATA_SIZE = 2;
// 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() {
}
@@ -58,11 +90,14 @@ public final class DataProcessor {
@Nullable
public static BatteryLevelData getBatteryLevelData(
Context context,
@Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
@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);
@@ -70,11 +105,33 @@ public final class DataProcessor {
final BatteryLevelData batteryLevelData =
getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap);
//TODO: Add the async task to compute diff usage data and load labels and icons.
// 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 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.
@@ -187,7 +244,7 @@ public final class DataProcessor {
@VisibleForTesting
static boolean isFromFullCharge(@Nullable final Map<String, BatteryHistEntry> entryList) {
if (entryList == null) {
Log.d(TAG, "entryList is nul in isFromFullCharge()");
Log.d(TAG, "entryList is null in isFromFullCharge()");
return false;
}
final List<String> entryKeys = new ArrayList<>(entryList.keySet());
@@ -205,14 +262,14 @@ public final class DataProcessor {
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.
for (long timestamp : timestamps) {
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];
@@ -224,7 +281,7 @@ public final class DataProcessor {
* timezone.
*/
@VisibleForTesting
static long getTimestampOfNextDay(long timestamp) {
static long getTimestampOfNextDay(long timestamp) {
final Calendar nextDayCalendar = Calendar.getInstance();
nextDayCalendar.setTimeInMillis(timestamp);
nextDayCalendar.add(Calendar.DAY_OF_YEAR, 1);
@@ -234,6 +291,40 @@ public final class DataProcessor {
return nextDayCalendar.getTimeInMillis();
}
/**
* @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.
@@ -458,11 +549,454 @@ public final class DataProcessor {
return Math.round(batteryLevelCounter / entryMap.size());
}
private static void log(Context context, String content, long timestamp,
BatteryHistEntry entry) {
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).get(SELECTED_INDEX_ALL) == null) {
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 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);
});
}
}
}
}