summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java572
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java534
2 files changed, 977 insertions, 129 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 44afbe6aff51..14cce19ef676 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.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_MAX;
+import static android.app.job.JobInfo.PRIORITY_MIN;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -43,9 +48,12 @@ import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseArrayMap;
+import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
@@ -91,6 +99,23 @@ public final class FlexibilityController extends StateController {
*/
private long mFallbackFlexibilityDeadlineMs =
FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+ /**
+ * The default deadline that all flexible constraints should be dropped by if a job lacks
+ * a deadline, keyed by job priority.
+ */
+ private SparseLongArray mFallbackFlexibilityDeadlines =
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
+ /**
+ * The scores to use for each job, keyed by job priority.
+ */
+ private SparseIntArray mFallbackFlexibilityDeadlineScores =
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+ /**
+ * The amount of time to add (scaled by job run score) to the fallback flexibility deadline,
+ * keyed by job priority.
+ */
+ private SparseLongArray mFallbackFlexibilityAdditionalScoreTimeFactors =
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
@@ -117,10 +142,10 @@ public final class FlexibilityController extends StateController {
/**
* The percent of a job's lifecycle to drop number of required constraints.
- * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
- * the controller should have i+1 constraints dropped.
+ * mPercentsToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
+ * the controller should have i+1 constraints dropped. Keyed by job priority.
*/
- private int[] mPercentToDropConstraints;
+ private SparseArray<int[]> mPercentsToDropConstraints;
/**
* Keeps track of what flexible constraints are satisfied at the moment.
@@ -199,6 +224,86 @@ public final class FlexibilityController extends StateController {
}
};
+ /** Helper object to track job run score for each app. */
+ private static class JobScoreTracker {
+ private static class JobScoreBucket {
+ @ElapsedRealtimeLong
+ public long startTimeElapsed;
+ public int score;
+
+ private void reset() {
+ startTimeElapsed = 0;
+ score = 0;
+ }
+ }
+
+ private static final int NUM_SCORE_BUCKETS = 24;
+ private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS;
+ private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS];
+ private int mScoreBucketIndex = 0;
+
+ public void addScore(int add, long nowElapsed) {
+ JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex];
+ if (bucket == null) {
+ bucket = new JobScoreBucket();
+ bucket.startTimeElapsed = nowElapsed;
+ mScoreBuckets[mScoreBucketIndex] = bucket;
+ } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) {
+ // The bucket is too old.
+ bucket.reset();
+ bucket.startTimeElapsed = nowElapsed;
+ } else if (bucket.startTimeElapsed
+ < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) {
+ // The current bucket's duration has completed. Move on to the next bucket.
+ mScoreBucketIndex = (mScoreBucketIndex + 1) % NUM_SCORE_BUCKETS;
+ addScore(add, nowElapsed);
+ return;
+ }
+
+ bucket.score += add;
+ }
+
+ public int getScore(long nowElapsed) {
+ int score = 0;
+ final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS;
+ for (JobScoreBucket bucket : mScoreBuckets) {
+ if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) {
+ score += bucket.score;
+ }
+ }
+ return score;
+ }
+
+ public void dump(@NonNull IndentingPrintWriter pw, long nowElapsed) {
+ pw.print("{");
+
+ boolean printed = false;
+ for (int x = 0; x < mScoreBuckets.length; ++x) {
+ final int idx = (mScoreBucketIndex + 1 + x) % mScoreBuckets.length;
+ final JobScoreBucket jsb = mScoreBuckets[idx];
+ if (jsb == null || jsb.startTimeElapsed == 0) {
+ continue;
+ }
+ if (printed) {
+ pw.print(", ");
+ }
+ TimeUtils.formatDuration(jsb.startTimeElapsed, nowElapsed, pw);
+ pw.print("=");
+ pw.print(jsb.score);
+ printed = true;
+ }
+
+ pw.print("}");
+ }
+ }
+
+ /**
+ * Set of {@link JobScoreTracker JobScoreTrackers} for each app.
+ * Keyed by source UID -> source package.
+ **/
+ private final SparseArrayMap<String, JobScoreTracker> mJobScoreTrackers =
+ new SparseArrayMap<>();
+
private static final int MSG_CHECK_ALL_JOBS = 0;
/** Check the jobs in {@link #mJobsToCheck} */
private static final int MSG_CHECK_JOBS = 1;
@@ -228,8 +333,8 @@ public final class FlexibilityController extends StateController {
mFcConfig = new FcConfig();
mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
mContext, AppSchedulingModuleThread.get().getLooper());
- mPercentToDropConstraints =
- mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ mPercentsToDropConstraints =
+ FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
mPrefetchController = prefetchController;
if (mFlexibilityEnabled) {
@@ -272,6 +377,36 @@ public final class FlexibilityController extends StateController {
}
@Override
+ public void prepareForExecutionLocked(JobStatus jobStatus) {
+ // Use the job's requested priority to determine its score since that is what the developer
+ // selected and it will be stable across job runs.
+ final int score = mFallbackFlexibilityDeadlineScores
+ .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+ JobScoreTracker jobScoreTracker =
+ mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
+ if (jobScoreTracker == null) {
+ jobScoreTracker = new JobScoreTracker();
+ mJobScoreTrackers.add(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(),
+ jobScoreTracker);
+ }
+ jobScoreTracker.addScore(score, sElapsedRealtimeClock.millis());
+ }
+
+ @Override
+ public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+ // The job didn't actually start. Undo the score increase.
+ JobScoreTracker jobScoreTracker =
+ mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
+ if (jobScoreTracker == null) {
+ Slog.e(TAG, "Unprepared a job that didn't result in a score change");
+ return;
+ }
+ final int score = mFallbackFlexibilityDeadlineScores
+ .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+ jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis());
+ }
+
+ @Override
@GuardedBy("mLock")
public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) {
if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
@@ -286,12 +421,33 @@ public final class FlexibilityController extends StateController {
public void onAppRemovedLocked(String packageName, int uid) {
final int userId = UserHandle.getUserId(uid);
mPrefetchLifeCycleStart.delete(userId, packageName);
+ mJobScoreTrackers.delete(uid, packageName);
+ for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+ final JobStatus js = mJobsToCheck.valueAt(i);
+ if ((js.getSourceUid() == uid && js.getSourcePackageName().equals(packageName))
+ || (js.getUid() == uid && js.getCallingPackageName().equals(packageName))) {
+ mJobsToCheck.removeAt(i);
+ }
+ }
}
@Override
@GuardedBy("mLock")
public void onUserRemovedLocked(int userId) {
mPrefetchLifeCycleStart.delete(userId);
+ for (int u = mJobScoreTrackers.numMaps() - 1; u >= 0; --u) {
+ final int uid = mJobScoreTrackers.keyAt(u);
+ if (UserHandle.getUserId(uid) == userId) {
+ mJobScoreTrackers.deleteAt(u);
+ }
+ }
+ for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
+ final JobStatus js = mJobsToCheck.valueAt(i);
+ if (UserHandle.getUserId(js.getSourceUid()) == userId
+ || UserHandle.getUserId(js.getUid()) == userId) {
+ mJobsToCheck.removeAt(i);
+ }
+ }
}
boolean isEnabled() {
@@ -308,9 +464,9 @@ public final class FlexibilityController extends StateController {
|| mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
// Only exclude DEFAULT+ priority jobs for BFGS+ apps
|| (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
- && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
+ && js.getEffectivePriority() >= PRIORITY_DEFAULT)
// For apps in the power allowlist, automatically exclude DEFAULT+ priority jobs.
- || (js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT
+ || (js.getEffectivePriority() >= PRIORITY_DEFAULT
&& mPowerAllowlistedApps.contains(js.getSourcePackageName()))
|| hasEnoughSatisfiedConstraintsLocked(js)
|| mService.isCurrentlyRunningLocked(js);
@@ -462,7 +618,14 @@ public final class FlexibilityController extends StateController {
@VisibleForTesting
@GuardedBy("mLock")
- long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) {
+ int getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed) {
+ final JobScoreTracker scoreTracker = mJobScoreTrackers.get(uid, pkgName);
+ return scoreTracker == null ? 0 : scoreTracker.getScore(nowElapsed);
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ long getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest) {
if (js.getJob().isPrefetch()) {
final long estimatedLaunchTime =
mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
@@ -486,15 +649,28 @@ public final class FlexibilityController extends StateController {
(long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
mMaxRescheduledDeadline);
}
- return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
- ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed();
+ if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {
+ // Intentionally use the effective priority here. If a job's priority was effectively
+ // lowered, it will be less likely to run quickly given other policies in JobScheduler.
+ // Thus, there's no need to further delay the job based on flex policy.
+ final int jobPriority = js.getEffectivePriority();
+ final int jobScore =
+ getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
+ // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
+ final long fallbackDeadlineMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
+ mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
+ + mFallbackFlexibilityAdditionalScoreTimeFactors
+ .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
+ return earliest + fallbackDeadlineMs;
+ }
+ return js.getLatestRunTimeElapsed();
}
@VisibleForTesting
@GuardedBy("mLock")
int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
- final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
return 0;
}
@@ -510,7 +686,8 @@ public final class FlexibilityController extends StateController {
@GuardedBy("mLock")
long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
- final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long latest =
+ getLifeCycleEndElapsedLocked(js, sElapsedRealtimeClock.millis(), earliest);
return getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
}
@@ -518,15 +695,27 @@ public final class FlexibilityController extends StateController {
@ElapsedRealtimeLong
@GuardedBy("mLock")
long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
+ final int[] percentsToDropConstraints =
+ getPercentsToDropConstraints(js.getEffectivePriority());
if (latest == NO_LIFECYCLE_END
- || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) {
+ || js.getNumDroppedFlexibleConstraints() == percentsToDropConstraints.length) {
return NO_LIFECYCLE_END;
}
- final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
+ final int percent = percentsToDropConstraints[js.getNumDroppedFlexibleConstraints()];
final long percentInTime = ((latest - earliest) * percent) / 100;
return earliest + percentInTime;
}
+ @NonNull
+ private int[] getPercentsToDropConstraints(int priority) {
+ int[] percentsToDropConstraints = mPercentsToDropConstraints.get(priority);
+ if (percentsToDropConstraints == null) {
+ Slog.wtf(TAG, "No %-to-drop for priority " + JobInfo.getPriorityString(priority));
+ return new int[]{50, 60, 70, 80};
+ }
+ return percentsToDropConstraints;
+ }
+
@Override
@GuardedBy("mLock")
public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
@@ -681,10 +870,12 @@ public final class FlexibilityController extends StateController {
Integer.bitCount(getRelevantAppliedConstraintsLocked(js));
js.setNumAppliedFlexibleConstraints(numAppliedConstraints);
+ final int[] percentsToDropConstraints =
+ getPercentsToDropConstraints(js.getEffectivePriority());
final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
int toDrop = 0;
for (int i = 0; i < numAppliedConstraints; i++) {
- if (curPercent >= mPercentToDropConstraints[i]) {
+ if (curPercent >= percentsToDropConstraints[i]) {
toDrop++;
}
}
@@ -705,8 +896,10 @@ public final class FlexibilityController extends StateController {
final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
int toDrop = 0;
final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints();
+ final int[] percentsToDropConstraints =
+ getPercentsToDropConstraints(js.getEffectivePriority());
for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
- if (curPercent >= mPercentToDropConstraints[i]) {
+ if (curPercent >= percentsToDropConstraints[i]) {
toDrop++;
}
}
@@ -733,7 +926,7 @@ public final class FlexibilityController extends StateController {
return mTrackedJobs.size();
}
- public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
+ public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed) {
for (int i = 0; i < mTrackedJobs.size(); i++) {
ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
for (int j = 0; j < jobs.size(); j++) {
@@ -744,8 +937,18 @@ public final class FlexibilityController extends StateController {
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
- pw.print(" Num Required Constraints: ");
+ pw.print("-> Num Required Constraints: ");
pw.print(js.getNumRequiredFlexibleConstraints());
+
+ pw.print(", lifecycle=[");
+ final long earliest = getLifeCycleBeginningElapsedLocked(js);
+ pw.print(earliest);
+ pw.print(", (");
+ pw.print(getCurPercentOfLifecycleLocked(js, nowElapsed));
+ pw.print("%), ");
+ pw.print(getLifeCycleEndElapsedLocked(js, nowElapsed, earliest));
+ pw.print("]");
+
pw.println();
}
}
@@ -768,7 +971,7 @@ public final class FlexibilityController extends StateController {
public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) {
synchronized (mLock) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
- final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
final long nextTimeElapsed =
getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
@@ -936,10 +1139,16 @@ public final class FlexibilityController extends StateController {
FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
+ static final String KEY_FALLBACK_FLEXIBILITY_DEADLINES =
+ FC_CONFIG_PREFIX + "fallback_flexibility_deadlines";
+ static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
+ FC_CONFIG_PREFIX + "fallback_flexibility_deadline_scores";
+ static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+ FC_CONFIG_PREFIX + "fallback_flexibility_deadline_additional_score_time_factors";
static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms";
- static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
- FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints";
+ static final String KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
+ FC_CONFIG_PREFIX + "percents_to_drop_flexible_constraints";
static final String KEY_MAX_RESCHEDULED_DEADLINE_MS =
FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms";
static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
@@ -952,9 +1161,50 @@ public final class FlexibilityController extends StateController {
static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@VisibleForTesting
static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
- private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
+ static final SparseLongArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
+ static final SparseIntArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
+ new SparseIntArray();
+ static final SparseLongArray
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+ new SparseLongArray();
@VisibleForTesting
- final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
+ static final SparseArray<int[]> DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
+ new SparseArray<>();
+
+ static {
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_MAX, 0);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_HIGH, 4 * MINUTE_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_DEFAULT, 3 * MINUTE_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_LOW, 2 * MINUTE_IN_MILLIS);
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS);
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_MAX, new int[]{1, 2, 3, 4});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_HIGH, new int[]{33, 50, 60, 75});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_LOW, new int[]{50, 60, 70, 80});
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .put(PRIORITY_MIN, new int[]{55, 65, 75, 85});
+ }
+
+ private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
@VisibleForTesting
@@ -968,9 +1218,11 @@ public final class FlexibilityController extends StateController {
public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
- /** The percentages of a jobs' lifecycle to drop the number of required constraints. */
- public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
- DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ /**
+ * The percentages of a jobs' lifecycle to drop the number of required constraints.
+ * Keyed by job priority.
+ */
+ public SparseArray<int[]> PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = new SparseArray<>();
/** Initial fallback flexible deadline for rescheduled jobs. */
public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
/** The max deadline for rescheduled jobs. */
@@ -980,10 +1232,56 @@ public final class FlexibilityController extends StateController {
* it in order to run jobs.
*/
public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
+ /**
+ * The base fallback deadlines to use if a job doesn't have its own deadline. Values are in
+ * milliseconds and keyed by job priority.
+ */
+ public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
+ /**
+ * The score to ascribe to each job, keyed by job priority.
+ */
+ public final SparseIntArray FALLBACK_FLEXIBILITY_DEADLINE_SCORES = new SparseIntArray();
+ /**
+ * How much additional time to increase the fallback deadline by based on the app's current
+ * job run score. Values are in
+ * milliseconds and keyed by job priority.
+ */
+ public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
+ new SparseLongArray();
+
+ FcConfig() {
+ // Copy the values from the DEFAULT_* data structures to avoid accidentally modifying
+ // the DEFAULT_* data structures in other parts of the code.
+ for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.size(); ++i) {
+ FALLBACK_FLEXIBILITY_DEADLINES.put(
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.keyAt(i),
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.valueAt(i));
+ }
+ for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.size(); ++i) {
+ FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.keyAt(i),
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.valueAt(i));
+ }
+ for (int i = 0;
+ i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.size();
+ ++i) {
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.put(
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .keyAt(i),
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
+ .valueAt(i));
+ }
+ for (int i = 0; i < DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.size(); ++i) {
+ PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.put(
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.keyAt(i),
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.valueAt(i));
+ }
+ }
@GuardedBy("mLock")
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
+ // TODO(257322915): add appropriate minimums and maximums to constants when parsing
switch (key) {
case KEY_APPLIED_CONSTRAINTS:
APPLIED_CONSTRAINTS =
@@ -1034,6 +1332,33 @@ public final class FlexibilityController extends StateController {
properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS);
if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) {
mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS;
+ }
+ break;
+ case KEY_FALLBACK_FLEXIBILITY_DEADLINES:
+ if (parsePriorityToLongKeyValueString(
+ properties.getString(key, null),
+ FALLBACK_FLEXIBILITY_DEADLINES,
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES)) {
+ mFallbackFlexibilityDeadlines = FALLBACK_FLEXIBILITY_DEADLINES;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES:
+ if (parsePriorityToIntKeyValueString(
+ properties.getString(key, null),
+ FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES)) {
+ mFallbackFlexibilityDeadlineScores = FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS:
+ if (parsePriorityToLongKeyValueString(
+ properties.getString(key, null),
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+ DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS)) {
+ mFallbackFlexibilityAdditionalScoreTimeFactors =
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
mShouldReevaluateConstraints = true;
}
break;
@@ -1056,25 +1381,69 @@ public final class FlexibilityController extends StateController {
mShouldReevaluateConstraints = true;
}
break;
- case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS:
- String dropPercentString = properties.getString(key, "");
- PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
- parsePercentToDropString(dropPercentString);
- if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null
- && !Arrays.equals(mPercentToDropConstraints,
- PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) {
- mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+ case KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS:
+ if (parsePercentToDropKeyValueString(
+ properties.getString(key, null),
+ PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS)) {
+ mPercentsToDropConstraints = PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
mShouldReevaluateConstraints = true;
}
break;
}
}
- private int[] parsePercentToDropString(String s) {
- String[] dropPercentString = s.split(",");
+ private boolean parsePercentToDropKeyValueString(@Nullable String s,
+ SparseArray<int[]> into, SparseArray<int[]> defaults) {
+ final KeyValueListParser priorityParser = new KeyValueListParser(',');
+ try {
+ priorityParser.setString(s);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad percent to drop key value string given", e);
+ // Clear the string and continue with the defaults.
+ priorityParser.setString(null);
+ }
+
+ final int[] oldMax = into.get(PRIORITY_MAX);
+ final int[] oldHigh = into.get(PRIORITY_HIGH);
+ final int[] oldDefault = into.get(PRIORITY_DEFAULT);
+ final int[] oldLow = into.get(PRIORITY_LOW);
+ final int[] oldMin = into.get(PRIORITY_MIN);
+
+ final int[] newMax = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_MAX), null));
+ final int[] newHigh = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_HIGH), null));
+ final int[] newDefault = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_DEFAULT), null));
+ final int[] newLow = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_LOW), null));
+ final int[] newMin = parsePercentToDropString(priorityParser.getString(
+ String.valueOf(PRIORITY_MIN), null));
+
+ into.put(PRIORITY_MAX, newMax == null ? defaults.get(PRIORITY_MAX) : newMax);
+ into.put(PRIORITY_HIGH, newHigh == null ? defaults.get(PRIORITY_HIGH) : newHigh);
+ into.put(PRIORITY_DEFAULT,
+ newDefault == null ? defaults.get(PRIORITY_DEFAULT) : newDefault);
+ into.put(PRIORITY_LOW, newLow == null ? defaults.get(PRIORITY_LOW) : newLow);
+ into.put(PRIORITY_MIN, newMin == null ? defaults.get(PRIORITY_MIN) : newMin);
+
+ return !Arrays.equals(oldMax, into.get(PRIORITY_MAX))
+ || !Arrays.equals(oldHigh, into.get(PRIORITY_HIGH))
+ || !Arrays.equals(oldDefault, into.get(PRIORITY_DEFAULT))
+ || !Arrays.equals(oldLow, into.get(PRIORITY_LOW))
+ || !Arrays.equals(oldMin, into.get(PRIORITY_MIN));
+ }
+
+ @Nullable
+ private int[] parsePercentToDropString(@Nullable String s) {
+ if (s == null || s.isEmpty()) {
+ return null;
+ }
+ final String[] dropPercentString = s.split("\\|");
int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)];
if (dropPercentInt.length != dropPercentString.length) {
- return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ return null;
}
int prevPercent = 0;
for (int i = 0; i < dropPercentString.length; i++) {
@@ -1083,11 +1452,15 @@ public final class FlexibilityController extends StateController {
Integer.parseInt(dropPercentString[i]);
} catch (NumberFormatException ex) {
Slog.e(TAG, "Provided string was improperly formatted.", ex);
- return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ return null;
}
if (dropPercentInt[i] < prevPercent) {
Slog.wtf(TAG, "Percents to drop constraints were not in increasing order.");
- return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ return null;
+ }
+ if (dropPercentInt[i] > 100) {
+ Slog.e(TAG, "Found % over 100");
+ return null;
}
prevPercent = dropPercentInt[i];
}
@@ -1095,6 +1468,102 @@ public final class FlexibilityController extends StateController {
return dropPercentInt;
}
+ /**
+ * Parses the input string, expecting it to a key-value string where the keys are job
+ * priorities, and replaces everything in {@code into} with the values from the string,
+ * or the default values if the string contains none.
+ *
+ * Returns true if any values changed.
+ */
+ private boolean parsePriorityToIntKeyValueString(@Nullable String s,
+ SparseIntArray into, SparseIntArray defaults) {
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(s);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad string given", e);
+ // Clear the string and continue with the defaults.
+ parser.setString(null);
+ }
+
+ final int oldMax = into.get(PRIORITY_MAX);
+ final int oldHigh = into.get(PRIORITY_HIGH);
+ final int oldDefault = into.get(PRIORITY_DEFAULT);
+ final int oldLow = into.get(PRIORITY_LOW);
+ final int oldMin = into.get(PRIORITY_MIN);
+
+ final int newMax = parser.getInt(String.valueOf(PRIORITY_MAX),
+ defaults.get(PRIORITY_MAX));
+ final int newHigh = parser.getInt(String.valueOf(PRIORITY_HIGH),
+ defaults.get(PRIORITY_HIGH));
+ final int newDefault = parser.getInt(String.valueOf(PRIORITY_DEFAULT),
+ defaults.get(PRIORITY_DEFAULT));
+ final int newLow = parser.getInt(String.valueOf(PRIORITY_LOW),
+ defaults.get(PRIORITY_LOW));
+ final int newMin = parser.getInt(String.valueOf(PRIORITY_MIN),
+ defaults.get(PRIORITY_MIN));
+
+ into.put(PRIORITY_MAX, newMax);
+ into.put(PRIORITY_HIGH, newHigh);
+ into.put(PRIORITY_DEFAULT, newDefault);
+ into.put(PRIORITY_LOW, newLow);
+ into.put(PRIORITY_MIN, newMin);
+
+ return oldMax != newMax
+ || oldHigh != newHigh
+ || oldDefault != newDefault
+ || oldLow != newLow
+ || oldMin != newMin;
+ }
+
+ /**
+ * Parses the input string, expecting it to a key-value string where the keys are job
+ * priorities, and replaces everything in {@code into} with the values from the string,
+ * or the default values if the string contains none.
+ *
+ * Returns true if any values changed.
+ */
+ private boolean parsePriorityToLongKeyValueString(@Nullable String s,
+ SparseLongArray into, SparseLongArray defaults) {
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(s);
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad string given", e);
+ // Clear the string and continue with the defaults.
+ parser.setString(null);
+ }
+
+ final long oldMax = into.get(PRIORITY_MAX);
+ final long oldHigh = into.get(PRIORITY_HIGH);
+ final long oldDefault = into.get(PRIORITY_DEFAULT);
+ final long oldLow = into.get(PRIORITY_LOW);
+ final long oldMin = into.get(PRIORITY_MIN);
+
+ final long newMax = parser.getLong(String.valueOf(PRIORITY_MAX),
+ defaults.get(PRIORITY_MAX));
+ final long newHigh = parser.getLong(String.valueOf(PRIORITY_HIGH),
+ defaults.get(PRIORITY_HIGH));
+ final long newDefault = parser.getLong(String.valueOf(PRIORITY_DEFAULT),
+ defaults.get(PRIORITY_DEFAULT));
+ final long newLow = parser.getLong(String.valueOf(PRIORITY_LOW),
+ defaults.get(PRIORITY_LOW));
+ final long newMin = parser.getLong(String.valueOf(PRIORITY_MIN),
+ defaults.get(PRIORITY_MIN));
+
+ into.put(PRIORITY_MAX, newMax);
+ into.put(PRIORITY_HIGH, newHigh);
+ into.put(PRIORITY_DEFAULT, newDefault);
+ into.put(PRIORITY_LOW, newLow);
+ into.put(PRIORITY_MIN, newMin);
+
+ return oldMax != newMax
+ || oldHigh != newHigh
+ || oldDefault != newDefault
+ || oldLow != newLow
+ || oldMin != newMin;
+ }
+
private void dump(IndentingPrintWriter pw) {
pw.println();
pw.print(FlexibilityController.class.getSimpleName());
@@ -1111,10 +1580,15 @@ public final class FlexibilityController extends StateController {
pw.println(")");
pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
+ pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINES, FALLBACK_FLEXIBILITY_DEADLINES).println();
+ pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+ FALLBACK_FLEXIBILITY_DEADLINE_SCORES).println();
+ pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+ FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS).println();
pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println();
- pw.print(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
- PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println();
+ pw.print(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS).println();
pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println();
pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println();
pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS)
@@ -1171,7 +1645,21 @@ public final class FlexibilityController extends StateController {
pw.println(mPowerAllowlistedApps);
pw.println();
- mFlexibilityTracker.dump(pw, predicate);
+ mFlexibilityTracker.dump(pw, predicate, nowElapsed);
+
+ pw.println();
+ pw.println("Job scores:");
+ pw.increaseIndent();
+ mJobScoreTrackers.forEach((uid, pkgName, jobScoreTracker) -> {
+ pw.print(uid);
+ pw.print("/");
+ pw.print(pkgName);
+ pw.print(": ");
+ jobScoreTracker.dump(pw, nowElapsed);
+ pw.println();
+ });
+ pw.decreaseIndent();
+
pw.println();
mFlexibilityAlarmQueue.dump(pw);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 284e491f8081..93a2eefba5b1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -31,13 +31,17 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
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.sElapsedRealtimeClock;
import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS;
-import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
-import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINES;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -76,6 +80,7 @@ import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.EmptyArray;
+import android.util.SparseArray;
import com.android.server.AppSchedulingModuleThread;
import com.android.server.DeviceIdleInternal;
@@ -96,6 +101,7 @@ import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.time.Clock;
+import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
@@ -182,7 +188,12 @@ public class FlexibilityControllerTest {
mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80");
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=50|60|70|80"
+ + ",400=50|60|70|80"
+ + ",300=50|60|70|80"
+ + ",200=50|60|70|80"
+ + ",100=50|60|70|80");
setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS);
waitForQuietModuleThread();
@@ -200,6 +211,11 @@ public class FlexibilityControllerTest {
}
}
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
+ sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
+ }
+
private void setDeviceConfigInt(String key, int val) {
mDeviceConfigPropertiesBuilder.setInt(key, val);
updateDeviceConfig(key);
@@ -250,9 +266,12 @@ public class FlexibilityControllerTest {
*/
@Test
public void testDefaultVariableValues() {
- assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
- mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
- );
+ SparseArray<int[]> defaultPercentsToDrop =
+ FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS),
+ defaultPercentsToDrop.valueAt(i).length);
+ }
}
@Test
@@ -379,10 +398,13 @@ public class FlexibilityControllerTest {
@Test
public void testOnConstantsUpdated_FallbackDeadline() {
JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
- assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
- setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L);
- assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.get(JobInfo.PRIORITY_DEFAULT),
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 123L);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=500,400=400,300=300,200=200,100=100");
+ assertEquals(300L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0L));
}
@Test
@@ -392,10 +414,32 @@ public class FlexibilityControllerTest {
JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=1|2|3|4"
+ + ",400=5|6|7|8"
+ + ",300=10|20|30|40"
+ + ",200=50|51|52|53"
+ + ",100=54|55|56|57");
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_MAX),
+ new int[]{1, 2, 3, 4});
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_HIGH),
+ new int[]{5, 6, 7, 8});
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_DEFAULT),
+ new int[]{10, 20, 30, 40});
assertArrayEquals(
- mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
- new int[] {10, 20, 30, 40});
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_LOW),
+ new int[]{50, 51, 52, 53});
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
+ .get(JobInfo.PRIORITY_MIN),
+ new int[]{54, 55, 56, 57});
assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
js.setNumDroppedFlexibleConstraints(1);
@@ -408,25 +452,64 @@ public class FlexibilityControllerTest {
@Test
public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
- JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
- JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
- js.enqueueTime = JobSchedulerService.sElapsedRealtimeClock.millis();
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40");
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40");
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
- setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40");
- assertEquals(js.enqueueTime + HOUR_IN_MILLIS / 2,
- mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ // No priority mapping
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+ final SparseArray<int[]> defaultPercentsToDrop =
+ FlexibilityController.FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ final SparseArray<int[]> percentsToDrop =
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+ }
+
+ // Invalid priority-percentList string
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=10,20a,030,40"
+ + ",400=20|40|60|80"
+ + ",300=25|50|75|80"
+ + ",200=40|50|60|80"
+ + ",100=20|40|60|80");
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+ }
+
+ // Invalid percentList strings
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=10|20a|030|40" // Letters
+ + ",400=10|40" // Not enough
+ + ",300=.|50|_|80" // Characters
+ + ",200=50|40|10|40" // Out of order
+ + ",100=30|60|90|101"); // Over 100
+ for (int i = 0; i < defaultPercentsToDrop.size(); ++i) {
+ assertArrayEquals(defaultPercentsToDrop.valueAt(i), percentsToDrop.valueAt(i));
+ }
+
+ // Only partially invalid
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=10|20a|030|40" // Letters
+ + ",400=10|40" // Not enough
+ + ",300=.|50|_|80" // Characters
+ + ",200=10|20|30|40" // Valid
+ + ",100=20|40|60|80"); // Valid
+ assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_MAX),
+ percentsToDrop.get(JobInfo.PRIORITY_MAX));
+ assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_HIGH),
+ percentsToDrop.get(JobInfo.PRIORITY_HIGH));
+ assertArrayEquals(defaultPercentsToDrop.get(JobInfo.PRIORITY_DEFAULT),
+ percentsToDrop.get(JobInfo.PRIORITY_DEFAULT));
+ assertArrayEquals(new int[]{10, 20, 30, 40}, percentsToDrop.get(JobInfo.PRIORITY_LOW));
+ assertArrayEquals(new int[]{20, 40, 60, 80}, percentsToDrop.get(JobInfo.PRIORITY_MIN));
}
@Test
public void testGetNextConstraintDropTimeElapsedLocked() {
setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + 25 * HOUR_IN_MILLIS
+ + ",300=" + 50 * HOUR_IN_MILLIS
+ + ",200=" + 100 * HOUR_IN_MILLIS
+ + ",100=" + 200 * HOUR_IN_MILLIS);
long nextTimeToDropNumConstraints;
@@ -459,34 +542,34 @@ public class FlexibilityControllerTest {
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2,
+ assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) / 2,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10,
+ assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 6 / 10,
nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10,
+ assertEquals(FROZEN_TIME + 800000L + (50 * HOUR_IN_MILLIS) * 7 / 10,
nextTimeToDropNumConstraints);
// no delay, no deadline
- jb = createJob(0);
+ jb = createJob(0).setPriority(JobInfo.PRIORITY_LOW);
js = createJobStatus("time", jb);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (100 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
// delay, deadline
jb = createJob(0)
@@ -514,13 +597,13 @@ public class FlexibilityControllerTest {
@Test
public void testCurPercent() {
long deadline = 100 * MINUTE_IN_MILLIS;
- long nowElapsed;
+ long nowElapsed = FROZEN_TIME;
JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
JobStatus js = createJobStatus("time", jb);
assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(deadline + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME));
nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -547,7 +630,8 @@ public class FlexibilityControllerTest {
assertEquals(FROZEN_TIME + delay,
mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(deadline + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, FROZEN_TIME + delay));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed,
+ FROZEN_TIME + delay));
nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
@@ -670,34 +754,63 @@ public class FlexibilityControllerTest {
@Test
public void testGetLifeCycleEndElapsedLocked_Prefetch() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
// prefetch no estimate
JobInfo.Builder jb = createJob(0).setPrefetch(true);
JobStatus js = createJobStatus("time", jb);
doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
- assertEquals(Long.MAX_VALUE, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ assertEquals(Long.MAX_VALUE,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
// prefetch with estimate
jb = createJob(0).setPrefetch(true);
js = createJobStatus("time", jb);
doReturn(1000L).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(js);
- assertEquals(1000L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ assertEquals(1000L,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
}
@Test
public void testGetLifeCycleEndElapsedLocked_NonPrefetch() {
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + 2 * HOUR_IN_MILLIS
+ + ",300=" + 3 * HOUR_IN_MILLIS
+ + ",200=" + 4 * HOUR_IN_MILLIS
+ + ",100=" + 5 * HOUR_IN_MILLIS);
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
// deadline
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
// no deadline
- jb = createJob(0);
- js = createJobStatus("time", jb);
- assertEquals(FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
+ assertEquals(FROZEN_TIME + 2 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
+ nowElapsed, 100L));
+ assertEquals(FROZEN_TIME + 3 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
+ nowElapsed, 100L));
+ assertEquals(FROZEN_TIME + 4 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
+ nowElapsed, 100L));
+ assertEquals(FROZEN_TIME + 5 * HOUR_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
+ nowElapsed, 100L));
}
@Test
public void testGetLifeCycleEndElapsedLocked_Rescheduled() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
@@ -705,20 +818,91 @@ public class FlexibilityControllerTest {
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+ }
+
+ @Test
+ public void testGetLifeCycleEndElapsedLocked_ScoreAddition() {
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + HOUR_IN_MILLIS
+ + ",300=" + HOUR_IN_MILLIS
+ + ",200=" + HOUR_IN_MILLIS
+ + ",100=" + HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
+ "500=5,400=4,300=3,200=2,100=1");
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
+ "500=" + 5 * MINUTE_IN_MILLIS
+ + ",400=" + 4 * MINUTE_IN_MILLIS
+ + ",300=" + 3 * MINUTE_IN_MILLIS
+ + ",200=" + 2 * MINUTE_IN_MILLIS
+ + ",100=" + 1 * MINUTE_IN_MILLIS);
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
+ JobStatus jsMax = createJobStatus("testScoreCalculation",
+ createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
+ JobStatus jsHigh = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+ // Make score = 15
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ mFlexibilityController.prepareForExecutionLocked(jsHigh);
+ mFlexibilityController.prepareForExecutionLocked(jsDefault);
+ mFlexibilityController.prepareForExecutionLocked(jsLow);
+ mFlexibilityController.prepareForExecutionLocked(jsMin);
+
+ // deadline
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
+ JobStatus js = createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", jb);
+ assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+
+ final long earliestMs = 123L;
+ // no deadline
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 4 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
+ nowElapsed, earliestMs));
+ assertEquals(earliestMs + HOUR_IN_MILLIS + 1 * 15 * MINUTE_IN_MILLIS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(
+ createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
+ nowElapsed, earliestMs));
}
@Test
@@ -734,6 +918,14 @@ public class FlexibilityControllerTest {
@Test
public void testFlexibilityTracker() {
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100 * HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + 100 * HOUR_IN_MILLIS
+ + ",400=" + 100 * HOUR_IN_MILLIS
+ + ",300=" + 100 * HOUR_IN_MILLIS
+ + ",200=" + 100 * HOUR_IN_MILLIS
+ + ",100=" + 100 * HOUR_IN_MILLIS);
+
FlexibilityController.FlexibilityTracker flexTracker =
mFlexibilityController.new FlexibilityTracker(4);
// Plus one for jobs with 0 required constraint.
@@ -805,8 +997,7 @@ public class FlexibilityControllerTest {
assertEquals(1, trackedJobs.get(3).size());
assertEquals(0, trackedJobs.get(4).size());
- final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
- + HOUR_IN_MILLIS);
+ final long nowElapsed = 51 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
@@ -1220,66 +1411,161 @@ public class FlexibilityControllerTest {
@Test
public void testCalculateNumDroppedConstraints() {
- JobInfo.Builder jb = createJob(22);
- JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb);
- long nowElapsed = FROZEN_TIME;
-
- mFlexibilityController.mFlexibilityTracker.add(js);
-
- assertEquals(3, js.getNumRequiredFlexibleConstraints());
- assertEquals(0, js.getNumDroppedFlexibleConstraints());
- assertEquals(1, mFlexibilityController
+ setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
+ "500=" + HOUR_IN_MILLIS
+ + ",400=" + 5 * HOUR_IN_MILLIS
+ + ",300=" + 8 * HOUR_IN_MILLIS
+ + ",200=" + 10 * HOUR_IN_MILLIS
+ + ",100=" + 20 * HOUR_IN_MILLIS);
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
+ "500=20|40|60|80"
+ + ",400=20|40|60|80"
+ + ",300=25|50|75|80"
+ + ",200=40|50|60|80"
+ + ",100=20|40|60|80");
+
+ JobStatus jsHigh = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(24).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(23).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(22).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testCalculateNumDroppedConstraints",
+ createJob(21).setPriority(JobInfo.PRIORITY_MIN));
+ final long startElapsed = FROZEN_TIME;
+ long nowElapsed = startElapsed;
+
+ mFlexibilityController.mFlexibilityTracker.add(jsHigh);
+ mFlexibilityController.mFlexibilityTracker.add(jsDefault);
+ mFlexibilityController.mFlexibilityTracker.add(jsLow);
+ mFlexibilityController.mFlexibilityTracker.add(jsMin);
+
+ assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(4, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
- nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 5;
+ nowElapsed = startElapsed + HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
mFlexibilityController.mFlexibilityTracker
- .setNumDroppedFlexibleConstraints(js, 1);
-
- assertEquals(2, js.getNumRequiredFlexibleConstraints());
- assertEquals(1, js.getNumDroppedFlexibleConstraints());
- assertEquals(1, mFlexibilityController
- .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
-
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(2, js.getNumRequiredFlexibleConstraints());
- assertEquals(1, js.getNumDroppedFlexibleConstraints());
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(2, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(3, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
- nowElapsed = FROZEN_TIME;
+ nowElapsed = startElapsed;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(3, js.getNumRequiredFlexibleConstraints());
- assertEquals(0, js.getNumDroppedFlexibleConstraints());
- assertEquals(1, mFlexibilityController
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(3, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(4, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
- nowElapsed += DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 9;
+ nowElapsed = startElapsed + 3 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(0, js.getNumRequiredFlexibleConstraints());
- assertEquals(3, js.getNumDroppedFlexibleConstraints());
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(2, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(3, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(0, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(2, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
- nowElapsed = FROZEN_TIME + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 10 * 6;
+ nowElapsed = startElapsed + 4 * HOUR_IN_MILLIS;
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
- mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
-
- assertEquals(1, js.getNumRequiredFlexibleConstraints());
- assertEquals(2, js.getNumDroppedFlexibleConstraints());
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsHigh, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsDefault, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsLow, nowElapsed);
+ mFlexibilityController.mFlexibilityTracker
+ .calculateNumDroppedConstraints(jsMin, nowElapsed);
+
+ assertEquals(0, jsHigh.getNumRequiredFlexibleConstraints());
+ assertEquals(3, jsHigh.getNumDroppedFlexibleConstraints());
+ assertEquals(1, jsDefault.getNumRequiredFlexibleConstraints());
+ assertEquals(2, jsDefault.getNumDroppedFlexibleConstraints());
+ assertEquals(2, jsLow.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsLow.getNumDroppedFlexibleConstraints());
+ assertEquals(2, jsMin.getNumRequiredFlexibleConstraints());
+ assertEquals(1, jsMin.getNumDroppedFlexibleConstraints());
+ assertEquals(0, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+ assertEquals(2, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(0).size());
}
@Test
@@ -1308,7 +1594,7 @@ public class FlexibilityControllerTest {
.get(js.getSourceUserId(), js.getSourcePackageName()));
assertEquals(150L, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
assertEquals(1150L,
- mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 150L));
assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js, FROZEN_TIME));
assertEquals(650L, mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js));
@@ -1317,6 +1603,80 @@ public class FlexibilityControllerTest {
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
}
+ @Test
+ public void testScoreCalculation() {
+ JobStatus jsMax = createJobStatus("testScoreCalculation",
+ createJob(0).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX));
+ JobStatus jsHigh = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
+ JobStatus jsDefault = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT));
+ JobStatus jsLow = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW));
+ JobStatus jsMin = createJobStatus("testScoreCalculation",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN));
+
+ long nowElapsed = sElapsedRealtimeClock.millis();
+ assertEquals(0,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ assertEquals(5,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+ nowElapsed += 30 * MINUTE_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ assertEquals(10,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(31 * MINUTE_IN_MILLIS);
+ nowElapsed += 31 * MINUTE_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsHigh);
+ assertEquals(14,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(2 * HOUR_IN_MILLIS);
+ nowElapsed += 2 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsDefault);
+ assertEquals(17,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(3 * HOUR_IN_MILLIS);
+ nowElapsed += 3 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsLow);
+ assertEquals(19,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(3 * HOUR_IN_MILLIS);
+ nowElapsed += 3 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsMin);
+ assertEquals(20,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ advanceElapsedClock(3 * HOUR_IN_MILLIS);
+ nowElapsed += 3 * HOUR_IN_MILLIS;
+ mFlexibilityController.prepareForExecutionLocked(jsMax);
+ assertEquals(25,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+ mFlexibilityController.unprepareFromExecutionLocked(jsMax);
+ assertEquals(20,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ // 24 hours haven't passed yet. The jobs in the first hour bucket should still be included.
+ advanceElapsedClock(12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1);
+ nowElapsed += 12 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS - 1;
+ assertEquals(20,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ // Passed the 24 hour mark. The jobs in the first hour bucket should no longer be included.
+ advanceElapsedClock(2);
+ nowElapsed += 2;
+ assertEquals(10,
+ mFlexibilityController.getScoreLocked(mSourceUid, SOURCE_PACKAGE, nowElapsed));
+
+ }
+
/**
* The beginning of a lifecycle for prefetch jobs includes the cached maximum of the last time
* the estimated launch time was updated and the last time the app was opened.