Implement the app usage data loading from database function.

Bug: 260964903
Test: make RunSettingsRoboTests + manual
Change-Id: I459dbdebe53e6b7421642955f36976b3e7c95fcb
This commit is contained in:
Kuan Wang
2022-12-19 19:59:13 +08:00
parent 092d07fa60
commit 97924455ff
12 changed files with 581 additions and 50 deletions

View File

@@ -37,6 +37,9 @@ import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/** {@link ContentProvider} class to fetch battery usage data. */ /** {@link ContentProvider} class to fetch battery usage data. */
public class BatteryUsageContentProvider extends ContentProvider { public class BatteryUsageContentProvider extends ContentProvider {
@@ -100,6 +103,8 @@ public class BatteryUsageContentProvider extends ContentProvider {
switch (sUriMatcher.match(uri)) { switch (sUriMatcher.match(uri)) {
case BATTERY_STATE_CODE: case BATTERY_STATE_CODE:
return getBatteryStates(uri); return getBatteryStates(uri);
case APP_USAGE_EVENT_CODE:
return getAppUsageEvents(uri);
case APP_USAGE_LATEST_TIMESTAMP_CODE: case APP_USAGE_LATEST_TIMESTAMP_CODE:
return getAppUsageLatestTimestamp(uri); return getAppUsageLatestTimestamp(uri);
default: default:
@@ -153,8 +158,7 @@ public class BatteryUsageContentProvider extends ContentProvider {
} }
private Cursor getBatteryStates(Uri uri) { private Cursor getBatteryStates(Uri uri) {
final long defaultTimestamp = mClock.millis() - QUERY_DURATION_HOURS.toMillis(); final long queryTimestamp = getQueryTimestamp(uri);
final long queryTimestamp = getQueryTimestamp(uri, defaultTimestamp);
return getBatteryStates(uri, queryTimestamp); return getBatteryStates(uri, queryTimestamp);
} }
@@ -171,6 +175,24 @@ public class BatteryUsageContentProvider extends ContentProvider {
return cursor; return cursor;
} }
private Cursor getAppUsageEvents(Uri uri) {
final List<Long> queryUserIds = getQueryUserIds(uri);
if (queryUserIds == null || queryUserIds.isEmpty()) {
return null;
}
final long queryTimestamp = getQueryTimestamp(uri);
final long timestamp = mClock.millis();
Cursor cursor = null;
try {
cursor = mAppUsageEventDao.getAllForUsersAfter(queryUserIds, queryTimestamp);
} catch (RuntimeException e) {
Log.e(TAG, "query() from:" + uri + " error:" + e);
}
Log.w(TAG, "query app usage events in " + (mClock.millis() - timestamp) + "/ms");
return cursor;
}
private Cursor getAppUsageLatestTimestamp(Uri uri) { private Cursor getAppUsageLatestTimestamp(Uri uri) {
final long queryUserId = getQueryUserId(uri); final long queryUserId = getQueryUserId(uri);
if (queryUserId == DatabaseUtils.INVALID_USER_ID) { if (queryUserId == DatabaseUtils.INVALID_USER_ID) {
@@ -188,6 +210,26 @@ public class BatteryUsageContentProvider extends ContentProvider {
return cursor; return cursor;
} }
// If URI contains query parameter QUERY_KEY_USERID, use the value directly.
// Otherwise, return null.
private List<Long> getQueryUserIds(Uri uri) {
Log.d(TAG, "getQueryUserIds from uri: " + uri);
final String value = uri.getQueryParameter(DatabaseUtils.QUERY_KEY_USERID);
if (TextUtils.isEmpty(value)) {
Log.w(TAG, "empty query value");
return null;
}
try {
return Arrays.asList(value.split(","))
.stream()
.map(s -> Long.parseLong(s.trim()))
.collect(Collectors.toList());
} catch (NumberFormatException e) {
Log.e(TAG, "invalid query value: " + value, e);
return null;
}
}
// If URI contains query parameter QUERY_KEY_USERID, use the value directly. // If URI contains query parameter QUERY_KEY_USERID, use the value directly.
// Otherwise, return INVALID_USER_ID. // Otherwise, return INVALID_USER_ID.
private long getQueryUserId(Uri uri) { private long getQueryUserId(Uri uri) {
@@ -198,8 +240,9 @@ public class BatteryUsageContentProvider extends ContentProvider {
// If URI contains query parameter QUERY_KEY_TIMESTAMP, use the value directly. // If URI contains query parameter QUERY_KEY_TIMESTAMP, use the value directly.
// Otherwise, load the data for QUERY_DURATION_HOURS by default. // Otherwise, load the data for QUERY_DURATION_HOURS by default.
private long getQueryTimestamp(Uri uri, long defaultTimestamp) { private long getQueryTimestamp(Uri uri) {
Log.d(TAG, "getQueryTimestamp from uri: " + uri); Log.d(TAG, "getQueryTimestamp from uri: " + uri);
final long defaultTimestamp = mClock.millis() - QUERY_DURATION_HOURS.toMillis();
return getQueryValueFromUri(uri, DatabaseUtils.QUERY_KEY_TIMESTAMP, defaultTimestamp); return getQueryValueFromUri(uri, DatabaseUtils.QUERY_KEY_TIMESTAMP, defaultTimestamp);
} }

View File

@@ -209,6 +209,25 @@ public final class ConvertUtils {
return appUsageEventBuilder.build(); return appUsageEventBuilder.build();
} }
/** Converts to {@link AppUsageEvent} from {@link Cursor} */
public static AppUsageEvent convertToAppUsageEventFromCursor(final Cursor cursor) {
final AppUsageEvent.Builder eventBuilder = AppUsageEvent.newBuilder();
eventBuilder.setTimestamp(getLongFromCursor(cursor, AppUsageEventEntity.KEY_TIMESTAMP));
eventBuilder.setType(
AppUsageEventType.forNumber(
getIntegerFromCursor(
cursor, AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE)));
eventBuilder.setPackageName(
getStringFromCursor(cursor, AppUsageEventEntity.KEY_PACKAGE_NAME));
eventBuilder.setInstanceId(
getIntegerFromCursor(cursor, AppUsageEventEntity.KEY_INSTANCE_ID));
eventBuilder.setTaskRootPackageName(
getStringFromCursor(cursor, AppUsageEventEntity.KEY_TASK_ROOT_PACKAGE_NAME));
eventBuilder.setUserId(getLongFromCursor(cursor, AppUsageEventEntity.KEY_USER_ID));
eventBuilder.setUid(getLongFromCursor(cursor, AppUsageEventEntity.KEY_UID));
return eventBuilder.build();
}
/** Converts UTC timestamp to human readable local time string. */ /** Converts UTC timestamp to human readable local time string. */
public static String utcToLocalTime(Context context, long timestamp) { public static String utcToLocalTime(Context context, long timestamp) {
final Locale locale = getLocale(context); final Locale locale = getLocale(context);
@@ -331,4 +350,28 @@ public final class ConvertUtils {
return batteryInformationBuilder.build(); return batteryInformationBuilder.build();
} }
private static int getIntegerFromCursor(final Cursor cursor, final String key) {
final int columnIndex = cursor.getColumnIndex(key);
if (columnIndex >= 0) {
return cursor.getInt(columnIndex);
}
return 0;
}
private static long getLongFromCursor(final Cursor cursor, final String key) {
final int columnIndex = cursor.getColumnIndex(key);
if (columnIndex >= 0) {
return cursor.getLong(columnIndex);
}
return 0L;
}
private static String getStringFromCursor(final Cursor cursor, final String key) {
final int columnIndex = cursor.getColumnIndex(key);
if (columnIndex >= 0) {
return cursor.getString(columnIndex);
}
return "";
}
} }

View File

@@ -24,10 +24,14 @@ import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.Utils; import com.android.settings.Utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -63,9 +67,15 @@ public class DataProcessManager {
private final DataProcessor.UsageMapAsyncResponse mCallbackFunction; private final DataProcessor.UsageMapAsyncResponse mCallbackFunction;
private Context mContext; private Context mContext;
private UserManager mUserManager;
private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap; private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
// The start timestamp of battery level data. As we don't know when is the full charge cycle
// start time when loading app usage data, this value is used as the start time of querying app
// usage data.
private long mStartTimestampOfLevelData = 0;
private boolean mIsCurrentBatteryHistoryLoaded = false; private boolean mIsCurrentBatteryHistoryLoaded = false;
private boolean mIsCurrentAppUsageLoaded = false; private boolean mIsCurrentAppUsageLoaded = false;
private boolean mIsDatabaseAppUsageLoaded = false; private boolean mIsDatabaseAppUsageLoaded = false;
@@ -81,13 +91,15 @@ public class DataProcessManager {
Context context, Context context,
Handler handler, Handler handler,
final DataProcessor.UsageMapAsyncResponse callbackFunction, final DataProcessor.UsageMapAsyncResponse callbackFunction,
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, @NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) { @NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
mContext = context.getApplicationContext(); mContext = context.getApplicationContext();
mHandler = handler; mHandler = handler;
mUserManager = mContext.getSystemService(UserManager.class);
mCallbackFunction = callbackFunction; mCallbackFunction = callbackFunction;
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
mBatteryHistoryMap = batteryHistoryMap; mBatteryHistoryMap = batteryHistoryMap;
mStartTimestampOfLevelData = getStartTimestampOfBatteryLevelData();
} }
/** /**
@@ -102,6 +114,21 @@ public class DataProcessManager {
loadCurrentAppUsageList(); loadCurrentAppUsageList();
} }
@VisibleForTesting
long getStartTimestampOfBatteryLevelData() {
for (int dailyIndex = 0; dailyIndex < mHourlyBatteryLevelsPerDay.size(); dailyIndex++) {
if (mHourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
continue;
}
final List<Long> timestamps =
mHourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
if (timestamps.size() > 0) {
return timestamps.get(0);
}
}
return 0;
}
@VisibleForTesting @VisibleForTesting
List<AppUsageEvent> getAppUsageEventList() { List<AppUsageEvent> getAppUsageEventList() {
return mAppUsageEventList; return mAppUsageEventList;
@@ -164,12 +191,17 @@ public class DataProcessManager {
new AsyncTask<Void, Void, List<AppUsageEvent>>() { new AsyncTask<Void, Void, List<AppUsageEvent>>() {
@Override @Override
protected List<AppUsageEvent> doInBackground(Void... voids) { protected List<AppUsageEvent> doInBackground(Void... voids) {
if (!shouldLoadAppUsageData()) {
Log.d(TAG, "not loadCurrentAppUsageList");
return null;
}
final long startTime = System.currentTimeMillis(); final long startTime = System.currentTimeMillis();
// Loads the current battery usage data from the battery stats service. // Loads the current battery usage data from the battery stats service.
final int currentUserId = getCurrentUserId(); final int currentUserId = getCurrentUserId();
final int workProfileUserId = getWorkProfileUserId(); final int workProfileUserId = getWorkProfileUserId();
final UsageEvents usageEventsForCurrentUser = final UsageEvents usageEventsForCurrentUser =
DataProcessor.getAppUsageEventsForUser(mContext, currentUserId); DataProcessor.getAppUsageEventsForUser(
mContext, currentUserId, mStartTimestampOfLevelData);
// If fail to load usage events for current user, return null directly and screen-on // If fail to load usage events for current user, return null directly and screen-on
// time will not be shown in the UI. // time will not be shown in the UI.
if (usageEventsForCurrentUser == null) { if (usageEventsForCurrentUser == null) {
@@ -180,7 +212,7 @@ public class DataProcessManager {
if (workProfileUserId != Integer.MIN_VALUE) { if (workProfileUserId != Integer.MIN_VALUE) {
usageEventsForWorkProfile = usageEventsForWorkProfile =
DataProcessor.getAppUsageEventsForUser( DataProcessor.getAppUsageEventsForUser(
mContext, workProfileUserId); mContext, workProfileUserId, mStartTimestampOfLevelData);
} else { } else {
Log.d(TAG, "there is no work profile"); Log.d(TAG, "there is no work profile");
} }
@@ -203,16 +235,8 @@ public class DataProcessManager {
@Override @Override
protected void onPostExecute( protected void onPostExecute(
final List<AppUsageEvent> currentAppUsageList) { final List<AppUsageEvent> currentAppUsageList) {
final int currentUserId = getCurrentUserId(); if (currentAppUsageList == null || currentAppUsageList.isEmpty()) {
final UserManager userManager = mContext.getSystemService(UserManager.class); Log.d(TAG, "currentAppUsageList is null or empty");
// 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 { } else {
mAppUsageEventList.addAll(currentAppUsageList); mAppUsageEventList.addAll(currentAppUsageList);
} }
@@ -223,9 +247,36 @@ public class DataProcessManager {
} }
private void loadDatabaseAppUsageList() { private void loadDatabaseAppUsageList() {
// TODO: load app usage data from database. new AsyncTask<Void, Void, List<AppUsageEvent>>() {
mIsDatabaseAppUsageLoaded = true; @Override
tryToProcessAppUsageData(); protected List<AppUsageEvent> doInBackground(Void... voids) {
if (!shouldLoadAppUsageData()) {
Log.d(TAG, "not loadDatabaseAppUsageList");
return null;
}
final long startTime = System.currentTimeMillis();
// Loads the current battery usage data from the battery stats service.
final List<AppUsageEvent> appUsageEventList =
DatabaseUtils.getAppUsageEventForUsers(
mContext, Calendar.getInstance(), getCurrentUserIds(),
mStartTimestampOfLevelData);
Log.d(TAG, String.format("execute loadDatabaseAppUsageList size=%d in %d/ms",
appUsageEventList.size(), (System.currentTimeMillis() - startTime)));
return appUsageEventList;
}
@Override
protected void onPostExecute(
final List<AppUsageEvent> databaseAppUsageList) {
if (databaseAppUsageList == null || databaseAppUsageList.isEmpty()) {
Log.d(TAG, "databaseAppUsageList is null or empty");
} else {
mAppUsageEventList.addAll(databaseAppUsageList);
}
mIsDatabaseAppUsageLoaded = true;
tryToProcessAppUsageData();
}
}.execute();
} }
private void tryToProcessAppUsageData() { private void tryToProcessAppUsageData() {
@@ -243,6 +294,8 @@ public class DataProcessManager {
if (!mShowScreenOnTime) { if (!mShowScreenOnTime) {
return; return;
} }
// Sort the appUsageEventList in ascending order based on the timestamp.
Collections.sort(mAppUsageEventList, DataProcessor.TIMESTAMP_COMPARATOR);
// TODO: process app usage data to an intermediate result for further use. // TODO: process app usage data to an intermediate result for further use.
} }
@@ -262,14 +315,39 @@ public class DataProcessManager {
// then apply the callback function. // then apply the callback function.
} }
// Whether we should load app usage data from service or database.
private boolean shouldLoadAppUsageData() {
if (!mShowScreenOnTime) {
return false;
}
final int currentUserId = getCurrentUserId();
// If current user is locked, no need to load app usage data from service or database.
if (mUserManager == null || !mUserManager.isUserUnlocked(currentUserId)) {
Log.d(TAG, "shouldLoadAppUsageData: false, current user is locked");
mShowScreenOnTime = false;
return false;
}
return true;
}
// Returns the list of current user id and work profile id if exists.
private List<Integer> getCurrentUserIds() {
final List<Integer> userIds = new ArrayList<>();
userIds.add(getCurrentUserId());
final int workProfileUserId = getWorkProfileUserId();
if (workProfileUserId != Integer.MIN_VALUE) {
userIds.add(workProfileUserId);
}
return userIds;
}
private int getCurrentUserId() { private int getCurrentUserId() {
return mContext.getUserId(); return mContext.getUserId();
} }
private int getWorkProfileUserId() { private int getWorkProfileUserId() {
final UserHandle userHandle = final UserHandle userHandle =
Utils.getManagedProfile( Utils.getManagedProfile(mUserManager);
mContext.getSystemService(UserManager.class));
return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
} }
} }

View File

@@ -100,6 +100,8 @@ public final class DataProcessor {
public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER = public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER =
"CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER"; "CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER";
public static final Comparator<AppUsageEvent> TIMESTAMP_COMPARATOR =
Comparator.comparing(AppUsageEvent::getTimestamp);
/** 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 {
@@ -228,7 +230,8 @@ public final class DataProcessor {
* Gets the {@link UsageEvents} from system service for the specific user. * Gets the {@link UsageEvents} from system service for the specific user.
*/ */
@Nullable @Nullable
public static UsageEvents getAppUsageEventsForUser(Context context, final int userID) { public static UsageEvents getAppUsageEventsForUser(
Context context, final int userID, final long startTimestampOfLevelData) {
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();
context = DatabaseUtils.getOwnerContext(context); context = DatabaseUtils.getOwnerContext(context);
if (context == null) { if (context == null) {
@@ -240,8 +243,9 @@ public final class DataProcessor {
} }
final long sixDaysAgoTimestamp = final long sixDaysAgoTimestamp =
DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance()); DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
final long earliestTimestamp = Math.max(sixDaysAgoTimestamp, startTimestampOfLevelData);
final UsageEvents events = getAppUsageEventsForUser( final UsageEvents events = getAppUsageEventsForUser(
context, userManager, userID, sixDaysAgoTimestamp); context, userManager, userID, earliestTimestamp);
final long elapsedTime = System.currentTimeMillis() - start; final long elapsedTime = System.currentTimeMillis() - start;
Log.d(TAG, String.format("getAppUsageEventsForUser() for user %d in %d/ms", Log.d(TAG, String.format("getAppUsageEventsForUser() for user %d in %d/ms",
userID, elapsedTime)); userID, elapsedTime));
@@ -638,7 +642,7 @@ public final class DataProcessor {
@Nullable @Nullable
private static UsageEvents getAppUsageEventsForUser( private static UsageEvents getAppUsageEventsForUser(
Context context, final UserManager userManager, final int userID, Context context, final UserManager userManager, final int userID,
final long sixDaysAgoTimestamp) { final long earliestTimestamp) {
final String callingPackage = context.getPackageName(); final String callingPackage = context.getPackageName();
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
// When the user is not unlocked, UsageStatsManager will return null, so bypass the // When the user is not unlocked, UsageStatsManager will return null, so bypass the
@@ -648,7 +652,7 @@ public final class DataProcessor {
return null; return null;
} }
final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser( final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser(
context, userID, sixDaysAgoTimestamp); context, userID, earliestTimestamp);
return loadAppUsageEventsForUserFromService( return loadAppUsageEventsForUserFromService(
sUsageStatsManager, startTime, now, userID, callingPackage); sUsageStatsManager, startTime, now, userID, callingPackage);
} }

View File

@@ -46,6 +46,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
/** A utility class to operate battery usage database. */ /** A utility class to operate battery usage database. */
public final class DatabaseUtils { public final class DatabaseUtils {
@@ -93,6 +94,8 @@ public final class DatabaseUtils {
@VisibleForTesting @VisibleForTesting
static Supplier<Cursor> sFakeBatteryStateSupplier; static Supplier<Cursor> sFakeBatteryStateSupplier;
@VisibleForTesting @VisibleForTesting
static Supplier<Cursor> sFakeAppUsageEventSupplier;
@VisibleForTesting
static Supplier<Cursor> sFakeAppUsageLatestTimestampSupplier; static Supplier<Cursor> sFakeAppUsageLatestTimestampSupplier;
private DatabaseUtils() { private DatabaseUtils() {
@@ -125,6 +128,38 @@ public final class DatabaseUtils {
return Math.max(latestTimestamp, earliestTimestamp); return Math.max(latestTimestamp, earliestTimestamp);
} }
/** Returns the current user data in app usage event table. */
public static List<AppUsageEvent> getAppUsageEventForUsers(
Context context,
final Calendar calendar,
final List<Integer> userIds,
final long startTimestampOfLevelData) {
final long startTime = System.currentTimeMillis();
final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
final long queryTimestamp = Math.max(startTimestampOfLevelData, sixDaysAgoTimestamp);
Log.d(TAG, "sixDayAgoTimestamp: " + sixDaysAgoTimestamp);
final String queryUserIdString = userIds.stream()
.map(userId -> String.valueOf(userId))
.collect(Collectors.joining(","));
// Builds the content uri everytime to avoid cache.
final Uri appUsageEventUri =
new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(AUTHORITY)
.appendPath(APP_USAGE_EVENT_TABLE)
.appendQueryParameter(
QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
.appendQueryParameter(QUERY_KEY_USERID, queryUserIdString)
.build();
final List<AppUsageEvent> appUsageEventList =
loadAppUsageEventsFromContentProvider(context, appUsageEventUri);
Log.d(TAG, String.format("getAppUsageEventForUser userId=%s size=%d in %d/ms",
queryUserIdString, appUsageEventList.size(),
(System.currentTimeMillis() - startTime)));
return appUsageEventList;
}
/** Long: for timestamp and String: for BatteryHistEntry.getKey() */ /** Long: for timestamp and String: for BatteryHistEntry.getKey() */
public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge( public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
Context context, Calendar calendar) { Context context, Calendar calendar) {
@@ -357,6 +392,32 @@ public final class DatabaseUtils {
} }
} }
private static List<AppUsageEvent> loadAppUsageEventsFromContentProvider(
Context context, Uri appUsageEventUri) {
final List<AppUsageEvent> appUsageEventList = new ArrayList<>();
context = getOwnerContext(context);
if (context == null) {
return appUsageEventList;
}
try (Cursor cursor = sFakeAppUsageEventSupplier != null
? sFakeAppUsageEventSupplier.get()
: context.getContentResolver().query(appUsageEventUri, null, null, null)) {
if (cursor == null || cursor.getCount() == 0) {
return appUsageEventList;
}
// Loads and recovers all AppUsageEvent data from cursor.
while (cursor.moveToNext()) {
appUsageEventList.add(ConvertUtils.convertToAppUsageEventFromCursor(cursor));
}
try {
cursor.close();
} catch (Exception e) {
Log.e(TAG, "cursor.close() failed", e);
}
}
return appUsageEventList;
}
private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider( private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
Context context, Uri batteryStateUri) { Context context, Uri batteryStateUri) {
context = DatabaseUtils.getOwnerContext(context); context = DatabaseUtils.getOwnerContext(context);

View File

@@ -41,6 +41,11 @@ public interface AppUsageEventDao {
@Query("SELECT * FROM AppUsageEventEntity WHERE timestamp > :timestamp ORDER BY timestamp DESC") @Query("SELECT * FROM AppUsageEventEntity WHERE timestamp > :timestamp ORDER BY timestamp DESC")
List<AppUsageEventEntity> getAllAfter(long timestamp); List<AppUsageEventEntity> getAllAfter(long timestamp);
/** Gets the {@link Cursor} of all recorded data after a specific timestamp of the users. */
@Query("SELECT * FROM AppUsageEventEntity WHERE timestamp >= :timestamp"
+ " AND userId IN (:userIds) ORDER BY timestamp ASC")
Cursor getAllForUsersAfter(List<Long> userIds, long timestamp);
/** Gets the {@link Cursor} of the latest timestamp of the specific user. */ /** Gets the {@link Cursor} of the latest timestamp of the specific user. */
@Query("SELECT MAX(timestamp) as timestamp FROM AppUsageEventEntity WHERE userId = :userId") @Query("SELECT MAX(timestamp) as timestamp FROM AppUsageEventEntity WHERE userId = :userId")
Cursor getLatestTimestampOfUser(long userId); Cursor getLatestTimestampOfUser(long userId);

View File

@@ -43,13 +43,18 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows; import org.robolectric.Shadows;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** Tests for {@link BatteryUsageContentProvider}. */ /** Tests for {@link BatteryUsageContentProvider}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public final class BatteryUsageContentProviderTest { public final class BatteryUsageContentProviderTest {
private static final Uri VALID_BATTERY_STATE_CONTENT_URI = DatabaseUtils.BATTERY_CONTENT_URI; private static final Uri VALID_BATTERY_STATE_CONTENT_URI = DatabaseUtils.BATTERY_CONTENT_URI;
private static final long TIMESTAMP1 = System.currentTimeMillis();
private static final long TIMESTAMP2 = System.currentTimeMillis() + 2;
private static final long TIMESTAMP3 = System.currentTimeMillis() + 4;
private static final String PACKAGE_NAME1 = "com.android.settings1"; private static final String PACKAGE_NAME1 = "com.android.settings1";
private static final String PACKAGE_NAME2 = "com.android.settings2"; private static final String PACKAGE_NAME2 = "com.android.settings2";
private static final String PACKAGE_NAME3 = "com.android.settings3"; private static final String PACKAGE_NAME3 = "com.android.settings3";
@@ -180,29 +185,49 @@ public final class BatteryUsageContentProviderTest {
BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK); BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK);
} }
@Test
public void query_appUsageEvent_returnsExpectedResult() {
insertAppUsageEvent();
final List<Long> userIds1 = new ArrayList<>();
final long notExistingUserId = 3;
userIds1.add(USER_ID1);
userIds1.add(USER_ID2);
userIds1.add(notExistingUserId);
final Cursor cursor1 = getCursorOfAppUsage(userIds1, TIMESTAMP1);
assertThat(cursor1.getCount()).isEqualTo(3);
// Verifies the queried first battery state.
cursor1.moveToFirst();
assertThat(cursor1.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME1);
// Verifies the queried second battery state.
cursor1.moveToNext();
assertThat(cursor1.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME2);
// Verifies the queried third battery state.
cursor1.moveToNext();
assertThat(cursor1.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME3);
final List<Long> userIds2 = new ArrayList<>();
userIds2.add(USER_ID1);
final Cursor cursor2 = getCursorOfAppUsage(userIds2, TIMESTAMP3);
assertThat(cursor2.getCount()).isEqualTo(1);
// Verifies the queried first battery state.
cursor2.moveToFirst();
assertThat(cursor2.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME3);
}
@Test @Test
public void query_appUsageTimestamp_returnsExpectedResult() throws Exception { public void query_appUsageTimestamp_returnsExpectedResult() throws Exception {
mProvider.onCreate(); insertAppUsageEvent();
final long timestamp1 = System.currentTimeMillis();
final long timestamp2 = timestamp1 + 2;
final long timestamp3 = timestamp1 + 4;
// Inserts some valid testing data.
BatteryTestUtils.insertDataToAppUsageEventTable(
mContext, USER_ID1, timestamp1, PACKAGE_NAME1);
BatteryTestUtils.insertDataToAppUsageEventTable(
mContext, USER_ID2, timestamp2, PACKAGE_NAME2);
BatteryTestUtils.insertDataToAppUsageEventTable(
mContext, USER_ID1, timestamp3, PACKAGE_NAME3);
final Cursor cursor1 = getCursorOfLatestTimestamp(USER_ID1); final Cursor cursor1 = getCursorOfLatestTimestamp(USER_ID1);
assertThat(cursor1.getCount()).isEqualTo(1); assertThat(cursor1.getCount()).isEqualTo(1);
cursor1.moveToFirst(); cursor1.moveToFirst();
assertThat(cursor1.getLong(0)).isEqualTo(timestamp3); assertThat(cursor1.getLong(0)).isEqualTo(TIMESTAMP3);
final Cursor cursor2 = getCursorOfLatestTimestamp(USER_ID2); final Cursor cursor2 = getCursorOfLatestTimestamp(USER_ID2);
assertThat(cursor2.getCount()).isEqualTo(1); assertThat(cursor2.getCount()).isEqualTo(1);
cursor2.moveToFirst(); cursor2.moveToFirst();
assertThat(cursor2.getLong(0)).isEqualTo(timestamp2); assertThat(cursor2.getLong(0)).isEqualTo(TIMESTAMP2);
final long notExistingUserId = 3; final long notExistingUserId = 3;
final Cursor cursor3 = getCursorOfLatestTimestamp(notExistingUserId); final Cursor cursor3 = getCursorOfLatestTimestamp(notExistingUserId);
@@ -383,6 +408,17 @@ public final class BatteryUsageContentProviderTest {
return cursor; return cursor;
} }
private void insertAppUsageEvent() {
mProvider.onCreate();
// Inserts some valid testing data.
BatteryTestUtils.insertDataToAppUsageEventTable(
mContext, USER_ID1, TIMESTAMP1, PACKAGE_NAME1);
BatteryTestUtils.insertDataToAppUsageEventTable(
mContext, USER_ID2, TIMESTAMP2, PACKAGE_NAME2);
BatteryTestUtils.insertDataToAppUsageEventTable(
mContext, USER_ID1, TIMESTAMP3, PACKAGE_NAME3);
}
private Cursor getCursorOfLatestTimestamp(final long userId) { private Cursor getCursorOfLatestTimestamp(final long userId) {
final Uri appUsageLatestTimestampQueryContentUri = final Uri appUsageLatestTimestampQueryContentUri =
new Uri.Builder() new Uri.Builder()
@@ -394,10 +430,28 @@ public final class BatteryUsageContentProviderTest {
.build(); .build();
return mProvider.query( return mProvider.query(
appUsageLatestTimestampQueryContentUri, appUsageLatestTimestampQueryContentUri,
/*strings=*/ null, /*strings=*/ null,
/*s=*/ null, /*s=*/ null,
/*strings1=*/ null, /*strings1=*/ null,
/*s1=*/ null); /*s1=*/ null);
}
private Cursor getCursorOfAppUsage(final List<Long> userIds, final long queryTimestamp) {
final String queryUserIdString = userIds.stream()
.map(userId -> String.valueOf(userId))
.collect(Collectors.joining(","));
final Uri appUsageEventUri =
new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(DatabaseUtils.AUTHORITY)
.appendPath(DatabaseUtils.APP_USAGE_EVENT_TABLE)
.appendQueryParameter(
DatabaseUtils.QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
.appendQueryParameter(DatabaseUtils.QUERY_KEY_USERID, queryUserIdString)
.build();
return mProvider.query(
appUsageEventUri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null, /*s1=*/ null);
} }
} }

View File

@@ -27,6 +27,7 @@ import android.app.usage.UsageEvents.Event;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.MatrixCursor;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.BatteryUsageStats; import android.os.BatteryUsageStats;
import android.os.LocaleList; import android.os.LocaleList;
@@ -335,6 +336,68 @@ public final class ConvertUtilsTest {
assertThat(appUsageEvent).isNull(); assertThat(appUsageEvent).isNull();
} }
@Test
public void convertToAppUsageEventFromCursor_returnExpectedResult() {
final MatrixCursor cursor = new MatrixCursor(
new String[]{
AppUsageEventEntity.KEY_UID,
AppUsageEventEntity.KEY_USER_ID,
AppUsageEventEntity.KEY_PACKAGE_NAME,
AppUsageEventEntity.KEY_TIMESTAMP,
AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE,
AppUsageEventEntity.KEY_TASK_ROOT_PACKAGE_NAME,
AppUsageEventEntity.KEY_INSTANCE_ID});
cursor.addRow(
new Object[]{
101L,
1001L,
"com.android.settings1",
10001L,
AppUsageEventType.DEVICE_SHUTDOWN.getNumber(),
"com.android.settings2",
100001L});
cursor.moveToFirst();
final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEventFromCursor(cursor);
assertThat(appUsageEvent.getUid()).isEqualTo(101L);
assertThat(appUsageEvent.getUserId()).isEqualTo(1001L);
assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1");
assertThat(appUsageEvent.getTimestamp()).isEqualTo(10001L);
assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.DEVICE_SHUTDOWN);
assertThat(appUsageEvent.getTaskRootPackageName()).isEqualTo("com.android.settings2");
assertThat(appUsageEvent.getInstanceId()).isEqualTo(100001L);
}
@Test
public void convertToAppUsageEventFromCursor_emptyInstanceIdAndRootName_returnExpectedResult() {
final MatrixCursor cursor = new MatrixCursor(
new String[]{
AppUsageEventEntity.KEY_UID,
AppUsageEventEntity.KEY_USER_ID,
AppUsageEventEntity.KEY_PACKAGE_NAME,
AppUsageEventEntity.KEY_TIMESTAMP,
AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE});
cursor.addRow(
new Object[]{
101L,
1001L,
"com.android.settings1",
10001L,
AppUsageEventType.DEVICE_SHUTDOWN.getNumber()});
cursor.moveToFirst();
final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEventFromCursor(cursor);
assertThat(appUsageEvent.getUid()).isEqualTo(101L);
assertThat(appUsageEvent.getUserId()).isEqualTo(1001L);
assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1");
assertThat(appUsageEvent.getTimestamp()).isEqualTo(10001L);
assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.DEVICE_SHUTDOWN);
assertThat(appUsageEvent.getTaskRootPackageName()).isEqualTo("");
assertThat(appUsageEvent.getInstanceId()).isEqualTo(0);
}
@Test @Test
public void getLocale_nullContext_returnDefaultLocale() { public void getLocale_nullContext_returnDefaultLocale() {
assertThat(ConvertUtils.getLocale(/*context=*/ null)) assertThat(ConvertUtils.getLocale(/*context=*/ null))

View File

@@ -27,10 +27,13 @@ import static org.mockito.Mockito.spy;
import android.app.usage.IUsageStatsManager; import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents; import android.app.usage.UsageEvents;
import android.content.Context; import android.content.Context;
import android.database.MatrixCursor;
import android.os.Parcel; import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserManager; import android.os.UserManager;
import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -65,11 +68,31 @@ public final class DataProcessManagerTest {
mDataProcessManager = new DataProcessManager( mDataProcessManager = new DataProcessManager(
mContext, /*handler=*/ null, /*callbackFunction=*/ null, mContext, /*handler=*/ null, /*callbackFunction=*/ null,
/*hourlyBatteryLevelsPerDay=*/ null, /*batteryHistoryMap=*/ null); /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(), /*batteryHistoryMap=*/ null);
} }
@Test @Test
public void start_loadExpectedCurrentAppUsageData() throws RemoteException { public void start_loadEmptyDatabaseAppUsageData() {
final MatrixCursor cursor = new MatrixCursor(
new String[]{
AppUsageEventEntity.KEY_UID,
AppUsageEventEntity.KEY_PACKAGE_NAME,
AppUsageEventEntity.KEY_TIMESTAMP});
DatabaseUtils.sFakeAppUsageEventSupplier = () -> cursor;
doReturn(true).when(mUserManager).isUserUnlocked(anyInt());
mDataProcessManager.start();
assertThat(mDataProcessManager.getIsCurrentAppUsageLoaded()).isTrue();
assertThat(mDataProcessManager.getIsDatabaseAppUsageLoaded()).isTrue();
assertThat(mDataProcessManager.getIsCurrentBatteryHistoryLoaded()).isTrue();
assertThat(mDataProcessManager.getShowScreenOnTime()).isTrue();
assertThat(mDataProcessManager.getAppUsageEventList()).isEmpty();
}
@Test
public void start_loadExpectedAppUsageData() throws RemoteException {
// Fake current usage data.
final UsageEvents.Event event1 = final UsageEvents.Event event1 =
getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /*timestamp=*/ 1); getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /*timestamp=*/ 1);
final UsageEvents.Event event2 = final UsageEvents.Event event2 =
@@ -82,6 +105,22 @@ public final class DataProcessManagerTest {
.queryEventsForUser(anyLong(), anyLong(), anyInt(), any()); .queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
doReturn(true).when(mUserManager).isUserUnlocked(anyInt()); doReturn(true).when(mUserManager).isUserUnlocked(anyInt());
// Fake database usage data.
final MatrixCursor cursor = new MatrixCursor(
new String[]{
AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE,
AppUsageEventEntity.KEY_TIMESTAMP});
// Adds fake data into the cursor.
cursor.addRow(new Object[] {
AppUsageEventType.ACTIVITY_RESUMED.getNumber(), /*timestamp=*/ 3});
cursor.addRow(new Object[] {
AppUsageEventType.ACTIVITY_RESUMED.getNumber(), /*timestamp=*/ 4});
cursor.addRow(new Object[] {
AppUsageEventType.ACTIVITY_STOPPED.getNumber(), /*timestamp=*/ 5});
cursor.addRow(new Object[] {
AppUsageEventType.ACTIVITY_STOPPED.getNumber(), /*timestamp=*/ 6});
DatabaseUtils.sFakeAppUsageEventSupplier = () -> cursor;
mDataProcessManager.start(); mDataProcessManager.start();
assertThat(mDataProcessManager.getIsCurrentAppUsageLoaded()).isTrue(); assertThat(mDataProcessManager.getIsCurrentAppUsageLoaded()).isTrue();
@@ -89,11 +128,19 @@ public final class DataProcessManagerTest {
assertThat(mDataProcessManager.getIsCurrentBatteryHistoryLoaded()).isTrue(); assertThat(mDataProcessManager.getIsCurrentBatteryHistoryLoaded()).isTrue();
assertThat(mDataProcessManager.getShowScreenOnTime()).isTrue(); assertThat(mDataProcessManager.getShowScreenOnTime()).isTrue();
final List<AppUsageEvent> appUsageEventList = mDataProcessManager.getAppUsageEventList(); final List<AppUsageEvent> appUsageEventList = mDataProcessManager.getAppUsageEventList();
assertThat(appUsageEventList.size()).isEqualTo(2); assertThat(appUsageEventList.size()).isEqualTo(6);
assertAppUsageEvent( assertAppUsageEvent(
appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 1); appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 1);
assertAppUsageEvent( assertAppUsageEvent(
appUsageEventList.get(1), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 2); appUsageEventList.get(1), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 2);
assertAppUsageEvent(
appUsageEventList.get(2), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 3);
assertAppUsageEvent(
appUsageEventList.get(3), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 4);
assertAppUsageEvent(
appUsageEventList.get(4), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 5);
assertAppUsageEvent(
appUsageEventList.get(5), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 6);
} }
@Test @Test
@@ -106,6 +153,14 @@ public final class DataProcessManagerTest {
.when(mUsageStatsManager) .when(mUsageStatsManager)
.queryEventsForUser(anyLong(), anyLong(), anyInt(), any()); .queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
doReturn(false).when(mUserManager).isUserUnlocked(anyInt()); doReturn(false).when(mUserManager).isUserUnlocked(anyInt());
final MatrixCursor cursor = new MatrixCursor(
new String[]{
AppUsageEventEntity.KEY_UID,
AppUsageEventEntity.KEY_PACKAGE_NAME,
AppUsageEventEntity.KEY_TIMESTAMP});
// Adds fake data into the cursor.
cursor.addRow(new Object[] {101L, "app name1", 1001L});
DatabaseUtils.sFakeAppUsageEventSupplier = () -> cursor;
mDataProcessManager.start(); mDataProcessManager.start();
@@ -113,6 +168,42 @@ public final class DataProcessManagerTest {
assertThat(mDataProcessManager.getShowScreenOnTime()).isFalse(); assertThat(mDataProcessManager.getShowScreenOnTime()).isFalse();
} }
@Test
public void getStartTimestampOfBatteryLevelData_returnExpectedResult() {
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
final List<Long> timestamps = new ArrayList<>();
timestamps.add(101L);
timestamps.add(1001L);
final List<Integer> levels = new ArrayList<>();
levels.add(1);
levels.add(2);
hourlyBatteryLevelsPerDay.add(null);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final DataProcessManager dataProcessManager = new DataProcessManager(
mContext, /*handler=*/ null, /*callbackFunction=*/ null,
hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ null);
assertThat(dataProcessManager.getStartTimestampOfBatteryLevelData()).isEqualTo(101);
}
@Test
public void getStartTimestampOfBatteryLevelData_emptyLevels_returnZero() {
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
hourlyBatteryLevelsPerDay.add(null);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
final DataProcessManager dataProcessManager = new DataProcessManager(
mContext, /*handler=*/ null, /*callbackFunction=*/ null,
hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ null);
assertThat(dataProcessManager.getStartTimestampOfBatteryLevelData()).isEqualTo(0);
}
private UsageEvents getUsageEvents(final List<UsageEvents.Event> events) { private UsageEvents getUsageEvents(final List<UsageEvents.Event> events) {
UsageEvents usageEvents = new UsageEvents(events, new String[] {"package"}); UsageEvents usageEvents = new UsageEvents(events, new String[] {"package"});
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();

View File

@@ -213,7 +213,7 @@ public final class DataProcessorTest {
.when(mUsageStatsManager) .when(mUsageStatsManager)
.queryEventsForUser(anyLong(), anyLong(), anyInt(), any()); .queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId)) assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0))
.isEqualTo(mUsageEvents1); .isEqualTo(mUsageEvents1);
} }
@@ -223,7 +223,7 @@ public final class DataProcessorTest {
// Test locked user. // Test locked user.
doReturn(false).when(mUserManager).isUserUnlocked(userId); doReturn(false).when(mUserManager).isUserUnlocked(userId);
assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId)).isNull(); assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0)).isNull();
} }
@Test @Test
@@ -233,7 +233,7 @@ public final class DataProcessorTest {
doReturn(null) doReturn(null)
.when(mUsageStatsManager).queryEventsForUser(anyLong(), anyLong(), anyInt(), any()); .when(mUsageStatsManager).queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId)).isNull(); assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0)).isNull();
} }
@Test public void generateAppUsageEventListFromUsageEvents_returnExpectedResult() { @Test public void generateAppUsageEventListFromUsageEvents_returnExpectedResult() {
@@ -647,6 +647,7 @@ public final class DataProcessorTest {
public void getBatteryUsageMap_emptyHistoryMap_returnNull() { public void getBatteryUsageMap_emptyHistoryMap_returnNull() {
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay = final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>(); new ArrayList<>();
hourlyBatteryLevelsPerDay.add( hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>())); new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));

View File

@@ -276,6 +276,62 @@ public final class DatabaseUtilsTest {
mContext, /*userId=*/ 0, earliestTimestamp2)).isEqualTo(earliestTimestamp2); mContext, /*userId=*/ 0, earliestTimestamp2)).isEqualTo(earliestTimestamp2);
} }
@Test
public void getAppUsageEventForUsers_emptyCursorContent_returnEmptyMap() {
final MatrixCursor cursor = new MatrixCursor(
new String[]{
AppUsageEventEntity.KEY_UID,
AppUsageEventEntity.KEY_USER_ID,
AppUsageEventEntity.KEY_PACKAGE_NAME,
AppUsageEventEntity.KEY_TIMESTAMP,
AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE});
DatabaseUtils.sFakeAppUsageEventSupplier = () -> cursor;
assertThat(DatabaseUtils.getAppUsageEventForUsers(
mContext,
/*calendar=*/ null,
/*userIds=*/ new ArrayList<>(),
/*startTimestampOfLevelData=*/ 0)).isEmpty();
}
@Test
public void getAppUsageEventForUsers_nullCursor_returnEmptyMap() {
DatabaseUtils.sFakeAppUsageEventSupplier = () -> null;
assertThat(DatabaseUtils.getAppUsageEventForUsers(
mContext,
/*calendar=*/ null,
/*userIds=*/ new ArrayList<>(),
/*startTimestampOfLevelData=*/ 0)).isEmpty();
}
@Test
public void getAppUsageEventForUsers_returnExpectedMap() {
final Long timestamp1 = 1001L;
final Long timestamp2 = 1002L;
final MatrixCursor cursor = new MatrixCursor(
new String[]{
AppUsageEventEntity.KEY_UID,
AppUsageEventEntity.KEY_PACKAGE_NAME,
AppUsageEventEntity.KEY_TIMESTAMP});
// Adds fake data into the cursor.
cursor.addRow(new Object[] {101L, "app name1", timestamp1});
cursor.addRow(new Object[] {101L, "app name2", timestamp2});
cursor.addRow(new Object[] {101L, "app name3", timestamp2});
cursor.addRow(new Object[] {101L, "app name4", timestamp2});
DatabaseUtils.sFakeAppUsageEventSupplier = () -> cursor;
final List<AppUsageEvent> appUsageEventList = DatabaseUtils.getAppUsageEventForUsers(
mContext,
/*calendar=*/ null,
/*userIds=*/ new ArrayList<>(),
/*startTimestampOfLevelData=*/ 0);
assertThat(appUsageEventList.get(0).getPackageName()).isEqualTo("app name1");
assertThat(appUsageEventList.get(1).getPackageName()).isEqualTo("app name2");
assertThat(appUsageEventList.get(2).getPackageName()).isEqualTo("app name3");
assertThat(appUsageEventList.get(3).getPackageName()).isEqualTo("app name4");
}
@Test @Test
public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() { public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() {
final MatrixCursor cursor = new MatrixCursor( final MatrixCursor cursor = new MatrixCursor(

View File

@@ -31,11 +31,13 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** Tests for {@link AppUsageEventDao}. */ /** Tests for {@link AppUsageEventDao}. */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
public final class AppUsageEventDaoTest { public final class AppUsageEventDaoTest {
private static final int CURSOR_COLUMN_SIZE = 8;
private static final long TIMESTAMP1 = System.currentTimeMillis(); private static final long TIMESTAMP1 = System.currentTimeMillis();
private static final long TIMESTAMP2 = System.currentTimeMillis() + 2; private static final long TIMESTAMP2 = System.currentTimeMillis() + 2;
private static final long TIMESTAMP3 = System.currentTimeMillis() + 4; private static final long TIMESTAMP3 = System.currentTimeMillis() + 4;
@@ -77,6 +79,36 @@ public final class AppUsageEventDaoTest {
assertAppUsageEvent(entities.get(1), TIMESTAMP2, PACKAGE_NAME2); assertAppUsageEvent(entities.get(1), TIMESTAMP2, PACKAGE_NAME2);
} }
@Test
public void appUsageEventDao_getAllForUsersAfter() {
final List<Long> userIds1 = new ArrayList<>();
final long notExistingUserId = 3;
userIds1.add(USER_ID1);
userIds1.add(USER_ID2);
userIds1.add(notExistingUserId);
final Cursor cursor1 = mAppUsageEventDao.getAllForUsersAfter(userIds1, TIMESTAMP1);
assertThat(cursor1.getCount()).isEqualTo(3);
assertThat(cursor1.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
// Verifies the queried first battery state.
cursor1.moveToFirst();
assertThat(cursor1.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME1);
// Verifies the queried second battery state.
cursor1.moveToNext();
assertThat(cursor1.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME2);
// Verifies the queried third battery state.
cursor1.moveToNext();
assertThat(cursor1.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME3);
final List<Long> userIds2 = new ArrayList<>();
userIds2.add(USER_ID1);
final Cursor cursor2 = mAppUsageEventDao.getAllForUsersAfter(userIds2, TIMESTAMP3);
assertThat(cursor2.getCount()).isEqualTo(1);
assertThat(cursor2.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
// Verifies the queried first battery state.
cursor2.moveToFirst();
assertThat(cursor2.getString(5 /*packageName*/)).isEqualTo(PACKAGE_NAME3);
}
@Test @Test
public void appUsageEventDao_getLatestTimestampOfUser() throws Exception { public void appUsageEventDao_getLatestTimestampOfUser() throws Exception {
final Cursor cursor1 = mAppUsageEventDao.getLatestTimestampOfUser(USER_ID1); final Cursor cursor1 = mAppUsageEventDao.getLatestTimestampOfUser(USER_ID1);