From 86f4c940c80642eaa1aa0f6550d959051294b358 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Mon, 31 Jan 2022 21:05:16 +0000 Subject: [PATCH] Split allowed time by standby bucket. Make it so that each standby bucket can have a different "allowed time" per its window size. That way, we can say apps in bucket X can run for Y minutes total in a Z time period. Also separating EXEMPTED from ACTIVE so that any changes to ACTIVE quota don't affect EXEMPTED apps when we don't want it to. Bug: 143495340 Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job Test: atest CtsJobSchedulerTestCases Change-Id: I2a3f6da75a328505e4091a314a73c0f9d8bbb050 --- .../server/job/JobSchedulerService.java | 8 +- .../server/job/controllers/JobStatus.java | 6 +- .../job/controllers/QuotaController.java | 380 ++++++++++++++---- .../job/controllers/QuotaControllerTest.java | 133 +++++- 4 files changed, 441 insertions(+), 86 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index b2ae8ee62bc7a..f098e10ef020d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -357,6 +357,9 @@ public class JobSchedulerService extends com.android.server.SystemService // Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping // (ScheduledJobStateChanged and JobStatusDumpProto). public static final int RESTRICTED_INDEX = 5; + // Putting EXEMPTED_INDEX after RESTRICTED_INDEX to make it easier for proto dumping + // (ScheduledJobStateChanged and JobStatusDumpProto). + public static final int EXEMPTED_INDEX = 6; private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener, EconomyManagerInternal.TareStateChangeListener { @@ -2492,6 +2495,7 @@ public class JobSchedulerService extends com.android.server.SystemService shouldForceBatchJob = mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1 && job.getEffectiveStandbyBucket() != ACTIVE_INDEX + && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX && !batchDelayExpired; } @@ -3086,8 +3090,10 @@ public class JobSchedulerService extends com.android.server.SystemService return FREQUENT_INDEX; } else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) { return WORKING_INDEX; - } else { + } else if (bucket > UsageStatsManager.STANDBY_BUCKET_EXEMPTED) { return ACTIVE_INDEX; + } else { + return EXEMPTED_INDEX; } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 0eea701066080..0456a9bfeb2e1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -17,6 +17,7 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; @@ -844,12 +845,15 @@ public final class JobStatus { * exemptions. */ public int getEffectiveStandbyBucket() { + final int actualBucket = getStandbyBucket(); + if (actualBucket == EXEMPTED_INDEX) { + return actualBucket; + } if (uidActive || getJob().isExemptedFromAppStandby()) { // Treat these cases as if they're in the ACTIVE bucket so that they get throttled // like other ACTIVE apps. return ACTIVE_INDEX; } - final int actualBucket = getStandbyBucket(); if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX && mHasMediaBackupExemption) { // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the 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 dd5246aebbb47..c1728a3a8abea 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 @@ -21,6 +21,7 @@ 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.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; @@ -132,6 +133,7 @@ public final class QuotaController extends StateController { */ public long expirationTimeElapsed; + public long allowedTimePerPeriodMs; public long windowSizeMs; public int jobCountLimit; public int sessionCountLimit; @@ -213,6 +215,7 @@ public final class QuotaController extends StateController { @Override public String toString() { return "expirationTime=" + expirationTimeElapsed + ", " + + "allowedTimePerPeriodMs=" + allowedTimePerPeriodMs + ", " + "windowSizeMs=" + windowSizeMs + ", " + "jobCountLimit=" + jobCountLimit + ", " + "sessionCountLimit=" + sessionCountLimit + ", " @@ -236,6 +239,7 @@ public final class QuotaController extends StateController { if (obj instanceof ExecutionStats) { ExecutionStats other = (ExecutionStats) obj; return this.expirationTimeElapsed == other.expirationTimeElapsed + && this.allowedTimePerPeriodMs == other.allowedTimePerPeriodMs && this.windowSizeMs == other.windowSizeMs && this.jobCountLimit == other.jobCountLimit && this.sessionCountLimit == other.sessionCountLimit @@ -261,6 +265,7 @@ public final class QuotaController extends StateController { public int hashCode() { int result = 0; result = 31 * result + hashLong(expirationTimeElapsed); + result = 31 * result + hashLong(allowedTimePerPeriodMs); result = 31 * result + hashLong(windowSizeMs); result = 31 * result + hashLong(jobCountLimit); result = 31 * result + hashLong(sessionCountLimit); @@ -350,7 +355,15 @@ public final class QuotaController extends StateController { private boolean mIsEnabled; /** How much time each app will have to run jobs within their standby bucket window. */ - private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; + private final long[] mAllowedTimePerPeriodMs = new long[]{ + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS, + 0, // NEVER + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS + }; /** * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS} @@ -364,12 +377,6 @@ public final class QuotaController extends StateController { */ private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS; - /** - * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine - * when an app will have enough quota to transition from out-of-quota to in-quota. - */ - private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; - /** * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an * app will have enough quota to transition from out-of-quota to in-quota. @@ -450,7 +457,8 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS, QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS, 0, // NEVER - QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS + QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS, + QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS }; /** The maximum period any bucket can have. */ @@ -469,7 +477,8 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT, QcConstants.DEFAULT_MAX_JOB_COUNT_RARE, 0, // NEVER - QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED + QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED, + QcConstants.DEFAULT_MAX_JOB_COUNT_EXEMPTED }; /** @@ -487,6 +496,7 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE, 0, // NEVER QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED, + QcConstants.DEFAULT_MAX_SESSION_COUNT_EXEMPTED, }; /** @@ -506,7 +516,8 @@ public final class QuotaController extends StateController { QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS, QcConstants.DEFAULT_EJ_LIMIT_RARE_MS, 0, // NEVER - QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS + QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS, + QcConstants.DEFAULT_EJ_LIMIT_EXEMPTED_MS }; private long mEjLimitAdditionInstallerMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS; @@ -823,6 +834,11 @@ public final class QuotaController extends StateController { if (mService.isBatteryCharging()) { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } + if (jobStatus.getEffectiveStandbyBucket() == EXEMPTED_INDEX) { + return Math.max(mEJLimitsMs[EXEMPTED_INDEX] / 2, + getTimeUntilEJQuotaConsumedLocked( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); + } if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) { return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2, getTimeUntilEJQuotaConsumedLocked( @@ -929,9 +945,11 @@ public final class QuotaController extends StateController { final long minSurplus; if (priority <= JobInfo.PRIORITY_MIN) { - minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin); + minSurplus = (long) + (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityMin); } else if (priority <= JobInfo.PRIORITY_LOW) { - minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow); + minSurplus = (long) + (mAllowedTimePerPeriodMs[standbyBucket] * mAllowedTimeSurplusPriorityLow); } else { minSurplus = 0; } @@ -989,7 +1007,7 @@ public final class QuotaController extends StateController { } private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) { - return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs, + return Math.min(stats.allowedTimePerPeriodMs - stats.executionTimeInWindowMs, mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); } @@ -1068,15 +1086,15 @@ public final class QuotaController extends StateController { if (sessions == null || sessions.size() == 0) { // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. - if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { + if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { return mMaxExecutionTimeMs; } - return mAllowedTimePerPeriodMs; + return mAllowedTimePerPeriodMs[standbyBucket]; } final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; - final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority); + final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(standbyBucket, jobPriority); final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs; final long maxExecutionTimeRemainingMs = mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; @@ -1087,7 +1105,7 @@ public final class QuotaController extends StateController { // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can // essentially run until they reach the maximum limit. - if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { + if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { return calculateTimeUntilQuotaConsumedLocked( sessions, startMaxElapsed, maxExecutionTimeRemainingMs); } @@ -1103,14 +1121,19 @@ public final class QuotaController extends StateController { sessions, startWindowElapsed, allowedTimeRemainingMs)); } - private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) { + private long getAllowedTimePerPeriodMs(int standbyBucket, @JobInfo.Priority int jobPriority) { + return getAllowedTimePerPeriodMs(mAllowedTimePerPeriodMs[standbyBucket], jobPriority); + } + + private long getAllowedTimePerPeriodMs(long initialAllowedTime, + @JobInfo.Priority int jobPriority) { if (jobPriority <= JobInfo.PRIORITY_MIN) { - return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin)); + return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityMin)); } if (jobPriority <= JobInfo.PRIORITY_LOW) { - return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow)); + return (long) (initialAllowedTime * (1 - mAllowedTimeSurplusPriorityLow)); } - return mAllowedTimePerPeriodMs; + return initialAllowedTime; } /** @@ -1237,16 +1260,19 @@ public final class QuotaController extends StateController { appStats[standbyBucket] = stats; } if (refreshStatsIfOld) { + final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket]; 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.allowedTimePerPeriodMs != bucketAllowedTimeMs || stats.windowSizeMs != bucketWindowSizeMs || stats.jobCountLimit != jobCountLimit || stats.sessionCountLimit != sessionCountLimit) { // The stats are no longer valid. + stats.allowedTimePerPeriodMs = bucketAllowedTimeMs; stats.windowSizeMs = bucketWindowSizeMs; stats.jobCountLimit = jobCountLimit; stats.sessionCountLimit = sessionCountLimit; @@ -1272,8 +1298,11 @@ public final class QuotaController extends StateController { } else { stats.inQuotaTimeElapsed = 0; } - final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN); - final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW); + final long allowedTimeMinMs = + getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_MIN); + final long allowedTimeLowMs = + getAllowedTimePerPeriodMs(stats.allowedTimePerPeriodMs, JobInfo.PRIORITY_LOW); + final long allowedTimeIntoQuotaMs = stats.allowedTimePerPeriodMs - mQuotaBufferMs; Timer timer = mPkgTimers.get(userId, packageName); final long nowElapsed = sElapsedRealtimeClock.millis(); @@ -1287,9 +1316,9 @@ public final class QuotaController extends StateController { // If the timer is active, the value will be stale at the next method call, so // invalidate now. stats.expirationTimeElapsed = nowElapsed; - if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { + if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs); + nowElapsed - allowedTimeIntoQuotaMs + stats.windowSizeMs); } if (stats.executionTimeInWindowMs >= allowedTimeLowMs) { stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, @@ -1346,9 +1375,9 @@ public final class QuotaController extends StateController { stats.executionTimeInWindowMs += session.endTimeElapsed - start; stats.bgJobCountInWindow += session.bgJobCount; - if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { + if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, - start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs + start + stats.executionTimeInWindowMs - allowedTimeIntoQuotaMs + stats.windowSizeMs); } if (stats.executionTimeInWindowMs >= allowedTimeLowMs) { @@ -1697,7 +1726,7 @@ public final class QuotaController extends StateController { if (js.setQuotaConstraintSatisfied(nowElapsed, true)) { changedJobs.add(js); } - } else if (realStandbyBucket != ACTIVE_INDEX + } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX && realStandbyBucket == js.getEffectiveStandbyBucket() && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) { // An app in the ACTIVE bucket may be out of quota while the job could be in quota @@ -1842,7 +1871,8 @@ public final class QuotaController extends StateController { } } final boolean inRegularQuota = - stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority) + stats.executionTimeInWindowMs + < getAllowedTimePerPeriodMs(standbyBucket, minPriority) && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs && isUnderJobCountQuota && isUnderTimingSessionCountQuota; @@ -2921,9 +2951,29 @@ public final class QuotaController extends StateController { /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ private static final String QC_CONSTANT_PREFIX = "qc_"; + /** + * Previously used keys: + * * allowed_time_per_period_ms -- No longer used after splitting by bucket + */ + @VisibleForTesting - static final String KEY_ALLOWED_TIME_PER_PERIOD_MS = - QC_CONSTANT_PREFIX + "allowed_time_per_period_ms"; + static final String KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_exempted_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_active_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_working_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_frequent_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_rare_ms"; + @VisibleForTesting + static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = + QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms"; @VisibleForTesting static final String KEY_IN_QUOTA_BUFFER_MS = QC_CONSTANT_PREFIX + "in_quota_buffer_ms"; @@ -2934,6 +2984,9 @@ public final class QuotaController extends StateController { static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min"; @VisibleForTesting + static final String KEY_WINDOW_SIZE_EXEMPTED_MS = + QC_CONSTANT_PREFIX + "window_size_exempted_ms"; + @VisibleForTesting static final String KEY_WINDOW_SIZE_ACTIVE_MS = QC_CONSTANT_PREFIX + "window_size_active_ms"; @VisibleForTesting @@ -2952,6 +3005,9 @@ public final class QuotaController extends StateController { static final String KEY_MAX_EXECUTION_TIME_MS = QC_CONSTANT_PREFIX + "max_execution_time_ms"; @VisibleForTesting + static final String KEY_MAX_JOB_COUNT_EXEMPTED = + QC_CONSTANT_PREFIX + "max_job_count_exempted"; + @VisibleForTesting static final String KEY_MAX_JOB_COUNT_ACTIVE = QC_CONSTANT_PREFIX + "max_job_count_active"; @VisibleForTesting @@ -2973,6 +3029,9 @@ public final class QuotaController extends StateController { static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = QC_CONSTANT_PREFIX + "max_job_count_per_rate_limiting_window"; @VisibleForTesting + static final String KEY_MAX_SESSION_COUNT_EXEMPTED = + QC_CONSTANT_PREFIX + "max_session_count_exempted"; + @VisibleForTesting static final String KEY_MAX_SESSION_COUNT_ACTIVE = QC_CONSTANT_PREFIX + "max_session_count_active"; @VisibleForTesting @@ -2997,6 +3056,9 @@ public final class QuotaController extends StateController { static final String KEY_MIN_QUOTA_CHECK_DELAY_MS = QC_CONSTANT_PREFIX + "min_quota_check_delay_ms"; @VisibleForTesting + static final String KEY_EJ_LIMIT_EXEMPTED_MS = + QC_CONSTANT_PREFIX + "ej_limit_exempted_ms"; + @VisibleForTesting static final String KEY_EJ_LIMIT_ACTIVE_MS = QC_CONSTANT_PREFIX + "ej_limit_active_ms"; @VisibleForTesting @@ -3039,14 +3101,26 @@ public final class QuotaController extends StateController { static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; - private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS = + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS = + 10 * 60 * 1000L; // 10 minutes + private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW = .25f; private static final float DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN = .5f; + private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS = - DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time + DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_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 = @@ -3060,8 +3134,9 @@ public final class QuotaController extends StateController { private static final long DEFAULT_RATE_LIMITING_WINDOW_MS = 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 = + private static final int DEFAULT_MAX_JOB_COUNT_EXEMPTED = 75; // 75/window = 450/hr = 1/session + private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED; 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 @@ -3069,8 +3144,10 @@ public final class QuotaController extends StateController { 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_JOB_COUNT_RESTRICTED = 10; - private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = + private static final int DEFAULT_MAX_SESSION_COUNT_EXEMPTED = 75; // 450/hr + private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = + DEFAULT_MAX_SESSION_COUNT_EXEMPTED; private static final int DEFAULT_MAX_SESSION_COUNT_WORKING = 10; // 5/hr private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT = @@ -3081,6 +3158,7 @@ public final class QuotaController extends StateController { 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; + private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 45 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS; @@ -3096,8 +3174,39 @@ public final class QuotaController extends StateController { private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * 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; + /** + * How much time each app in the exempted bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; + /** + * How much time each app in the active bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + /** + * How much time each app in the working set bucket will have to run jobs within their + * standby bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_WORKING_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS; + /** + * How much time each app in the frequent bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS; + /** + * How much time each app in the rare bucket will have to run jobs within their standby + * bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_RARE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS; + /** + * How much time each app in the restricted bucket will have to run jobs within their + * standby bucket window. + */ + public long ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = + DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS; /** * How much time the package should have before transitioning from out-of-quota to in-quota. @@ -3106,7 +3215,7 @@ public final class QuotaController extends StateController { public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS; /** - * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by + * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by * {@link JobInfo#PRIORITY_LOW low priority} jobs. In other words, there must be a minimum * surplus of this amount of remaining allowed time before we start running low priority * jobs. @@ -3114,7 +3223,7 @@ public final class QuotaController extends StateController { public float ALLOWED_TIME_SURPLUS_PRIORITY_LOW = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW; /** - * The percentage of {@link #ALLOWED_TIME_PER_PERIOD_MS} that should not be used by + * The percentage of ALLOWED_TIME_PER_PERIOD_*_MS that should not be used by * {@link JobInfo#PRIORITY_MIN low priority} jobs. In other words, there must be a minimum * surplus of this amount of remaining allowed time before we start running min priority * jobs. @@ -3123,35 +3232,42 @@ public final class QuotaController extends StateController { /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past + * WINDOW_SIZE_MS. + */ + public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS; + + /** + * The quota window size of the particular standby bucket. Apps in this standby bucket are + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RARE_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are - * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past + * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS} within the past * WINDOW_SIZE_MS. */ public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS; @@ -3161,6 +3277,12 @@ public final class QuotaController extends StateController { */ public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS; + /** + * The maximum number of jobs an app can run within this particular standby bucket's + * window size. + */ + public int MAX_JOB_COUNT_EXEMPTED = DEFAULT_MAX_JOB_COUNT_EXEMPTED; + /** * The maximum number of jobs an app can run within this particular standby bucket's * window size. @@ -3200,6 +3322,12 @@ public final class QuotaController extends StateController { public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; + /** + * The maximum number of {@link TimingSession TimingSessions} an app can run within this + * particular standby bucket's window size. + */ + public int MAX_SESSION_COUNT_EXEMPTED = DEFAULT_MAX_SESSION_COUNT_EXEMPTED; + /** * The maximum number of {@link TimingSession TimingSessions} an app can run within this * particular standby bucket's window size. @@ -3232,7 +3360,7 @@ public final class QuotaController extends StateController { /** * The maximum number of {@link TimingSession TimingSessions} that can run within the past - * {@link #ALLOWED_TIME_PER_PERIOD_MS}. + * {@link #RATE_LIMITING_WINDOW_MS}. */ public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; @@ -3270,6 +3398,13 @@ public final class QuotaController extends StateController { /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */ private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS; + /** + * The total expedited job session limit of the particular standby bucket. Apps in this + * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring + * in any rewards or free EJs). + */ + public long EJ_LIMIT_EXEMPTED_MS = DEFAULT_EJ_LIMIT_EXEMPTED_MS; + /** * The total expedited job session limit of the particular standby bucket. Apps in this * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring @@ -3358,7 +3493,12 @@ public final class QuotaController extends StateController { public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { switch (key) { - case KEY_ALLOWED_TIME_PER_PERIOD_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS: + case KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS: case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW: case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN: case KEY_IN_QUOTA_BUFFER_MS: @@ -3388,6 +3528,15 @@ public final class QuotaController extends StateController { updateEJLimitConstantsLocked(); break; + case KEY_MAX_JOB_COUNT_EXEMPTED: + MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED); + int newExemptedMaxJobCount = + Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_EXEMPTED); + if (mMaxBucketJobCounts[EXEMPTED_INDEX] != newExemptedMaxJobCount) { + mMaxBucketJobCounts[EXEMPTED_INDEX] = newExemptedMaxJobCount; + mShouldReevaluateConstraints = true; + } + break; case KEY_MAX_JOB_COUNT_ACTIVE: MAX_JOB_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_ACTIVE); int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE); @@ -3432,6 +3581,16 @@ public final class QuotaController extends StateController { mShouldReevaluateConstraints = true; } break; + case KEY_MAX_SESSION_COUNT_EXEMPTED: + MAX_SESSION_COUNT_EXEMPTED = + properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_EXEMPTED); + int newExemptedMaxSessionCount = + Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_EXEMPTED); + if (mMaxBucketSessionCounts[EXEMPTED_INDEX] != newExemptedMaxSessionCount) { + mMaxBucketSessionCounts[EXEMPTED_INDEX] = newExemptedMaxSessionCount; + mShouldReevaluateConstraints = true; + } + break; case KEY_MAX_SESSION_COUNT_ACTIVE: MAX_SESSION_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_ACTIVE); @@ -3579,15 +3738,34 @@ public final class QuotaController extends StateController { // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, - KEY_ALLOWED_TIME_PER_PERIOD_MS, KEY_IN_QUOTA_BUFFER_MS, + KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + KEY_IN_QUOTA_BUFFER_MS, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, - KEY_MAX_EXECUTION_TIME_MS, KEY_WINDOW_SIZE_ACTIVE_MS, + KEY_MAX_EXECUTION_TIME_MS, + KEY_WINDOW_SIZE_EXEMPTED_MS, KEY_WINDOW_SIZE_ACTIVE_MS, KEY_WINDOW_SIZE_WORKING_MS, KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS, KEY_WINDOW_SIZE_RESTRICTED_MS); - ALLOWED_TIME_PER_PERIOD_MS = - properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS, - DEFAULT_ALLOWED_TIME_PER_PERIOD_MS); + ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); + ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); + ALLOWED_TIME_PER_PERIOD_WORKING_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS); + ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS); + ALLOWED_TIME_PER_PERIOD_RARE_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS); + ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = + properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS); ALLOWED_TIME_SURPLUS_PRIORITY_LOW = properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW); @@ -3598,6 +3776,8 @@ public final class QuotaController extends StateController { DEFAULT_IN_QUOTA_BUFFER_MS); MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); + WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, + DEFAULT_WINDOW_SIZE_EXEMPTED_MS); WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, DEFAULT_WINDOW_SIZE_ACTIVE_MS); WINDOW_SIZE_WORKING_MS = @@ -3618,20 +3798,55 @@ public final class QuotaController extends StateController { mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; mShouldReevaluateConstraints = true; } - long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs, - Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS)); - if (mAllowedTimePerPeriodMs != newAllowedTimeMs) { - mAllowedTimePerPeriodMs = newAllowedTimeMs; - mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; + long minAllowedTimeMs = Long.MAX_VALUE; + long newAllowedTimeExemptedMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeExemptedMs); + if (mAllowedTimePerPeriodMs[EXEMPTED_INDEX] != newAllowedTimeExemptedMs) { + mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = newAllowedTimeExemptedMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeActiveMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeActiveMs); + if (mAllowedTimePerPeriodMs[ACTIVE_INDEX] != newAllowedTimeActiveMs) { + mAllowedTimePerPeriodMs[ACTIVE_INDEX] = newAllowedTimeActiveMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeWorkingMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_WORKING_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeWorkingMs); + if (mAllowedTimePerPeriodMs[WORKING_INDEX] != newAllowedTimeWorkingMs) { + mAllowedTimePerPeriodMs[WORKING_INDEX] = newAllowedTimeWorkingMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeFrequentMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeFrequentMs); + if (mAllowedTimePerPeriodMs[FREQUENT_INDEX] != newAllowedTimeFrequentMs) { + mAllowedTimePerPeriodMs[FREQUENT_INDEX] = newAllowedTimeFrequentMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeRareMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RARE_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRareMs); + if (mAllowedTimePerPeriodMs[RARE_INDEX] != newAllowedTimeRareMs) { + mAllowedTimePerPeriodMs[RARE_INDEX] = newAllowedTimeRareMs; + mShouldReevaluateConstraints = true; + } + long newAllowedTimeRestrictedMs = Math.min(mMaxExecutionTimeMs, + Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS)); + minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRestrictedMs); + if (mAllowedTimePerPeriodMs[RESTRICTED_INDEX] != newAllowedTimeRestrictedMs) { + mAllowedTimePerPeriodMs[RESTRICTED_INDEX] = newAllowedTimeRestrictedMs; mShouldReevaluateConstraints = true; } // Make sure quota buffer is non-negative, not greater than allowed time per period, // and no more than 5 minutes. - long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs, + long newQuotaBufferMs = Math.max(0, Math.min(minAllowedTimeMs, Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS))); if (mQuotaBufferMs != newQuotaBufferMs) { mQuotaBufferMs = newQuotaBufferMs; - mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; mShouldReevaluateConstraints = true; } @@ -3652,32 +3867,38 @@ public final class QuotaController extends StateController { mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin; mShouldReevaluateConstraints = true; } - long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newExemptedPeriodMs = Math.max(mAllowedTimePerPeriodMs[EXEMPTED_INDEX], + Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS)); + if (mBucketPeriodsMs[EXEMPTED_INDEX] != newExemptedPeriodMs) { + mBucketPeriodsMs[EXEMPTED_INDEX] = newExemptedPeriodMs; + mShouldReevaluateConstraints = true; + } + long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs[ACTIVE_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS)); if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) { mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs; mShouldReevaluateConstraints = true; } - long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs[WORKING_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS)); if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) { mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs; mShouldReevaluateConstraints = true; } - long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs[FREQUENT_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) { mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs; mShouldReevaluateConstraints = true; } - long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs[RARE_INDEX], Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS)); if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) { mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs; mShouldReevaluateConstraints = true; } // Fit in the range [allowed time (10 mins), 1 week]. - long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs, + long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs[RESTRICTED_INDEX], Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS)); if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) { mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs; @@ -3740,11 +3961,14 @@ public final class QuotaController extends StateController { // Query the values as an atomic set. final DeviceConfig.Properties properties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_EJ_LIMIT_EXEMPTED_MS, KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS, KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS, KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, KEY_EJ_WINDOW_SIZE_MS); + EJ_LIMIT_EXEMPTED_MS = properties.getLong( + KEY_EJ_LIMIT_EXEMPTED_MS, DEFAULT_EJ_LIMIT_EXEMPTED_MS); EJ_LIMIT_ACTIVE_MS = properties.getLong( KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS); EJ_LIMIT_WORKING_MS = properties.getLong( @@ -3770,8 +3994,15 @@ public final class QuotaController extends StateController { mShouldReevaluateConstraints = true; } // The limit must be in the range [15 minutes, window size]. + long newExemptLimitMs = Math.max(15 * MINUTE_IN_MILLIS, + Math.min(newWindowSizeMs, EJ_LIMIT_EXEMPTED_MS)); + if (mEJLimitsMs[EXEMPTED_INDEX] != newExemptLimitMs) { + mEJLimitsMs[EXEMPTED_INDEX] = newExemptLimitMs; + mShouldReevaluateConstraints = true; + } + // The limit must be in the range [15 minutes, exempted limit]. long newActiveLimitMs = Math.max(15 * MINUTE_IN_MILLIS, - Math.min(newWindowSizeMs, EJ_LIMIT_ACTIVE_MS)); + Math.min(newExemptLimitMs, EJ_LIMIT_ACTIVE_MS)); if (mEJLimitsMs[ACTIVE_INDEX] != newActiveLimitMs) { mEJLimitsMs[ACTIVE_INDEX] = newActiveLimitMs; mShouldReevaluateConstraints = true; @@ -3823,18 +4054,31 @@ public final class QuotaController extends StateController { pw.println(); pw.println("QuotaController:"); pw.increaseIndent(); - pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, ALLOWED_TIME_PER_PERIOD_WORKING_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, ALLOWED_TIME_PER_PERIOD_RARE_MS) + .println(); + pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS).println(); pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, ALLOWED_TIME_SURPLUS_PRIORITY_LOW) .println(); pw.print(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, ALLOWED_TIME_SURPLUS_PRIORITY_MIN) .println(); pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println(); + pw.print(KEY_WINDOW_SIZE_EXEMPTED_MS, WINDOW_SIZE_EXEMPTED_MS).println(); pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println(); pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println(); pw.print(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println(); pw.print(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println(); pw.print(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println(); pw.print(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println(); + pw.print(KEY_MAX_JOB_COUNT_EXEMPTED, MAX_JOB_COUNT_EXEMPTED).println(); pw.print(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println(); pw.print(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println(); pw.print(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println(); @@ -3843,6 +4087,7 @@ public final class QuotaController extends StateController { pw.print(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println(); pw.print(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println(); + pw.print(KEY_MAX_SESSION_COUNT_EXEMPTED, MAX_SESSION_COUNT_EXEMPTED).println(); pw.print(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println(); pw.print(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println(); pw.print(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println(); @@ -3854,6 +4099,7 @@ public final class QuotaController extends StateController { TIMING_SESSION_COALESCING_DURATION_MS).println(); pw.print(KEY_MIN_QUOTA_CHECK_DELAY_MS, MIN_QUOTA_CHECK_DELAY_MS).println(); + pw.print(KEY_EJ_LIMIT_EXEMPTED_MS, EJ_LIMIT_EXEMPTED_MS).println(); pw.print(KEY_EJ_LIMIT_ACTIVE_MS, EJ_LIMIT_ACTIVE_MS).println(); pw.print(KEY_EJ_LIMIT_WORKING_MS, EJ_LIMIT_WORKING_MS).println(); pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println(); @@ -3875,8 +4121,6 @@ public final class QuotaController extends StateController { private void dump(ProtoOutputStream proto) { final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER); - proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS, - ALLOWED_TIME_PER_PERIOD_MS); proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS); proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS, WINDOW_SIZE_ACTIVE_MS); @@ -3946,7 +4190,7 @@ public final class QuotaController extends StateController { //////////////////////// TESTING HELPERS ///////////////////////////// @VisibleForTesting - long getAllowedTimePerPeriodMs() { + long[] getAllowedTimePerPeriodMs() { return mAllowedTimePerPeriodMs; } 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 153ce17ec9dd4..9d6793ca4b7b4 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 @@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RARE_INDEX; @@ -536,6 +537,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; @@ -595,6 +597,7 @@ public class QuotaControllerTest { assertNotNull(mQuotaController.getEJTimingSessions(10, "com.android.test")); ExecutionStats expectedStats = new ExecutionStats(); + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; @@ -638,11 +641,13 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); ExecutionStats inputStats = new ExecutionStats(); + inputStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; 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; + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; synchronized (mQuotaController.mLock) { mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); } @@ -827,6 +832,8 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); ExecutionStats inputStats = new ExecutionStats(); + inputStats.allowedTimePerPeriodMs = expectedStats.allowedTimePerPeriodMs = + 10 * MINUTE_IN_MILLIS; inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; inputStats.sessionCountLimit = expectedStats.sessionCountLimit = @@ -924,6 +931,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); // Active + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; @@ -1006,6 +1014,7 @@ public class QuotaControllerTest { ExecutionStats expectedStats = new ExecutionStats(); // Active + expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; @@ -1242,6 +1251,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 6 * HOUR_IN_MILLIS); ExecutionStats expectedStats = new ExecutionStats(); + expectedStats.allowedTimePerPeriodMs = originalStatsActive.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; expectedStats.jobCountLimit = originalStatsActive.jobCountLimit; expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit; @@ -1261,6 +1271,7 @@ public class QuotaControllerTest { assertTrue(originalStatsActive == newStatsActive); assertEquals(expectedStats, newStatsActive); + expectedStats.allowedTimePerPeriodMs = originalStatsWorking.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit; expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit; @@ -1277,6 +1288,7 @@ public class QuotaControllerTest { assertTrue(originalStatsWorking == newStatsWorking); assertNotEquals(expectedStats, newStatsWorking); + expectedStats.allowedTimePerPeriodMs = originalStatsFrequent.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit; expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit; @@ -1293,6 +1305,7 @@ public class QuotaControllerTest { assertTrue(originalStatsFrequent == newStatsFrequent); assertNotEquals(expectedStats, newStatsFrequent); + expectedStats.allowedTimePerPeriodMs = originalStatsRare.allowedTimePerPeriodMs; expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; expectedStats.jobCountLimit = originalStatsRare.jobCountLimit; expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit; @@ -1354,7 +1367,8 @@ public class QuotaControllerTest { @Test public void testGetMaxJobExecutionTimeLocked_Regular_Active() { JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked_Regular_Active", 0); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 2 * HOUR_IN_MILLIS); setDischarging(); @@ -2886,11 +2900,12 @@ public class QuotaControllerTest { public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() { // Set rate limiting period different from allowed time to confirm code sets based on // the former. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 10 * MINUTE_IN_MILLIS); + final int standbyBucket = WORKING_INDEX; + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 5 * MINUTE_IN_MILLIS); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - final int standbyBucket = WORKING_INDEX; JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1); setStandbyBucket(standbyBucket, jobStatus); @@ -2953,8 +2968,8 @@ public class QuotaControllerTest { @Test public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() { // Make sure any new value is used correctly. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, - mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2); runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); @@ -2977,8 +2992,8 @@ public class QuotaControllerTest { // Make sure any new value is used correctly. setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, mQcConstants.IN_QUOTA_BUFFER_MS * 2); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, - mQcConstants.ALLOWED_TIME_PER_PERIOD_MS / 2); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, mQcConstants.MAX_EXECUTION_TIME_MS / 2); @@ -3002,7 +3017,8 @@ public class QuotaControllerTest { // Working set window size is 2 hours. final int standbyBucket = WORKING_INDEX; final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; - final long remainingTimeMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_MS - contributionMs; + final long remainingTimeMs = + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS - contributionMs; // Session straddles edge of bucket window. Only the contribution should be counted towards // the quota. @@ -3062,16 +3078,28 @@ public class QuotaControllerTest { @Test public void testConstantsUpdating_ValidValues() { - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 8 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + 5 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 7 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + 2 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 4 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + 11 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, .7f); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .2f); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 99 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 60 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 120 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3 * HOUR_IN_MILLIS); + setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, 6000); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, 5000); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 4000); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 3000); @@ -3079,6 +3107,7 @@ public class QuotaControllerTest { setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, 2000); setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * MINUTE_IN_MILLIS); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 500); + setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, 600); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 500); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 400); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 300); @@ -3088,6 +3117,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 10 * SECOND_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 7 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 3 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 2 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 90 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS); @@ -3104,10 +3134,23 @@ public class QuotaControllerTest { 84 * SECOND_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS); - assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); + assertEquals(8 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); + assertEquals(5 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); + assertEquals(2 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); + assertEquals(4 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); + assertEquals(11 * MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); + assertEquals(99 * MINUTE_IN_MILLIS, + mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(45 * MINUTE_IN_MILLIS, @@ -3118,12 +3161,14 @@ public class QuotaControllerTest { assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow()); + assertEquals(6000, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]); 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(2000, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]); assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); + assertEquals(600, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]); assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); @@ -3132,6 +3177,7 @@ public class QuotaControllerTest { assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getTimingSessionCoalescingDurationMs()); assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs()); + assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); assertEquals(2 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); @@ -3151,16 +3197,24 @@ public class QuotaControllerTest { @Test public void testConstantsUpdating_InvalidValues() { // Test negatives/too low. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, -MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, -.1f); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, -.01f); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, -MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, -MINUTE_IN_MILLIS); + setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, -1); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, -1); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 1); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 1); @@ -3168,6 +3222,7 @@ public class QuotaControllerTest { setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, -1); setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * SECOND_IN_MILLIS); setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 0); + setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, -1); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, -1); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 0); setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, -3); @@ -3176,6 +3231,7 @@ public class QuotaControllerTest { setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 0); setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, -1); setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, -1); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1); @@ -3191,10 +3247,19 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1); - assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); assertEquals(0, mQuotaController.getInQuotaBufferMs()); assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); + assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); @@ -3203,12 +3268,14 @@ public class QuotaControllerTest { assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow()); + assertEquals(10, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]); 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.getBucketMaxJobCounts()[RESTRICTED_INDEX]); assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); + assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]); assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); @@ -3216,6 +3283,7 @@ public class QuotaControllerTest { assertEquals(0, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]); assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs()); assertEquals(0, mQuotaController.getMinQuotaCheckDelayMs()); + assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); @@ -3233,17 +3301,37 @@ public class QuotaControllerTest { // Invalid configurations. // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 2 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, + 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + 2 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 10 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + 10 * MINUTE_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS); assertTrue(mQuotaController.getInQuotaBufferMs() - <= mQuotaController.getAllowedTimePerPeriodMs()); + <= mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); // Test larger than a day. Controller should cap at one day. - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, + 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, + 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, + 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW, 1f); setDeviceConfigFloat(QcConstants.KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN, .95f); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS); @@ -3254,6 +3342,7 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 25 * HOUR_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS); @@ -3269,10 +3358,21 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS); - assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); + assertEquals(24 * HOUR_IN_MILLIS, + mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6); assertEquals(.9f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6); + assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); @@ -3284,6 +3384,7 @@ public class QuotaControllerTest { assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getTimingSessionCoalescingDurationMs()); assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs()); + assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);