Merge "Changing default limits, clarify flags, fix bug." into qt-dev
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 3f8ddff..2873379 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -273,9 +273,11 @@
         // The maximum number of jobs an app can run within this particular standby bucket's
         // window size.
         optional int32 max_job_count_rare = 11;
+        // The period of time used to rate limit recently run jobs.
+        optional int32 rate_limiting_window_ms = 19;
         // The maximum number of jobs that should be allowed to run in the past
-        // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
-        optional int32 max_job_count_per_allowed_time = 12;
+        // rate_limiting_window_ms.
+        optional int32 max_job_count_per_rate_limiting_window = 12;
         // The maximum number of timing sessions an app can run within this particular standby
         // bucket's window size.
         optional int32 max_session_count_active = 13;
@@ -289,8 +291,8 @@
         // bucket's window size.
         optional int32 max_session_count_rare = 16;
         // The maximum number of timing sessions that should be allowed to run in the past
-        // {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS}.
-        optional int32 max_session_count_per_allowed_time = 17;
+        // rate_limiting_window_ms.
+        optional int32 max_session_count_per_rate_limiting_window = 17;
         // 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;
@@ -517,63 +519,56 @@
             optional int64 expiration_time_elapsed = 2;
             optional int64 window_size_ms = 3;
 
-            /** The total amount of time the app ran in its respective bucket window size. */
+            optional int32 job_count_limit = 14;
+            optional int32 session_count_limit = 15;
+
+            // The total amount of time the app ran in its respective bucket window size.
             optional int64 execution_time_in_window_ms = 4;
             optional int32 bg_job_count_in_window = 5;
 
-            /**
-             * The total amount of time the app ran in the last
-             * {@link QuotaController#MAX_PERIOD_MS}.
-             */
+            // The total amount of time the app ran in the last
+            // {@link QuotaController#MAX_PERIOD_MS}.
             optional int64 execution_time_in_max_period_ms = 6;
             optional int32 bg_job_count_in_max_period = 7;
 
-            /**
-             * The number of {@link TimingSession}s within the bucket window size. This will include
-             * sessions that started before the window as long as they end within the window.
-             */
+            // The number of {@link TimingSession}s within the bucket window size. This will include
+            // sessions that started before the window as long as they end within the window.
             optional int32 session_count_in_window = 11;
 
-            /**
-             * The time after which the sum of all the app's sessions plus
-             * ConstantsProto.QuotaController.in_quota_buffer_ms equals the quota. This is only
-             * valid if
-             * execution_time_in_window_ms >=
-             *   ConstantsProto.QuotaController.allowed_time_per_period_ms
-             * or
-             * execution_time_in_max_period_ms >=
-             *   ConstantsProto.QuotaController.max_execution_time_ms.
-             */
-            optional int64 quota_cutoff_time_elapsed = 8;
+            // The time after which the app will be under the bucket quota. This is only valid if
+            // execution_time_in_window_ms >=
+            //   ConstantsProto.QuotaController.allowed_time_per_period_ms
+            // or
+            // execution_time_in_max_period_ms >=
+            //   ConstantsProto.QuotaController.max_execution_time_ms
+            // or
+            // bg_job_count_in_window >= job_count_limit
+            // or
+            // session_count_in_window >= session_count_limit.
+            optional int64 in_quota_time_elapsed = 8;
 
-            /**
-             * The time after which job_count_in_allowed_time should be considered invalid, in the
-             * elapsed realtime timebase.
-             */
+            // The time after which job_count_in_rate_limiting_window should be considered invalid,
+            // in the elapsed realtime timebase.
             optional int64 job_count_expiration_time_elapsed = 9;
 
-            /**
-             * The number of jobs that ran in at least the last
-             * ConstantsProto.QuotaController.allowed_time_per_period_ms.
-             * It may contain a few stale entries since cleanup won't happen exactly every
-             * ConstantsProto.QuotaController.allowed_time_per_period_ms.
-             */
-            optional int32 job_count_in_allowed_time = 10;
+            // The number of jobs that ran in at least the last
+            // ConstantsProto.QuotaController.rate_limiting_window_ms.
+            // It may contain a few stale entries since cleanup won't happen exactly every
+            // ConstantsProto.QuotaController.rate_limiting_window_ms. This should only be
+            // considered valid before elapsed realtime has reached
+            // job_count_expiration_time_elapsed.
+            optional int32 job_count_in_rate_limiting_window = 10;
 
-            /**
-             * The time after which {@link #timingSessionCountInAllowedTime} should be considered
-             * invalid, in the elapsed realtime timebase.
-             */
+            // The time after which {@link #timingSessionCountInAllowedTime} should be considered
+            // invalid, in the elapsed realtime timebase.
             optional int64 session_count_expiration_time_elapsed = 12;
 
