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());