summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kweku Adams <kwekua@google.com> 2022-01-24 22:55:21 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-01-24 22:55:21 +0000
commit24f779dcf090af0e76f13330f0f2769bb85a6522 (patch)
treed05a5c77a9e3b67df23646c9210df404b5e3d6c0
parent2f3e7488cb56a1d54c5d580433010ff344b0179f (diff)
parent6765e85033ab823270cdd9573a7437123059680d (diff)
Merge "Deferring low and min priority jobs when quota is low."
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java393
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java561
2 files changed, 774 insertions, 180 deletions
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 65e1d49d1510..dd5246aebbb4 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
@@ -36,6 +36,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.IUidObserver;
+import android.app.job.JobInfo;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
@@ -161,6 +162,28 @@ public final class QuotaController extends StateController {
public long inQuotaTimeElapsed;
/**
+ * The time after which the app will be under the bucket quota and can start running
+ * low priority jobs again. This is only valid if
+ * {@link #executionTimeInWindowMs} >=
+ * {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityLow}),
+ * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+ * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+ * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
+ */
+ public long inQuotaTimeLowElapsed;
+
+ /**
+ * The time after which the app will be under the bucket quota and can start running
+ * min priority jobs again. This is only valid if
+ * {@link #executionTimeInWindowMs} >=
+ * {@link #mAllowedTimePerPeriodMs} * (1 - {@link #mAllowedTimeSurplusPriorityMin}),
+ * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
+ * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
+ * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
+ */
+ public long inQuotaTimeMinElapsed;
+
+ /**
* The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
* in the elapsed realtime timebase.
*/
@@ -199,6 +222,8 @@ public final class QuotaController extends StateController {
+ "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
+ "sessionCountInWindow=" + sessionCountInWindow + ", "
+ "inQuotaTime=" + inQuotaTimeElapsed + ", "
+ + "inQuotaTimeLow=" + inQuotaTimeLowElapsed + ", "
+ + "inQuotaTimeMin=" + inQuotaTimeMinElapsed + ", "
+ "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
+ "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", "
+ "rateLimitSessionCountExpirationTime="
@@ -351,6 +376,24 @@ public final class QuotaController extends StateController {
*/
private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ /**
+ * The percentage of {@link #mAllowedTimePerPeriodMs} 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.
+ */
+ private float mAllowedTimeSurplusPriorityLow =
+ QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW;
+
+ /**
+ * The percentage of {@link #mAllowedTimePerPeriodMs} that should not be used by
+ * {@link JobInfo#PRIORITY_MIN min 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.
+ */
+ private float mAllowedTimeSurplusPriorityMin =
+ QcConstants.DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN;
+
/** The period of time used to rate limit recently run jobs. */
private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
@@ -653,10 +696,11 @@ public final class QuotaController extends StateController {
boolean forUpdate) {
if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
unprepareFromExecutionLocked(jobStatus);
- ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
- jobStatus.getSourcePackageName());
- if (jobs != null) {
- jobs.remove(jobStatus);
+ final int userId = jobStatus.getSourceUserId();
+ final String pkgName = jobStatus.getSourcePackageName();
+ ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
+ if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
+ mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
}
}
}
@@ -771,7 +815,8 @@ public final class QuotaController extends StateController {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
return getTimeUntilQuotaConsumedLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
+ jobStatus.getEffectivePriority());
}
// Expedited job.
@@ -856,7 +901,8 @@ public final class QuotaController extends StateController {
return isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())
|| isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket,
+ jobStatus.getEffectivePriority());
}
@GuardedBy("mLock")
@@ -873,7 +919,7 @@ public final class QuotaController extends StateController {
@VisibleForTesting
@GuardedBy("mLock")
boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
- final int standbyBucket) {
+ final int standbyBucket, final int priority) {
if (!mIsEnabled) {
return true;
}
@@ -881,9 +927,16 @@ public final class QuotaController extends StateController {
if (isQuotaFreeLocked(standbyBucket)) return true;
+ final long minSurplus;
+ if (priority <= JobInfo.PRIORITY_MIN) {
+ minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityMin);
+ } else if (priority <= JobInfo.PRIORITY_LOW) {
+ minSurplus = (long) (mAllowedTimePerPeriodMs * mAllowedTimeSurplusPriorityLow);
+ } else {
+ minSurplus = 0;
+ }
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- // TODO: use a higher minimum remaining time for jobs with MINIMUM priority
- return getRemainingExecutionTimeLocked(stats) > 0
+ return getRemainingExecutionTimeLocked(stats) > minSurplus
&& isUnderJobCountQuotaLocked(stats, standbyBucket)
&& isUnderSessionCountQuotaLocked(stats, standbyBucket);
}
@@ -1001,7 +1054,8 @@ public final class QuotaController extends StateController {
* job is running.
*/
@VisibleForTesting
- long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) {
+ long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName,
+ @JobInfo.Priority int jobPriority) {
final long nowElapsed = sElapsedRealtimeClock.millis();
final int standbyBucket = JobSchedulerService.standbyBucketForPackage(
packageName, userId, nowElapsed);
@@ -1022,10 +1076,15 @@ public final class QuotaController extends StateController {
final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
- final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs;
+ final long allowedTimePerPeriodMs = getAllowedTimePerPeriodMs(jobPriority);
+ final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs;
final long maxExecutionTimeRemainingMs =
mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
+ if (allowedTimeRemainingMs <= 0 || maxExecutionTimeRemainingMs <= 0) {
+ return 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) {
@@ -1044,6 +1103,16 @@ public final class QuotaController extends StateController {
sessions, startWindowElapsed, allowedTimeRemainingMs));
}
+ private long getAllowedTimePerPeriodMs(@JobInfo.Priority int jobPriority) {
+ if (jobPriority <= JobInfo.PRIORITY_MIN) {
+ return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityMin));
+ }
+ if (jobPriority <= JobInfo.PRIORITY_LOW) {
+ return (long) (mAllowedTimePerPeriodMs * (1 - mAllowedTimeSurplusPriorityLow));
+ }
+ return mAllowedTimePerPeriodMs;
+ }
+
/**
* Calculates how much time it will take, in milliseconds, until the quota is fully consumed.
*
@@ -1198,10 +1267,13 @@ public final class QuotaController extends StateController {
stats.sessionCountInWindow = 0;
if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) {
// App won't be in quota until configuration changes.
- stats.inQuotaTimeElapsed = Long.MAX_VALUE;
+ stats.inQuotaTimeElapsed = stats.inQuotaTimeLowElapsed = stats.inQuotaTimeMinElapsed =
+ Long.MAX_VALUE;
} else {
stats.inQuotaTimeElapsed = 0;
}
+ final long allowedTimeMinMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_MIN);
+ final long allowedTimeLowMs = getAllowedTimePerPeriodMs(JobInfo.PRIORITY_LOW);
Timer timer = mPkgTimers.get(userId, packageName);
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -1219,13 +1291,25 @@ public final class QuotaController extends StateController {
stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
}
+ if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ nowElapsed - allowedTimeLowMs + stats.windowSizeMs);
+ }
+ if (stats.executionTimeInWindowMs >= allowedTimeMinMs) {
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ nowElapsed - allowedTimeMinMs + stats.windowSizeMs);
+ }
if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
+ final long inQuotaTime = nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime);
}
if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- nowElapsed + stats.windowSizeMs);
+ final long inQuotaTime = nowElapsed + stats.windowSizeMs;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed, inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed, inQuotaTime);
}
}
@@ -1267,9 +1351,23 @@ public final class QuotaController extends StateController {
start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
+ stats.windowSizeMs);
}
+ if (stats.executionTimeInWindowMs >= allowedTimeLowMs) {
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ start + stats.executionTimeInWindowMs - allowedTimeLowMs
+ + stats.windowSizeMs);
+ }
+ if (stats.executionTimeInWindowMs >= allowedTimeMinMs) {
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ start + stats.executionTimeInWindowMs - allowedTimeMinMs
+ + stats.windowSizeMs);
+ }
if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- session.endTimeElapsed + stats.windowSizeMs);
+ final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ inQuotaTime);
}
if (i == loopStart
|| (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
@@ -1278,8 +1376,12 @@ public final class QuotaController extends StateController {
sessionCountInWindow++;
if (sessionCountInWindow >= stats.sessionCountLimit) {
- stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
- session.endTimeElapsed + stats.windowSizeMs);
+ final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
+ stats.inQuotaTimeLowElapsed = Math.max(stats.inQuotaTimeLowElapsed,
+ inQuotaTime);
+ stats.inQuotaTimeMinElapsed = Math.max(stats.inQuotaTimeMinElapsed,
+ inQuotaTime);
}
}
}
@@ -1425,10 +1527,9 @@ public final class QuotaController extends StateController {
synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis();
final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
- if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)
- && maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) {
- mStateChangedListener
- .onControllerStateChanged(mTrackedJobs.get(userId, packageName));
+ if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)) {
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName));
}
}
}
@@ -1558,9 +1659,8 @@ public final class QuotaController extends StateController {
final int userId = mTrackedJobs.keyAt(u);
for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) {
final String packageName = mTrackedJobs.keyAt(u, p);
- if (maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) {
- changedJobs.addAll(mTrackedJobs.valueAt(u, p));
- }
+ changedJobs.addAll(
+ maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName));
}
}
if (changedJobs.size() > 0) {
@@ -1573,18 +1673,20 @@ public final class QuotaController extends StateController {
*
* @return true if at least one job had its bit changed
*/
- private boolean maybeUpdateConstraintForPkgLocked(final long nowElapsed, final int userId,
- @NonNull final String packageName) {
+ @NonNull
+ private ArraySet<JobStatus> maybeUpdateConstraintForPkgLocked(final long nowElapsed,
+ final int userId, @NonNull final String packageName) {
ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+ final ArraySet<JobStatus> changedJobs = new ArraySet<>();
if (jobs == null || jobs.size() == 0) {
- return false;
+ return changedJobs;
}
// Quota is the same for all jobs within a package.
final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket();
- final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket);
+ final boolean realInQuota = isWithinQuotaLocked(
+ userId, packageName, realStandbyBucket, JobInfo.PRIORITY_DEFAULT);
boolean outOfEJQuota = false;
- boolean changed = false;
for (int i = jobs.size() - 1; i >= 0; --i) {
final JobStatus js = jobs.valueAt(i);
final boolean isWithinEJQuota =
@@ -1592,21 +1694,30 @@ public final class QuotaController extends StateController {
if (isTopStartedJobLocked(js)) {
// Job was started while the app was in the TOP state so we should allow it to
// finish.
- changed |= js.setQuotaConstraintSatisfied(nowElapsed, true);
+ if (js.setQuotaConstraintSatisfied(nowElapsed, true)) {
+ changedJobs.add(js);
+ }
} else if (realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+ && 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
// for some reason. Therefore, avoid setting the real value here and check each job
// individually.
- changed |= setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota);
+ if (setConstraintSatisfied(js, nowElapsed, isWithinEJQuota || realInQuota)) {
+ changedJobs.add(js);
+ }
} else {
// This job is somehow exempted. Need to determine its own quota status.
- changed |= setConstraintSatisfied(js, nowElapsed,
- isWithinEJQuota || isWithinQuotaLocked(js));
+ if (setConstraintSatisfied(js, nowElapsed,
+ isWithinEJQuota || isWithinQuotaLocked(js))) {
+ changedJobs.add(js);
+ }
}
if (js.isRequestedExpeditedJob()) {
- changed |= setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota);
+ if (setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota)) {
+ changedJobs.add(js);
+ }
outOfEJQuota |= !isWithinEJQuota;
}
}
@@ -1618,7 +1729,7 @@ public final class QuotaController extends StateController {
} else {
mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
}
- return changed;
+ return changedJobs;
}
private class UidConstraintUpdater implements Consumer<JobStatus> {
@@ -1651,9 +1762,9 @@ public final class QuotaController extends StateController {
final int userId = jobStatus.getSourceUserId();
final String packageName = jobStatus.getSourcePackageName();
final int realStandbyBucket = jobStatus.getStandbyBucket();
- if (isWithinQuotaLocked(userId, packageName, realStandbyBucket) && isWithinEJQuota) {
- // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
- // that all jobs for the userId-package are within quota.
+ if (isWithinEJQuota
+ && isWithinQuotaLocked(userId, packageName, realStandbyBucket,
+ JobInfo.PRIORITY_MIN)) {
mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
} else {
mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
@@ -1700,16 +1811,41 @@ public final class QuotaController extends StateController {
return;
}
+ ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
+ if (jobs == null || jobs.size() == 0) {
+ Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
+ + packageToString(userId, packageName) + " that has no jobs");
+ mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+ return;
+ }
+
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
standbyBucket);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
- final boolean inRegularQuota = stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
- && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
- && isUnderJobCountQuota
- && isUnderTimingSessionCountQuota;
+ int minPriority = JobInfo.PRIORITY_MAX;
+ boolean hasDefPlus = false, hasLow = false, hasMin = false;
+ for (int i = jobs.size() - 1; i >= 0; --i) {
+ final int priority = jobs.valueAt(i).getEffectivePriority();
+ minPriority = Math.min(minPriority, priority);
+ if (priority <= JobInfo.PRIORITY_MIN) {
+ hasMin = true;
+ } else if (priority <= JobInfo.PRIORITY_LOW) {
+ hasLow = true;
+ } else {
+ hasDefPlus = true;
+ }
+ if (hasMin && hasLow && hasDefPlus) {
+ break;
+ }
+ }
+ final boolean inRegularQuota =
+ stats.executionTimeInWindowMs < getAllowedTimePerPeriodMs(minPriority)
+ && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
+ && isUnderJobCountQuota
+ && isUnderTimingSessionCountQuota;
if (inRegularQuota && remainingEJQuota > 0) {
// Already in quota. Why was this method called?
if (DEBUG) {
@@ -1728,7 +1864,24 @@ public final class QuotaController extends StateController {
long inEJQuotaTimeElapsed = Long.MAX_VALUE;
if (!inRegularQuota) {
// The time this app will have quota again.
- long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
+ long executionInQuotaTime = Long.MAX_VALUE;
+ boolean hasExecutionInQuotaTime = false;
+ if (hasMin && stats.inQuotaTimeMinElapsed > 0) {
+ executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeMinElapsed);
+ hasExecutionInQuotaTime = true;
+ }
+ if (hasLow && stats.inQuotaTimeLowElapsed > 0) {
+ executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeLowElapsed);
+ hasExecutionInQuotaTime = true;
+ }
+ if (hasDefPlus && stats.inQuotaTimeElapsed > 0) {
+ executionInQuotaTime = Math.min(executionInQuotaTime, stats.inQuotaTimeElapsed);
+ hasExecutionInQuotaTime = true;
+ }
+ long inQuotaTimeElapsed = 0;
+ if (hasExecutionInQuotaTime) {
+ inQuotaTimeElapsed = executionInQuotaTime;
+ }
if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
// App hit the rate limit.
inQuotaTimeElapsed =
@@ -1941,6 +2094,7 @@ public final class QuotaController extends StateController {
private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
private long mStartTimeElapsed;
private int mBgJobCount;
+ private int mLowestPriority = JobInfo.PRIORITY_MAX;
private long mDebitAdjustment;
Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
@@ -1963,6 +2117,7 @@ public final class QuotaController extends StateController {
Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
}
// Always maintain list of running jobs, even when quota is free.
+ mLowestPriority = Math.min(mLowestPriority, jobStatus.getEffectivePriority());
if (mRunningBgJobs.add(jobStatus) && shouldTrackLocked()) {
mBgJobCount++;
if (mRegularJobTimer) {
@@ -2002,6 +2157,13 @@ public final class QuotaController extends StateController {
&& !isQuotaFreeLocked(standbyBucket)) {
emitSessionLocked(nowElapsed);
cancelCutoff();
+ mLowestPriority = JobInfo.PRIORITY_MAX;
+ } else if (mLowestPriority == jobStatus.getEffectivePriority()) {
+ mLowestPriority = JobInfo.PRIORITY_MAX;
+ for (int i = mRunningBgJobs.size() - 1; i >= 0; --i) {
+ mLowestPriority = Math.min(mLowestPriority,
+ mRunningBgJobs.valueAt(i).getEffectivePriority());
+ }
}
}
}
@@ -2128,9 +2290,14 @@ public final class QuotaController extends StateController {
}
Message msg = mHandler.obtainMessage(
mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
- final long timeRemainingMs = mRegularJobTimer
- ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
- : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
+ final long timeRemainingMs;
+ if (mRegularJobTimer) {
+ timeRemainingMs = getTimeUntilQuotaConsumedLocked(
+ mPkg.userId, mPkg.packageName, mLowestPriority);
+ } else {
+ timeRemainingMs =
+ getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
+ }
if (DEBUG) {
Slog.i(TAG,
(mRegularJobTimer ? "Regular job" : "EJ") + " for " + mPkg + " has "
@@ -2250,11 +2417,10 @@ public final class QuotaController extends StateController {
final ShrinkableDebits debits =
getEJDebitsLocked(mPkg.userId, mPkg.packageName);
if (transactQuotaLocked(mPkg.userId, mPkg.packageName,
- nowElapsed, debits, pendingReward)
- && maybeUpdateConstraintForPkgLocked(nowElapsed,
- mPkg.userId, mPkg.packageName)) {
+ nowElapsed, debits, pendingReward)) {
mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(mPkg.userId, mPkg.packageName));
+ maybeUpdateConstraintForPkgLocked(nowElapsed,
+ mPkg.userId, mPkg.packageName));
}
}
break;
@@ -2356,11 +2522,9 @@ public final class QuotaController extends StateController {
if (timer != null && timer.isActive()) {
timer.rescheduleCutoff();
}
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- userId, packageName)) {
- mStateChangedListener
- .onControllerStateChanged(mTrackedJobs.get(userId, packageName));
- }
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(), userId, packageName));
}
if (restrictedChanges.size() > 0) {
mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
@@ -2486,27 +2650,19 @@ public final class QuotaController extends StateController {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
}
- long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
- pkg.packageName);
- if (timeRemainingMs <= 50) {
- // Less than 50 milliseconds left. Start process of shutting down jobs.
+ final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName);
+ if (changedJobs.size() > 0) {
if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- pkg.userId, pkg.packageName)) {
- mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(pkg.userId, pkg.packageName));
- }
+ mStateChangedListener.onControllerStateChanged(changedJobs);
} else {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
- timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
- pkg.packageName);
if (DEBUG) {
- Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left.");
+ Slog.d(TAG, pkg + " had early REACHED_QUOTA message");
}
- sendMessageDelayed(rescheduleMsg, timeRemainingMs);
+ mPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff();
}
break;
}
@@ -2516,26 +2672,19 @@ public final class QuotaController extends StateController {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
}
- long timeRemainingMs = getRemainingEJExecutionTimeLocked(
- pkg.userId, pkg.packageName);
- if (timeRemainingMs <= 0) {
+ final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(), pkg.userId, pkg.packageName);
+ if (changedJobs.size() > 0) {
if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- pkg.userId, pkg.packageName)) {
- mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(pkg.userId, pkg.packageName));
- }
+ mStateChangedListener.onControllerStateChanged(changedJobs);
} else {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
- timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
- pkg.userId, pkg.packageName);
if (DEBUG) {
- Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left for EJ");
+ Slog.d(TAG, pkg + " had early REACHED_EJ_QUOTA message");
}
- sendMessageDelayed(rescheduleMsg, timeRemainingMs);
+ mEJPkgTimers.get(pkg.userId, pkg.packageName).scheduleCutoff();
}
break;
}
@@ -2553,11 +2702,9 @@ public final class QuotaController extends StateController {
if (DEBUG) {
Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName));
}
- if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
- userId, packageName)) {
- mStateChangedListener.onControllerStateChanged(
- mTrackedJobs.get(userId, packageName));
- }
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
+ userId, packageName));
break;
}
case MSG_UID_PROCESS_STATE_CHANGED: {
@@ -2781,6 +2928,12 @@ public final class QuotaController extends StateController {
static final String KEY_IN_QUOTA_BUFFER_MS =
QC_CONSTANT_PREFIX + "in_quota_buffer_ms";
@VisibleForTesting
+ static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
+ QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_low";
+ @VisibleForTesting
+ static final String KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
+ QC_CONSTANT_PREFIX + "allowed_time_surplus_priority_min";
+ @VisibleForTesting
static final String KEY_WINDOW_SIZE_ACTIVE_MS =
QC_CONSTANT_PREFIX + "window_size_active_ms";
@VisibleForTesting
@@ -2890,6 +3043,8 @@ public final class QuotaController extends StateController {
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_ACTIVE_MS =
DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
@@ -2951,6 +3106,22 @@ 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
+ * {@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.
+ */
+ 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
+ * {@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.
+ */
+ public float ALLOWED_TIME_SURPLUS_PRIORITY_MIN = DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN;
+
+ /**
* 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
* WINDOW_SIZE_MS.
@@ -3188,6 +3359,8 @@ public final class QuotaController extends StateController {
@NonNull String key) {
switch (key) {
case KEY_ALLOWED_TIME_PER_PERIOD_MS:
+ case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW:
+ case KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN:
case KEY_IN_QUOTA_BUFFER_MS:
case KEY_MAX_EXECUTION_TIME_MS:
case KEY_WINDOW_SIZE_ACTIVE_MS:
@@ -3407,6 +3580,7 @@ public final class QuotaController extends StateController {
final DeviceConfig.Properties properties = DeviceConfig.getProperties(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_ALLOWED_TIME_PER_PERIOD_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_WINDOW_SIZE_WORKING_MS,
KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS,
@@ -3414,6 +3588,12 @@ public final class QuotaController extends StateController {
ALLOWED_TIME_PER_PERIOD_MS =
properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_MS,
DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
+ ALLOWED_TIME_SURPLUS_PRIORITY_LOW =
+ properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_LOW,
+ DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_LOW);
+ ALLOWED_TIME_SURPLUS_PRIORITY_MIN =
+ properties.getFloat(KEY_ALLOWED_TIME_SURPLUS_PRIORITY_MIN,
+ DEFAULT_ALLOWED_TIME_SURPLUS_PRIORITY_MIN);
IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS,
DEFAULT_IN_QUOTA_BUFFER_MS);
MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS,
@@ -3455,6 +3635,23 @@ public final class QuotaController extends StateController {
mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
mShouldReevaluateConstraints = true;
}
+ // Low priority surplus should be in the range [0, .9]. A value of 1 would essentially
+ // mean never run low priority jobs.
+ float newAllowedTimeSurplusPriorityLow =
+ Math.max(0f, Math.min(.9f, ALLOWED_TIME_SURPLUS_PRIORITY_LOW));
+ if (Float.compare(
+ mAllowedTimeSurplusPriorityLow, newAllowedTimeSurplusPriorityLow) != 0) {
+ mAllowedTimeSurplusPriorityLow = newAllowedTimeSurplusPriorityLow;
+ mShouldReevaluateConstraints = true;
+ }
+ // Min priority surplus should be in the range [0, mAllowedTimeSurplusPriorityLow].
+ float newAllowedTimeSurplusPriorityMin = Math.max(0f,
+ Math.min(mAllowedTimeSurplusPriorityLow, ALLOWED_TIME_SURPLUS_PRIORITY_MIN));
+ if (Float.compare(
+ mAllowedTimeSurplusPriorityMin, newAllowedTimeSurplusPriorityMin) != 0) {
+ mAllowedTimeSurplusPriorityMin = newAllowedTimeSurplusPriorityMin;
+ mShouldReevaluateConstraints = true;
+ }
long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
@@ -3627,6 +3824,10 @@ public final class QuotaController extends StateController {
pw.println("QuotaController:");
pw.increaseIndent();
pw.print(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_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_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
@@ -3750,6 +3951,16 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
+ float getAllowedTimeSurplusPriorityLow() {
+ return mAllowedTimeSurplusPriorityLow;
+ }
+
+ @VisibleForTesting
+ float getAllowedTimeSurplusPriorityMin() {
+ return mAllowedTimeSurplusPriorityMin;
+ }
+
+ @VisibleForTesting
@NonNull
int[] getBucketMaxJobCounts() {
return mMaxBucketJobCounts;
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 cfae9a3d586a..153ce17ec9dd 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
@@ -16,6 +16,11 @@
package com.android.server.job.controllers;
+import static android.app.job.JobInfo.PRIORITY_DEFAULT;
+import static android.app.job.JobInfo.PRIORITY_HIGH;
+import static android.app.job.JobInfo.PRIORITY_LOW;
+import static android.app.job.JobInfo.PRIORITY_MIN;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -269,14 +274,14 @@ public class QuotaControllerTest {
}
private void setCharging() {
- doReturn(true).when(mJobSchedulerService).isBatteryCharging();
+ when(mJobSchedulerService.isBatteryCharging()).thenReturn(true);
synchronized (mQuotaController.mLock) {
mQuotaController.onBatteryStateChangedLocked();
}
}
private void setDischarging() {
- doReturn(false).when(mJobSchedulerService).isBatteryCharging();
+ when(mJobSchedulerService.isBatteryCharging()).thenReturn(false);
synchronized (mQuotaController.mLock) {
mQuotaController.onBatteryStateChangedLocked();
}
@@ -407,6 +412,14 @@ public class QuotaControllerTest {
}
}
+ private void setDeviceConfigFloat(String key, float val) {
+ mDeviceConfigPropertiesBuilder.setFloat(key, val);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForUpdatedConstantsLocked();
+ mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ }
+ }
+
private void waitForNonDelayedMessagesProcessed() {
mQuotaController.getHandler().runWithScissors(() -> {}, 15_000);
}
@@ -839,7 +852,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
assertEquals(expectedStats, inputStats);
assertTrue(mQuotaController.isWithinQuotaLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
}
assertTrue("Job not ready: " + jobStatus, jobStatus.isReady());
}
@@ -863,7 +876,7 @@ public class QuotaControllerTest {
assertEquals(expectedStats, inputStats);
assertFalse(
mQuotaController.isWithinQuotaLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
}
// Quota should be exceeded due to activity in active timer.
@@ -888,7 +901,7 @@ public class QuotaControllerTest {
assertEquals(expectedStats, inputStats);
assertFalse(
mQuotaController.isWithinQuotaLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX, PRIORITY_DEFAULT));
assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady());
}
}
@@ -1484,7 +1497,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
setStandbyBucket(FREQUENT_INDEX);
@@ -1494,7 +1507,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
setStandbyBucket(WORKING_INDEX);
@@ -1504,7 +1517,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(7 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
// ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
@@ -1516,7 +1529,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
}
@@ -1540,7 +1553,7 @@ public class QuotaControllerTest {
// Max time will phase out, so should use bucket limit.
assertEquals(10 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1556,7 +1569,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(10 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1573,7 +1586,7 @@ public class QuotaControllerTest {
SOURCE_USER_ID, SOURCE_PACKAGE));
assertEquals(3 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
}
@@ -1606,7 +1619,7 @@ public class QuotaControllerTest {
// window time.
assertEquals(10 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
- SOURCE_USER_ID, SOURCE_PACKAGE));
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
}
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
@@ -1633,15 +1646,115 @@ public class QuotaControllerTest {
// Max time only has one minute phase out. Bucket time has 2 minute phase out.
assertEquals(9 * MINUTE_IN_MILLIS,
mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ }
+ }
+
+ /**
+ * Test getTimeUntilQuotaConsumedLocked when the determination is based on the job's priority.
+ */
+ @Test
+ public void testGetTimeUntilQuotaConsumedLocked_Priority() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Close to RARE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
+ 150 * SECOND_IN_MILLIS, 5), false);
+ // Far away from FREQUENT boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), false);
+ // Overlap WORKING_SET boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
+ 2 * MINUTE_IN_MILLIS, 5), false);
+ // Close to ACTIVE boundary.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+
+ setStandbyBucket(RARE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(30 * SECOND_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(0,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(0,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+ }
+
+ setStandbyBucket(FREQUENT_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(3 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(30 * SECOND_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(0,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+ }
+
+ setStandbyBucket(WORKING_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(6 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(4 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(2 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
+ }
+
+ // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
+ // max execution time.
+ setStandbyBucket(ACTIVE_INDEX);
+ synchronized (mQuotaController.mLock) {
+ assertEquals(7 * MINUTE_IN_MILLIS,
+ mQuotaController.getRemainingExecutionTimeLocked(
SOURCE_USER_ID, SOURCE_PACKAGE));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_HIGH));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_DEFAULT));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_LOW));
+ assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 7 * MINUTE_IN_MILLIS,
+ mQuotaController.getTimeUntilQuotaConsumedLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, PRIORITY_MIN));
}
}
@Test
public void testIsWithinQuotaLocked_NeverApp() {
synchronized (mQuotaController.mLock) {
- assertFalse(
- mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test.never", NEVER_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1649,7 +1762,8 @@ public class QuotaControllerTest {
public void testIsWithinQuotaLocked_Charging() {
setCharging();
synchronized (mQuotaController.mLock) {
- assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1663,7 +1777,8 @@ public class QuotaControllerTest {
createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
- assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1680,7 +1795,7 @@ public class QuotaControllerTest {
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test.spam", jobCount);
assertFalse(mQuotaController.isWithinQuotaLocked(
- 0, "com.android.test.spam", WORKING_INDEX));
+ 0, "com.android.test.spam", WORKING_INDEX, PRIORITY_DEFAULT));
}
mQuotaController.saveTimingSession(0, "com.android.test.frequent",
@@ -1690,7 +1805,7 @@ public class QuotaControllerTest {
createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500), false);
synchronized (mQuotaController.mLock) {
assertFalse(mQuotaController.isWithinQuotaLocked(
- 0, "com.android.test.frequent", FREQUENT_INDEX));
+ 0, "com.android.test.frequent", FREQUENT_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1706,7 +1821,8 @@ public class QuotaControllerTest {
createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), false);
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
- assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1722,7 +1838,8 @@ public class QuotaControllerTest {
false);
synchronized (mQuotaController.mLock) {
mQuotaController.incrementJobCountLocked(0, "com.android.test", jobCount);
- assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
}
}
@@ -1875,22 +1992,66 @@ public class QuotaControllerTest {
assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions",
i < 2,
- mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
+ mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", RARE_INDEX, PRIORITY_DEFAULT));
assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions",
i < 3,
mQuotaController.isWithinQuotaLocked(
- 0, "com.android.test", FREQUENT_INDEX));
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT));
assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions",
i < 4,
- mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
+ mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions",
i < 5,
- mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX));
+ mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT));
}
}
}
@Test
+ public void testIsWithinQuotaLocked_Priority() {
+ setDischarging();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_HIGH));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_DEFAULT));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_LOW));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", FREQUENT_INDEX, PRIORITY_MIN));
+
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_HIGH));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_DEFAULT));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_LOW));
+ assertFalse(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", WORKING_INDEX, PRIORITY_MIN));
+
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_HIGH));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_DEFAULT));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_LOW));
+ assertTrue(mQuotaController.isWithinQuotaLocked(
+ 0, "com.android.test", ACTIVE_INDEX, PRIORITY_MIN));
+ }
+ }
+
+ @Test
public void testIsWithinEJQuotaLocked_NeverApp() {
JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1);
setStandbyBucket(NEVER_INDEX, js);
@@ -2116,6 +2277,12 @@ public class QuotaControllerTest {
final int standbyBucket = ACTIVE_INDEX;
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
// No sessions saved yet.
synchronized (mQuotaController.mLock) {
mQuotaController.maybeScheduleStartAlarmLocked(
@@ -2150,10 +2317,7 @@ public class QuotaControllerTest {
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
- JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
- setStandbyBucket(standbyBucket, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
mQuotaController.prepareForExecutionLocked(jobStatus);
}
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
@@ -2179,19 +2343,24 @@ public class QuotaControllerTest {
// Working set window size is 2 hours.
final int standbyBucket = WORKING_INDEX;
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_WorkingSet", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
// No sessions saved yet.
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2201,37 +2370,41 @@ public class QuotaControllerTest {
// Counting backwards, the quota will come back one minute before the end.
final long expectedAlarmTime =
end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Add some more sessions, but still in quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test when out of quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2244,22 +2417,29 @@ public class QuotaControllerTest {
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Frequent", 1), null);
+ }
+
// Frequent window size is 8 hours.
final int standbyBucket = FREQUENT_INDEX;
// No sessions saved yet.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2267,37 +2447,41 @@ public class QuotaControllerTest {
// Test with timing sessions in window but still in quota.
final long start = now - (6 * HOUR_IN_MILLIS);
final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Add some more sessions, but still in quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test when out of quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2314,6 +2498,11 @@ public class QuotaControllerTest {
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Never", 1), null);
+ }
+
// The app is really in the NEVER bucket but is elevated somehow (eg via uidActive).
setStandbyBucket(NEVER_INDEX);
final int effectiveStandbyBucket = FREQUENT_INDEX;
@@ -2390,22 +2579,30 @@ public class QuotaControllerTest {
// Rare window size is 24 hours.
final int standbyBucket = RARE_INDEX;
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Rare", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
// Prevent timing session throttling from affecting the test.
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 50);
// No sessions saved yet.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test with timing sessions out of window.
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2417,42 +2614,168 @@ public class QuotaControllerTest {
final long expectedAlarmTime =
start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
+ mQcConstants.IN_QUOTA_BUFFER_MS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Add some more sessions, but still in quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(0)).setWindow(
anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Test when out of quota.
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1), false);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// Alarm already scheduled, so make sure it's not scheduled again.
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
}
verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
}
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_Priority() {
+ // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaController);
+ doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+ setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 5);
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ JobStatus jobDef = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+ SOURCE_PACKAGE, CALLING_UID,
+ new JobInfo.Builder(1, new ComponentName(mContext, "TestQuotaJobService"))
+ .setPriority(PRIORITY_DEFAULT)
+ .build());
+ JobStatus jobLow = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+ SOURCE_PACKAGE, CALLING_UID,
+ new JobInfo.Builder(2, new ComponentName(mContext, "TestQuotaJobService"))
+ .setPriority(PRIORITY_LOW)
+ .build());
+ JobStatus jobMin = createJobStatus("testMaybeScheduleStartAlarmLocked_Priority",
+ SOURCE_PACKAGE, CALLING_UID,
+ new JobInfo.Builder(3, new ComponentName(mContext, "TestQuotaJobService"))
+ .setPriority(PRIORITY_MIN)
+ .build());
+
+ setStandbyBucket(RARE_INDEX, jobDef, jobLow, jobMin);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+ // Min job requires 5 mins of surplus.
+ long expectedAlarmTime = now + 23 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+ // Low job requires 2.5 mins of surplus.
+ expectedAlarmTime = now + 17 * HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
+ // Default+ jobs require IN_QUOTA_BUFFER_MS.
+ expectedAlarmTime = now + mQcConstants.IN_QUOTA_BUFFER_MS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false);
+
+ setStandbyBucket(FREQUENT_INDEX, jobDef, jobLow, jobMin);
+
+ mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+ // Min job requires 5 mins of surplus.
+ expectedAlarmTime = now + 7 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+ // Low job requires 2.5 mins of surplus.
+ expectedAlarmTime = now + HOUR_IN_MILLIS + 90 * SECOND_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
+ // Default+ jobs already have enough quota.
+ inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStopTrackingJobLocked(jobMin, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobLow, null, false);
+ mQuotaController.maybeStopTrackingJobLocked(jobDef, null, false);
+
+ setStandbyBucket(WORKING_INDEX, jobDef, jobLow, jobMin);
+
+ mQuotaController.maybeStartTrackingJobLocked(jobMin, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ // Min job requires 5 mins of surplus.
+ expectedAlarmTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
+ inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
+ anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobLow, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ // Low job has enough surplus.
+ inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.maybeStartTrackingJobLocked(jobDef, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ // Default+ jobs already have enough quota.
+ inOrder.verify(mAlarmManager, timeout(1000).times(0)).setWindow(
+ anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+ }
+
/** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@Test
public void testMaybeScheduleStartAlarmLocked_BucketChange() {
@@ -2464,24 +2787,29 @@ public class QuotaControllerTest {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Affects rare bucket
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), false);
// Affects frequent and rare buckets
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false);
// Affects working, frequent, and rare buckets
final long outOfQuotaTime = now - HOUR_IN_MILLIS;
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10), false);
// Affects all buckets
- mQuotaController.saveTimingSession(0, "com.android.test",
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3), false);
InOrder inOrder = inOrder(mAlarmManager);
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_BucketChange", 1);
+
// Start in ACTIVE bucket.
+ setStandbyBucket(ACTIVE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
.setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2492,8 +2820,10 @@ public class QuotaControllerTest {
final long expectedWorkingAlarmTime =
outOfQuotaTime + (2 * HOUR_IN_MILLIS)
+ mQcConstants.IN_QUOTA_BUFFER_MS;
+ setStandbyBucket(WORKING_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
@@ -2502,8 +2832,10 @@ public class QuotaControllerTest {
final long expectedFrequentAlarmTime =
outOfQuotaTime + (8 * HOUR_IN_MILLIS)
+ mQcConstants.IN_QUOTA_BUFFER_MS;
+ setStandbyBucket(FREQUENT_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
@@ -2512,29 +2844,37 @@ public class QuotaControllerTest {
final long expectedRareAlarmTime =
outOfQuotaTime + (24 * HOUR_IN_MILLIS)
+ mQcConstants.IN_QUOTA_BUFFER_MS;
+ setStandbyBucket(RARE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
// And back up again.
+ setStandbyBucket(FREQUENT_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
eq(TAG_QUOTA_CHECK), any(), any());
+ setStandbyBucket(WORKING_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
eq(TAG_QUOTA_CHECK), any(), any());
+ setStandbyBucket(ACTIVE_INDEX, jobStatus);
synchronized (mQuotaController.mLock) {
- mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
+ mQuotaController.maybeScheduleStartAlarmLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
}
inOrder.verify(mAlarmManager, timeout(1000).times(0))
.setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
@@ -2551,6 +2891,13 @@ public class QuotaControllerTest {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int standbyBucket = WORKING_INDEX;
+
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1);
+ setStandbyBucket(standbyBucket, jobStatus);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
ExecutionStats stats;
synchronized (mQuotaController.mLock) {
stats = mQuotaController.getExecutionStatsLocked(
@@ -2646,6 +2993,11 @@ public class QuotaControllerTest {
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
+ }
+
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Working set window size is 2 hours.
final int standbyBucket = WORKING_INDEX;
@@ -2671,13 +3023,17 @@ public class QuotaControllerTest {
anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
}
-
private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
// saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
// because it schedules an alarm too. Prevent it from doing so.
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
+ }
+
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Working set window size is 2 hours.
final int standbyBucket = WORKING_INDEX;
@@ -2708,6 +3064,8 @@ public class QuotaControllerTest {
public void testConstantsUpdating_ValidValues() {
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_MS, 5 * 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_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);
@@ -2748,6 +3106,8 @@ public class QuotaControllerTest {
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
+ assertEquals(.7f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+ assertEquals(.2f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(45 * MINUTE_IN_MILLIS,
@@ -2793,6 +3153,8 @@ public class QuotaControllerTest {
// Test negatives/too low.
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_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_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);
@@ -2831,6 +3193,8 @@ public class QuotaControllerTest {
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
assertEquals(0, mQuotaController.getInQuotaBufferMs());
+ assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityLow(), 1e-6);
+ assertEquals(0f, mQuotaController.getAllowedTimeSurplusPriorityMin(), 1e-6);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -2878,6 +3242,8 @@ public class QuotaControllerTest {
// 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_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_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);
@@ -2905,6 +3271,8 @@ public class QuotaControllerTest {
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
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()[ACTIVE_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
@@ -3669,8 +4037,8 @@ public class QuotaControllerTest {
// Wait for some extra time to allow for job processing.
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
synchronized (mQuotaController.mLock) {
assertEquals(remainingTimeMs / 2,
mQuotaController.getRemainingExecutionTimeLocked(jobBg));
@@ -3681,8 +4049,8 @@ public class QuotaControllerTest {
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
advanceElapsedClock(remainingTimeMs / 2 + 1);
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
// Top job should still be allowed to run.
assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -3696,7 +4064,7 @@ public class QuotaControllerTest {
advanceElapsedClock(20 * SECOND_IN_MILLIS);
setProcessState(ActivityManager.PROCESS_STATE_TOP);
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
trackJobs(jobFg, jobTop);
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobTop);
@@ -3715,7 +4083,7 @@ public class QuotaControllerTest {
advanceElapsedClock(20 * SECOND_IN_MILLIS);
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
// App is now in background and out of quota. Fg should now change to out of quota since it
// wasn't started. Top should remain in quota since it started when the app was in TOP.
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -3753,7 +4121,7 @@ public class QuotaControllerTest {
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(),
jobStatus.getWhenStandbyDeferred());
@@ -3797,7 +4165,7 @@ public class QuotaControllerTest {
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
// The job used up the remaining quota, but in that time, the same amount of time in the
// old TimingSession also fell out of the quota window, so it should still have the same
@@ -3819,7 +4187,7 @@ public class QuotaControllerTest {
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(12 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
verify(handler, never()).sendMessageDelayed(any(), anyInt());
}
@@ -4406,6 +4774,11 @@ public class QuotaControllerTest {
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_EJ", 1), null);
+ }
+
final int standbyBucket = WORKING_INDEX;
setStandbyBucket(standbyBucket);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
@@ -4482,6 +4855,11 @@ public class QuotaControllerTest {
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_BucketChange", 1), null);
+ }
+
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS);
@@ -4590,6 +4968,11 @@ public class QuotaControllerTest {
spyOn(mQuotaController);
doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(
+ createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_SRQ", 1), null);
+ }
+
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
setStandbyBucket(WORKING_INDEX);
final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
@@ -5437,8 +5820,8 @@ public class QuotaControllerTest {
// Wait for some extra time to allow for job processing.
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
synchronized (mQuotaController.mLock) {
assertEquals(remainingTimeMs / 2,
mQuotaController.getRemainingEJExecutionTimeLocked(
@@ -5448,8 +5831,8 @@ public class QuotaControllerTest {
setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
advanceElapsedClock(remainingTimeMs / 2 + 1);
inOrder.verify(mJobSchedulerService,
- timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
// Top should still be "in quota" since it started before the app ran on top out of quota.
assertFalse(jobBg.isExpeditedQuotaApproved());
assertTrue(jobTop.isExpeditedQuotaApproved());
@@ -5473,7 +5856,7 @@ public class QuotaControllerTest {
setProcessState(ActivityManager.PROCESS_STATE_TOP);
// Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota.
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
trackJobs(jobTop2, jobFg);
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobTop2);
@@ -5494,7 +5877,7 @@ public class QuotaControllerTest {
advanceElapsedClock(20 * SECOND_IN_MILLIS);
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
// App is now in background and out of quota. Fg should now change to out of quota since it
// wasn't started. Top should remain in quota since it started when the app was in TOP.
assertTrue(jobTop2.isExpeditedQuotaApproved());
@@ -5633,7 +6016,7 @@ public class QuotaControllerTest {
// Wait for some extra time to allow for job processing.
verify(mJobSchedulerService,
timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
- .onControllerStateChanged(any());
+ .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
assertTrue(jobStatus.isExpeditedQuotaApproved());
// The job used up the remaining quota, but in that time, the same amount of time in the
// old TimingSession also fell out of the quota window, so it should still have the same