-            /**
-             * The number of {@link TimingSession}s that ran in at least the last
-             * {@link #mAllowedTimePerPeriodMs}. It may contain a few stale entries since cleanup won't
-             * happen exactly every {@link #mAllowedTimePerPeriodMs}. This should only be considered
-             * valid before elapsed realtime has reached
-             * {@link #timingSessionCountExpirationTimeElapsed}.
-             */
-            optional int32 session_count_in_allowed_time = 13;
+            // The number of {@link TimingSession}s that ran in at least the last
+            // ConstantsProto.QuotaController.rate_limiting_window_ms. It may contain a few stale
+            // entries since cleanup won't happen exactly every
+            // ConstantsProto.QuotaController.rate_limiting_window_ms. This should only be considered
+            // valid before elapsed realtime has reached session_count_expiration_time_elapsed.
+            optional int32 session_count_in_rate_limiting_window = 13;
         }
 
         message Package {
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index f560d69..ef6944e 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -16,6 +16,10 @@
 
 package com.android.server.job.controllers;
 
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -75,17 +79,28 @@
 /**
  * Controller that tracks whether an app has exceeded its standby bucket quota.
  *
- * Each job in each bucket is given 10 minutes to run within its respective time window. Active
- * jobs can run indefinitely, working set jobs can run for 10 minutes within a 2 hour window,
- * frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run 10 minutes in
- * a 24 hour window. The windows are rolling, so as soon as a job would have some quota based on its
- * bucket, it will be eligible to run. When a job's bucket changes, its new quota is immediately
- * applied to it.
+ * With initial defaults, each app in each bucket is given 10 minutes to run within its respective
+ * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a
+ * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run
+ * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some
+ * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new
+ * quota is immediately applied to it.
+ *
+ * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on
+ * 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 are not
- * restricted regardless of the app's state change.
+ * 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 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.
+ *
+ * Note: all limits are enforced per bucket window unless explicitly stated otherwise.
+ * All stated values are configurable and subject to change. See {@link QcConstants} for current
+ * defaults.
  *
  * Test: atest com.android.server.job.controllers.QuotaControllerTest
  */
@@ -94,8 +109,6 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final long MINUTE_IN_MILLIS = 60 * 1000L;
-
     private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
     private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
 
@@ -244,6 +257,8 @@
         public long expirationTimeElapsed;
 
         public long windowSizeMs;
+        public int jobCountLimit;
+        public int sessionCountLimit;
 
         /** The total amount of time the app ran in its respective bucket window size. */
         public long executionTimeInWindowMs;
@@ -260,54 +275,58 @@
         public int sessionCountInWindow;
 
         /**
-         * The time after which the sum of all the app's sessions plus {@link #mQuotaBufferMs}
-         * equals the quota. This is only valid if
-         * executionTimeInWindowMs >= {@link #mAllowedTimePerPeriodMs} or
-         * executionTimeInMaxPeriodMs >= {@link #mMaxExecutionTimeMs}.
+         * The time after which the app will be under the bucket quota and can start running jobs
+         * again. This is only valid if
+         * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs},
+         * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+         * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+         * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
          */
-        public long quotaCutoffTimeElapsed;
+        public long inQuotaTimeElapsed;
 
         /**
-         * The time after which {@link #jobCountInAllowedTime} should be considered invalid, in the
-         * elapsed realtime timebase.
+         * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
+         * in the elapsed realtime timebase.
          */
-        public long jobCountExpirationTimeElapsed;
+        public long jobRateLimitExpirationTimeElapsed;
 
         /**
-         * The number of jobs that ran in at least the last {@link #mAllowedTimePerPeriodMs}.
+         * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}.
          * It may contain a few stale entries since cleanup won't happen exactly every
-         * {@link #mAllowedTimePerPeriodMs}.
+         * {@link #mRateLimitingWindowMs}.
          */
-        public int jobCountInAllowedTime;
+        public int jobCountInRateLimitingWindow;
 
         /**
-         * The time after which {@link #sessionCountInAllowedTime} should be considered
+         * The time after which {@link #sessionCountInRateLimitingWindow} should be considered
          * invalid, in the elapsed realtime timebase.
          */
-        public long sessionCountExpirationTimeElapsed;
+        public long sessionRateLimitExpirationTimeElapsed;
 
         /**
          * The number of {@link TimingSession}s that ran in at least the last
-         * {@link #mAllowedTimePerPeriodMs}. It may contain a few stale entries since cleanup won't
-         * happen exactly every {@link #mAllowedTimePerPeriodMs}. This should only be considered
-         * valid before elapsed realtime has reached {@link #sessionCountExpirationTimeElapsed}.
+         * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't
+         * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered
+         * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}.
          */
-        public int sessionCountInAllowedTime;
+        public int sessionCountInRateLimitingWindow;
 
         @Override
         public String toString() {
             return "expirationTime=" + expirationTimeElapsed + ", "
-                    + "windowSize=" + windowSizeMs + ", "
+                    + "windowSizeMs=" + windowSizeMs + ", "
+                    + "jobCountLimit=" + jobCountLimit + ", "
+                    + "sessionCountLimit=" + sessionCountLimit + ", "
                     + "executionTimeInWindow=" + executionTimeInWindowMs + ", "
                     + "bgJobCountInWindow=" + bgJobCountInWindow + ", "
                     + "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", "
                     + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
                     + "sessionCountInWindow=" + sessionCountInWindow + ", "
-                    + "quotaCutoffTime=" + quotaCutoffTimeElapsed + ", "
-                    + "jobCountExpirationTime=" + jobCountExpirationTimeElapsed + ", "
-                    + "jobCountInAllowedTime=" + jobCountInAllowedTime + ", "
-                    + "sessionCountExpirationTime=" + sessionCountExpirationTimeElapsed + ", "
-                    + "sessionCountInAllowedTime=" + sessionCountInAllowedTime;
+                    + "inQuotaTime=" + inQuotaTimeElapsed + ", "
+                    + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
+                    + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", "
+                    + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", "
+                    + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow;
         }
 
         @Override
@@ -316,18 +335,21 @@
                 ExecutionStats other = (ExecutionStats) obj;
                 return this.expirationTimeElapsed == other.expirationTimeElapsed
                         && this.windowSizeMs == other.windowSizeMs
+                        && this.jobCountLimit == other.jobCountLimit
+                        && this.sessionCountLimit == other.sessionCountLimit
                         && this.executionTimeInWindowMs == other.executionTimeInWindowMs
                         && this.bgJobCountInWindow == other.bgJobCountInWindow
                         && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
                         && this.sessionCountInWindow == other.sessionCountInWindow
                         && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
-                        && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed
-                        && this.jobCountExpirationTimeElapsed == other.jobCountExpirationTimeElapsed
-                        && this.jobCountInAllowedTime == other.jobCountInAllowedTime
-                        && this.sessionCountExpirationTimeElapsed
-                        == other.sessionCountExpirationTimeElapsed
-                        && this.sessionCountInAllowedTime
-                        == other.sessionCountInAllowedTime;
+                        && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed
+                        && this.jobRateLimitExpirationTimeElapsed
+                                == other.jobRateLimitExpirationTimeElapsed
+                        && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow
+                        && this.sessionRateLimitExpirationTimeElapsed
+                                == other.sessionRateLimitExpirationTimeElapsed
+                        && this.sessionCountInRateLimitingWindow
+                                == other.sessionCountInRateLimitingWindow;
             } else {
                 return false;
             }
@@ -338,16 +360,18 @@
             int result = 0;
             result = 31 * result + hashLong(expirationTimeElapsed);
             result = 31 * result + hashLong(windowSizeMs);
