Loading apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +46 −7 Original line number Diff line number Diff line Loading @@ -36,10 +36,14 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.UidObserver; import android.app.compat.CompatChanges; import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.Overridable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; Loading Loading @@ -132,6 +136,27 @@ public final class QuotaController extends StateController { return (int) (val ^ (val >>> 32)); } /** * When enabled this change id overrides the default quota policy enforcement to the jobs * running in the foreground process state. */ // TODO: b/379681266 - Might need some refactoring for a better app-compat strategy. @VisibleForTesting @ChangeId @Disabled // Disabled by default @Overridable // The change can be overridden in user build static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS = 341201311L; /** * When enabled this change id overrides the default quota policy enforcement policy * the jobs started when app was in the TOP state. */ @VisibleForTesting @ChangeId @Disabled // Disabled by default @Overridable // The change can be overridden in user build. static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L; @VisibleForTesting static class ExecutionStats { /** Loading Loading @@ -622,7 +647,9 @@ public final class QuotaController extends StateController { } final int uid = jobStatus.getSourceUid(); if (!Flags.enforceQuotaPolicyToTopStartedJobs() && mTopAppCache.get(uid)) { if ((!Flags.enforceQuotaPolicyToTopStartedJobs() || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, uid)) && mTopAppCache.get(uid)) { if (DEBUG) { Slog.d(TAG, jobStatus.toShortString() + " is top started job"); } Loading Loading @@ -659,7 +686,9 @@ public final class QuotaController extends StateController { timer.stopTrackingJob(jobStatus); } } if (!Flags.enforceQuotaPolicyToTopStartedJobs()) { if (!Flags.enforceQuotaPolicyToTopStartedJobs() || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { mTopStartedJobs.remove(jobStatus); } } Loading Loading @@ -772,7 +801,13 @@ public final class QuotaController extends StateController { /** @return true if the job was started while the app was in the TOP state. */ private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { return !Flags.enforceQuotaPolicyToTopStartedJobs() && mTopStartedJobs.contains(jobStatus); if (!Flags.enforceQuotaPolicyToTopStartedJobs() || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { return mTopStartedJobs.contains(jobStatus); } return false; } /** Returns the maximum amount of time this job could run for. */ Loading Loading @@ -2634,9 +2669,13 @@ public final class QuotaController extends StateController { } @VisibleForTesting int getProcessStateQuotaFreeThreshold() { return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP : ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; int getProcessStateQuotaFreeThreshold(int uid) { if (Flags.enforceQuotaPolicyToFgsJobs() && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) { return ActivityManager.PROCESS_STATE_BOUND_TOP; } return ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; } private class QcHandler extends Handler { Loading Loading @@ -2776,7 +2815,7 @@ public final class QuotaController extends StateController { isQuotaFree = true; } else { final boolean reprocess; if (procState <= getProcessStateQuotaFreeThreshold()) { if (procState <= getProcessStateQuotaFreeThreshold(uid)) { reprocess = !mForegroundUids.get(uid); mForegroundUids.put(uid, true); isQuotaFree = true; Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +103 −1 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; Loading Loading @@ -103,10 +104,13 @@ import com.android.server.job.controllers.QuotaController.TimedEvent; import com.android.server.job.controllers.QuotaController.TimingSession; import com.android.server.usage.AppStandbyInternal; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; Loading Loading @@ -135,6 +139,9 @@ public class QuotaControllerTest { private static final int SOURCE_USER_ID = 0; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); private QuotaController mQuotaController; private QuotaController.QcConstants mQcConstants; private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); Loading Loading @@ -303,7 +310,7 @@ public class QuotaControllerTest { private int getProcessStateQuotaFreeThreshold() { synchronized (mQuotaController.mLock) { return mQuotaController.getProcessStateQuotaFreeThreshold(); return mQuotaController.getProcessStateQuotaFreeThreshold(mSourceUid); } } Loading Loading @@ -5197,6 +5204,101 @@ public class QuotaControllerTest { assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); } @Test @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS}) @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS, Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS}) public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() { setDischarging(); JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1); JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2); trackJobs(jobBg, jobTop); setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window // Now the package only has 20 seconds to run. final long remainingTimeMs = 20 * SECOND_IN_MILLIS; mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false); InOrder inOrder = inOrder(mJobSchedulerService); // UID starts out inactive. setProcessState(ActivityManager.PROCESS_STATE_SERVICE); // Start the job. synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobBg); } advanceElapsedClock(remainingTimeMs / 2); // New job starts after UID is in the foreground. Since the app is now in the foreground, it // should continue to have remainingTimeMs / 2 time remaining. setProcessState(ActivityManager.PROCESS_STATE_TOP); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobTop); } advanceElapsedClock(remainingTimeMs); // Wait for some extra time to allow for job processing. inOrder.verify(mJobSchedulerService, timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); synchronized (mQuotaController.mLock) { assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg)); assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop)); } // Go to a background state. setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); advanceElapsedClock(remainingTimeMs / 2 + 1); // Only Bg job will be changed from in-quota to out-of-quota. inOrder.verify(mJobSchedulerService, timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); // Top job should still be allowed to run. assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); // New jobs to run. JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3); JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4); JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5); setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg); advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_TOP); inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); trackJobs(jobFg, jobTop); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobTop); } assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); // App still in foreground so everything should be in quota. advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_SERVICE); inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); // App is now in background and out of quota. Fg should now change to out of quota since it // wasn't started. Top should remain in quota since it started when the app was in TOP. assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); trackJobs(jobBg2); assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); } /** * Tests that TOP jobs are stopped when an app runs out of quota. */ Loading Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +46 −7 Original line number Diff line number Diff line Loading @@ -36,10 +36,14 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.UidObserver; import android.app.compat.CompatChanges; import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.compat.annotation.Overridable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; Loading Loading @@ -132,6 +136,27 @@ public final class QuotaController extends StateController { return (int) (val ^ (val >>> 32)); } /** * When enabled this change id overrides the default quota policy enforcement to the jobs * running in the foreground process state. */ // TODO: b/379681266 - Might need some refactoring for a better app-compat strategy. @VisibleForTesting @ChangeId @Disabled // Disabled by default @Overridable // The change can be overridden in user build static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS = 341201311L; /** * When enabled this change id overrides the default quota policy enforcement policy * the jobs started when app was in the TOP state. */ @VisibleForTesting @ChangeId @Disabled // Disabled by default @Overridable // The change can be overridden in user build. static final long OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS = 374323858L; @VisibleForTesting static class ExecutionStats { /** Loading Loading @@ -622,7 +647,9 @@ public final class QuotaController extends StateController { } final int uid = jobStatus.getSourceUid(); if (!Flags.enforceQuotaPolicyToTopStartedJobs() && mTopAppCache.get(uid)) { if ((!Flags.enforceQuotaPolicyToTopStartedJobs() || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, uid)) && mTopAppCache.get(uid)) { if (DEBUG) { Slog.d(TAG, jobStatus.toShortString() + " is top started job"); } Loading Loading @@ -659,7 +686,9 @@ public final class QuotaController extends StateController { timer.stopTrackingJob(jobStatus); } } if (!Flags.enforceQuotaPolicyToTopStartedJobs()) { if (!Flags.enforceQuotaPolicyToTopStartedJobs() || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { mTopStartedJobs.remove(jobStatus); } } Loading Loading @@ -772,7 +801,13 @@ public final class QuotaController extends StateController { /** @return true if the job was started while the app was in the TOP state. */ private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { return !Flags.enforceQuotaPolicyToTopStartedJobs() && mTopStartedJobs.contains(jobStatus); if (!Flags.enforceQuotaPolicyToTopStartedJobs() || CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, jobStatus.getSourceUid())) { return mTopStartedJobs.contains(jobStatus); } return false; } /** Returns the maximum amount of time this job could run for. */ Loading Loading @@ -2634,9 +2669,13 @@ public final class QuotaController extends StateController { } @VisibleForTesting int getProcessStateQuotaFreeThreshold() { return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP : ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; int getProcessStateQuotaFreeThreshold(int uid) { if (Flags.enforceQuotaPolicyToFgsJobs() && !CompatChanges.isChangeEnabled(OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS, uid)) { return ActivityManager.PROCESS_STATE_BOUND_TOP; } return ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; } private class QcHandler extends Handler { Loading Loading @@ -2776,7 +2815,7 @@ public final class QuotaController extends StateController { isQuotaFree = true; } else { final boolean reprocess; if (procState <= getProcessStateQuotaFreeThreshold()) { if (procState <= getProcessStateQuotaFreeThreshold(uid)) { reprocess = !mForegroundUids.get(uid); mForegroundUids.put(uid, true); isQuotaFree = true; Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +103 −1 Original line number Diff line number Diff line Loading @@ -65,6 +65,7 @@ import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; Loading Loading @@ -103,10 +104,13 @@ import com.android.server.job.controllers.QuotaController.TimedEvent; import com.android.server.job.controllers.QuotaController.TimingSession; import com.android.server.usage.AppStandbyInternal; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; Loading Loading @@ -135,6 +139,9 @@ public class QuotaControllerTest { private static final int SOURCE_USER_ID = 0; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule(); private QuotaController mQuotaController; private QuotaController.QcConstants mQcConstants; private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); Loading Loading @@ -303,7 +310,7 @@ public class QuotaControllerTest { private int getProcessStateQuotaFreeThreshold() { synchronized (mQuotaController.mLock) { return mQuotaController.getProcessStateQuotaFreeThreshold(); return mQuotaController.getProcessStateQuotaFreeThreshold(mSourceUid); } } Loading Loading @@ -5197,6 +5204,101 @@ public class QuotaControllerTest { assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); } @Test @EnableCompatChanges({QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_TOP_STARTED_JOBS, QuotaController.OVERRIDE_QUOTA_ENFORCEMENT_TO_FGS_JOBS}) @RequiresFlagsEnabled({Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_TOP_STARTED_JOBS, Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS}) public void testTracking_OutOfQuota_ForegroundAndBackground_CompactChangeOverrides() { setDischarging(); JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1); JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2); trackJobs(jobBg, jobTop); setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window // Now the package only has 20 seconds to run. final long remainingTimeMs = 20 * SECOND_IN_MILLIS; mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession( JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false); InOrder inOrder = inOrder(mJobSchedulerService); // UID starts out inactive. setProcessState(ActivityManager.PROCESS_STATE_SERVICE); // Start the job. synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobBg); } advanceElapsedClock(remainingTimeMs / 2); // New job starts after UID is in the foreground. Since the app is now in the foreground, it // should continue to have remainingTimeMs / 2 time remaining. setProcessState(ActivityManager.PROCESS_STATE_TOP); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobTop); } advanceElapsedClock(remainingTimeMs); // Wait for some extra time to allow for job processing. inOrder.verify(mJobSchedulerService, timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); synchronized (mQuotaController.mLock) { assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg)); assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop)); } // Go to a background state. setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); advanceElapsedClock(remainingTimeMs / 2 + 1); // Only Bg job will be changed from in-quota to out-of-quota. inOrder.verify(mJobSchedulerService, timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); // Top job should still be allowed to run. assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); // New jobs to run. JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3); JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4); JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5); setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg); advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_TOP); inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); trackJobs(jobFg, jobTop); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobTop); } assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); // App still in foreground so everything should be in quota. advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); advanceElapsedClock(20 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_SERVICE); inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); // App is now in background and out of quota. Fg should now change to out of quota since it // wasn't started. Top should remain in quota since it started when the app was in TOP. assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); trackJobs(jobBg2); assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); } /** * Tests that TOP jobs are stopped when an app runs out of quota. */ Loading