Generate app and device screen-on time data and return back to UI for
rendering. Test: make RunSettingsRoboTests + manually Bug: 260964903 Change-Id: I2fd69b4686cc2e1413ad0eb43c07b6164e411411
This commit is contained in:
@@ -27,12 +27,10 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.BatteryConsumer;
|
||||
import android.os.BatteryStatsManager;
|
||||
import android.os.BatteryUsageStats;
|
||||
import android.os.BatteryUsageStatsQuery;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
@@ -68,6 +66,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A utility class to process data loaded from database and make the data easy to use for battery
|
||||
@@ -111,8 +110,7 @@ public final class DataProcessor {
|
||||
/** 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);
|
||||
void onBatteryCallbackDataLoaded(BatteryCallbackData batteryCallbackData);
|
||||
}
|
||||
|
||||
private DataProcessor() {
|
||||
@@ -141,7 +139,8 @@ public final class DataProcessor {
|
||||
: getBatteryUsageMap(
|
||||
context,
|
||||
batteryLevelData.getHourlyBatteryLevelsPerDay(),
|
||||
processedBatteryHistoryMap);
|
||||
processedBatteryHistoryMap,
|
||||
/*appUsagePeriodMap=*/ null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,6 +363,35 @@ public final class DataProcessor {
|
||||
return appUsageEventList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the device screen-on time data.
|
||||
*
|
||||
* <p>There could be 2 cases of the returned value:</p>
|
||||
* <ul>
|
||||
* <li>null: empty or invalid data.</li>
|
||||
* <li>non-null: must be a 2d map and composed by 3 parts:</li>
|
||||
* <p> 1 - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]</p>
|
||||
* <p> 2 - [0][SELECTED_INDEX_ALL] ~ [maxDailyIndex][SELECTED_INDEX_ALL]</p>
|
||||
* <p> 3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]</p>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The structure is consistent with the battery usage map returned by
|
||||
* {@code getBatteryUsageMap}.</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static Map<Integer, Map<Integer, Long>> getDeviceScreenOnTime(
|
||||
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
|
||||
appUsagePeriodMap) {
|
||||
if (appUsagePeriodMap == null) {
|
||||
return null;
|
||||
}
|
||||
final Map<Integer, Map<Integer, Long>> deviceScreenOnTime = new HashMap<>();
|
||||
insertHourlyDeviceScreenOnTime(appUsagePeriodMap, deviceScreenOnTime);
|
||||
insertDailyDeviceScreenOnTime(appUsagePeriodMap, deviceScreenOnTime);
|
||||
insertAllDeviceScreenOnTime(deviceScreenOnTime);
|
||||
return deviceScreenOnTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the list of {@link BatteryEntry} from the supplied {@link BatteryUsageStats}.
|
||||
*/
|
||||
@@ -597,19 +625,21 @@ public final class DataProcessor {
|
||||
* <p> 3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]</p>
|
||||
* </ul>
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMap(
|
||||
final Context context,
|
||||
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
|
||||
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
|
||||
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
|
||||
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
|
||||
appUsagePeriodMap) {
|
||||
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);
|
||||
context, hourlyBatteryLevelsPerDay, batteryHistoryMap, appUsagePeriodMap,
|
||||
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].
|
||||
@@ -649,6 +679,7 @@ public final class DataProcessor {
|
||||
context,
|
||||
entry.mForegroundUsageTimeInMs,
|
||||
entry.mBackgroundUsageTimeInMs,
|
||||
/*screenOnTimeInMs=*/ 0,
|
||||
entry.mConsumePower,
|
||||
entry.mForegroundUsageConsumePower,
|
||||
entry.mForegroundServiceUsageConsumePower,
|
||||
@@ -805,6 +836,18 @@ public final class DataProcessor {
|
||||
return usagePeriodList;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static long getScreenOnTime(
|
||||
final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap,
|
||||
final long userId,
|
||||
final String packageName) {
|
||||
if (appUsageMap == null || appUsageMap.get(userId) == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return getScreenOnTime(appUsageMap.get(userId).get(packageName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the overall battery usage data from battery stats service directly.
|
||||
*
|
||||
@@ -889,6 +932,84 @@ public final class DataProcessor {
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
private static void insertHourlyDeviceScreenOnTime(
|
||||
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
|
||||
appUsagePeriodMap,
|
||||
final Map<Integer, Map<Integer, Long>> resultMap) {
|
||||
for (final int dailyIndex : appUsagePeriodMap.keySet()) {
|
||||
final Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>> dailyAppUsageMap =
|
||||
appUsagePeriodMap.get(dailyIndex);
|
||||
final Map<Integer, Long> dailyScreenOnTime = new HashMap<>();
|
||||
resultMap.put(dailyIndex, dailyScreenOnTime);
|
||||
if (dailyAppUsageMap == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (final int hourlyIndex : dailyAppUsageMap.keySet()) {
|
||||
final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap =
|
||||
dailyAppUsageMap.get(hourlyIndex);
|
||||
if (appUsageMap == null || appUsageMap.isEmpty()) {
|
||||
dailyScreenOnTime.put(hourlyIndex, 0L);
|
||||
} else {
|
||||
final List<AppUsagePeriod> flatUsageList = new ArrayList<>();
|
||||
for (final long userId: appUsageMap.keySet()) {
|
||||
if (appUsageMap.get(userId) == null) {
|
||||
continue;
|
||||
}
|
||||
for (final String packageName: appUsageMap.get(userId).keySet()) {
|
||||
final List<AppUsagePeriod> appUsagePeriodList =
|
||||
appUsageMap.get(userId).get(packageName);
|
||||
if (appUsagePeriodList != null && !appUsagePeriodList.isEmpty()) {
|
||||
flatUsageList.addAll(appUsagePeriodList);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compute the screen on time and make sure it won't exceed the threshold.
|
||||
final long screenOnTime = Math.min(
|
||||
(long) TOTAL_HOURLY_TIME_THRESHOLD, getScreenOnTime(flatUsageList));
|
||||
dailyScreenOnTime.put(hourlyIndex, screenOnTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void insertDailyDeviceScreenOnTime(
|
||||
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
|
||||
appUsagePeriodMap,
|
||||
final Map<Integer, Map<Integer, Long>> resultMap) {
|
||||
for (final int dailyIndex : appUsagePeriodMap.keySet()) {
|
||||
Map<Integer, Long> dailyResultMap = resultMap.get(dailyIndex);
|
||||
if (dailyResultMap == null) {
|
||||
dailyResultMap = new HashMap<>();
|
||||
resultMap.put(dailyIndex, dailyResultMap);
|
||||
}
|
||||
dailyResultMap.put(
|
||||
SELECTED_INDEX_ALL,
|
||||
getAccumulatedScreenOnTime(dailyResultMap));
|
||||
}
|
||||
}
|
||||
|
||||
private static void insertAllDeviceScreenOnTime(
|
||||
final Map<Integer, Map<Integer, Long>> resultMap) {
|
||||
final Map<Integer, Long> dailyAllMap = new HashMap<>();
|
||||
resultMap.keySet().forEach(
|
||||
key -> dailyAllMap.put(key, resultMap.get(key).get(SELECTED_INDEX_ALL)));
|
||||
final Map<Integer, Long> allUsageMap = new HashMap<>();
|
||||
allUsageMap.put(SELECTED_INDEX_ALL, getAccumulatedScreenOnTime(dailyAllMap));
|
||||
resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
|
||||
}
|
||||
|
||||
private static long getAccumulatedScreenOnTime(final Map<Integer, Long> screenOnTimeMap) {
|
||||
if (screenOnTimeMap == null || screenOnTimeMap.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
long sum = 0;
|
||||
for (final int index : screenOnTimeMap.keySet()) {
|
||||
sum += screenOnTimeMap.get(index) == null ? 0 : screenOnTimeMap.get(index);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static UsageEvents getAppUsageEventsForUser(
|
||||
Context context, final UserManager userManager, final int userID,
|
||||
@@ -1226,6 +1347,8 @@ public final class DataProcessor {
|
||||
Context context,
|
||||
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
|
||||
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
|
||||
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
|
||||
appUsagePeriodMap,
|
||||
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
|
||||
final int currentUserId = context.getUserId();
|
||||
final UserHandle userHandle =
|
||||
@@ -1251,6 +1374,10 @@ public final class DataProcessor {
|
||||
workProfileUserId,
|
||||
hourlyIndex,
|
||||
timestamps,
|
||||
appUsagePeriodMap == null
|
||||
|| appUsagePeriodMap.get(dailyIndex) == null
|
||||
? null
|
||||
: appUsagePeriodMap.get(dailyIndex).get(hourlyIndex),
|
||||
batteryHistoryMap);
|
||||
dailyDiffMap.put(hourlyIndex, hourlyBatteryDiffData);
|
||||
}
|
||||
@@ -1289,6 +1416,7 @@ public final class DataProcessor {
|
||||
final int workProfileUserId,
|
||||
final int currentIndex,
|
||||
final List<Long> timestamps,
|
||||
final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap,
|
||||
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
|
||||
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
|
||||
final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
|
||||
@@ -1405,10 +1533,18 @@ public final class DataProcessor {
|
||||
cachedUsageConsumePower = cachedUsageConsumePower * ratio;
|
||||
}
|
||||
|
||||
// Compute the screen on time and make sure it won't exceed the threshold.
|
||||
final long screenOnTime = Math.min(
|
||||
(long) TOTAL_HOURLY_TIME_THRESHOLD,
|
||||
getScreenOnTime(
|
||||
appUsageMap,
|
||||
selectedBatteryEntry.mUserId,
|
||||
selectedBatteryEntry.mPackageName));
|
||||
final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
|
||||
context,
|
||||
foregroundUsageTimeInMs,
|
||||
backgroundUsageTimeInMs,
|
||||
screenOnTime,
|
||||
consumePower,
|
||||
foregroundUsageConsumePower,
|
||||
foregroundServiceUsageConsumePower,
|
||||
@@ -1430,6 +1566,49 @@ public final class DataProcessor {
|
||||
return new BatteryDiffData(appEntries, systemEntries);
|
||||
}
|
||||
|
||||
private static long getScreenOnTime(@Nullable final List<AppUsagePeriod> appUsagePeriodList) {
|
||||
if (appUsagePeriodList == null || appUsagePeriodList.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
// Create a list of endpoints (the beginning or the end) of usage periods and order the list
|
||||
// chronologically.
|
||||
final List<AppUsageEndPoint> endPoints =
|
||||
appUsagePeriodList.stream()
|
||||
.flatMap(
|
||||
foregroundUsage ->
|
||||
Stream.of(
|
||||
AppUsageEndPoint.newBuilder()
|
||||
.setTimestamp(
|
||||
foregroundUsage.getStartTime())
|
||||
.setType(AppUsageEndPointType.START)
|
||||
.build(),
|
||||
AppUsageEndPoint.newBuilder()
|
||||
.setTimestamp(foregroundUsage.getEndTime())
|
||||
.setType(AppUsageEndPointType.END)
|
||||
.build()))
|
||||
.sorted((x, y) -> (int) (x.getTimestamp() - y.getTimestamp()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Traverse the list of endpoints in order to determine the non-overlapping usage duration.
|
||||
int numberOfActiveAppUsagePeriods = 0;
|
||||
long startOfCurrentContiguousAppUsagePeriod = 0;
|
||||
long totalScreenOnTime = 0;
|
||||
for (final AppUsageEndPoint endPoint : endPoints) {
|
||||
if (endPoint.getType() == AppUsageEndPointType.START) {
|
||||
if (numberOfActiveAppUsagePeriods++ == 0) {
|
||||
startOfCurrentContiguousAppUsagePeriod = endPoint.getTimestamp();
|
||||
}
|
||||
} else {
|
||||
if (--numberOfActiveAppUsagePeriods == 0) {
|
||||
totalScreenOnTime +=
|
||||
(endPoint.getTimestamp() - startOfCurrentContiguousAppUsagePeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalScreenOnTime;
|
||||
}
|
||||
|
||||
private static boolean isConsumedFromOtherUsers(
|
||||
final int currentUserId,
|
||||
final int workProfileUserId,
|
||||
@@ -1484,6 +1663,8 @@ public final class DataProcessor {
|
||||
entry.mForegroundUsageTimeInMs;
|
||||
oldBatteryDiffEntry.mBackgroundUsageTimeInMs +=
|
||||
entry.mBackgroundUsageTimeInMs;
|
||||
oldBatteryDiffEntry.mScreenOnTimeInMs +=
|
||||
entry.mScreenOnTimeInMs;
|
||||
oldBatteryDiffEntry.mConsumePower += entry.mConsumePower;
|
||||
oldBatteryDiffEntry.mForegroundUsageConsumePower += entry.mForegroundUsageConsumePower;
|
||||
oldBatteryDiffEntry.mForegroundServiceUsageConsumePower
|
||||
@@ -1752,73 +1933,4 @@ public final class DataProcessor {
|
||||
utcToLocalTime(context, timestamp), content, entry));
|
||||
}
|
||||
}
|
||||
|
||||
// Compute diff map and loads all items (icon and label) in the background.
|
||||
static class ComputeUsageMapAndLoadItemsTask
|
||||
extends AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>> {
|
||||
|
||||
Context mApplicationContext;
|
||||
final Handler mHandler;
|
||||
final UsageMapAsyncResponse mAsyncResponseDelegate;
|
||||
private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
|
||||
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
|
||||
|
||||
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();
|
||||
// Loads the current battery usage data from the battery stats service and replaces the
|
||||
// placeholder in mBatteryHistoryMap.
|
||||
Map<String, BatteryHistEntry> currentBatteryHistoryMap =
|
||||
getCurrentBatteryHistoryMapFromStatsService(mApplicationContext);
|
||||
for (Map.Entry<Long, Map<String, BatteryHistEntry>> mapEntry
|
||||
: mBatteryHistoryMap.entrySet()) {
|
||||
if (mapEntry.getValue().containsKey(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
|
||||
mapEntry.setValue(currentBatteryHistoryMap);
|
||||
}
|
||||
}
|
||||
|
||||
final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
|
||||
getBatteryUsageMap(
|
||||
mApplicationContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap);
|
||||
loadLabelAndIcon(batteryUsageMap);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user