+            result = 31 * result + hashLong(jobCountLimit);
+            result = 31 * result + hashLong(sessionCountLimit);
             result = 31 * result + hashLong(executionTimeInWindowMs);
             result = 31 * result + bgJobCountInWindow;
             result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
             result = 31 * result + bgJobCountInMaxPeriod;
             result = 31 * result + sessionCountInWindow;
-            result = 31 * result + hashLong(quotaCutoffTimeElapsed);
-            result = 31 * result + hashLong(jobCountExpirationTimeElapsed);
-            result = 31 * result + jobCountInAllowedTime;
-            result = 31 * result + hashLong(sessionCountExpirationTimeElapsed);
-            result = 31 * result + sessionCountInAllowedTime;
+            result = 31 * result + hashLong(inQuotaTimeElapsed);
+            result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed);
+            result = 31 * result + jobCountInRateLimitingWindow;
+            result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed);
+            result = 31 * result + sessionCountInRateLimitingWindow;
             return result;
         }
     }
@@ -399,19 +423,19 @@
     private boolean mShouldThrottle;
 
     /** How much time each app will have to run jobs within their standby bucket window. */
-    private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
+    private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
 
     /**
      * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
      * window.
      */
-    private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
+    private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS;
 
     /**
      * How much time the app should have before transitioning from out-of-quota to in-quota.
      * This should not affect processing if the app is already in-quota.
      */
-    private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
+    private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
 
     /**
      * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
@@ -425,14 +449,19 @@
      */
     private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
 
-    /** The maximum number of jobs that can run within the past {@link #mAllowedTimePerPeriodMs}. */
-    private int mMaxJobCountPerAllowedTime = 20;
+    /** The period of time used to rate limit recently run jobs. */
+    private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
+
+    /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */
+    private int mMaxJobCountPerRateLimitingWindow =
+            QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
 
     /**
      * The maximum number of {@link TimingSession}s that can run within the past {@link
-     * #mAllowedTimePerPeriodMs}.
+     * #mRateLimitingWindowMs}.
      */
-    private int mMaxSessionCountPerAllowedTime = 20;
+    private int mMaxSessionCountPerRateLimitingWindow =
+            QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
 
     private long mNextCleanupTimeElapsed = 0;
     private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
@@ -486,11 +515,11 @@
      * The rolling window size for each standby bucket. Within each window, an app will have 10
      * minutes to run its jobs.
      */
-    private final long[] mBucketPeriodsMs = new long[] {
-            10 * MINUTE_IN_MILLIS, // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time
-            2 * 60 * MINUTE_IN_MILLIS, // 2 hours for WORKING
-            8 * 60 * MINUTE_IN_MILLIS, // 8 hours for FREQUENT
-            24 * 60 * MINUTE_IN_MILLIS // 24 hours for RARE
+    private final long[] mBucketPeriodsMs = new long[]{
+            QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
+            QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
+            QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
+            QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
     };
 
     /** The maximum period any bucket can have. */
@@ -503,16 +532,13 @@
      *
      * @see #mBucketPeriodsMs
      */
-    private final int[] mMaxBucketJobCounts = new int[] {
-            200,  // ACTIVE   -- 1200/hr
-            1200, // WORKING  -- 600/hr
-            1800, // FREQUENT -- 225/hr
-            2400  // RARE     -- 100/hr
+    private final int[] mMaxBucketJobCounts = new int[]{
+            QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
+            QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
+            QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
+            QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
     };
 
-    /** The minimum number of jobs that any bucket will be allowed to run. */
-    private static final int MIN_BUCKET_JOB_COUNT = 100;
-
     /**
      * The maximum number of {@link TimingSession}s based on its standby bucket. For each max value
      * count in the array, the app will not be allowed to have more than that many number of
@@ -527,14 +553,12 @@
             QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
     };
 
-    /** The minimum number of {@link TimingSession}s that any bucket will be allowed to run. */
-    private static final int MIN_BUCKET_SESSION_COUNT = 3;
-
     /**
      * Treat two distinct {@link TimingSession}s as the same if they start and end within this
      * amount of time of each other.
      */
-    private long mTimingSessionCoalescingDurationMs = 0;
+    private long mTimingSessionCoalescingDurationMs =
+            QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
 
     /** An app has reached its quota. The message should contain a {@link Package} object. */
     private static final int MSG_REACHED_QUOTA = 0;
@@ -761,8 +785,8 @@
             final int standbyBucket) {
         final long now = sElapsedRealtimeClock.millis();
         final boolean isUnderAllowedTimeQuota =
-                (stats.jobCountExpirationTimeElapsed <= now
-                        || stats.jobCountInAllowedTime < mMaxJobCountPerAllowedTime);
+                (stats.jobRateLimitExpirationTimeElapsed <= now
+                        || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
         return isUnderAllowedTimeQuota
                 && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
     }
@@ -770,10 +794,8 @@
     private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
             final int standbyBucket) {
         final long now = sElapsedRealtimeClock.millis();
-        final boolean isUnderAllowedTimeQuota =
-                (stats.sessionCountExpirationTimeElapsed <= now
-                        || stats.sessionCountInAllowedTime
-                        < mMaxSessionCountPerAllowedTime);
+        final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
+                || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
         return isUnderAllowedTimeQuota
                 && stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket];
     }
@@ -924,12 +946,18 @@
         }
         if (refreshStatsIfOld) {
             final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
+            final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
+            final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
             Timer timer = mPkgTimers.get(userId, packageName);
             if ((timer != null && timer.isActive())
                     || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
-                    || stats.windowSizeMs != bucketWindowSizeMs) {
+                    || stats.windowSizeMs != bucketWindowSizeMs
+                    || stats.jobCountLimit != jobCountLimit
+                    || stats.sessionCountLimit != sessionCountLimit) {
                 // The stats are no longer valid.
                 stats.windowSizeMs = bucketWindowSizeMs;
+                stats.jobCountLimit = jobCountLimit;
+                stats.sessionCountLimit = sessionCountLimit;
                 updateExecutionStatsLocked(userId, packageName, stats);
             }
         }
@@ -945,7 +973,7 @@
         stats.executionTimeInMaxPeriodMs = 0;
         stats.bgJobCountInMaxPeriod = 0;
         stats.sessionCountInWindow = 0;
