Add DataProcessManager to manage the async tasks of battery usage data
processing. Test: make RunSettingsRoboTests + manually Bug: 260964903 Change-Id: Id3b2772a98ec2ab3b03910c8a5e81adf7ccd5646
This commit is contained in:
@@ -0,0 +1,275 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.app.usage.UsageEvents;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.os.UserManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.settings.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the async tasks to process battery and app usage data.
|
||||||
|
*
|
||||||
|
* For now, there exist 3 async tasks in this manager:
|
||||||
|
* <ul>
|
||||||
|
* <li>loadCurrentBatteryHistoryMap: load the latest battery history data from battery stats
|
||||||
|
* service.</li>
|
||||||
|
* <li>loadCurrentAppUsageList: load the latest app usage data (last timestamp in database - now)
|
||||||
|
* from usage stats service.</li>
|
||||||
|
* <li>loadDatabaseAppUsageList: load the necessary app usage data (after last full charge) from
|
||||||
|
* database</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* The 3 async tasks will be started at the same time.
|
||||||
|
* <ul>
|
||||||
|
* <li>After loadCurrentAppUsageList and loadDatabaseAppUsageList complete, which means all app
|
||||||
|
* usage data has been loaded, the intermediate usage result will be generated.</li>
|
||||||
|
* <li>Then after all 3 async tasks complete, the battery history data and app usage data will be
|
||||||
|
* combined to generate final data used for UI rendering. And the callback function will be
|
||||||
|
* applied.</li>
|
||||||
|
* <li>If current user is locked, which means we couldn't get the latest app usage data,
|
||||||
|
* screen-on time will not be shown in the UI and empty screen-on time data will be returned.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class DataProcessManager {
|
||||||
|
private static final String TAG = "DataProcessManager";
|
||||||
|
|
||||||
|
private final Handler mHandler;
|
||||||
|
private final DataProcessor.UsageMapAsyncResponse mCallbackFunction;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
|
||||||
|
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
|
||||||
|
|
||||||
|
private boolean mIsCurrentBatteryHistoryLoaded = false;
|
||||||
|
private boolean mIsCurrentAppUsageLoaded = false;
|
||||||
|
private boolean mIsDatabaseAppUsageLoaded = false;
|
||||||
|
// Used to identify whether screen-on time data should be shown in the UI.
|
||||||
|
private boolean mShowScreenOnTime = true;
|
||||||
|
|
||||||
|
private List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor when this exists battery level data.
|
||||||
|
*/
|
||||||
|
DataProcessManager(
|
||||||
|
Context context,
|
||||||
|
Handler handler,
|
||||||
|
final DataProcessor.UsageMapAsyncResponse callbackFunction,
|
||||||
|
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
|
||||||
|
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
|
||||||
|
mContext = context.getApplicationContext();
|
||||||
|
mHandler = handler;
|
||||||
|
mCallbackFunction = callbackFunction;
|
||||||
|
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
|
||||||
|
mBatteryHistoryMap = batteryHistoryMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the async tasks to load battery history data and app usage data.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
// Load the latest battery history data from the service.
|
||||||
|
loadCurrentBatteryHistoryMap();
|
||||||
|
// Load app usage list from database.
|
||||||
|
loadDatabaseAppUsageList();
|
||||||
|
// Load the latest app usage list from the service.
|
||||||
|
loadCurrentAppUsageList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
List<AppUsageEvent> getAppUsageEventList() {
|
||||||
|
return mAppUsageEventList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean getIsCurrentAppUsageLoaded() {
|
||||||
|
return mIsCurrentAppUsageLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean getIsDatabaseAppUsageLoaded() {
|
||||||
|
return mIsDatabaseAppUsageLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean getIsCurrentBatteryHistoryLoaded() {
|
||||||
|
return mIsCurrentBatteryHistoryLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean getShowScreenOnTime() {
|
||||||
|
return mShowScreenOnTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCurrentBatteryHistoryMap() {
|
||||||
|
new AsyncTask<Void, Void, Map<String, BatteryHistEntry>>() {
|
||||||
|
@Override
|
||||||
|
protected Map<String, BatteryHistEntry> doInBackground(Void... voids) {
|
||||||
|
final long startTime = System.currentTimeMillis();
|
||||||
|
// Loads the current battery usage data from the battery stats service.
|
||||||
|
final Map<String, BatteryHistEntry> currentBatteryHistoryMap =
|
||||||
|
DataProcessor.getCurrentBatteryHistoryMapFromStatsService(
|
||||||
|
mContext);
|
||||||
|
Log.d(TAG, String.format("execute loadCurrentBatteryHistoryMap size=%d in %d/ms",
|
||||||
|
currentBatteryHistoryMap.size(), (System.currentTimeMillis() - startTime)));
|
||||||
|
return currentBatteryHistoryMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(
|
||||||
|
final Map<String, BatteryHistEntry> currentBatteryHistoryMap) {
|
||||||
|
if (mBatteryHistoryMap != null) {
|
||||||
|
// Replaces the placeholder in mBatteryHistoryMap.
|
||||||
|
for (Map.Entry<Long, Map<String, BatteryHistEntry>> mapEntry
|
||||||
|
: mBatteryHistoryMap.entrySet()) {
|
||||||
|
if (mapEntry.getValue().containsKey(
|
||||||
|
DataProcessor.CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
|
||||||
|
mapEntry.setValue(currentBatteryHistoryMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mIsCurrentBatteryHistoryLoaded = true;
|
||||||
|
tryToGenerateFinalDataAndApplyCallback();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCurrentAppUsageList() {
|
||||||
|
new AsyncTask<Void, Void, List<AppUsageEvent>>() {
|
||||||
|
@Override
|
||||||
|
protected List<AppUsageEvent> doInBackground(Void... voids) {
|
||||||
|
final long startTime = System.currentTimeMillis();
|
||||||
|
// Loads the current battery usage data from the battery stats service.
|
||||||
|
final int currentUserId = getCurrentUserId();
|
||||||
|
final int workProfileUserId = getWorkProfileUserId();
|
||||||
|
final UsageEvents usageEventsForCurrentUser =
|
||||||
|
DataProcessor.getAppUsageEventsForUser(mContext, currentUserId);
|
||||||
|
// If fail to load usage events for current user, return null directly and screen-on
|
||||||
|
// time will not be shown in the UI.
|
||||||
|
if (usageEventsForCurrentUser == null) {
|
||||||
|
Log.w(TAG, "usageEventsForCurrentUser is null");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
UsageEvents usageEventsForWorkProfile = null;
|
||||||
|
if (workProfileUserId != Integer.MIN_VALUE) {
|
||||||
|
usageEventsForWorkProfile =
|
||||||
|
DataProcessor.getAppUsageEventsForUser(
|
||||||
|
mContext, workProfileUserId);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "there is no work profile");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<Long, UsageEvents> usageEventsMap = new HashMap<>();
|
||||||
|
usageEventsMap.put(Long.valueOf(currentUserId), usageEventsForCurrentUser);
|
||||||
|
if (usageEventsForWorkProfile != null) {
|
||||||
|
Log.d(TAG, "usageEventsForWorkProfile is null");
|
||||||
|
usageEventsMap.put(Long.valueOf(workProfileUserId), usageEventsForWorkProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<AppUsageEvent> appUsageEventList =
|
||||||
|
DataProcessor.generateAppUsageEventListFromUsageEvents(
|
||||||
|
mContext, usageEventsMap);
|
||||||
|
Log.d(TAG, String.format("execute loadCurrentAppUsageList size=%d in %d/ms",
|
||||||
|
appUsageEventList.size(), (System.currentTimeMillis() - startTime)));
|
||||||
|
return appUsageEventList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(
|
||||||
|
final List<AppUsageEvent> currentAppUsageList) {
|
||||||
|
final int currentUserId = getCurrentUserId();
|
||||||
|
final UserManager userManager = mContext.getSystemService(UserManager.class);
|
||||||
|
// If current user is locked, don't show screen-on time data in the UI.
|
||||||
|
// Even if we have data in the database, we won't show screen-on time because we
|
||||||
|
// don't have the latest data.
|
||||||
|
if (userManager == null || !userManager.isUserUnlocked(currentUserId)) {
|
||||||
|
Log.d(TAG, "current user is locked");
|
||||||
|
mShowScreenOnTime = false;
|
||||||
|
} else if (currentAppUsageList == null || currentAppUsageList.isEmpty()) {
|
||||||
|
Log.d(TAG, "usageEventsForWorkProfile is null or empty");
|
||||||
|
} else {
|
||||||
|
mAppUsageEventList.addAll(currentAppUsageList);
|
||||||
|
}
|
||||||
|
mIsCurrentAppUsageLoaded = true;
|
||||||
|
tryToProcessAppUsageData();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadDatabaseAppUsageList() {
|
||||||
|
// TODO: load app usage data from database.
|
||||||
|
mIsDatabaseAppUsageLoaded = true;
|
||||||
|
tryToProcessAppUsageData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToProcessAppUsageData() {
|
||||||
|
// Only when all app usage events has been loaded, start processing app usage data to an
|
||||||
|
// intermediate result for further use.
|
||||||
|
if (!mIsCurrentAppUsageLoaded || !mIsDatabaseAppUsageLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processAppUsageData();
|
||||||
|
tryToGenerateFinalDataAndApplyCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processAppUsageData() {
|
||||||
|
// If there is no screen-on time data, no need to process.
|
||||||
|
if (!mShowScreenOnTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: process app usage data to an intermediate result for further use.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToGenerateFinalDataAndApplyCallback() {
|
||||||
|
// Only when both battery history data and app usage events data has been loaded, start the
|
||||||
|
// final data processing.
|
||||||
|
if (!mIsCurrentBatteryHistoryLoaded
|
||||||
|
|| !mIsCurrentAppUsageLoaded
|
||||||
|
|| !mIsDatabaseAppUsageLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
generateFinalDataAndApplyCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateFinalDataAndApplyCallback() {
|
||||||
|
// TODO: generate the final data including battery usage map and device screen-on time and
|
||||||
|
// then apply the callback function.
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCurrentUserId() {
|
||||||
|
return mContext.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getWorkProfileUserId() {
|
||||||
|
final UserHandle userHandle =
|
||||||
|
Utils.getManagedProfile(
|
||||||
|
mContext.getSystemService(UserManager.class));
|
||||||
|
return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
|
||||||
|
}
|
||||||
|
}
|
@@ -89,9 +89,6 @@ public final class DataProcessor {
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int SELECTED_INDEX_ALL = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
static final int SELECTED_INDEX_ALL = BatteryChartViewModel.SELECTED_INDEX_ALL;
|
||||||
@VisibleForTesting
|
|
||||||
static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER =
|
|
||||||
"CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER";
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static long sFakeCurrentTimeMillis = 0;
|
static long sFakeCurrentTimeMillis = 0;
|
||||||
@@ -101,6 +98,9 @@ public final class DataProcessor {
|
|||||||
IUsageStatsManager.Stub.asInterface(
|
IUsageStatsManager.Stub.asInterface(
|
||||||
ServiceManager.getService(Context.USAGE_STATS_SERVICE));
|
ServiceManager.getService(Context.USAGE_STATS_SERVICE));
|
||||||
|
|
||||||
|
public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER =
|
||||||
|
"CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER";
|
||||||
|
|
||||||
/** A callback listener when battery usage loading async task is executed. */
|
/** A callback listener when battery usage loading async task is executed. */
|
||||||
public interface UsageMapAsyncResponse {
|
public interface UsageMapAsyncResponse {
|
||||||
/** The callback function when batteryUsageMap is loaded. */
|
/** The callback function when batteryUsageMap is loaded. */
|
||||||
@@ -200,18 +200,9 @@ public final class DataProcessor {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public static Map<Long, UsageEvents> getAppUsageEvents(Context context) {
|
public static Map<Long, UsageEvents> getAppUsageEvents(Context context) {
|
||||||
final long start = System.currentTimeMillis();
|
final long start = System.currentTimeMillis();
|
||||||
final boolean isWorkProfileUser = DatabaseUtils.isWorkProfile(context);
|
context = DatabaseUtils.getOwnerContext(context);
|
||||||
Log.d(TAG, "getAppUsageEvents() isWorkProfileUser:" + isWorkProfileUser);
|
if (context == null) {
|
||||||
if (isWorkProfileUser) {
|
return null;
|
||||||
try {
|
|
||||||
context = context.createPackageContextAsUser(
|
|
||||||
/*packageName=*/ context.getPackageName(),
|
|
||||||
/*flags=*/ 0,
|
|
||||||
/*user=*/ UserHandle.OWNER);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
final Map<Long, UsageEvents> resultMap = new HashMap();
|
final Map<Long, UsageEvents> resultMap = new HashMap();
|
||||||
final UserManager userManager = context.getSystemService(UserManager.class);
|
final UserManager userManager = context.getSystemService(UserManager.class);
|
||||||
@@ -220,19 +211,9 @@ public final class DataProcessor {
|
|||||||
}
|
}
|
||||||
final long sixDaysAgoTimestamp =
|
final long sixDaysAgoTimestamp =
|
||||||
DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
|
DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
|
||||||
final String callingPackage = context.getPackageName();
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
for (final UserInfo user : userManager.getAliveUsers()) {
|
for (final UserInfo user : userManager.getAliveUsers()) {
|
||||||
// When the user is not unlocked, UsageStatsManager will return null, so bypass the
|
|
||||||
// following data loading logics directly.
|
|
||||||
if (!userManager.isUserUnlocked(user.id)) {
|
|
||||||
Log.w(TAG, "fail to load app usage event for user :" + user.id + " because locked");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser(
|
|
||||||
context, user.id, sixDaysAgoTimestamp);
|
|
||||||
final UsageEvents events = getAppUsageEventsForUser(
|
final UsageEvents events = getAppUsageEventsForUser(
|
||||||
sUsageStatsManager, startTime, now, user.id, callingPackage);
|
context, userManager, user.id, sixDaysAgoTimestamp);
|
||||||
if (events != null) {
|
if (events != null) {
|
||||||
resultMap.put(Long.valueOf(user.id), events);
|
resultMap.put(Long.valueOf(user.id), events);
|
||||||
}
|
}
|
||||||
@@ -243,6 +224,30 @@ public final class DataProcessor {
|
|||||||
return resultMap.isEmpty() ? null : resultMap;
|
return resultMap.isEmpty() ? null : resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link UsageEvents} from system service for the specific user.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static UsageEvents getAppUsageEventsForUser(Context context, final int userID) {
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
context = DatabaseUtils.getOwnerContext(context);
|
||||||
|
if (context == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final UserManager userManager = context.getSystemService(UserManager.class);
|
||||||
|
if (userManager == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final long sixDaysAgoTimestamp =
|
||||||
|
DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
|
||||||
|
final UsageEvents events = getAppUsageEventsForUser(
|
||||||
|
context, userManager, userID, sixDaysAgoTimestamp);
|
||||||
|
final long elapsedTime = System.currentTimeMillis() - start;
|
||||||
|
Log.d(TAG, String.format("getAppUsageEventsForUser() for user %d in %d/ms",
|
||||||
|
userID, elapsedTime));
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the {@link BatteryUsageStats} after using it.
|
* Closes the {@link BatteryUsageStats} after using it.
|
||||||
*/
|
*/
|
||||||
@@ -335,6 +340,17 @@ public final class DataProcessor {
|
|||||||
return usageList;
|
return usageList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns the latest battery history map loaded from the battery stats service.
|
||||||
|
*/
|
||||||
|
public static Map<String, BatteryHistEntry> getCurrentBatteryHistoryMapFromStatsService(
|
||||||
|
final Context context) {
|
||||||
|
final List<BatteryHistEntry> batteryHistEntryList =
|
||||||
|
getBatteryHistListFromFromStatsService(context);
|
||||||
|
return batteryHistEntryList == null ? new HashMap<>()
|
||||||
|
: batteryHistEntryList.stream().collect(Collectors.toMap(e -> e.getKey(), e -> e));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Returns the processed history map which has interpolated to every hour data.
|
* @return Returns the processed history map which has interpolated to every hour data.
|
||||||
* The start and end timestamp must be the even hours.
|
* The start and end timestamp must be the even hours.
|
||||||
@@ -621,6 +637,24 @@ public final class DataProcessor {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static UsageEvents getAppUsageEventsForUser(
|
private static UsageEvents getAppUsageEventsForUser(
|
||||||
|
Context context, final UserManager userManager, final int userID,
|
||||||
|
final long sixDaysAgoTimestamp) {
|
||||||
|
final String callingPackage = context.getPackageName();
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
// When the user is not unlocked, UsageStatsManager will return null, so bypass the
|
||||||
|
// following data loading logics directly.
|
||||||
|
if (!userManager.isUserUnlocked(userID)) {
|
||||||
|
Log.w(TAG, "fail to load app usage event for user :" + userID + " because locked");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser(
|
||||||
|
context, userID, sixDaysAgoTimestamp);
|
||||||
|
return loadAppUsageEventsForUserFromService(
|
||||||
|
sUsageStatsManager, startTime, now, userID, callingPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static UsageEvents loadAppUsageEventsForUserFromService(
|
||||||
final IUsageStatsManager usageStatsManager, final long startTime, final long endTime,
|
final IUsageStatsManager usageStatsManager, final long startTime, final long endTime,
|
||||||
final int userId, final String callingPackage) {
|
final int userId, final String callingPackage) {
|
||||||
final long start = System.currentTimeMillis();
|
final long start = System.currentTimeMillis();
|
||||||
@@ -672,14 +706,6 @@ public final class DataProcessor {
|
|||||||
return batteryHistEntryList;
|
return batteryHistEntryList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, BatteryHistEntry> getCurrentBatteryHistoryMapFromStatsService(
|
|
||||||
final Context context) {
|
|
||||||
final List<BatteryHistEntry> batteryHistEntryList =
|
|
||||||
getBatteryHistListFromFromStatsService(context);
|
|
||||||
return batteryHistEntryList == null ? new HashMap<>()
|
|
||||||
: batteryHistEntryList.stream().collect(Collectors.toMap(e -> e.getKey(), e -> e));
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Nullable
|
@Nullable
|
||||||
static List<BatteryHistEntry> convertToBatteryHistEntry(
|
static List<BatteryHistEntry> convertToBatteryHistEntry(
|
||||||
|
@@ -194,6 +194,23 @@ public final class DatabaseUtils {
|
|||||||
return startCalendar.getTimeInMillis();
|
return startCalendar.getTimeInMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the context with OWNER identity when current user is work profile. */
|
||||||
|
public static Context getOwnerContext(Context context) {
|
||||||
|
final boolean isWorkProfileUser = isWorkProfile(context);
|
||||||
|
if (isWorkProfileUser) {
|
||||||
|
try {
|
||||||
|
return context.createPackageContextAsUser(
|
||||||
|
/*packageName=*/ context.getPackageName(),
|
||||||
|
/*flags=*/ 0,
|
||||||
|
/*user=*/ UserHandle.OWNER);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
static List<ContentValues> sendAppUsageEventData(
|
static List<ContentValues> sendAppUsageEventData(
|
||||||
final Context context, final List<AppUsageEvent> appUsageEventList) {
|
final Context context, final List<AppUsageEvent> appUsageEventList) {
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
@@ -342,18 +359,9 @@ public final class DatabaseUtils {
|
|||||||
|
|
||||||
private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
|
private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
|
||||||
Context context, Uri batteryStateUri) {
|
Context context, Uri batteryStateUri) {
|
||||||
final boolean isWorkProfileUser = isWorkProfile(context);
|
context = DatabaseUtils.getOwnerContext(context);
|
||||||
Log.d(TAG, "loadHistoryMapFromContentProvider() isWorkProfileUser:" + isWorkProfileUser);
|
if (context == null) {
|
||||||
if (isWorkProfileUser) {
|
return null;
|
||||||
try {
|
|
||||||
context = context.createPackageContextAsUser(
|
|
||||||
/*packageName=*/ context.getPackageName(),
|
|
||||||
/*flags=*/ 0,
|
|
||||||
/*user=*/ UserHandle.OWNER);
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
|
final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
|
||||||
try (Cursor cursor = sFakeBatteryStateSupplier != null ? sFakeBatteryStateSupplier.get() :
|
try (Cursor cursor = sFakeBatteryStateSupplier != null ? sFakeBatteryStateSupplier.get() :
|
||||||
|
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
|
import android.app.usage.IUsageStatsManager;
|
||||||
|
import android.app.usage.UsageEvents;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.UserManager;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public final class DataProcessManagerTest {
|
||||||
|
private Context mContext;
|
||||||
|
private DataProcessManager mDataProcessManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private IUsageStatsManager mUsageStatsManager;
|
||||||
|
@Mock
|
||||||
|
private UserManager mUserManager;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
mContext = spy(RuntimeEnvironment.application);
|
||||||
|
DataProcessor.sUsageStatsManager = mUsageStatsManager;
|
||||||
|
doReturn(mContext).when(mContext).getApplicationContext();
|
||||||
|
doReturn(mUserManager)
|
||||||
|
.when(mContext)
|
||||||
|
.getSystemService(UserManager.class);
|
||||||
|
|
||||||
|
mDataProcessManager = new DataProcessManager(
|
||||||
|
mContext, /*handler=*/ null, /*callbackFunction=*/ null,
|
||||||
|
/*hourlyBatteryLevelsPerDay=*/ null, /*batteryHistoryMap=*/ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void start_loadExpectedCurrentAppUsageData() throws RemoteException {
|
||||||
|
final UsageEvents.Event event1 =
|
||||||
|
getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /*timestamp=*/ 1);
|
||||||
|
final UsageEvents.Event event2 =
|
||||||
|
getUsageEvent(UsageEvents.Event.ACTIVITY_STOPPED, /*timestamp=*/ 2);
|
||||||
|
final List<UsageEvents.Event> events = new ArrayList<>();
|
||||||
|
events.add(event1);
|
||||||
|
events.add(event2);
|
||||||
|
doReturn(getUsageEvents(events))
|
||||||
|
.when(mUsageStatsManager)
|
||||||
|
.queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
|
||||||
|
doReturn(true).when(mUserManager).isUserUnlocked(anyInt());
|
||||||
|
|
||||||
|
mDataProcessManager.start();
|
||||||
|
|
||||||
|
assertThat(mDataProcessManager.getIsCurrentAppUsageLoaded()).isTrue();
|
||||||
|
assertThat(mDataProcessManager.getIsDatabaseAppUsageLoaded()).isTrue();
|
||||||
|
assertThat(mDataProcessManager.getIsCurrentBatteryHistoryLoaded()).isTrue();
|
||||||
|
assertThat(mDataProcessManager.getShowScreenOnTime()).isTrue();
|
||||||
|
final List<AppUsageEvent> appUsageEventList = mDataProcessManager.getAppUsageEventList();
|
||||||
|
assertThat(appUsageEventList.size()).isEqualTo(2);
|
||||||
|
assertAppUsageEvent(
|
||||||
|
appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 1);
|
||||||
|
assertAppUsageEvent(
|
||||||
|
appUsageEventList.get(1), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void start_currentUserLocked_emptyAppUsageList() throws RemoteException {
|
||||||
|
final UsageEvents.Event event =
|
||||||
|
getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /*timestamp=*/ 1);
|
||||||
|
final List<UsageEvents.Event> events = new ArrayList<>();
|
||||||
|
events.add(event);
|
||||||
|
doReturn(getUsageEvents(events))
|
||||||
|
.when(mUsageStatsManager)
|
||||||
|
.queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
|
||||||
|
doReturn(false).when(mUserManager).isUserUnlocked(anyInt());
|
||||||
|
|
||||||
|
mDataProcessManager.start();
|
||||||
|
|
||||||
|
assertThat(mDataProcessManager.getAppUsageEventList()).isEmpty();
|
||||||
|
assertThat(mDataProcessManager.getShowScreenOnTime()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UsageEvents getUsageEvents(final List<UsageEvents.Event> events) {
|
||||||
|
UsageEvents usageEvents = new UsageEvents(events, new String[] {"package"});
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
usageEvents.writeToParcel(parcel, 0);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
return UsageEvents.CREATOR.createFromParcel(parcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UsageEvents.Event getUsageEvent(
|
||||||
|
final int eventType, final long timestamp) {
|
||||||
|
final UsageEvents.Event event = new UsageEvents.Event();
|
||||||
|
event.mEventType = eventType;
|
||||||
|
event.mPackage = "package";
|
||||||
|
event.mTimeStamp = timestamp;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertAppUsageEvent(
|
||||||
|
final AppUsageEvent event, final AppUsageEventType eventType, final long timestamp) {
|
||||||
|
assertThat(event.getType()).isEqualTo(eventType);
|
||||||
|
assertThat(event.getTimestamp()).isEqualTo(timestamp);
|
||||||
|
}
|
||||||
|
}
|
@@ -64,7 +64,7 @@ import java.util.Set;
|
|||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class DataProcessorTest {
|
public final class DataProcessorTest {
|
||||||
private static final String FAKE_ENTRY_KEY = "fake_entry_key";
|
private static final String FAKE_ENTRY_KEY = "fake_entry_key";
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@@ -177,7 +177,7 @@ public class DataProcessorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAppUsageEvents_lockedUser_returnNull() throws RemoteException {
|
public void getAppUsageEvents_lockedUser_returnNull() {
|
||||||
UserInfo userInfo = new UserInfo(/*id=*/ 0, "user_0", /*flags=*/ 0);
|
UserInfo userInfo = new UserInfo(/*id=*/ 0, "user_0", /*flags=*/ 0);
|
||||||
final List<UserInfo> userInfoList = new ArrayList<>();
|
final List<UserInfo> userInfoList = new ArrayList<>();
|
||||||
userInfoList.add(userInfo);
|
userInfoList.add(userInfo);
|
||||||
@@ -205,6 +205,37 @@ public class DataProcessorTest {
|
|||||||
assertThat(resultMap).isNull();
|
assertThat(resultMap).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAppUsageEventsForUser_returnExpectedResult() throws RemoteException {
|
||||||
|
final int userId = 1;
|
||||||
|
doReturn(true).when(mUserManager).isUserUnlocked(userId);
|
||||||
|
doReturn(mUsageEvents1)
|
||||||
|
.when(mUsageStatsManager)
|
||||||
|
.queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
|
||||||
|
|
||||||
|
assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId))
|
||||||
|
.isEqualTo(mUsageEvents1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAppUsageEventsForUser_lockedUser_returnNull() {
|
||||||
|
final int userId = 1;
|
||||||
|
// Test locked user.
|
||||||
|
doReturn(false).when(mUserManager).isUserUnlocked(userId);
|
||||||
|
|
||||||
|
assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getAppUsageEventsForUser_nullUsageEvents_returnNull() throws RemoteException {
|
||||||
|
final int userId = 1;
|
||||||
|
doReturn(true).when(mUserManager).isUserUnlocked(userId);
|
||||||
|
doReturn(null)
|
||||||
|
.when(mUsageStatsManager).queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
|
||||||
|
|
||||||
|
assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test public void generateAppUsageEventListFromUsageEvents_returnExpectedResult() {
|
@Test public void generateAppUsageEventListFromUsageEvents_returnExpectedResult() {
|
||||||
Event event1 = getUsageEvent(Event.NOTIFICATION_INTERRUPTION, /*timestamp=*/ 1);
|
Event event1 = getUsageEvent(Event.NOTIFICATION_INTERRUPTION, /*timestamp=*/ 1);
|
||||||
Event event2 = getUsageEvent(Event.ACTIVITY_RESUMED, /*timestamp=*/ 2);
|
Event event2 = getUsageEvent(Event.ACTIVITY_RESUMED, /*timestamp=*/ 2);
|
||||||
@@ -231,11 +262,11 @@ public class DataProcessorTest {
|
|||||||
DataProcessor.generateAppUsageEventListFromUsageEvents(mContext, appUsageEvents);
|
DataProcessor.generateAppUsageEventListFromUsageEvents(mContext, appUsageEvents);
|
||||||
|
|
||||||
assertThat(appUsageEventList.size()).isEqualTo(3);
|
assertThat(appUsageEventList.size()).isEqualTo(3);
|
||||||
assetAppUsageEvent(
|
assertAppUsageEvent(
|
||||||
appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 2);
|
appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 2);
|
||||||
assetAppUsageEvent(
|
assertAppUsageEvent(
|
||||||
appUsageEventList.get(1), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 3);
|
appUsageEventList.get(1), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 3);
|
||||||
assetAppUsageEvent(
|
assertAppUsageEvent(
|
||||||
appUsageEventList.get(2), AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 4);
|
appUsageEventList.get(2), AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1327,7 +1358,7 @@ public class DataProcessorTest {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assetAppUsageEvent(
|
private void assertAppUsageEvent(
|
||||||
final AppUsageEvent event, final AppUsageEventType eventType, final long timestamp) {
|
final AppUsageEvent event, final AppUsageEventType eventType, final long timestamp) {
|
||||||
assertThat(event.getType()).isEqualTo(eventType);
|
assertThat(event.getType()).isEqualTo(eventType);
|
||||||
assertThat(event.getTimestamp()).isEqualTo(timestamp);
|
assertThat(event.getTimestamp()).isEqualTo(timestamp);
|
||||||
|
Reference in New Issue
Block a user