Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 30317dd2 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Query usage event for a longer time period and then trim the usage...

Merge "Query usage event for a longer time period and then trim the usage events outside the expected period to make sure the app usage calculation near the boundaries are correct."
parents eac5326a 4a6b2655
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -376,7 +376,7 @@ public class DataProcessManager {
        // Generates the indexed AppUsagePeriod list data for each corresponding time slot for
        // further use.
        mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(
                mHourlyBatteryLevelsPerDay, mAppUsageEventList, mStartTimestampOfLevelData);
                mHourlyBatteryLevelsPerDay, mAppUsageEventList);
    }

    private void tryToGenerateFinalDataAndApplyCallback() {
+74 −65
Original line number Diff line number Diff line
@@ -72,7 +72,6 @@ import java.util.stream.Stream;
 * usage UI.
 */
public final class DataProcessor {
    private static final boolean DEBUG = false;
    private static final String TAG = "DataProcessor";
    private static final int POWER_COMPONENT_SYSTEM_SERVICES = 7;
    private static final int POWER_COMPONENT_WAKELOCK = 12;
@@ -98,6 +97,9 @@ public final class DataProcessor {
    @VisibleForTesting
    static long sFakeCurrentTimeMillis = 0;

    @VisibleForTesting
    static boolean sDebug = false;

    @VisibleForTesting
    static IUsageStatsManager sUsageStatsManager =
            IUsageStatsManager.Stub.asInterface(
@@ -250,8 +252,7 @@ public final class DataProcessor {
    public static Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
            generateAppUsagePeriodMap(
            final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
            final List<AppUsageEvent> appUsageEventList,
            final long startTimestampOfLevelData) {
            final List<AppUsageEvent> appUsageEventList) {
        if (appUsageEventList.isEmpty()) {
            Log.w(TAG, "appUsageEventList is empty");
            return null;
@@ -261,12 +262,8 @@ public final class DataProcessor {
        Collections.sort(appUsageEventList, TIMESTAMP_COMPARATOR);
        final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> resultMap =
                new HashMap<>();
        // Starts from the first index within expected time range. The events before the time range
        // will be bypassed directly.
        int currentAppUsageEventIndex =
                getFirstUsageEventIndex(appUsageEventList, startTimestampOfLevelData);
        final int appUsageEventSize = appUsageEventList.size();

        final long dailySize = hourlyBatteryLevelsPerDay.size();
        for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
            final Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>> dailyMap =
                    new HashMap<>();
@@ -275,32 +272,21 @@ public final class DataProcessor {
                continue;
            }
            final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
            final long hourlySize = timestamps.size() - 1;
            for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
                // The start and end timestamps of this slot should be the adjacent timestamps.
                final long startTimestamp = timestamps.get(hourlyIndex);
                final long endTimestamp = timestamps.get(hourlyIndex + 1);
                // The final slot is to show the data from last even hour until now but the
                // timestamp in hourlyBatteryLevelsPerDay is not the real value. So use current
                // timestamp instead of reading the timestamp from hourlyBatteryLevelsPerDay here.
                final long endTimestamp =
                        dailyIndex == dailySize - 1 && hourlyIndex == hourlySize - 1 && !sDebug
                                ? System.currentTimeMillis() : timestamps.get(hourlyIndex + 1);

                // Gets the app usage event list for this hourly slot first.
                final List<AppUsageEvent> hourlyAppUsageEventList = new ArrayList<>();
                while (currentAppUsageEventIndex < appUsageEventSize) {
                    // If current event is null, go for next directly.
                    if (appUsageEventList.get(currentAppUsageEventIndex) == null) {
                        currentAppUsageEventIndex++;
                        continue;
                    }
                    final long timestamp =
                            appUsageEventList.get(currentAppUsageEventIndex).getTimestamp();
                    // If the timestamp is already later than the end time, stop the loop.
                    if (timestamp >= endTimestamp) {
                        break;
                    }
                    // If timestamp is within the time range, add it into the list.
                    if (timestamp >= startTimestamp) {
                        hourlyAppUsageEventList.add(
                                appUsageEventList.get(currentAppUsageEventIndex));
                    }
                    currentAppUsageEventIndex++;
                }
                final List<AppUsageEvent> hourlyAppUsageEventList =
                        getAppUsageEventListWithinTimeRangeWithBuffer(
                                appUsageEventList, startTimestamp, endTimestamp);

                // The value could be null when there is no data in the hourly slot.
                dailyMap.put(
@@ -785,13 +771,11 @@ public final class DataProcessor {
            final List<AppUsageEvent> usageEvents, final long startTime, final long endTime) {
        final List<AppUsagePeriod> usagePeriodList = new ArrayList<>();
        AppUsagePeriod.Builder pendingUsagePeriod = AppUsagePeriod.newBuilder();
        boolean hasMetStartEvent = false;

        for (final AppUsageEvent event : usageEvents) {
            final long eventTime = event.getTimestamp();

            if (event.getType() == AppUsageEventType.ACTIVITY_RESUMED) {
                hasMetStartEvent = true;
                // If there is an existing start time, simply ignore this start event.
                // If there was no start time, then start a new period.
                if (!pendingUsagePeriod.hasStartTime()) {
@@ -800,37 +784,31 @@ public final class DataProcessor {
            } else if (event.getType() == AppUsageEventType.ACTIVITY_STOPPED) {
                pendingUsagePeriod.setEndTime(eventTime);
                if (!pendingUsagePeriod.hasStartTime()) {
                    // If we haven't met start event in this list, the start event might happen
                    // in the previous time slot. use the startTime for this period.
                    // Otherwise, add one for the default duration.
                    if (!hasMetStartEvent) {
                        hasMetStartEvent = true;
                        pendingUsagePeriod.setStartTime(startTime);
                    } else {
                    pendingUsagePeriod.setStartTime(
                            getStartTimeForIncompleteUsagePeriod(pendingUsagePeriod));
                }
                }
                // If we already have start time, add it directly.
                addToPeriodList(usagePeriodList, pendingUsagePeriod.build());
                validateAndAddToPeriodList(
                        usagePeriodList, pendingUsagePeriod.build(), startTime, endTime);
                pendingUsagePeriod.clear();
            } else if (event.getType() == AppUsageEventType.DEVICE_SHUTDOWN) {
                hasMetStartEvent = true;
                // The end event might be lost when device is shutdown. Use the estimated end
                // time for the period.
                if (pendingUsagePeriod.hasStartTime()) {
                    pendingUsagePeriod.setEndTime(
                            getEndTimeForIncompleteUsagePeriod(pendingUsagePeriod, eventTime));
                    addToPeriodList(usagePeriodList, pendingUsagePeriod.build());
                    validateAndAddToPeriodList(
                            usagePeriodList, pendingUsagePeriod.build(), startTime, endTime);
                    pendingUsagePeriod.clear();
                }
            }
        }
        // If there exists unclosed period, the stop event might happen in the next time
        // slot. Use the endTime for the period.
        if (pendingUsagePeriod.hasStartTime()) {
        if (pendingUsagePeriod.hasStartTime() && pendingUsagePeriod.getStartTime() < endTime) {
            pendingUsagePeriod.setEndTime(endTime);
            addToPeriodList(usagePeriodList, pendingUsagePeriod.build());
            validateAndAddToPeriodList(
                    usagePeriodList, pendingUsagePeriod.build(), startTime, endTime);
            pendingUsagePeriod.clear();
        }
        return usagePeriodList;
@@ -881,12 +859,56 @@ public final class DataProcessor {
        }
    }

    private static void addToPeriodList(
            final List<AppUsagePeriod> appUsagePeriodList, final AppUsagePeriod appUsagePeriod) {
    /**
     * Generates the list of {@link AppUsageEvent} within the specific time range.
     * The buffer is added to make sure the app usage calculation near the boundaries is correct.
     *
     * Note: The appUsageEventList should have been sorted when calling this function.
     */
    private static List<AppUsageEvent> getAppUsageEventListWithinTimeRangeWithBuffer(
            final List<AppUsageEvent> appUsageEventList, final long startTime, final long endTime) {
        final long start = startTime - DatabaseUtils.USAGE_QUERY_BUFFER_HOURS;
        final long end = endTime + DatabaseUtils.USAGE_QUERY_BUFFER_HOURS;
        final List<AppUsageEvent> resultList = new ArrayList<>();
        for (final AppUsageEvent event : appUsageEventList) {
            final long eventTime = event.getTimestamp();
            // Because the appUsageEventList has been sorted, if any event is already after the end
            // time, all the following events should be able to drop directly.
            if (eventTime > end) {
                break;
            }
            // If the event timestamp is in [start, end], add it into the result list.
            if (eventTime >= start) {
                resultList.add(event);
            }
        }
        return resultList;
    }

    private static void validateAndAddToPeriodList(
            final List<AppUsagePeriod> appUsagePeriodList,
            final AppUsagePeriod appUsagePeriod,
            final long startTime,
            final long endTime) {
        final long periodStartTime =
                trimPeriodTime(appUsagePeriod.getStartTime(), startTime, endTime);
        final long periodEndTime = trimPeriodTime(appUsagePeriod.getEndTime(), startTime, endTime);
        // Only when the period is valid, add it into the list.
        if (appUsagePeriod.getStartTime() < appUsagePeriod.getEndTime()) {
            appUsagePeriodList.add(appUsagePeriod);
        if (periodStartTime < periodEndTime) {
            final AppUsagePeriod period =
                    AppUsagePeriod.newBuilder()
                            .setStartTime(periodStartTime)
                            .setEndTime(periodEndTime)
                            .build();
            appUsagePeriodList.add(period);
        }
    }

    private static long trimPeriodTime(
            final long originalTime, final long startTime, final long endTime) {
        long finalTime = Math.max(originalTime, startTime);
        finalTime = Math.min(finalTime, endTime);
        return finalTime;
    }

    private static void addToUsagePeriodMap(
@@ -918,19 +940,6 @@ public final class DataProcessor {
                eventTime);
    }

    private static int getFirstUsageEventIndex(
            final List<AppUsageEvent> appUsageEventList,
            final long startTimestampOfLevelData) {
        int currentIndex = 0;
        while (currentIndex < appUsageEventList.size()
                && (appUsageEventList.get(currentIndex) == null
                || appUsageEventList.get(currentIndex).getTimestamp()
                < startTimestampOfLevelData)) {
            currentIndex++;
        }
        return currentIndex;
    }

    private static void insertHourlyDeviceScreenOnTime(
            final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
                    appUsagePeriodMap,
@@ -1520,7 +1529,7 @@ public final class DataProcessor {
                    foregroundUsageTimeInMs + backgroundUsageTimeInMs;
            if (totalUsageTimeInMs > TOTAL_HOURLY_TIME_THRESHOLD) {
                final float ratio = TOTAL_HOURLY_TIME_THRESHOLD / totalUsageTimeInMs;
                if (DEBUG) {
                if (sDebug) {
                    Log.w(TAG, String.format("abnormal usage time %d|%d for:\n%s",
                            Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(),
                            Duration.ofMillis(backgroundUsageTimeInMs).getSeconds(),
@@ -1876,7 +1885,7 @@ public final class DataProcessor {

    private static void log(Context context, final String content, final long timestamp,
            final BatteryHistEntry entry) {
        if (DEBUG) {
        if (sDebug) {
            Log.d(TAG, String.format(entry != null ? "%s %s:\n%s" : "%s %s:%s",
                    utcToLocalTime(context, timestamp), content, entry));
        }
+9 −1
Original line number Diff line number Diff line
@@ -74,6 +74,11 @@ public final class DatabaseUtils {
    public static final String QUERY_KEY_USERID = "userid";

    public static final long INVALID_USER_ID = Integer.MIN_VALUE;
    /**
     * The buffer hours to query app usage events that may have begun or ended out of the final
     * desired time frame.
     */
    public static final long USAGE_QUERY_BUFFER_HOURS = Duration.ofHours(3).toMillis();

    /** A content URI to access battery usage states data. */
    public static final Uri BATTERY_CONTENT_URI =
@@ -138,7 +143,10 @@ public final class DatabaseUtils {
            final long startTimestampOfLevelData) {
        final long startTime = System.currentTimeMillis();
        final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
        final long queryTimestamp = Math.max(startTimestampOfLevelData, sixDaysAgoTimestamp);
        // Query a longer time period and then trim to the original time period in order to make
        // sure the app usage calculation near the boundaries is correct.
        final long queryTimestamp =
                Math.max(startTimestampOfLevelData, sixDaysAgoTimestamp) - USAGE_QUERY_BUFFER_HOURS;
        Log.d(TAG, "sixDayAgoTimestamp: " + sixDaysAgoTimestamp);
        final String queryUserIdString = userIds.stream()
                .map(userId -> String.valueOf(userId))
+53 −46
Original line number Diff line number Diff line
@@ -178,22 +178,22 @@ public final class DataProcessorTest {

    @Test
    public void generateAppUsagePeriodMap_returnExpectedResult() {
        DataProcessor.sDebug = true;
        final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
                new ArrayList<>();
        final String packageName = "com.android.settings";
        // Adds the day 1 data.
        final List<Long> timestamps1 = List.of(10000L, 20000L, 30000L);
        final List<Long> timestamps1 = List.of(14400000L, 18000000L, 21600000L);
        final List<Integer> levels1 = List.of(100, 100, 100);
        hourlyBatteryLevelsPerDay.add(
                new BatteryLevelData.PeriodBatteryLevelData(timestamps1, levels1));
        // Adds the day 2 data.
        hourlyBatteryLevelsPerDay.add(null);
        // Adds the day 3 data.
        final List<Long> timestamps2 = List.of(40000L, 50000L);
        final List<Long> timestamps2 = List.of(45200000L, 48800000L);
        final List<Integer> levels2 = List.of(100, 100);
        hourlyBatteryLevelsPerDay.add(
                new BatteryLevelData.PeriodBatteryLevelData(timestamps2, levels2));
        final long startTimestampOfLevelData = 10000L;
        final List<AppUsageEvent> appUsageEventList = new ArrayList<>();
        // Adds some events before the start timestamp.
        appUsageEventList.add(buildAppUsageEvent(
@@ -204,39 +204,48 @@ public final class DataProcessorTest {
                /*instanceId=*/ 2, packageName));
        // Adds the valid app usage events.
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 10000L, /*userId=*/ 1,
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 4200000L, /*userId=*/ 1,
                /*instanceId=*/ 2, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 15000L, /*userId=*/ 1,
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 4500000L, /*userId=*/ 1,
                /*instanceId=*/ 2, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 12000L, /*userId=*/ 2,
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 12600000L, /*userId=*/ 2,
                /*instanceId=*/ 3, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 18000L, /*userId=*/ 2,
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 15600000L, /*userId=*/ 2,
                /*instanceId=*/ 3, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 35000L, /*userId=*/ 1,
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 16200000L, /*userId=*/ 2,
                /*instanceId=*/ 3, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 18000000L, /*userId=*/ 2,
                /*instanceId=*/ 3, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 17200000L, /*userId=*/ 1,
                /*instanceId=*/ 2, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 45000L, /*userId=*/ 1,
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 17800000L, /*userId=*/ 1,
                /*instanceId=*/ 2, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 42000L, /*userId=*/ 1,
                /*instanceId=*/ 4, packageName));
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 46000000L, /*userId=*/ 1,
                /*instanceId=*/ 2, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 52000L, /*userId=*/ 1,
                /*instanceId=*/ 4, packageName));
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 47800000L, /*userId=*/ 1,
                /*instanceId=*/ 2, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 49000000L, /*userId=*/ 1,
                /*instanceId=*/ 2, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 55000L, /*userId=*/ 1,
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 59600000L, /*userId=*/ 1,
                /*instanceId=*/ 4, packageName));
        appUsageEventList.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 58000L, /*userId=*/ 1,
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 61200000L, /*userId=*/ 1,
                /*instanceId=*/ 4, packageName));

        final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> periodMap =
                DataProcessor.generateAppUsagePeriodMap(
                        hourlyBatteryLevelsPerDay, appUsageEventList, startTimestampOfLevelData);
                        hourlyBatteryLevelsPerDay, appUsageEventList);

        assertThat(periodMap.size()).isEqualTo(3);
        // Day 1
@@ -246,11 +255,12 @@ public final class DataProcessorTest {
        Map<String, List<AppUsagePeriod>> userMap = hourlyMap.get(1L);
        assertThat(userMap.size()).isEqualTo(1);
        assertThat(userMap.get(packageName).size()).isEqualTo(1);
        assertAppUsagePeriod(userMap.get(packageName).get(0), 10000, 15000);
        assertAppUsagePeriod(userMap.get(packageName).get(0), 17200000L, 17800000L);
        userMap = hourlyMap.get(2L);
        assertThat(userMap.size()).isEqualTo(1);
        assertThat(userMap.get(packageName).size()).isEqualTo(1);
        assertAppUsagePeriod(userMap.get(packageName).get(0), 12000, 18000);
        assertThat(userMap.get(packageName).size()).isEqualTo(2);
        assertAppUsagePeriod(userMap.get(packageName).get(0), 14400000L, 15600000L);
        assertAppUsagePeriod(userMap.get(packageName).get(1), 16200000L, 18000000L);
        hourlyMap = periodMap.get(0).get(1);
        assertThat(hourlyMap).isNull();
        // Day 2
@@ -262,8 +272,8 @@ public final class DataProcessorTest {
        userMap = hourlyMap.get(1L);
        assertThat(userMap.size()).isEqualTo(1);
        assertThat(userMap.get(packageName).size()).isEqualTo(2);
        assertAppUsagePeriod(userMap.get(packageName).get(0), 40000, 45000);
        assertAppUsagePeriod(userMap.get(packageName).get(1), 42000, 50000);
        assertAppUsagePeriod(userMap.get(packageName).get(0), 45970000L, 46000000L);
        assertAppUsagePeriod(userMap.get(packageName).get(1), 47800000L, 48800000L);
    }

    @Test
@@ -273,7 +283,7 @@ public final class DataProcessorTest {
        hourlyBatteryLevelsPerDay.add(
                new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
        assertThat(DataProcessor.generateAppUsagePeriodMap(
                hourlyBatteryLevelsPerDay, new ArrayList<>(), 0)).isNull();
                hourlyBatteryLevelsPerDay, new ArrayList<>())).isNull();
    }

    @Test
@@ -1508,9 +1518,19 @@ public final class DataProcessorTest {
    @Test
    public void buildAppUsagePeriodListPerInstance_returnExpectedResult() {
        final List<AppUsageEvent> appUsageEvents = new ArrayList<>();
        // Fake data earlier than time range.
        appUsageEvents.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 1));
        appUsageEvents.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 2));
        // Fake resume event earlier than time range.
        appUsageEvents.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 3));
        appUsageEvents.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 120000));
        // Fake normal data.
        appUsageEvents.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 100000));
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 150000));
        appUsageEvents.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 200000));
        // Fake two adjacent resume events.
@@ -1540,29 +1560,16 @@ public final class DataProcessorTest {
                AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 1000000));

        final List<AppUsagePeriod> appUsagePeriodList =
                DataProcessor.buildAppUsagePeriodListPerInstance(appUsageEvents, 0, 1100000);

        assertThat(appUsagePeriodList.size()).isEqualTo(6);
        assertAppUsagePeriod(appUsagePeriodList.get(0), 100000, 200000);
        assertAppUsagePeriod(appUsagePeriodList.get(1), 300000, 500000);
        assertAppUsagePeriod(appUsagePeriodList.get(2), 570000, 600000);
        assertAppUsagePeriod(appUsagePeriodList.get(3), 700000, 730000);
        assertAppUsagePeriod(appUsagePeriodList.get(4), 900000, 910000);
        assertAppUsagePeriod(appUsagePeriodList.get(5), 1000000, 1100000);
    }

    @Test
    public void buildAppUsagePeriodListPerInstance_notMetStart_returnExpectedResult() {
        final List<AppUsageEvent> appUsageEvents = new ArrayList<>();
        // Start with stop event.
        appUsageEvents.add(buildAppUsageEvent(
                AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 100000));

        final List<AppUsagePeriod> appUsagePeriodList =
                DataProcessor.buildAppUsagePeriodListPerInstance(appUsageEvents, 0, 200000);

        assertThat(appUsageEvents.size()).isEqualTo(1);
        assertAppUsagePeriod(appUsagePeriodList.get(0), 0, 100000);
                DataProcessor.buildAppUsagePeriodListPerInstance(appUsageEvents, 100000, 1100000);

        assertThat(appUsagePeriodList.size()).isEqualTo(7);
        assertAppUsagePeriod(appUsagePeriodList.get(0), 100000, 120000);
        assertAppUsagePeriod(appUsagePeriodList.get(1), 150000, 200000);
        assertAppUsagePeriod(appUsagePeriodList.get(2), 300000, 500000);
        assertAppUsagePeriod(appUsagePeriodList.get(3), 570000, 600000);
        assertAppUsagePeriod(appUsagePeriodList.get(4), 700000, 730000);
        assertAppUsagePeriod(appUsagePeriodList.get(5), 900000, 910000);
        assertAppUsagePeriod(appUsagePeriodList.get(6), 1000000, 1100000);
    }

    @Test