-        stats.quotaCutoffTimeElapsed = 0;
+        stats.inQuotaTimeElapsed = 0;
 
         Timer timer = mPkgTimers.get(userId, packageName);
         final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -958,12 +986,12 @@
             // invalidate now.
             stats.expirationTimeElapsed = nowElapsed;
             if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                        nowElapsed - mAllowedTimeIntoQuotaMs);
+                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                        nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
             }
             if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                        nowElapsed - mMaxExecutionTimeIntoQuotaMs);
+                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                        nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
             }
         }
 
@@ -985,36 +1013,40 @@
             TimingSession session = sessions.get(i);
 
             // Window management.
-            if (startWindowElapsed < session.startTimeElapsed) {
-                stats.executionTimeInWindowMs += session.endTimeElapsed - session.startTimeElapsed;
+            if (startWindowElapsed < session.endTimeElapsed) {
+                final long start;
+                if (startWindowElapsed < session.startTimeElapsed) {
+                    start = session.startTimeElapsed;
+                    emptyTimeMs =
+                            Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
+                } else {
+                    // The session started before the window but ended within the window. Only
+                    // include the portion that was within the window.
+                    start = startWindowElapsed;
+                    emptyTimeMs = 0;
+                }
+
+                stats.executionTimeInWindowMs += session.endTimeElapsed - start;
                 stats.bgJobCountInWindow += session.bgJobCount;
-                emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
                 if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                            session.startTimeElapsed + stats.executionTimeInWindowMs
-                                    - mAllowedTimeIntoQuotaMs);
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                            start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
+                                    + stats.windowSizeMs);
+                }
+                if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                            session.endTimeElapsed + stats.windowSizeMs);
                 }
                 if (i == loopStart
                         || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
                                 > mTimingSessionCoalescingDurationMs) {
                     // Coalesce sessions if they are very close to each other in time
                     sessionCountInWindow++;
-                }
-            } else if (startWindowElapsed < session.endTimeElapsed) {
-                // The session started before the window but ended within the window. Only include
-                // the portion that was within the window.
-                stats.executionTimeInWindowMs += session.endTimeElapsed - startWindowElapsed;
-                stats.bgJobCountInWindow += session.bgJobCount;
-                emptyTimeMs = 0;
-                if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
-                            startWindowElapsed + stats.executionTimeInWindowMs
-                                    - mAllowedTimeIntoQuotaMs);
-                }
-                if (i == loopStart
-                        || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
-                                > mTimingSessionCoalescingDurationMs) {
-                    sessionCountInWindow++;
+
+                    if (sessionCountInWindow >= stats.sessionCountLimit) {
+                        stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                                session.endTimeElapsed + stats.windowSizeMs);
+                    }
                 }
             }
 
@@ -1025,9 +1057,9 @@
                 stats.bgJobCountInMaxPeriod += session.bgJobCount;
                 emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
                 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
                             session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
-                                    - mMaxExecutionTimeIntoQuotaMs);
+                                    - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
                 }
             } else if (startMaxElapsed < session.endTimeElapsed) {
                 // The session started before the window but ended within the window. Only include
@@ -1036,9 +1068,9 @@
                 stats.bgJobCountInMaxPeriod += session.bgJobCount;
                 emptyTimeMs = 0;
                 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
-                    stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+                    stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
                             startMaxElapsed + stats.executionTimeInMaxPeriodMs
-                                    - mMaxExecutionTimeIntoQuotaMs);
+                                    - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
                 }
             } else {
                 // This session ended before the window. No point in going any further.
@@ -1094,11 +1126,11 @@
                 stats = new ExecutionStats();
                 appStats[i] = stats;
             }
-            if (stats.jobCountExpirationTimeElapsed <= now) {
-                stats.jobCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
-                stats.jobCountInAllowedTime = 0;
+            if (stats.jobRateLimitExpirationTimeElapsed <= now) {
+                stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
+                stats.jobCountInRateLimitingWindow = 0;
             }
-            stats.jobCountInAllowedTime += count;
+            stats.jobCountInRateLimitingWindow += count;
         }
     }
 
@@ -1115,11 +1147,11 @@
                 stats = new ExecutionStats();
                 appStats[i] = stats;
             }
-            if (stats.sessionCountExpirationTimeElapsed <= now) {
-                stats.sessionCountExpirationTimeElapsed = now + mAllowedTimePerPeriodMs;
-                stats.sessionCountInAllowedTime = 0;
+            if (stats.sessionRateLimitExpirationTimeElapsed <= now) {
+                stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
+                stats.sessionCountInRateLimitingWindow = 0;
             }
-            stats.sessionCountInAllowedTime++;
+            stats.sessionCountInRateLimitingWindow++;
         }
     }
 
@@ -1367,18 +1399,17 @@
         }
 
         // The time this app will have quota again.
-        long inQuotaTimeElapsed = stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
-        if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
+        long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
+        if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
+            // App hit the rate limit.
             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
+                    stats.jobRateLimitExpirationTimeElapsed);
         }
-        if (!isUnderJobCountQuota) {
+        if (!isUnderTimingSessionCountQuota
+                && stats.sessionCountInWindow < stats.sessionCountLimit) {
+            // App hit the rate limit.
             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.jobCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
-        }
-        if (!isUnderTimingSessionCountQuota) {
-            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
-                    stats.sessionCountExpirationTimeElapsed + mAllowedTimePerPeriodMs);
+                    stats.sessionRateLimitExpirationTimeElapsed);
         }
         // Only schedule the alarm if:
         // 1. There isn't one currently scheduled
@@ -1399,7 +1430,7 @@
                     ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
             alarmListener.setTriggerTime(inQuotaTimeElapsed);
         } else if (DEBUG) {
-            Slog.d(TAG, "No need to scheduling start alarm for " + pkgString);
+            Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
         }
     }
 
@@ -1968,14 +1999,15 @@
         private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
         private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
         private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
-        private static final String KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME =
-                "max_count_per_allowed_time";
+        private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
+        private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+                "max_job_count_per_rate_limiting_window";
         private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active";
         private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
         private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
         private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
