Merge "JobScheduler: Enforce quota to jobs running in FGS." into main
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index e5389b4..11c5b51 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -75,3 +75,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enforce_quota_policy_to_fgs_jobs"
+ namespace: "backstage_power"
+ description: "Applies the normal quota policy to FGS jobs"
+ bug: "341201311"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index a1c72fb..03a3a0d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -99,10 +99,10 @@
* the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
* not be allowed to run more than 20 jobs within the past 10 minutes.
*
- * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
- * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state do not count
- * towards any quota and are not restricted regardless of the app's state change.
+ * Jobs are throttled while an app is not in a TOP or BOUND_TOP state. All jobs are allowed to run
+ * freely when an app enters the TOP or BOUND_TOP state and are restricted when the app leaves those
+ * states. However, jobs that are started while the app is in the TOP state do not count towards any
+ * quota and are not restricted regardless of the app's state change.
*
* Jobs will not be throttled when the device is charging. The device is considered to be charging
* once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
@@ -567,6 +567,11 @@
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+ if (Flags.enforceQuotaPolicyToFgsJobs()) {
+ ActivityManager.getService().registerUidObserver(new QcUidObserver(),
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_BOUND_TOP, null);
+ }
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, null);
@@ -2706,6 +2711,12 @@
}
}
+ @VisibleForTesting
+ int getProcessStateQuotaFreeThreshold() {
+ return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP :
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
private class QcHandler extends Handler {
QcHandler(Looper looper) {
@@ -2832,15 +2843,15 @@
mTopAppCache.put(uid, true);
mTopAppGraceCache.delete(uid);
if (mForegroundUids.get(uid)) {
- // Went from FGS to TOP. We don't need to reprocess timers or
- // jobs.
+ // Went from a process state with quota free to TOP. We don't
+ // need to reprocess timers or jobs.
break;
}
mForegroundUids.put(uid, true);
isQuotaFree = true;
} else {
final boolean reprocess;
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
reprocess = !mForegroundUids.get(uid);
mForegroundUids.put(uid, true);
isQuotaFree = true;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 584fd62..40b9c61 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
+import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -303,6 +304,12 @@
}
}
+ private int getProcessStateQuotaFreeThreshold() {
+ synchronized (mQuotaController.mLock) {
+ return mQuotaController.getProcessStateQuotaFreeThreshold();
+ }
+ }
+
private void setProcessState(int procState) {
setProcessState(procState, mSourceUid);
}
@@ -315,7 +322,7 @@
final boolean contained = foregroundUids.get(uid);
mUidObserver.onUidStateChanged(uid, procState, 0,
ActivityManager.PROCESS_CAPABILITY_NONE);
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
if (!contained) {
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
.put(eq(uid), eq(true));
@@ -1371,7 +1378,7 @@
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
@@ -1473,7 +1480,7 @@
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -1505,7 +1512,7 @@
createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
timeUsedMs, 5), true);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -4126,7 +4133,7 @@
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
// Change to a state that should still be considered foreground.
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
advanceElapsedClock(5 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
@@ -4134,6 +4141,36 @@
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ /** Tests that Timers count FOREGROUND_SERVICE jobs. */
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
+ public void testTimerTracking_Fgs() {
+ setDischarging();
+
+ JobStatus jobStatus = createJobStatus("testTimerTracking_Fgs", 1);
+ setProcessState(ActivityManager.PROCESS_STATE_BOUND_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ // Change to FOREGROUND_SERVICE state that should count.
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
+ }
+ List<TimingSession> expected = new ArrayList<>();
+ expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that Timers properly track sessions when switching between foreground and background
* states.
@@ -4180,7 +4217,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4213,7 +4250,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4262,7 +4299,7 @@
}
assertEquals(0, stats.jobCountInRateLimitingWindow);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4412,7 +4449,7 @@
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4625,7 +4662,7 @@
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -5901,7 +5938,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -5935,7 +5972,7 @@
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -6056,7 +6093,7 @@
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -6534,7 +6571,7 @@
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop2.isExpeditedQuotaApproved());
assertTrue(jobFg.isExpeditedQuotaApproved());
assertTrue(jobBg.isExpeditedQuotaApproved());