Loading services/core/java/com/android/server/job/controllers/QuotaController.java +88 −1 Original line number Diff line number Diff line Loading @@ -769,6 +769,91 @@ public final class QuotaController extends StateController { mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); } /** * Returns the amount of time, in milliseconds, until the package would have reached its * duration quota, assuming it has a job counting towards its quota the entire time. This takes * into account any {@link TimingSession}s that may roll out of the window as the job is * running. */ @VisibleForTesting long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) { final long nowElapsed = sElapsedRealtimeClock.millis(); final int standbyBucket = JobSchedulerService.standbyBucketForPackage( packageName, userId, nowElapsed); if (standbyBucket == NEVER_INDEX) { return 0; } List<TimingSession> sessions = mTimingSessions.get(userId, packageName); if (sessions == null || sessions.size() == 0) { return mAllowedTimePerPeriodMs; } final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs; final long maxExecutionTimeRemainingMs = mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { return calculateTimeUntilQuotaConsumedLocked( sessions, startMaxElapsed, maxExecutionTimeRemainingMs); } // Need to check both max time and period time in case one is less than the other. // For example, max time remaining could be less than bucket time remaining, but sessions // contributing to the max time remaining could phase out enough that we'd want to use the // bucket value. return Math.min( calculateTimeUntilQuotaConsumedLocked( sessions, startMaxElapsed, maxExecutionTimeRemainingMs), calculateTimeUntilQuotaConsumedLocked( sessions, startWindowElapsed, allowedTimeRemainingMs)); } /** * Calculates how much time it will take, in milliseconds, until the quota is fully consumed. * * @param windowStartElapsed The start of the window, in the elapsed realtime timebase. * @param deadSpaceMs How much time can be allowed to count towards the quota */ private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions, final long windowStartElapsed, long deadSpaceMs) { long timeUntilQuotaConsumedMs = 0; long start = windowStartElapsed; for (int i = 0; i < sessions.size(); ++i) { TimingSession session = sessions.get(i); if (session.endTimeElapsed < windowStartElapsed) { // Outside of window. Ignore. continue; } else if (session.startTimeElapsed <= windowStartElapsed) { // Overlapping session. Can extend time by portion of session in window. timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed; start = session.endTimeElapsed; } else { // Completely within the window. Can only consider if there's enough dead space // to get to the start of the session. long diff = session.startTimeElapsed - start; if (diff > deadSpaceMs) { break; } timeUntilQuotaConsumedMs += diff + (session.endTimeElapsed - session.startTimeElapsed); deadSpaceMs -= diff; start = session.endTimeElapsed; } } // Will be non-zero if the loop didn't look at any sessions. timeUntilQuotaConsumedMs += deadSpaceMs; if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) { Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs); } return timeUntilQuotaConsumedMs; } /** Returns the execution stats of the app in the most recent window. */ @VisibleForTesting @NonNull Loading Loading @@ -1483,7 +1568,7 @@ public final class QuotaController extends StateController { return; } Message msg = mHandler.obtainMessage(MSG_REACHED_QUOTA, mPkg); final long timeRemainingMs = getRemainingExecutionTimeLocked(mPkg.userId, final long timeRemainingMs = getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName); if (DEBUG) { Slog.i(TAG, "Job for " + mPkg + " has " + timeRemainingMs + "ms left."); Loading Loading @@ -1642,6 +1727,8 @@ public final class QuotaController extends StateController { // job is currently running. // Reschedule message Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, pkg.packageName); if (DEBUG) { Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left."); } Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +158 −2 Original line number Diff line number Diff line Loading @@ -722,6 +722,147 @@ public class QuotaControllerTest { assertEquals(expectedStats, newStatsRare); } /** * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket * window. */ @Test public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Close to RARE boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), 30 * SECOND_IN_MILLIS, 5)); // Far away from FREQUENT boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Overlap WORKING_SET boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Close to ACTIVE boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); setStandbyBucket(RARE_INDEX); assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(FREQUENT_INDEX); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(WORKING_INDEX); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the // max execution time. setStandbyBucket(ACTIVE_INDEX); assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit. */ @Test public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Overlap boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5)); setStandbyBucket(WORKING_INDEX); assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // Max time will phase out, so should use bucket limit. assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); // Close to boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5)); setStandbyBucket(WORKING_INDEX); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); // Far from boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5)); setStandbyBucket(WORKING_INDEX); assertEquals(3 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(3 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time * remaining are equal. */ @Test public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); setStandbyBucket(FREQUENT_INDEX); // Overlap boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Both max and bucket time have 8 minutes left. assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute // window time. assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); // Overlap boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (20 * HOUR_IN_MILLIS), 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Both max and bucket time have 8 minutes left. assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // Max time only has one minute phase out. Bucket time has 2 minute phase out. assertEquals(9 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); } @Test public void testIsWithinQuotaLocked_NeverApp() { assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); Loading Loading @@ -1902,7 +2043,10 @@ public class QuotaControllerTest { // window, so as the package "reaches its quota" it will have more to keep running. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 2 * HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1)); 10 * SECOND_IN_MILLIS - remainingTimeMs, 1)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1)); assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); // Start the job. Loading @@ -1919,6 +2063,18 @@ public class QuotaControllerTest { // amount of remaining time left its quota. assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(remainingTimeMs)); // Handler is told to check when the quota will be consumed, not when the initial // remaining time is over. verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(10 * SECOND_IN_MILLIS)); verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); // After 10 seconds, the job should finally be out of quota. advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs); // Wait for some extra time to allow for job processing. verify(mJobSchedulerService, timeout(12 * SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); verify(handler, never()).sendMessageDelayed(any(), anyInt()); } } Loading
services/core/java/com/android/server/job/controllers/QuotaController.java +88 −1 Original line number Diff line number Diff line Loading @@ -769,6 +769,91 @@ public final class QuotaController extends StateController { mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); } /** * Returns the amount of time, in milliseconds, until the package would have reached its * duration quota, assuming it has a job counting towards its quota the entire time. This takes * into account any {@link TimingSession}s that may roll out of the window as the job is * running. */ @VisibleForTesting long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) { final long nowElapsed = sElapsedRealtimeClock.millis(); final int standbyBucket = JobSchedulerService.standbyBucketForPackage( packageName, userId, nowElapsed); if (standbyBucket == NEVER_INDEX) { return 0; } List<TimingSession> sessions = mTimingSessions.get(userId, packageName); if (sessions == null || sessions.size() == 0) { return mAllowedTimePerPeriodMs; } final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs; final long maxExecutionTimeRemainingMs = mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { return calculateTimeUntilQuotaConsumedLocked( sessions, startMaxElapsed, maxExecutionTimeRemainingMs); } // Need to check both max time and period time in case one is less than the other. // For example, max time remaining could be less than bucket time remaining, but sessions // contributing to the max time remaining could phase out enough that we'd want to use the // bucket value. return Math.min( calculateTimeUntilQuotaConsumedLocked( sessions, startMaxElapsed, maxExecutionTimeRemainingMs), calculateTimeUntilQuotaConsumedLocked( sessions, startWindowElapsed, allowedTimeRemainingMs)); } /** * Calculates how much time it will take, in milliseconds, until the quota is fully consumed. * * @param windowStartElapsed The start of the window, in the elapsed realtime timebase. * @param deadSpaceMs How much time can be allowed to count towards the quota */ private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions, final long windowStartElapsed, long deadSpaceMs) { long timeUntilQuotaConsumedMs = 0; long start = windowStartElapsed; for (int i = 0; i < sessions.size(); ++i) { TimingSession session = sessions.get(i); if (session.endTimeElapsed < windowStartElapsed) { // Outside of window. Ignore. continue; } else if (session.startTimeElapsed <= windowStartElapsed) { // Overlapping session. Can extend time by portion of session in window. timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed; start = session.endTimeElapsed; } else { // Completely within the window. Can only consider if there's enough dead space // to get to the start of the session. long diff = session.startTimeElapsed - start; if (diff > deadSpaceMs) { break; } timeUntilQuotaConsumedMs += diff + (session.endTimeElapsed - session.startTimeElapsed); deadSpaceMs -= diff; start = session.endTimeElapsed; } } // Will be non-zero if the loop didn't look at any sessions. timeUntilQuotaConsumedMs += deadSpaceMs; if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) { Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs); } return timeUntilQuotaConsumedMs; } /** Returns the execution stats of the app in the most recent window. */ @VisibleForTesting @NonNull Loading Loading @@ -1483,7 +1568,7 @@ public final class QuotaController extends StateController { return; } Message msg = mHandler.obtainMessage(MSG_REACHED_QUOTA, mPkg); final long timeRemainingMs = getRemainingExecutionTimeLocked(mPkg.userId, final long timeRemainingMs = getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName); if (DEBUG) { Slog.i(TAG, "Job for " + mPkg + " has " + timeRemainingMs + "ms left."); Loading Loading @@ -1642,6 +1727,8 @@ public final class QuotaController extends StateController { // job is currently running. // Reschedule message Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, pkg.packageName); if (DEBUG) { Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left."); } Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +158 −2 Original line number Diff line number Diff line Loading @@ -722,6 +722,147 @@ public class QuotaControllerTest { assertEquals(expectedStats, newStatsRare); } /** * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket * window. */ @Test public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Close to RARE boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), 30 * SECOND_IN_MILLIS, 5)); // Far away from FREQUENT boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Overlap WORKING_SET boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Close to ACTIVE boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); setStandbyBucket(RARE_INDEX); assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(FREQUENT_INDEX); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(WORKING_INDEX); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the // max execution time. setStandbyBucket(ACTIVE_INDEX); assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit. */ @Test public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Overlap boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5)); setStandbyBucket(WORKING_INDEX); assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // Max time will phase out, so should use bucket limit. assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); // Close to boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5)); setStandbyBucket(WORKING_INDEX); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); // Far from boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5)); setStandbyBucket(WORKING_INDEX); assertEquals(3 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); assertEquals(3 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); } /** * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time * remaining are equal. */ @Test public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); setStandbyBucket(FREQUENT_INDEX); // Overlap boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Both max and bucket time have 8 minutes left. assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute // window time. assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); // Overlap boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (20 * HOUR_IN_MILLIS), 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); // Both max and bucket time have 8 minutes left. assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); // Max time only has one minute phase out. Bucket time has 2 minute phase out. assertEquals(9 * MINUTE_IN_MILLIS, mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); } @Test public void testIsWithinQuotaLocked_NeverApp() { assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); Loading Loading @@ -1902,7 +2043,10 @@ public class QuotaControllerTest { // window, so as the package "reaches its quota" it will have more to keep running. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 2 * HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1)); 10 * SECOND_IN_MILLIS - remainingTimeMs, 1)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1)); assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); // Start the job. Loading @@ -1919,6 +2063,18 @@ public class QuotaControllerTest { // amount of remaining time left its quota. assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(remainingTimeMs)); // Handler is told to check when the quota will be consumed, not when the initial // remaining time is over. verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(10 * SECOND_IN_MILLIS)); verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); // After 10 seconds, the job should finally be out of quota. advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs); // Wait for some extra time to allow for job processing. verify(mJobSchedulerService, timeout(12 * SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); verify(handler, never()).sendMessageDelayed(any(), anyInt()); } }