-        private static final String KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME =
-                "max_session_count_per_allowed_time";
+        private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+                "max_session_count_per_rate_limiting_window";
         private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
                 "timing_session_coalescing_duration_ms";
 
@@ -1984,7 +2016,7 @@
         private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
                 30 * 1000L; // 30 seconds
         private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
-                10 * 60 * 1000L; // 10 minutes for ACTIVE -- ACTIVE apps can run jobs at any time
+                DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
         private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
                 2 * 60 * 60 * 1000L; // 2 hours
         private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
@@ -1992,16 +2024,18 @@
         private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
                 24 * 60 * 60 * 1000L; // 24 hours
         private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
-                4 * 60 * 60 * 1000L; // 4 hours
-        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
-                200; // 1200/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_WORKING =
-                1200; // 600/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT =
-                1800; // 225/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_RARE =
-                2400; // 100/hr
-        private static final int DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME = 20;
+                4 * HOUR_IN_MILLIS;
+        private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
+                10 * MINUTE_IN_MILLIS;
+        private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
+        private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = // 20/window = 120/hr = 1/session
+                DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
+        private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
+                (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
+        private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
+                (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
+        private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
+                (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
         private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
                 20; // 120/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
@@ -2010,8 +2044,8 @@
                 8; // 1/hr
         private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
                 3; // .125/hr
-        private static final int DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME = 20;
-        private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 0;
+        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
 
         /** 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;
@@ -2079,11 +2113,14 @@
          */
         public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
 
+        /** The period of time used to rate limit recently run jobs. */
+        public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
+
         /**
-         * The maximum number of jobs that can run within the past
-         * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
+         * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}.
          */
-        public int MAX_JOB_COUNT_PER_ALLOWED_TIME = DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
+                DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
 
         /**
          * The maximum number of {@link TimingSession}s an app can run within this particular
@@ -2113,7 +2150,8 @@
          * The maximum number of {@link TimingSession}s that can run within the past
          * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
          */
-        public int MAX_SESSION_COUNT_PER_ALLOWED_TIME = DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME;
+        public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
+                DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
 
         /**
          * Treat two distinct {@link TimingSession}s as the same if they start and end within this
@@ -2122,6 +2160,29 @@
         public long TIMING_SESSION_COALESCING_DURATION_MS =
                 DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
 
+        // Safeguards
+
+        /** The minimum number of jobs that any bucket will be allowed to run within its window. */
+        private static final int MIN_BUCKET_JOB_COUNT = 10;
+
+        /**
+         * The minimum number of {@link TimingSession}s that any bucket will be allowed to run
+         * within its window.
+         */
+        private static final int MIN_BUCKET_SESSION_COUNT = 1;
+
+        /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */
+        private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS;
+
+        /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
+        private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10;
+
+        /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
+        private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10;
+
+        /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */
+        private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS;
+
         QcConstants(Handler handler) {
             super(handler);
         }
@@ -2167,8 +2228,11 @@
                     KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
             MAX_JOB_COUNT_RARE = mParser.getInt(
                     KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
-            MAX_JOB_COUNT_PER_ALLOWED_TIME = mParser.getInt(
-                    KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME, DEFAULT_MAX_JOB_COUNT_PER_ALLOWED_TIME);
+            RATE_LIMITING_WINDOW_MS = mParser.getLong(
+                    KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
+            MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
+                    KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                    DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
             MAX_SESSION_COUNT_ACTIVE = mParser.getInt(
                     KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
             MAX_SESSION_COUNT_WORKING = mParser.getInt(
@@ -2177,9 +2241,9 @@
                     KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
             MAX_SESSION_COUNT_RARE = mParser.getInt(
                     KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
-            MAX_SESSION_COUNT_PER_ALLOWED_TIME = mParser.getInt(
-                    KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME,
-                    DEFAULT_MAX_SESSION_COUNT_PER_ALLOWED_TIME);
+            MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
+                    KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                    DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
             TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
                     KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                     DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
@@ -2192,7 +2256,14 @@
             synchronized (mLock) {
                 boolean changed = false;
 
-                long newAllowedTimeMs = Math.min(MAX_PERIOD_MS,
+                long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
+                        Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
+                if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
+                    mMaxExecutionTimeMs = newMaxExecutionTimeMs;
+                    mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+                    changed = true;
+                }
+                long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
                         Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
                 if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
                     mAllowedTimePerPeriodMs = newAllowedTimeMs;
@@ -2231,47 +2302,44 @@
                     mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
                     changed = true;
                 }
-                long newMaxExecutionTimeMs = Math.max(60 * MINUTE_IN_MILLIS,
-                        Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
-                if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
-                    mMaxExecutionTimeMs = newMaxExecutionTimeMs;
-                    mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+                long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
+                        Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
+                if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
+                    mRateLimitingWindowMs = newRateLimitingWindowMs;
                     changed = true;
                 }
-                int newMaxCountPerAllowedPeriod = Math.max(10,
-                        MAX_JOB_COUNT_PER_ALLOWED_TIME);
-                if (mMaxJobCountPerAllowedTime != newMaxCountPerAllowedPeriod) {
-                    mMaxJobCountPerAllowedTime = newMaxCountPerAllowedPeriod;
+                int newMaxJobCountPerRateLimitingWindow = Math.max(
+                        MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                        MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
+                if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
+                    mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
                     changed = true;
                 }
-                int newActiveMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE));
+                int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
                 if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
                     mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
                     changed = true;
                 }
-                int newWorkingMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING));
+                int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING);
                 if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
                     mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
                     changed = true;
                 }
-                int newFrequentMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT));
+                int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT);
                 if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
                     mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
                     changed = true;
                 }
-                int newRareMaxJobCount = Math.max(mMaxJobCountPerAllowedTime,
-                        Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE));
+                int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
                 if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
                     mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
                     changed = true;
                 }
