Add delay between quota check alarms.

Since we don't need to mark existing jobs as in quota immediately when
the quota status changes, adding a min delay between quota check alarms
will prevent the system from possibly spinning on checking quotas.

Bug: 154444435
Test: atest CtsJobSchedulerTestCases
Test: atest QuotaControllerTest
Change-Id: I5b5efe9c973090c0f413812afc7479f012768c44
(cherry picked from commit 7e4fc4dcf0b6f3928927fe8ec3e1f631891d246d)
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 7256371..8c55b50 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
@@ -1863,6 +1863,9 @@
         /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
         @GuardedBy("mLock")
         private long mTriggerTimeElapsed = 0;
+        /** The minimum amount of time between quota check alarms. */
+        @GuardedBy("mLock")
+        private long mMinQuotaCheckDelayMs = QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS;
 
         @GuardedBy("mLock")
         void addAlarmLocked(int userId, @NonNull String pkgName, long inQuotaTimeElapsed) {
@@ -1873,6 +1876,11 @@
         }
 
         @GuardedBy("mLock")
+        void setMinQuotaCheckDelayMs(long minDelayMs) {
+            mMinQuotaCheckDelayMs = minDelayMs;
+        }
+
+        @GuardedBy("mLock")
         void removeAlarmLocked(@NonNull Package pkg) {
             if (mAlarmQueue.remove(pkg)) {
                 setNextAlarmLocked();
@@ -1902,8 +1910,14 @@
 
         @GuardedBy("mLock")
         private void setNextAlarmLocked() {
+            setNextAlarmLocked(sElapsedRealtimeClock.millis());
+        }
+
+        @GuardedBy("mLock")
+        private void setNextAlarmLocked(long earliestTriggerElapsed) {
             if (mAlarmQueue.size() > 0) {
-                final long nextTriggerTimeElapsed = mAlarmQueue.peek().second;
+                final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed,
+                        mAlarmQueue.peek().second);
                 // Only schedule the alarm if one of the following is true:
                 // 1. There isn't one currently scheduled
                 // 2. The new alarm is significantly earlier than the previous alarm. If it's
@@ -1939,7 +1953,7 @@
                         break;
                     }
                 }
-                setNextAlarmLocked();
+                setNextAlarmLocked(sElapsedRealtimeClock.millis() + mMinQuotaCheckDelayMs);
             }
         }
 
@@ -2022,6 +2036,7 @@
                 "max_session_count_per_rate_limiting_window";
         private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
                 "timing_session_coalescing_duration_ms";
+        private static final String KEY_MIN_QUOTA_CHECK_DELAY_MS = "min_quota_check_delay_ms";
 
         private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
                 10 * 60 * 1000L; // 10 minutes
@@ -2062,6 +2077,7 @@
         private static final int DEFAULT_MAX_SESSION_COUNT_RESTRICTED = 1; // 1/day
         private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
         private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
+        private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS;
 
         /** How much time each app will have to run jobs within their standby bucket window. */
         public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
@@ -2195,6 +2211,9 @@
         public long TIMING_SESSION_COALESCING_DURATION_MS =
                 DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
 
+        /** The minimum amount of time between quota check alarms. */
+        public long MIN_QUOTA_CHECK_DELAY_MS = DEFAULT_MIN_QUOTA_CHECK_DELAY_MS;
+
         // Safeguards
 
         /** The minimum number of jobs that any bucket will be allowed to run within its window. */
@@ -2288,6 +2307,8 @@
             TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
                     KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                     DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
+            MIN_QUOTA_CHECK_DELAY_MS = mParser.getDurationMillis(KEY_MIN_QUOTA_CHECK_DELAY_MS,
+                    DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
 
             updateConstants();
         }
@@ -2433,6 +2454,11 @@
                     mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs;
                     changed = true;
                 }
+                // Don't set changed to true for this one since we don't need to re-evaluate
+                // execution stats or constraint status. Limit the delay to the range [0, 15]
+                // minutes.
+                mInQuotaAlarmListener.setMinQuotaCheckDelayMs(
+                        Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS)));
 
                 if (changed) {
                     // Update job bookkeeping out of band.
@@ -2475,6 +2501,7 @@
                     MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
             pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                     TIMING_SESSION_COALESCING_DURATION_MS).println();
+            pw.printPair(KEY_MIN_QUOTA_CHECK_DELAY_MS, MIN_QUOTA_CHECK_DELAY_MS).println();
             pw.decreaseIndent();
         }
 
@@ -2520,6 +2547,8 @@
                     MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
             proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
                     TIMING_SESSION_COALESCING_DURATION_MS);
+            proto.write(ConstantsProto.QuotaController.MIN_QUOTA_CHECK_DELAY_MS,
+                    MIN_QUOTA_CHECK_DELAY_MS);
             proto.end(qcToken);
         }
     }
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index b678d62..ec99684 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -308,8 +308,10 @@
         // Treat two distinct {@link TimingSession}s as the same if they start and end within this
         // amount of time of each other.
         optional int64 timing_session_coalescing_duration_ms = 18;
+        // The minimum amount of time between quota check alarms.
+        optional int64 min_quota_check_delay_ms = 23;
 
-        // Next tag: 23
+        // Next tag: 24
     }
     optional QuotaController quota_controller = 24;