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
|
||||
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
|
||||
static long sFakeCurrentTimeMillis = 0;
|
||||
@@ -101,6 +98,9 @@ public final class DataProcessor {
|
||||
IUsageStatsManager.Stub.asInterface(
|
||||
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. */
|
||||
public interface UsageMapAsyncResponse {
|
||||
/** The callback function when batteryUsageMap is loaded. */
|
||||
@@ -200,18 +200,9 @@ public final class DataProcessor {
|
||||
@Nullable
|
||||
public static Map<Long, UsageEvents> getAppUsageEvents(Context context) {
|
||||
final long start = System.currentTimeMillis();
|
||||
final boolean isWorkProfileUser = DatabaseUtils.isWorkProfile(context);
|
||||
Log.d(TAG, "getAppUsageEvents() isWorkProfileUser:" + isWorkProfileUser);
|
||||
if (isWorkProfileUser) {
|
||||
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;
|
||||
}
|
||||
context = DatabaseUtils.getOwnerContext(context);
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
final Map<Long, UsageEvents> resultMap = new HashMap();
|
||||
final UserManager userManager = context.getSystemService(UserManager.class);
|
||||
@@ -220,19 +211,9 @@ public final class DataProcessor {
|
||||
}
|
||||
final long sixDaysAgoTimestamp =
|
||||
DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
|
||||
final String callingPackage = context.getPackageName();
|
||||
final long now = System.currentTimeMillis();
|
||||
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(
|
||||
sUsageStatsManager, startTime, now, user.id, callingPackage);
|
||||
context, userManager, user.id, sixDaysAgoTimestamp);
|
||||
if (events != null) {
|
||||
resultMap.put(Long.valueOf(user.id), events);
|
||||
}
|
||||
@@ -243,6 +224,30 @@ public final class DataProcessor {
|
||||
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.
|
||||
*/
|
||||
@@ -335,6 +340,17 @@ public final class DataProcessor {
|
||||
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.
|
||||
* The start and end timestamp must be the even hours.
|
||||
@@ -621,6 +637,24 @@ public final class DataProcessor {
|
||||
|
||||
@Nullable
|
||||
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 int userId, final String callingPackage) {
|
||||
final long start = System.currentTimeMillis();
|
||||
@@ -672,14 +706,6 @@ public final class DataProcessor {
|
||||
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
|
||||
@Nullable
|
||||
static List<BatteryHistEntry> convertToBatteryHistEntry(
|
||||
|
@@ -194,6 +194,23 @@ public final class DatabaseUtils {
|
||||
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(
|
||||
final Context context, final List<AppUsageEvent> appUsageEventList) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
@@ -342,18 +359,9 @@ public final class DatabaseUtils {
|
||||
|
||||
private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
|
||||
Context context, Uri batteryStateUri) {
|
||||
final boolean isWorkProfileUser = isWorkProfile(context);
|
||||
Log.d(TAG, "loadHistoryMapFromContentProvider() isWorkProfileUser:" + isWorkProfileUser);
|
||||
if (isWorkProfileUser) {
|
||||
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;
|
||||
}
|
||||
context = DatabaseUtils.getOwnerContext(context);
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
|
||||
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;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DataProcessorTest {
|
||||
public final class DataProcessorTest {
|
||||
private static final String FAKE_ENTRY_KEY = "fake_entry_key";
|
||||
|
||||
private Context mContext;
|
||||
@@ -177,7 +177,7 @@ public class DataProcessorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAppUsageEvents_lockedUser_returnNull() throws RemoteException {
|
||||
public void getAppUsageEvents_lockedUser_returnNull() {
|
||||
UserInfo userInfo = new UserInfo(/*id=*/ 0, "user_0", /*flags=*/ 0);
|
||||
final List<UserInfo> userInfoList = new ArrayList<>();
|
||||
userInfoList.add(userInfo);
|
||||
@@ -205,6 +205,37 @@ public class DataProcessorTest {
|
||||
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() {
|
||||
Event event1 = getUsageEvent(Event.NOTIFICATION_INTERRUPTION, /*timestamp=*/ 1);
|
||||
Event event2 = getUsageEvent(Event.ACTIVITY_RESUMED, /*timestamp=*/ 2);
|
||||
@@ -231,11 +262,11 @@ public class DataProcessorTest {
|
||||
DataProcessor.generateAppUsageEventListFromUsageEvents(mContext, appUsageEvents);
|
||||
|
||||
assertThat(appUsageEventList.size()).isEqualTo(3);
|
||||
assetAppUsageEvent(
|
||||
assertAppUsageEvent(
|
||||
appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 2);
|
||||
assetAppUsageEvent(
|
||||
assertAppUsageEvent(
|
||||
appUsageEventList.get(1), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 3);
|
||||
assetAppUsageEvent(
|
||||
assertAppUsageEvent(
|
||||
appUsageEventList.get(2), AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 4);
|
||||
}
|
||||
|
||||
@@ -1327,7 +1358,7 @@ public class DataProcessorTest {
|
||||
return event;
|
||||
}
|
||||
|
||||
private void assetAppUsageEvent(
|
||||
private void assertAppUsageEvent(
|
||||
final AppUsageEvent event, final AppUsageEventType eventType, final long timestamp) {
|
||||
assertThat(event.getType()).isEqualTo(eventType);
|
||||
assertThat(event.getTimestamp()).isEqualTo(timestamp);
|
||||
|
Reference in New Issue
Block a user