-                int newMaxSessionCountPerAllowedPeriod = Math.max(10,
-                        MAX_SESSION_COUNT_PER_ALLOWED_TIME);
-                if (mMaxSessionCountPerAllowedTime != newMaxSessionCountPerAllowedPeriod) {
-                    mMaxSessionCountPerAllowedTime = newMaxSessionCountPerAllowedPeriod;
+                int newMaxSessionCountPerRateLimitPeriod = Math.max(
+                        MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                        MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
+                if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
+                    mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
                     changed = true;
                 }
                 int newActiveMaxSessionCount =
@@ -2332,14 +2400,15 @@
             pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
             pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
             pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
-            pw.printPair(KEY_MAX_JOB_COUNT_PER_ALLOWED_TIME, MAX_JOB_COUNT_PER_ALLOWED_TIME)
-                    .println();
+            pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
+            pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
             pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
-            pw.printPair(KEY_MAX_SESSION_COUNT_PER_ALLOWED_TIME, MAX_SESSION_COUNT_PER_ALLOWED_TIME)
-                    .println();
+            pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
             pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
                     TIMING_SESSION_COALESCING_DURATION_MS).println();
             pw.decreaseIndent();
@@ -2365,8 +2434,10 @@
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
                     MAX_JOB_COUNT_FREQUENT);
             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
-            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_ALLOWED_TIME,
-                    MAX_JOB_COUNT_PER_ALLOWED_TIME);
+            proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
+                    RATE_LIMITING_WINDOW_MS);
+            proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE,
                     MAX_SESSION_COUNT_ACTIVE);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING,
@@ -2375,8 +2446,8 @@
                     MAX_SESSION_COUNT_FREQUENT);
             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
                     MAX_SESSION_COUNT_RARE);
-            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_ALLOWED_TIME,
-                    MAX_SESSION_COUNT_PER_ALLOWED_TIME);
+            proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
+                    MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
             proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
                     TIMING_SESSION_COALESCING_DURATION_MS);
             proto.end(qcToken);
@@ -2431,8 +2502,18 @@
     }
 
     @VisibleForTesting
-    int getMaxJobCountPerAllowedTime() {
-        return mMaxJobCountPerAllowedTime;
+    int getMaxJobCountPerRateLimitingWindow() {
+        return mMaxJobCountPerRateLimitingWindow;
+    }
+
+    @VisibleForTesting
+    int getMaxSessionCountPerRateLimitingWindow() {
+        return mMaxSessionCountPerRateLimitingWindow;
+    }
+
+    @VisibleForTesting
+    long getRateLimitingWindowMs() {
+        return mRateLimitingWindowMs;
     }
 
     @VisibleForTesting
@@ -2441,11 +2522,6 @@
     }
 
     @VisibleForTesting
-    int getMaxSessionCountPerAllowedTime() {
-        return mMaxSessionCountPerAllowedTime;
-    }
-
-    @VisibleForTesting
     @Nullable
     List<TimingSession> getTimingSessions(int userId, String packageName) {
         return mTimingSessions.get(userId, packageName);
@@ -2659,6 +2735,12 @@
                                 StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS,
                                 es.windowSizeMs);
                         proto.write(
+                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT,
+                                es.jobCountLimit);
+                        proto.write(
+                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT,
+                                es.sessionCountLimit);
+                        proto.write(
                                 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS,
                                 es.executionTimeInWindowMs);
                         proto.write(
@@ -2674,20 +2756,20 @@
                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW,
                                 es.sessionCountInWindow);
                         proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.QUOTA_CUTOFF_TIME_ELAPSED,
-                                es.quotaCutoffTimeElapsed);
+                                StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
+                                es.inQuotaTimeElapsed);
                         proto.write(
                                 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED,
-                                es.jobCountExpirationTimeElapsed);
+                                es.jobRateLimitExpirationTimeElapsed);
                         proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_ALLOWED_TIME,
-                                es.jobCountInAllowedTime);
+                                StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW,
+                                es.jobCountInRateLimitingWindow);
                         proto.write(
                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED,
-                                es.sessionCountExpirationTimeElapsed);
+                                es.sessionRateLimitExpirationTimeElapsed);
                         proto.write(
-                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_ALLOWED_TIME,
-                                es.sessionCountInAllowedTime);
+                                StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW,
+                                es.sessionCountInRateLimitingWindow);
                         proto.end(esToken);
                     }
                 }
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 4e89357..1db6b8e 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
@@ -386,6 +386,8 @@
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
 
         final int uid = 10001;
         mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
@@ -424,6 +426,8 @@
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
 
         mQuotaController.onUserRemovedLocked(0);
         assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
@@ -456,6 +460,8 @@
         ExecutionStats inputStats = new ExecutionStats();
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+        inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
+        inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
         // Invalid time is now +24 hours since there are no sessions at all for the app.
         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
@@ -520,6 +526,7 @@
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+        inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 2;
         // Invalid time is now since the start of the session is at the very edge of the window
         // cutoff time.
         expectedStats.expirationTimeElapsed = now;
@@ -528,10 +535,13 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 3;
+        expectedStats.inQuotaTimeElapsed = now + 11 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        inputStats.jobCountLimit = expectedStats.jobCountLimit = 6;
+        inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
         // Invalid time is now since the session straddles the window cutoff time.
         expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
@@ -539,8 +549,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 4;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
-                + mQcConstants.IN_QUOTA_BUFFER_MS;
+        expectedStats.inQuotaTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
@@ -553,8 +562,9 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 4;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
-                + mQcConstants.IN_QUOTA_BUFFER_MS;
+        // App goes under job execution time limit in ~61 minutes, but will be under job count limit
+        // in 65 minutes.
+        expectedStats.inQuotaTimeElapsed = now + 65 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
@@ -567,8 +577,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 15;
         expectedStats.sessionCountInWindow = 5;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
-                + mQcConstants.IN_QUOTA_BUFFER_MS;
+        expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS + 5 * MINUTE_IN_MILLIS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
 
