Loading apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +10 −1 Original line number Diff line number Diff line Loading @@ -813,12 +813,21 @@ public final class QuotaController extends StateController { // 1. it's already running (already executing expedited jobs should be allowed to finish) // 2. the app is currently in the foreground // 3. the app overall is within its quota // 4. It's on the temp allowlist (or within the grace period) if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { return true; } final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(jobStatus.getSourceUid()); final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid()) || sElapsedRealtimeClock.millis() < tempAllowlistGracePeriodEndElapsed; if (hasTempAllowlistExemption) { return true; } Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); // Any already executing expedited jbos should be allowed to finish. // Any already executing expedited jobs should be allowed to finish. if (ejTimer != null && ejTimer.isRunning(jobStatus)) { return true; } Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +187 −0 Original line number Diff line number Diff line Loading @@ -1879,6 +1879,144 @@ public class QuotaControllerTest { } } @Test public void testIsWithinEJQuotaLocked_NeverApp() { JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1); setStandbyBucket(NEVER_INDEX, js); synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_Charging() { setCharging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_Charging", 1); synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_UnderDuration() { setDischarging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_UnderDuration", 1); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_OverDuration() { setDischarging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_OverDuration", 1); setStandbyBucket(FREQUENT_INDEX, js); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_TimingSession() { setDischarging(); setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 8 * MINUTE_IN_MILLIS); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TimingSession", 1); for (int i = 0; i < 25; ++i) { mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 2), true); synchronized (mQuotaController.mLock) { setStandbyBucket(ACTIVE_INDEX, js); assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions", i < 19, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(WORKING_INDEX, js); assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions", i < 14, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(FREQUENT_INDEX, js); assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions", i < 12, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(RARE_INDEX, js); assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions", i < 9, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(RESTRICTED_INDEX, js); assertEquals("Restricted has incorrect quota status with " + (i + 1) + " sessions", i < 7, mQuotaController.isWithinEJQuotaLocked(js)); } } } /** * Tests that Timers properly track sessions when an app is added and removed from the temp * allowlist. */ @Test public void testIsWithinEJQuotaLocked_TempAllowlisting() { setDischarging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting", 1); setStandbyBucket(FREQUENT_INDEX, js); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); final long gracePeriodMs = 15 * SECOND_IN_MILLIS; setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, gracePeriodMs); Handler handler = mQuotaController.getHandler(); spyOn(handler); // Apps on the temp allowlist should be able to schedule & start EJs, even if they're out // of quota (as long as they are in the temp allowlist grace period). mTempAllowlistListener.onAppAdded(mSourceUid); synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } advanceElapsedClock(10 * SECOND_IN_MILLIS); mTempAllowlistListener.onAppRemoved(mSourceUid); advanceElapsedClock(10 * SECOND_IN_MILLIS); // Still in grace period synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } advanceElapsedClock(6 * SECOND_IN_MILLIS); // Out of grace period. synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testMaybeScheduleCleanupAlarmLocked() { // No sessions saved yet. Loading Loading @@ -3880,6 +4018,55 @@ public class QuotaControllerTest { } } @Test public void testGetRemainingEJExecutionTimeLocked_IncrementalTimingSessions() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS); for (int i = 1; i <= 25; ++i) { mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 2), true); synchronized (mQuotaController.mLock) { setStandbyBucket(ACTIVE_INDEX); assertEquals("Active has incorrect remaining EJ time with " + i + " sessions", (20 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(WORKING_INDEX); assertEquals("Working has incorrect remaining EJ time with " + i + " sessions", (15 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(FREQUENT_INDEX); assertEquals("Frequent has incorrect remaining EJ time with " + i + " sessions", (13 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(RARE_INDEX); assertEquals("Rare has incorrect remaining EJ time with " + i + " sessions", (10 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(RESTRICTED_INDEX); assertEquals("Restricted has incorrect remaining EJ time with " + i + " sessions", (5 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } } } @Test public void testGetTimeUntilEJQuotaConsumedLocked_NoHistory() { final long[] limits = mQuotaController.getEJLimitsMs(); Loading Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +10 −1 Original line number Diff line number Diff line Loading @@ -813,12 +813,21 @@ public final class QuotaController extends StateController { // 1. it's already running (already executing expedited jobs should be allowed to finish) // 2. the app is currently in the foreground // 3. the app overall is within its quota // 4. It's on the temp allowlist (or within the grace period) if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { return true; } final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(jobStatus.getSourceUid()); final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid()) || sElapsedRealtimeClock.millis() < tempAllowlistGracePeriodEndElapsed; if (hasTempAllowlistExemption) { return true; } Timer ejTimer = mEJPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); // Any already executing expedited jbos should be allowed to finish. // Any already executing expedited jobs should be allowed to finish. if (ejTimer != null && ejTimer.isRunning(jobStatus)) { return true; } Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +187 −0 Original line number Diff line number Diff line Loading @@ -1879,6 +1879,144 @@ public class QuotaControllerTest { } } @Test public void testIsWithinEJQuotaLocked_NeverApp() { JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1); setStandbyBucket(NEVER_INDEX, js); synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_Charging() { setCharging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_Charging", 1); synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_UnderDuration() { setDischarging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_UnderDuration", 1); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_OverDuration() { setDischarging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_OverDuration", 1); setStandbyBucket(FREQUENT_INDEX, js); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testIsWithinEJQuotaLocked_TimingSession() { setDischarging(); setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 8 * MINUTE_IN_MILLIS); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TimingSession", 1); for (int i = 0; i < 25; ++i) { mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 2), true); synchronized (mQuotaController.mLock) { setStandbyBucket(ACTIVE_INDEX, js); assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions", i < 19, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(WORKING_INDEX, js); assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions", i < 14, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(FREQUENT_INDEX, js); assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions", i < 12, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(RARE_INDEX, js); assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions", i < 9, mQuotaController.isWithinEJQuotaLocked(js)); setStandbyBucket(RESTRICTED_INDEX, js); assertEquals("Restricted has incorrect quota status with " + (i + 1) + " sessions", i < 7, mQuotaController.isWithinEJQuotaLocked(js)); } } } /** * Tests that Timers properly track sessions when an app is added and removed from the temp * allowlist. */ @Test public void testIsWithinEJQuotaLocked_TempAllowlisting() { setDischarging(); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting", 1); setStandbyBucket(FREQUENT_INDEX, js); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); final long gracePeriodMs = 15 * SECOND_IN_MILLIS; setDeviceConfigLong(QcConstants.KEY_EJ_TEMP_ALLOWLIST_GRACE_PERIOD_MS, gracePeriodMs); Handler handler = mQuotaController.getHandler(); spyOn(handler); // Apps on the temp allowlist should be able to schedule & start EJs, even if they're out // of quota (as long as they are in the temp allowlist grace period). mTempAllowlistListener.onAppAdded(mSourceUid); synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } advanceElapsedClock(10 * SECOND_IN_MILLIS); mTempAllowlistListener.onAppRemoved(mSourceUid); advanceElapsedClock(10 * SECOND_IN_MILLIS); // Still in grace period synchronized (mQuotaController.mLock) { assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); } advanceElapsedClock(6 * SECOND_IN_MILLIS); // Out of grace period. synchronized (mQuotaController.mLock) { assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); } } @Test public void testMaybeScheduleCleanupAlarmLocked() { // No sessions saved yet. Loading Loading @@ -3880,6 +4018,55 @@ public class QuotaControllerTest { } } @Test public void testGetRemainingEJExecutionTimeLocked_IncrementalTimingSessions() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS); for (int i = 1; i <= 25; ++i) { mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 2), true); synchronized (mQuotaController.mLock) { setStandbyBucket(ACTIVE_INDEX); assertEquals("Active has incorrect remaining EJ time with " + i + " sessions", (20 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(WORKING_INDEX); assertEquals("Working has incorrect remaining EJ time with " + i + " sessions", (15 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(FREQUENT_INDEX); assertEquals("Frequent has incorrect remaining EJ time with " + i + " sessions", (13 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(RARE_INDEX); assertEquals("Rare has incorrect remaining EJ time with " + i + " sessions", (10 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); setStandbyBucket(RESTRICTED_INDEX); assertEquals("Restricted has incorrect remaining EJ time with " + i + " sessions", (5 - i) * MINUTE_IN_MILLIS, mQuotaController.getRemainingEJExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } } } @Test public void testGetTimeUntilEJQuotaConsumedLocked_NoHistory() { final long[] limits = mQuotaController.getEJLimitsMs(); Loading