From 97924455ffcbc7e0496cff897d12072ed75b4c37 Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Mon, 19 Dec 2022 19:59:13 +0800 Subject: [PATCH] Implement the app usage data loading from database function. Bug: 260964903 Test: make RunSettingsRoboTests + manual Change-Id: I459dbdebe53e6b7421642955f36976b3e7c95fcb --- .../BatteryUsageContentProvider.java | 49 +++++++- .../fuelgauge/batteryusage/ConvertUtils.java | 43 +++++++ .../batteryusage/DataProcessManager.java | 116 +++++++++++++++--- .../fuelgauge/batteryusage/DataProcessor.java | 12 +- .../fuelgauge/batteryusage/DatabaseUtils.java | 61 +++++++++ .../batteryusage/db/AppUsageEventDao.java | 5 + .../BatteryUsageContentProviderTest.java | 90 +++++++++++--- .../batteryusage/ConvertUtilsTest.java | 63 ++++++++++ .../batteryusage/DataProcessManagerTest.java | 97 ++++++++++++++- .../batteryusage/DataProcessorTest.java | 7 +- .../batteryusage/DatabaseUtilsTest.java | 56 +++++++++ .../batteryusage/db/AppUsageEventDaoTest.java | 32 +++++ 12 files changed, 581 insertions(+), 50 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java index 4abcdc3268c..42937e5688a 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java @@ -37,6 +37,9 @@ import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; import java.time.Clock; 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. */ public class BatteryUsageContentProvider extends ContentProvider { @@ -100,6 +103,8 @@ public class BatteryUsageContentProvider extends ContentProvider { switch (sUriMatcher.match(uri)) { case BATTERY_STATE_CODE: return getBatteryStates(uri); + case APP_USAGE_EVENT_CODE: + return getAppUsageEvents(uri); case APP_USAGE_LATEST_TIMESTAMP_CODE: return getAppUsageLatestTimestamp(uri); default: @@ -153,8 +158,7 @@ public class BatteryUsageContentProvider extends ContentProvider { } private Cursor getBatteryStates(Uri uri) { - final long defaultTimestamp = mClock.millis() - QUERY_DURATION_HOURS.toMillis(); - final long queryTimestamp = getQueryTimestamp(uri, defaultTimestamp); + final long queryTimestamp = getQueryTimestamp(uri); return getBatteryStates(uri, queryTimestamp); } @@ -171,6 +175,24 @@ public class BatteryUsageContentProvider extends ContentProvider { return cursor; } + private Cursor getAppUsageEvents(Uri uri) { + final List 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) { final long queryUserId = getQueryUserId(uri); if (queryUserId == DatabaseUtils.INVALID_USER_ID) { @@ -188,6 +210,26 @@ public class BatteryUsageContentProvider extends ContentProvider { return cursor; } + // If URI contains query parameter QUERY_KEY_USERID, use the value directly. + // Otherwise, return null. + private List 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. // Otherwise, return INVALID_USER_ID. 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. // 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); + final long defaultTimestamp = mClock.millis() - QUERY_DURATION_HOURS.toMillis(); return getQueryValueFromUri(uri, DatabaseUtils.QUERY_KEY_TIMESTAMP, defaultTimestamp); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java index 38879d9e820..2e977fd143d 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java @@ -209,6 +209,25 @@ public final class ConvertUtils { 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. */ public static String utcToLocalTime(Context context, long timestamp) { final Locale locale = getLocale(context); @@ -331,4 +350,28 @@ public final class ConvertUtils { 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 ""; + } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java index 350bbcef0ba..784f64fab1e 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java @@ -24,10 +24,14 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.settings.Utils; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -63,9 +67,15 @@ public class DataProcessManager { private final DataProcessor.UsageMapAsyncResponse mCallbackFunction; private Context mContext; + private UserManager mUserManager; private List mHourlyBatteryLevelsPerDay; private Map> 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 mIsCurrentAppUsageLoaded = false; private boolean mIsDatabaseAppUsageLoaded = false; @@ -81,13 +91,15 @@ public class DataProcessManager { Context context, Handler handler, final DataProcessor.UsageMapAsyncResponse callbackFunction, - final List hourlyBatteryLevelsPerDay, - final Map> batteryHistoryMap) { + @NonNull final List hourlyBatteryLevelsPerDay, + @NonNull final Map> batteryHistoryMap) { mContext = context.getApplicationContext(); mHandler = handler; + mUserManager = mContext.getSystemService(UserManager.class); mCallbackFunction = callbackFunction; mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; mBatteryHistoryMap = batteryHistoryMap; + mStartTimestampOfLevelData = getStartTimestampOfBatteryLevelData(); } /** @@ -102,6 +114,21 @@ public class DataProcessManager { loadCurrentAppUsageList(); } + @VisibleForTesting + long getStartTimestampOfBatteryLevelData() { + for (int dailyIndex = 0; dailyIndex < mHourlyBatteryLevelsPerDay.size(); dailyIndex++) { + if (mHourlyBatteryLevelsPerDay.get(dailyIndex) == null) { + continue; + } + final List timestamps = + mHourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps(); + if (timestamps.size() > 0) { + return timestamps.get(0); + } + } + return 0; + } + @VisibleForTesting List getAppUsageEventList() { return mAppUsageEventList; @@ -164,12 +191,17 @@ public class DataProcessManager { new AsyncTask>() { @Override protected List doInBackground(Void... voids) { + if (!shouldLoadAppUsageData()) { + Log.d(TAG, "not loadCurrentAppUsageList"); + return null; + } 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); + DataProcessor.getAppUsageEventsForUser( + mContext, currentUserId, mStartTimestampOfLevelData); // 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) { @@ -180,7 +212,7 @@ public class DataProcessManager { if (workProfileUserId != Integer.MIN_VALUE) { usageEventsForWorkProfile = DataProcessor.getAppUsageEventsForUser( - mContext, workProfileUserId); + mContext, workProfileUserId, mStartTimestampOfLevelData); } else { Log.d(TAG, "there is no work profile"); } @@ -203,16 +235,8 @@ public class DataProcessManager { @Override protected void onPostExecute( final List 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"); + if (currentAppUsageList == null || currentAppUsageList.isEmpty()) { + Log.d(TAG, "currentAppUsageList is null or empty"); } else { mAppUsageEventList.addAll(currentAppUsageList); } @@ -223,9 +247,36 @@ public class DataProcessManager { } private void loadDatabaseAppUsageList() { - // TODO: load app usage data from database. - mIsDatabaseAppUsageLoaded = true; - tryToProcessAppUsageData(); + new AsyncTask>() { + @Override + protected List 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 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 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() { @@ -243,6 +294,8 @@ public class DataProcessManager { if (!mShowScreenOnTime) { 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. } @@ -262,14 +315,39 @@ public class DataProcessManager { // 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 getCurrentUserIds() { + final List userIds = new ArrayList<>(); + userIds.add(getCurrentUserId()); + final int workProfileUserId = getWorkProfileUserId(); + if (workProfileUserId != Integer.MIN_VALUE) { + userIds.add(workProfileUserId); + } + return userIds; + } + private int getCurrentUserId() { return mContext.getUserId(); } private int getWorkProfileUserId() { final UserHandle userHandle = - Utils.getManagedProfile( - mContext.getSystemService(UserManager.class)); + Utils.getManagedProfile(mUserManager); return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index bc5c031d399..14455883367 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -100,6 +100,8 @@ public final class DataProcessor { public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER = "CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER"; + public static final Comparator TIMESTAMP_COMPARATOR = + Comparator.comparing(AppUsageEvent::getTimestamp); /** A callback listener when battery usage loading async task is executed. */ public interface UsageMapAsyncResponse { @@ -228,7 +230,8 @@ public final class DataProcessor { * Gets the {@link UsageEvents} from system service for the specific user. */ @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(); context = DatabaseUtils.getOwnerContext(context); if (context == null) { @@ -240,8 +243,9 @@ public final class DataProcessor { } final long sixDaysAgoTimestamp = DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance()); + final long earliestTimestamp = Math.max(sixDaysAgoTimestamp, startTimestampOfLevelData); final UsageEvents events = getAppUsageEventsForUser( - context, userManager, userID, sixDaysAgoTimestamp); + context, userManager, userID, earliestTimestamp); final long elapsedTime = System.currentTimeMillis() - start; Log.d(TAG, String.format("getAppUsageEventsForUser() for user %d in %d/ms", userID, elapsedTime)); @@ -638,7 +642,7 @@ public final class DataProcessor { @Nullable private static UsageEvents getAppUsageEventsForUser( Context context, final UserManager userManager, final int userID, - final long sixDaysAgoTimestamp) { + final long earliestTimestamp) { final String callingPackage = context.getPackageName(); final long now = System.currentTimeMillis(); // When the user is not unlocked, UsageStatsManager will return null, so bypass the @@ -648,7 +652,7 @@ public final class DataProcessor { return null; } final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser( - context, userID, sixDaysAgoTimestamp); + context, userID, earliestTimestamp); return loadAppUsageEventsForUserFromService( sUsageStatsManager, startTime, now, userID, callingPackage); } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java index f6b4e03d80e..b52938a7ba0 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; +import java.util.stream.Collectors; /** A utility class to operate battery usage database. */ public final class DatabaseUtils { @@ -93,6 +94,8 @@ public final class DatabaseUtils { @VisibleForTesting static Supplier sFakeBatteryStateSupplier; @VisibleForTesting + static Supplier sFakeAppUsageEventSupplier; + @VisibleForTesting static Supplier sFakeAppUsageLatestTimestampSupplier; private DatabaseUtils() { @@ -125,6 +128,38 @@ public final class DatabaseUtils { return Math.max(latestTimestamp, earliestTimestamp); } + /** Returns the current user data in app usage event table. */ + public static List getAppUsageEventForUsers( + Context context, + final Calendar calendar, + final List 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 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() */ public static Map> getHistoryMapSinceLastFullCharge( Context context, Calendar calendar) { @@ -357,6 +392,32 @@ public final class DatabaseUtils { } } + private static List loadAppUsageEventsFromContentProvider( + Context context, Uri appUsageEventUri) { + final List 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> loadHistoryMapFromContentProvider( Context context, Uri batteryStateUri) { context = DatabaseUtils.getOwnerContext(context); diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java index 578a1ffadad..19360f43148 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java +++ b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java @@ -41,6 +41,11 @@ public interface AppUsageEventDao { @Query("SELECT * FROM AppUsageEventEntity WHERE timestamp > :timestamp ORDER BY timestamp DESC") List 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 userIds, long timestamp); + /** Gets the {@link Cursor} of the latest timestamp of the specific user. */ @Query("SELECT MAX(timestamp) as timestamp FROM AppUsageEventEntity WHERE userId = :userId") Cursor getLatestTimestampOfUser(long userId); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java index b43727d7197..e5547c59b5c 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java @@ -43,13 +43,18 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.Shadows; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** Tests for {@link BatteryUsageContentProvider}. */ @RunWith(RobolectricTestRunner.class) public final class BatteryUsageContentProviderTest { 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_NAME2 = "com.android.settings2"; private static final String PACKAGE_NAME3 = "com.android.settings3"; @@ -180,29 +185,49 @@ public final class BatteryUsageContentProviderTest { BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK); } + @Test + public void query_appUsageEvent_returnsExpectedResult() { + insertAppUsageEvent(); + + final List 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 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 public void query_appUsageTimestamp_returnsExpectedResult() throws Exception { - mProvider.onCreate(); - 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); + insertAppUsageEvent(); final Cursor cursor1 = getCursorOfLatestTimestamp(USER_ID1); assertThat(cursor1.getCount()).isEqualTo(1); cursor1.moveToFirst(); - assertThat(cursor1.getLong(0)).isEqualTo(timestamp3); + assertThat(cursor1.getLong(0)).isEqualTo(TIMESTAMP3); final Cursor cursor2 = getCursorOfLatestTimestamp(USER_ID2); assertThat(cursor2.getCount()).isEqualTo(1); cursor2.moveToFirst(); - assertThat(cursor2.getLong(0)).isEqualTo(timestamp2); + assertThat(cursor2.getLong(0)).isEqualTo(TIMESTAMP2); final long notExistingUserId = 3; final Cursor cursor3 = getCursorOfLatestTimestamp(notExistingUserId); @@ -383,6 +408,17 @@ public final class BatteryUsageContentProviderTest { 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) { final Uri appUsageLatestTimestampQueryContentUri = new Uri.Builder() @@ -394,10 +430,28 @@ public final class BatteryUsageContentProviderTest { .build(); return mProvider.query( - appUsageLatestTimestampQueryContentUri, - /*strings=*/ null, - /*s=*/ null, - /*strings1=*/ null, - /*s1=*/ null); + appUsageLatestTimestampQueryContentUri, + /*strings=*/ null, + /*s=*/ null, + /*strings1=*/ null, + /*s1=*/ null); + } + + private Cursor getCursorOfAppUsage(final List 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); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java index 42cd7ef99c3..aee8398198c 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java @@ -27,6 +27,7 @@ import android.app.usage.UsageEvents.Event; import android.content.ContentValues; import android.content.Context; import android.content.pm.PackageManager; +import android.database.MatrixCursor; import android.os.BatteryManager; import android.os.BatteryUsageStats; import android.os.LocaleList; @@ -335,6 +336,68 @@ public final class ConvertUtilsTest { 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 public void getLocale_nullContext_returnDefaultLocale() { assertThat(ConvertUtils.getLocale(/*context=*/ null)) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java index a3578cb8f9d..1bfff0749bb 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java @@ -27,10 +27,13 @@ import static org.mockito.Mockito.spy; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; import android.content.Context; +import android.database.MatrixCursor; import android.os.Parcel; import android.os.RemoteException; import android.os.UserManager; +import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -65,11 +68,31 @@ public final class DataProcessManagerTest { mDataProcessManager = new DataProcessManager( mContext, /*handler=*/ null, /*callbackFunction=*/ null, - /*hourlyBatteryLevelsPerDay=*/ null, /*batteryHistoryMap=*/ null); + /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(), /*batteryHistoryMap=*/ null); } @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 = getUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, /*timestamp=*/ 1); final UsageEvents.Event event2 = @@ -82,6 +105,22 @@ public final class DataProcessManagerTest { .queryEventsForUser(anyLong(), anyLong(), anyInt(), any()); 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(); assertThat(mDataProcessManager.getIsCurrentAppUsageLoaded()).isTrue(); @@ -89,11 +128,19 @@ public final class DataProcessManagerTest { assertThat(mDataProcessManager.getIsCurrentBatteryHistoryLoaded()).isTrue(); assertThat(mDataProcessManager.getShowScreenOnTime()).isTrue(); final List appUsageEventList = mDataProcessManager.getAppUsageEventList(); - assertThat(appUsageEventList.size()).isEqualTo(2); + assertThat(appUsageEventList.size()).isEqualTo(6); assertAppUsageEvent( appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 1); assertAppUsageEvent( 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 @@ -106,6 +153,14 @@ public final class DataProcessManagerTest { .when(mUsageStatsManager) .queryEventsForUser(anyLong(), anyLong(), anyInt(), any()); 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(); @@ -113,6 +168,42 @@ public final class DataProcessManagerTest { assertThat(mDataProcessManager.getShowScreenOnTime()).isFalse(); } + @Test + public void getStartTimestampOfBatteryLevelData_returnExpectedResult() { + final List hourlyBatteryLevelsPerDay = + new ArrayList<>(); + final List timestamps = new ArrayList<>(); + timestamps.add(101L); + timestamps.add(1001L); + final List 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 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 events) { UsageEvents usageEvents = new UsageEvents(events, new String[] {"package"}); Parcel parcel = Parcel.obtain(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index f0412dfa76e..747acfe1a37 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -213,7 +213,7 @@ public final class DataProcessorTest { .when(mUsageStatsManager) .queryEventsForUser(anyLong(), anyLong(), anyInt(), any()); - assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId)) + assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0)) .isEqualTo(mUsageEvents1); } @@ -223,7 +223,7 @@ public final class DataProcessorTest { // Test locked user. doReturn(false).when(mUserManager).isUserUnlocked(userId); - assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId)).isNull(); + assertThat(DataProcessor.getAppUsageEventsForUser(mContext, userId, 0)).isNull(); } @Test @@ -233,7 +233,7 @@ public final class DataProcessorTest { doReturn(null) .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() { @@ -647,6 +647,7 @@ public final class DataProcessorTest { public void getBatteryUsageMap_emptyHistoryMap_returnNull() { final List hourlyBatteryLevelsPerDay = new ArrayList<>(); + hourlyBatteryLevelsPerDay.add( new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>())); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java index a04baa0320e..7d04e859586 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java @@ -276,6 +276,62 @@ public final class DatabaseUtilsTest { 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 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 public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() { final MatrixCursor cursor = new MatrixCursor( diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDaoTest.java index ade585f5f07..4883d4f34fd 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDaoTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDaoTest.java @@ -31,11 +31,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import java.util.ArrayList; import java.util.List; /** Tests for {@link AppUsageEventDao}. */ @RunWith(RobolectricTestRunner.class) public final class AppUsageEventDaoTest { + private static final int CURSOR_COLUMN_SIZE = 8; private static final long TIMESTAMP1 = System.currentTimeMillis(); private static final long TIMESTAMP2 = System.currentTimeMillis() + 2; private static final long TIMESTAMP3 = System.currentTimeMillis() + 4; @@ -77,6 +79,36 @@ public final class AppUsageEventDaoTest { assertAppUsageEvent(entities.get(1), TIMESTAMP2, PACKAGE_NAME2); } + @Test + public void appUsageEventDao_getAllForUsersAfter() { + final List 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 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 public void appUsageEventDao_getLatestTimestampOfUser() throws Exception { final Cursor cursor1 = mAppUsageEventDao.getLatestTimestampOfUser(USER_ID1);