@@ -577,6 +586,7 @@
                 .add(0,
                         createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
         // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
         // before the end of the max period cutoff time.
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
@@ -585,7 +595,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 18;
         expectedStats.sessionCountInWindow = 5;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
@@ -602,7 +612,7 @@
         expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 5;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
         assertEquals(expectedStats, inputStats);
@@ -627,6 +637,8 @@
 
         // Active
         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
         expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 5;
@@ -638,45 +650,103 @@
 
         // Working
         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
         expectedStats.expirationTimeElapsed = now;
         expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 10;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 2;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 3 * MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
 
         // Frequent
         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 15;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 3;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
 
         // Rare
         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
         expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInWindow = 20;
         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
         expectedStats.bgJobCountInMaxPeriod = 20;
         expectedStats.sessionCountInWindow = 4;
-        expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+        expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
                 + mQcConstants.IN_QUOTA_BUFFER_MS;
         assertEquals(expectedStats,
                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
     }
 
     /**
+     * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
+     */
+    @Test
+    public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+        // Set time to 3 minutes after boot.
+        advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
+        advanceElapsedClock(3 * MINUTE_IN_MILLIS);
+
+        mQuotaController.saveTimingSession(0, "com.android.test",
+                createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2));
+
+        ExecutionStats expectedStats = new ExecutionStats();
+
+        // Active
+        expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
+        expectedStats.expirationTimeElapsed = 11 * MINUTE_IN_MILLIS;
+        expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInWindow = 2;
+        expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
+        expectedStats.bgJobCountInMaxPeriod = 2;
+        expectedStats.sessionCountInWindow = 1;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
+
+        // Working
+        expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
+        expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
+
+        // Frequent
+        expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
+        expectedStats.expirationTimeElapsed = 8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
+
+        // Rare
+        expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
+        expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
+        expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+        assertEquals(expectedStats,
+                mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+    }
+
+    /**
      * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
      */
     @Test
@@ -850,13 +920,15 @@
 
         ExecutionStats expectedStats = new ExecutionStats();
         expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsActive.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
         expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
         expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
         expectedStats.sessionCountInWindow = originalStatsActive.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsActive.inQuotaTimeElapsed;
         final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", ACTIVE_INDEX);
         // Stats for the same bucket should use the same object.
@@ -864,33 +936,39 @@
         assertEquals(expectedStats, newStatsActive);
 
         expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
         expectedStats.sessionCountInWindow = originalStatsWorking.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsWorking.inQuotaTimeElapsed;
         final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", WORKING_INDEX);
         assertTrue(originalStatsWorking == newStatsWorking);
         assertNotEquals(expectedStats, newStatsWorking);
 
         expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
         expectedStats.sessionCountInWindow = originalStatsFrequent.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsFrequent.inQuotaTimeElapsed;
         final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", FREQUENT_INDEX);
         assertTrue(originalStatsFrequent == newStatsFrequent);
         assertNotEquals(expectedStats, newStatsFrequent);
 
         expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
+        expectedStats.jobCountLimit = originalStatsRare.jobCountLimit;
+        expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit;
         expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
         expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
         expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
         expectedStats.sessionCountInWindow = originalStatsRare.sessionCountInWindow;
-        expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
+        expectedStats.inQuotaTimeElapsed = originalStatsRare.inQuotaTimeElapsed;
         final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
                 "com.android.test", RARE_INDEX);
         assertTrue(originalStatsRare == newStatsRare);
@@ -1065,7 +1143,7 @@
     public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
         setDischarging();
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
         mQuotaController.saveTimingSession(0, "com.android.test.spam",
                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
         mQuotaController.saveTimingSession(0, "com.android.test.spam",
@@ -1100,7 +1178,7 @@
     public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
         setDischarging();
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME;
+        final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
         mQuotaController.saveTimingSession(0, "com.android.test",
                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
         mQuotaController.saveTimingSession(0, "com.android.test",
@@ -1141,7 +1219,7 @@
         advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
 
         assertEquals(2, mQuotaController.getExecutionStatsLocked(
-                SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInAllowedTime);
+                SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInRateLimitingWindow);
         assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus));
     }
 
@@ -1212,9 +1290,9 @@
                 mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size());
         for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) {
             assertEquals(42, mQuotaController.getExecutionStatsLocked(
-                    SOURCE_USER_ID, fgChangerPkgName, i).jobCountInAllowedTime);
+                    SOURCE_USER_ID, fgChangerPkgName, i).jobCountInRateLimitingWindow);
             assertEquals(1, mQuotaController.getExecutionStatsLocked(
-                    SOURCE_USER_ID, unaffectedPkgName, i).jobCountInAllowedTime);
+                    SOURCE_USER_ID, unaffectedPkgName, i).jobCountInRateLimitingWindow);
         }
     }
 
@@ -1555,26 +1633,29 @@
     }
 
     @Test
-    public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
+    public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
+        // Set rate limiting period different from allowed time to confirm code sets based on
+        // the former.
+        mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 10 * MINUTE_IN_MILLIS;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 5 * MINUTE_IN_MILLIS;
+        mQcConstants.updateConstants();
+
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         final int standbyBucket = WORKING_INDEX;
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        stats.jobCountInAllowedTime =
-                mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
+        stats.jobCountInRateLimitingWindow =
+                mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW + 2;
 
         // Invalid time in the past, so the count shouldn't be used.
-        stats.jobCountExpirationTimeElapsed =
-                now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
+        stats.jobRateLimitExpirationTimeElapsed = now - 5 * MINUTE_IN_MILLIS / 2;
         mQuotaController.maybeScheduleStartAlarmLocked(
                 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
-        // Invalid time in the future, so the count should be used.
-        stats.jobCountExpirationTimeElapsed =
-                now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
-        final long expectedWorkingAlarmTime =
-                stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
+        // Valid time in the future, so the count should be used.
+        stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
+        final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
         mQuotaController.maybeScheduleStartAlarmLocked(
                 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         verify(mAlarmManager, times(1))
@@ -1732,12 +1813,13 @@
         mQcConstants.MAX_JOB_COUNT_WORKING = 4000;
         mQcConstants.MAX_JOB_COUNT_FREQUENT = 3000;
         mQcConstants.MAX_JOB_COUNT_RARE = 2000;
-        mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * MINUTE_IN_MILLIS;
+        mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 500;
         mQcConstants.MAX_SESSION_COUNT_ACTIVE = 500;
         mQcConstants.MAX_SESSION_COUNT_WORKING = 400;
         mQcConstants.MAX_SESSION_COUNT_FREQUENT = 300;
         mQcConstants.MAX_SESSION_COUNT_RARE = 200;
-        mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = 50;
+        mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 50;
         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 10 * SECOND_IN_MILLIS;
 
         mQcConstants.updateConstants();
@@ -1750,12 +1832,13 @@
                 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
-        assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
+        assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
+        assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
         assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
         assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
         assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
         assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
-        assertEquals(50, mQuotaController.getMaxSessionCountPerAllowedTime());
+        assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
         assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
         assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
         assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
@@ -1778,12 +1861,13 @@
         mQcConstants.MAX_JOB_COUNT_WORKING = 1;
         mQcConstants.MAX_JOB_COUNT_FREQUENT = 1;
         mQcConstants.MAX_JOB_COUNT_RARE = 1;
-        mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * SECOND_IN_MILLIS;
+        mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 0;
         mQcConstants.MAX_SESSION_COUNT_ACTIVE = -1;
-        mQcConstants.MAX_SESSION_COUNT_WORKING = 1;
-        mQcConstants.MAX_SESSION_COUNT_FREQUENT = 2;
-        mQcConstants.MAX_SESSION_COUNT_RARE = 1;
-        mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = 0;
+        mQcConstants.MAX_SESSION_COUNT_WORKING = 0;
+        mQcConstants.MAX_SESSION_COUNT_FREQUENT = -3;
+        mQcConstants.MAX_SESSION_COUNT_RARE = 0;
+        mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 0;
         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = -1;
 
         mQcConstants.updateConstants();
@@ -1795,16 +1879,17 @@
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
-        assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
-        assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
-        assertEquals(10, mQuotaController.getMaxSessionCountPerAllowedTime());
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
-        assertEquals(3, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
+        assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
+        assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
+        assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
+        assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
+        assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
         assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
 
         // Test larger than a day. Controller should cap at one day.
@@ -1815,6 +1900,7 @@
         mQcConstants.WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
         mQcConstants.WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
         mQcConstants.MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
+        mQcConstants.RATE_LIMITING_WINDOW_MS = 25 * HOUR_IN_MILLIS;
         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 25 * HOUR_IN_MILLIS;
 
         mQcConstants.updateConstants();
@@ -1826,6 +1912,7 @@
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
+        assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
         assertEquals(15 * MINUTE_IN_MILLIS,
                 mQuotaController.getTimingSessionCoalescingDurationMs());
     }
@@ -2126,7 +2213,7 @@
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        assertEquals(0, stats.jobCountInAllowedTime);
+        assertEquals(0, stats.jobCountInRateLimitingWindow);
 
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         mQuotaController.prepareForExecutionLocked(jobFg1);
@@ -2138,7 +2225,7 @@
         mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
-        assertEquals(0, stats.jobCountInAllowedTime);
+        assertEquals(0, stats.jobCountInRateLimitingWindow);
     }
 
     /**
@@ -2154,7 +2241,7 @@
 
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        assertEquals(0, stats.jobCountInAllowedTime);
+        assertEquals(0, stats.jobCountInRateLimitingWindow);
 
         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
         mQuotaController.prepareForExecutionLocked(jobBg1);
@@ -2165,7 +2252,7 @@
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
 
-        assertEquals(2, stats.jobCountInAllowedTime);
+        assertEquals(2, stats.jobCountInRateLimitingWindow);
     }
 
     /**
@@ -2421,10 +2508,10 @@
 
     /**
      * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
-     * count quota.
+     * count rate limiting.
      */
     @Test
-    public void testStartAlarmScheduled_JobCount_AllowedTime() {
+    public void testStartAlarmScheduled_JobCount_RateLimitingWindow() {
         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
         // because it schedules an alarm too. Prevent it from doing so.
         spyOn(mQuotaController);
@@ -2432,7 +2519,7 @@
 
         // Essentially disable session throttling.
         mQcConstants.MAX_SESSION_COUNT_WORKING =
-                mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME = Integer.MAX_VALUE;
+                mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
         mQcConstants.updateConstants();
 
         final int standbyBucket = WORKING_INDEX;
@@ -2444,7 +2531,7 @@
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
-        for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME; ++i) {
+        for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
             JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i);
             setStandbyBucket(WORKING_INDEX, job);
             mQuotaController.maybeStartTrackingJobLocked(job, null);
@@ -2466,18 +2553,17 @@
 
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        final long expectedWorkingAlarmTime =
-                stats.jobCountExpirationTimeElapsed + mQcConstants.ALLOWED_TIME_PER_PERIOD_MS;
+        final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
         verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
     }
 
     /**
-     * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
-     * count quota.
+     * Tests that the start alarm is properly scheduled when a job has been throttled due to the
+     * session count rate limiting.
      */
     @Test
-    public void testStartAlarmScheduled_TimingSessionCount_AllowedTime() {
+    public void testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow() {
         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
         // because it schedules an alarm too. Prevent it from doing so.
         spyOn(mQuotaController);
@@ -2485,10 +2571,10 @@
 
         // Essentially disable job count throttling.
         mQcConstants.MAX_JOB_COUNT_FREQUENT =
-                mQcConstants.MAX_JOB_COUNT_PER_ALLOWED_TIME = Integer.MAX_VALUE;
-        // Make sure throttling is because of COUNT_PER_ALLOWED_TIME.
+                mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
+        // Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW.
         mQcConstants.MAX_SESSION_COUNT_FREQUENT =
-                mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME + 1;
+                mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1;
         mQcConstants.updateConstants();
 
         final int standbyBucket = FREQUENT_INDEX;
@@ -2500,7 +2586,7 @@
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
-        for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_ALLOWED_TIME; ++i) {
+        for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
             JobStatus job = createJobStatus(
                     "testStartAlarmScheduled_TimingSessionCount_AllowedTime", i);
             setStandbyBucket(FREQUENT_INDEX, job);
@@ -2515,7 +2601,7 @@
         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
 
-        // The app is now out of job count quota
+        // The app is now out of session count quota
         JobStatus throttledJob = createJobStatus(
                 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", 42);
         mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
@@ -2523,8 +2609,7 @@
 
         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
                 SOURCE_PACKAGE, standbyBucket);
-        final long expectedWorkingAlarmTime =
-                stats.sessionCountExpirationTimeElapsed + mQcConstants.ALLOWED_TIME_PER_PERIOD_MS;
+        final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
         verify(mAlarmManager, times(1))
                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
     }