Merge "Make job execution limits flexible." into sc-dev
This commit is contained in:
@@ -59,6 +59,12 @@ import java.util.Objects;
|
||||
* constraint on the JobInfo object that you are creating. Otherwise, the builder would throw an
|
||||
* exception when building. From Android version {@link Build.VERSION_CODES#Q} and onwards, it is
|
||||
* valid to schedule jobs with no constraints.
|
||||
* <p> In Android version {@link Build.VERSION_CODES#LOLLIPOP}, jobs had a maximum execution time
|
||||
* of one minute. Starting with Android version {@link Build.VERSION_CODES#M} and ending with
|
||||
* Android version {@link Build.VERSION_CODES#R}, jobs had a maximum execution time of 10 minutes.
|
||||
* Starting from Android version {@link Build.VERSION_CODES#S}, jobs will still be stopped after
|
||||
* 10 minutes if the system is busy or needs the resources, but if not, jobs may continue running
|
||||
* longer than 10 minutes.
|
||||
*/
|
||||
public class JobInfo implements Parcelable {
|
||||
private static String TAG = "JobInfo";
|
||||
@@ -1461,11 +1467,13 @@ public class JobInfo implements Parcelable {
|
||||
* possible with stronger guarantees than regular jobs. These "expedited" jobs will:
|
||||
* <ol>
|
||||
* <li>Run as soon as possible</li>
|
||||
* <li>Be exempted from Doze and battery saver restrictions</li>
|
||||
* <li>Be less restricted during Doze and battery saver</li>
|
||||
* <li>Have network access</li>
|
||||
* <li>Less likely to be killed than regular jobs</li>
|
||||
* <li>Be less likely to be killed than regular jobs</li>
|
||||
* <li>Be subject to background location throttling</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* Since these jobs have stronger guarantees than regular jobs, they will be subject to
|
||||
* stricter quotas. As long as an app has available expedited quota, jobs scheduled with
|
||||
* this set to true will run with these guarantees. If an app has run out of available
|
||||
@@ -1475,9 +1483,18 @@ public class JobInfo implements Parcelable {
|
||||
* will immediately return {@link JobScheduler#RESULT_FAILURE} if the app does not have
|
||||
* available quota (and the job will not be successfully scheduled).
|
||||
*
|
||||
* <p>
|
||||
* Expedited jobs may only set network, storage-not-low, and persistence constraints.
|
||||
* No other constraints are allowed.
|
||||
*
|
||||
* <p>
|
||||
* Assuming all constraints remain satisfied (including ideal system load conditions),
|
||||
* expedited jobs are guaranteed to have a minimum allowed runtime of 1 minute. If your
|
||||
* app has remaining expedited job quota, then the expedited job <i>may</i> potentially run
|
||||
* longer until remaining quota is used up. Just like with regular jobs, quota is not
|
||||
* consumed while the app is on top and visible to the user.
|
||||
*
|
||||
* <p>
|
||||
* Note: Even though expedited jobs are meant to run as soon as possible, they may be
|
||||
* deferred if the system is under heavy load or requested constraints are not satisfied.
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.UserSwitchObserver;
|
||||
@@ -80,6 +81,8 @@ class JobConcurrencyManager {
|
||||
static final int WORK_TYPE_BGUSER = 1 << 3;
|
||||
@VisibleForTesting
|
||||
static final int NUM_WORK_TYPES = 4;
|
||||
private static final int ALL_WORK_TYPES =
|
||||
WORK_TYPE_TOP | WORK_TYPE_EJ | WORK_TYPE_BG | WORK_TYPE_BGUSER;
|
||||
|
||||
@IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
|
||||
WORK_TYPE_NONE,
|
||||
@@ -92,6 +95,23 @@ class JobConcurrencyManager {
|
||||
public @interface WorkType {
|
||||
}
|
||||
|
||||
private static String workTypeToString(@WorkType int workType) {
|
||||
switch (workType) {
|
||||
case WORK_TYPE_NONE:
|
||||
return "NONE";
|
||||
case WORK_TYPE_TOP:
|
||||
return "TOP";
|
||||
case WORK_TYPE_EJ:
|
||||
return "EJ";
|
||||
case WORK_TYPE_BG:
|
||||
return "BG";
|
||||
case WORK_TYPE_BGUSER:
|
||||
return "BGUSER";
|
||||
default:
|
||||
return "WORK(" + workType + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private final Object mLock;
|
||||
private final JobSchedulerService mService;
|
||||
private final Context mContext;
|
||||
@@ -182,10 +202,16 @@ class JobConcurrencyManager {
|
||||
|
||||
int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
|
||||
|
||||
String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];
|
||||
|
||||
String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];
|
||||
|
||||
private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
|
||||
|
||||
private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
|
||||
|
||||
private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;
|
||||
|
||||
/** Wait for this long after screen off before adjusting the job concurrency. */
|
||||
private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
|
||||
|
||||
@@ -353,23 +379,22 @@ class JobConcurrencyManager {
|
||||
final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
|
||||
? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
|
||||
|
||||
WorkTypeConfig workTypeConfig;
|
||||
switch (mLastMemoryTrimLevel) {
|
||||
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
|
||||
workTypeConfig = workConfigs.moderate;
|
||||
mWorkTypeConfig = workConfigs.moderate;
|
||||
break;
|
||||
case ProcessStats.ADJ_MEM_FACTOR_LOW:
|
||||
workTypeConfig = workConfigs.low;
|
||||
mWorkTypeConfig = workConfigs.low;
|
||||
break;
|
||||
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
|
||||
workTypeConfig = workConfigs.critical;
|
||||
mWorkTypeConfig = workConfigs.critical;
|
||||
break;
|
||||
default:
|
||||
workTypeConfig = workConfigs.normal;
|
||||
mWorkTypeConfig = workConfigs.normal;
|
||||
break;
|
||||
}
|
||||
|
||||
mWorkCountTracker.setConfig(workTypeConfig);
|
||||
mWorkCountTracker.setConfig(mWorkTypeConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,13 +426,20 @@ class JobConcurrencyManager {
|
||||
boolean[] slotChanged = mRecycledSlotChanged;
|
||||
int[] preferredUidForContext = mRecycledPreferredUidForContext;
|
||||
int[] workTypeForContext = mRecycledWorkTypeForContext;
|
||||
String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
|
||||
String[] shouldStopJobReason = mRecycledShouldStopJobReason;
|
||||
|
||||
updateCounterConfigLocked();
|
||||
// Reset everything since we'll re-evaluate the current state.
|
||||
mWorkCountTracker.resetCounts();
|
||||
|
||||
// Update the priorities of jobs that aren't running, and also count the pending work types.
|
||||
// Do this before the following loop to hopefully reduce the cost of
|
||||
// shouldStopRunningJobLocked().
|
||||
updateNonRunningPriorities(pendingJobs, true);
|
||||
|
||||
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
|
||||
final JobServiceContext js = mService.mActiveServices.get(i);
|
||||
final JobServiceContext js = activeServices.get(i);
|
||||
final JobStatus status = js.getRunningJobLocked();
|
||||
|
||||
if ((contextIdToJobMap[i] = status) != null) {
|
||||
@@ -417,14 +449,13 @@ class JobConcurrencyManager {
|
||||
|
||||
slotChanged[i] = false;
|
||||
preferredUidForContext[i] = js.getPreferredUid();
|
||||
preemptReasonForContext[i] = null;
|
||||
shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
|
||||
}
|
||||
|
||||
// Next, update the job priorities, and also count the pending FG / BG jobs.
|
||||
updateNonRunningPriorities(pendingJobs, true);
|
||||
|
||||
mWorkCountTracker.onCountDone();
|
||||
|
||||
for (int i = 0; i < pendingJobs.size(); i++) {
|
||||
@@ -434,8 +465,6 @@ class JobConcurrencyManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them
|
||||
|
||||
// Find an available slot for nextPending. The context should be available OR
|
||||
// it should have lowest priority among all running jobs
|
||||
// (sharing the same Uid as nextPending)
|
||||
@@ -444,6 +473,9 @@ class JobConcurrencyManager {
|
||||
int allWorkTypes = getJobWorkTypes(nextPending);
|
||||
int workType = mWorkCountTracker.canJobStart(allWorkTypes);
|
||||
boolean startingJob = false;
|
||||
String preemptReason = null;
|
||||
// TODO(141645789): rewrite this to look at empty contexts first so we don't
|
||||
// unnecessarily preempt
|
||||
for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
|
||||
JobStatus job = contextIdToJobMap[j];
|
||||
int preferredUid = preferredUidForContext[j];
|
||||
@@ -464,6 +496,15 @@ class JobConcurrencyManager {
|
||||
continue;
|
||||
}
|
||||
if (job.getUid() != nextPending.getUid()) {
|
||||
// Maybe stop the job if it has had its day in the sun.
|
||||
final String reason = shouldStopJobReason[j];
|
||||
if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
|
||||
activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
|
||||
// Right now, the way the code is set up, we don't need to explicitly
|
||||
// assign the new job to this context since we'll reassign when the
|
||||
// preempted job finally stops.
|
||||
preemptReason = reason;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -477,6 +518,7 @@ class JobConcurrencyManager {
|
||||
// the lowest-priority running job
|
||||
minPriorityForPreemption = jobPriority;
|
||||
selectedContextId = j;
|
||||
preemptReason = "higher priority job found";
|
||||
// In this case, we're just going to preempt a low priority job, we're not
|
||||
// actually starting a job, so don't set startingJob.
|
||||
}
|
||||
@@ -484,6 +526,7 @@ class JobConcurrencyManager {
|
||||
if (selectedContextId != -1) {
|
||||
contextIdToJobMap[selectedContextId] = nextPending;
|
||||
slotChanged[selectedContextId] = true;
|
||||
preemptReasonForContext[selectedContextId] = preemptReason;
|
||||
}
|
||||
if (startingJob) {
|
||||
// Increase the counters when we're going to start a job.
|
||||
@@ -509,7 +552,7 @@ class JobConcurrencyManager {
|
||||
+ activeServices.get(i).getRunningJobLocked());
|
||||
}
|
||||
// preferredUid will be set to uid of currently running job.
|
||||
activeServices.get(i).preemptExecutingJobLocked();
|
||||
activeServices.get(i).preemptExecutingJobLocked(preemptReasonForContext[i]);
|
||||
preservePreferredUid = true;
|
||||
} else {
|
||||
final JobStatus pendingJob = contextIdToJobMap[i];
|
||||
@@ -692,6 +735,91 @@ class JobConcurrencyManager {
|
||||
noteConcurrency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code null} if the job can continue running and a non-null String if the job should
|
||||
* be stopped. The non-null String details the reason for stopping the job. A job will generally
|
||||
* be stopped if there similar job types waiting to be run and stopping this job would allow
|
||||
* another job to run, or if system state suggests the job should stop.
|
||||
*/
|
||||
@Nullable
|
||||
String shouldStopRunningJobLocked(@NonNull JobServiceContext context) {
|
||||
final JobStatus js = context.getRunningJobLocked();
|
||||
if (js == null) {
|
||||
// This can happen when we try to assign newly found pending jobs to contexts.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.isWithinExecutionGuaranteeTime()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update config in case memory usage has changed significantly.
|
||||
updateCounterConfigLocked();
|
||||
|
||||
@WorkType final int workType = context.getRunningJobWorkType();
|
||||
|
||||
// We're over the minimum guaranteed runtime. Stop the job if we're over config limits or
|
||||
// there are pending jobs that could replace this one.
|
||||
if (mRunningJobs.size() > mWorkTypeConfig.getMaxTotal()
|
||||
|| mWorkCountTracker.isOverTypeLimit(workType)) {
|
||||
return "too many jobs running";
|
||||
}
|
||||
|
||||
final List<JobStatus> pendingJobs = mService.mPendingJobs;
|
||||
final int numPending = pendingJobs.size();
|
||||
if (numPending == 0) {
|
||||
// All quiet. We can let this job run to completion.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Only expedited jobs can replace expedited jobs.
|
||||
if (js.shouldTreatAsExpeditedJob()) {
|
||||
// Keep fg/bg user distinction.
|
||||
if (workType == WORK_TYPE_BGUSER) {
|
||||
// For now, let any bg user job replace a bg user expedited job.
|
||||
// TODO: limit to ej once we have dedicated bg user ej slots.
|
||||
if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER) > 0) {
|
||||
return "blocking " + workTypeToString(workType) + " queue";
|
||||
}
|
||||
} else {
|
||||
if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
|
||||
return "blocking " + workTypeToString(workType) + " queue";
|
||||
}
|
||||
}
|
||||
|
||||
if (mPowerManager.isPowerSaveMode()) {
|
||||
return "battery saver";
|
||||
}
|
||||
if (mPowerManager.isDeviceIdleMode()) {
|
||||
return "deep doze";
|
||||
}
|
||||
}
|
||||
|
||||
// Easy check. If there are pending jobs of the same work type, then we know that
|
||||
// something will replace this.
|
||||
if (mWorkCountTracker.getPendingJobCount(workType) > 0) {
|
||||
return "blocking " + workTypeToString(workType) + " queue";
|
||||
}
|
||||
|
||||
// Harder check. We need to see if a different work type can replace this job.
|
||||
int remainingWorkTypes = ALL_WORK_TYPES;
|
||||
for (int i = 0; i < numPending; ++i) {
|
||||
final JobStatus pending = pendingJobs.get(i);
|
||||
final int workTypes = getJobWorkTypes(pending);
|
||||
if ((workTypes & remainingWorkTypes) > 0
|
||||
&& mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) {
|
||||
return "blocking other pending jobs";
|
||||
}
|
||||
|
||||
remainingWorkTypes = remainingWorkTypes & ~workTypes;
|
||||
if (remainingWorkTypes == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private String printPendingQueueLocked() {
|
||||
StringBuilder s = new StringBuilder("Pending queue: ");
|
||||
@@ -1362,10 +1490,40 @@ class JobConcurrencyManager {
|
||||
return WORK_TYPE_NONE;
|
||||
}
|
||||
|
||||
int canJobStart(int workTypes, @WorkType int replacingWorkType) {
|
||||
final boolean changedNums;
|
||||
int oldNumRunning = mNumRunningJobs.get(replacingWorkType);
|
||||
if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) {
|
||||
mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1);
|
||||
// Lazy implementation to avoid lots of processing. Best way would be to go
|
||||
// through the whole process of adjusting reservations, but the processing cost
|
||||
// is likely not worth it.
|
||||
mNumUnspecializedRemaining++;
|
||||
changedNums = true;
|
||||
} else {
|
||||
changedNums = false;
|
||||
}
|
||||
|
||||
final int ret = canJobStart(workTypes);
|
||||
if (changedNums) {
|
||||
mNumRunningJobs.put(replacingWorkType, oldNumRunning);
|
||||
mNumUnspecializedRemaining--;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int getPendingJobCount(@WorkType final int workType) {
|
||||
return mNumPendingJobs.get(workType, 0);
|
||||
}
|
||||
|
||||
int getRunningJobCount(@WorkType final int workType) {
|
||||
return mNumRunningJobs.get(workType, 0);
|
||||
}
|
||||
|
||||
boolean isOverTypeLimit(@WorkType final int workType) {
|
||||
return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
|
||||
@@ -339,6 +339,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
public void onPropertiesChanged(DeviceConfig.Properties properties) {
|
||||
boolean apiQuotaScheduleUpdated = false;
|
||||
boolean concurrencyUpdated = false;
|
||||
boolean runtimeUpdated = false;
|
||||
for (int controller = 0; controller < mControllers.size(); controller++) {
|
||||
final StateController sc = mControllers.get(controller);
|
||||
sc.prepareForUpdatedConstantsLocked();
|
||||
@@ -377,6 +378,14 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
|
||||
mConstants.updateConnectivityConstantsLocked();
|
||||
break;
|
||||
case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS:
|
||||
case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
|
||||
case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
|
||||
if (!runtimeUpdated) {
|
||||
mConstants.updateRuntimeConstantsLocked();
|
||||
runtimeUpdated = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
|
||||
&& !concurrencyUpdated) {
|
||||
@@ -432,6 +441,11 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
|
||||
"aq_schedule_return_failure";
|
||||
|
||||
private static final String KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS =
|
||||
"runtime_free_quota_max_limit_ms";
|
||||
private static final String KEY_RUNTIME_MIN_GUARANTEE_MS = "runtime_min_guarantee_ms";
|
||||
private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
|
||||
|
||||
private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
|
||||
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
|
||||
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
|
||||
@@ -445,6 +459,12 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
|
||||
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
|
||||
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
|
||||
@VisibleForTesting
|
||||
public static final long DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 30 * MINUTE_IN_MILLIS;
|
||||
@VisibleForTesting
|
||||
public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
|
||||
@VisibleForTesting
|
||||
public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
|
||||
|
||||
/**
|
||||
* Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
|
||||
@@ -509,6 +529,19 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
|
||||
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;
|
||||
|
||||
/** The maximum amount of time we will let a job run for when quota is "free". */
|
||||
public long RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
|
||||
|
||||
/**
|
||||
* The minimum amount of time we try to guarantee regular jobs will run for.
|
||||
*/
|
||||
public long RUNTIME_MIN_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
|
||||
|
||||
/**
|
||||
* The minimum amount of time we try to guarantee EJs will run for.
|
||||
*/
|
||||
public long RUNTIME_MIN_EJ_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS;
|
||||
|
||||
private void updateBatchingConstantsLocked() {
|
||||
MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
|
||||
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
@@ -568,6 +601,25 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
|
||||
}
|
||||
|
||||
private void updateRuntimeConstantsLocked() {
|
||||
DeviceConfig.Properties properties = DeviceConfig.getProperties(
|
||||
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
|
||||
KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS);
|
||||
|
||||
// Make sure min runtime for regular jobs is at least 10 minutes.
|
||||
RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
|
||||
properties.getLong(
|
||||
KEY_RUNTIME_MIN_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS));
|
||||
// Make sure min runtime for expedited jobs is at least one minute.
|
||||
RUNTIME_MIN_EJ_GUARANTEE_MS = Math.max(MINUTE_IN_MILLIS,
|
||||
properties.getLong(
|
||||
KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS));
|
||||
RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
|
||||
properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
|
||||
DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
|
||||
}
|
||||
|
||||
void dump(IndentingPrintWriter pw) {
|
||||
pw.println("Settings:");
|
||||
pw.increaseIndent();
|
||||
@@ -591,6 +643,11 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
|
||||
API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println();
|
||||
|
||||
pw.print(KEY_RUNTIME_MIN_GUARANTEE_MS, RUNTIME_MIN_GUARANTEE_MS).println();
|
||||
pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
|
||||
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
|
||||
.println();
|
||||
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
@@ -1602,7 +1659,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* time of the job to be the time of completion (i.e. the time at which this function is
|
||||
* called).
|
||||
* <p>This could be inaccurate b/c the job can run for as long as
|
||||
* {@link com.android.server.job.JobServiceContext#DEFAULT_EXECUTING_TIMESLICE_MILLIS}, but
|
||||
* {@link Constants#DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS}, but
|
||||
* will lead to underscheduling at least, rather than if we had taken the last execution time
|
||||
* to be the start of the execution.
|
||||
*
|
||||
@@ -2213,11 +2270,24 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return isComponentUsable(job);
|
||||
}
|
||||
|
||||
/** Returns the minimum amount of time we should let this job run before timing out. */
|
||||
public long getMinJobExecutionGuaranteeMs(JobStatus job) {
|
||||
synchronized (mLock) {
|
||||
if (job.shouldTreatAsExpeditedJob()) {
|
||||
// Don't guarantee RESTRICTED jobs more than 5 minutes.
|
||||
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
|
||||
? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
|
||||
: Math.min(mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, 5 * MINUTE_IN_MILLIS);
|
||||
} else {
|
||||
return mConstants.RUNTIME_MIN_GUARANTEE_MS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the maximum amount of time this job could run for. */
|
||||
public long getMaxJobExecutionTimeMs(JobStatus job) {
|
||||
synchronized (mLock) {
|
||||
return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
|
||||
JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS);
|
||||
return mQuotaController.getMaxJobExecutionTimeMsLocked(job);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
package com.android.server.job;
|
||||
|
||||
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
|
||||
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
|
||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.job.IJobCallback;
|
||||
import android.app.job.IJobService;
|
||||
@@ -76,14 +76,6 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
|
||||
|
||||
private static final String TAG = "JobServiceContext";
|
||||
/** Amount of time a job is allowed to execute for before being considered timed-out. */
|
||||
public static final long DEFAULT_EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
|
||||
/**
|
||||
* Amount of time a RESTRICTED expedited job is allowed to execute for before being considered
|
||||
* timed-out.
|
||||
*/
|
||||
public static final long DEFAULT_RESTRICTED_EXPEDITED_JOB_EXECUTING_TIMESLICE_MILLIS =
|
||||
DEFAULT_EXECUTING_TIMESLICE_MILLIS / 2;
|
||||
/** Amount of time the JobScheduler waits for the initial service launch+bind. */
|
||||
private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
|
||||
/** Amount of time the JobScheduler will wait for a response from an app for a message. */
|
||||
@@ -110,6 +102,7 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
/** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
|
||||
private final JobCompletedListener mCompletedListener;
|
||||
private final JobConcurrencyManager mJobConcurrencyManager;
|
||||
private final JobSchedulerService mService;
|
||||
/** Used for service binding, etc. */
|
||||
private final Context mContext;
|
||||
private final Object mLock;
|
||||
@@ -149,6 +142,13 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
private long mExecutionStartTimeElapsed;
|
||||
/** Track when job will timeout. */
|
||||
private long mTimeoutElapsed;
|
||||
/**
|
||||
* The minimum amount of time the context will allow the job to run before checking whether to
|
||||
* stop it or not.
|
||||
*/
|
||||
private long mMinExecutionGuaranteeMillis;
|
||||
/** The absolute maximum amount of time the job can run */
|
||||
private long mMaxExecutionTimeMillis;
|
||||
|
||||
// Debugging: reason this job was last stopped.
|
||||
public String mStoppedReason;
|
||||
@@ -190,6 +190,7 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) {
|
||||
mContext = service.getContext();
|
||||
mLock = service.getLock();
|
||||
mService = service;
|
||||
mBatteryStats = batteryStats;
|
||||
mJobPackageTracker = tracker;
|
||||
mCallbackHandler = new JobServiceHandler(looper);
|
||||
@@ -239,6 +240,9 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
|
||||
triggeredUris, triggeredAuthorities, job.network);
|
||||
mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
|
||||
mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job);
|
||||
mMaxExecutionTimeMillis =
|
||||
Math.max(mService.getMaxJobExecutionTimeMs(job), mMinExecutionGuaranteeMillis);
|
||||
|
||||
final long whenDeferred = job.getWhenStandbyDeferred();
|
||||
if (whenDeferred > 0) {
|
||||
@@ -352,8 +356,8 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void preemptExecutingJobLocked() {
|
||||
doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption");
|
||||
void preemptExecutingJobLocked(@NonNull String reason) {
|
||||
doCancelLocked(JobParameters.REASON_PREEMPT, reason);
|
||||
}
|
||||
|
||||
int getPreferredUid() {
|
||||
@@ -372,6 +376,11 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
return mTimeoutElapsed;
|
||||
}
|
||||
|
||||
boolean isWithinExecutionGuaranteeTime() {
|
||||
return mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis
|
||||
< sElapsedRealtimeClock.millis();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
|
||||
String reason) {
|
||||
@@ -607,7 +616,7 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void doCancelLocked(int arg1, String debugReason) {
|
||||
private void doCancelLocked(int stopReasonCode, String debugReason) {
|
||||
if (mVerb == VERB_FINISHED) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG,
|
||||
@@ -615,8 +624,8 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
}
|
||||
return;
|
||||
}
|
||||
mParams.setStopReason(arg1, debugReason);
|
||||
if (arg1 == JobParameters.REASON_PREEMPT) {
|
||||
mParams.setStopReason(stopReasonCode, debugReason);
|
||||
if (stopReasonCode == JobParameters.REASON_PREEMPT) {
|
||||
mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
|
||||
NO_PREFERRED_UID;
|
||||
}
|
||||
@@ -767,11 +776,30 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
|
||||
break;
|
||||
case VERB_EXECUTING:
|
||||
// Not an error - client ran out of time.
|
||||
Slog.i(TAG, "Client timed out while executing (no jobFinished received), " +
|
||||
"sending onStop: " + getRunningJobNameLocked());
|
||||
mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
|
||||
sendStopMessageLocked("timeout while executing");
|
||||
final long latestStopTimeElapsed =
|
||||
mExecutionStartTimeElapsed + mMaxExecutionTimeMillis;
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
if (nowElapsed >= latestStopTimeElapsed) {
|
||||
// Not an error - client ran out of time.
|
||||
Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
|
||||
+ " Sending onStop: " + getRunningJobNameLocked());
|
||||
mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
|
||||
sendStopMessageLocked("timeout while executing");
|
||||
} else {
|
||||
// We've given the app the minimum execution time. See if we should stop it or
|
||||
// let it continue running
|
||||
final String reason = mJobConcurrencyManager.shouldStopRunningJobLocked(this);
|
||||
if (reason != null) {
|
||||
Slog.i(TAG, "Stopping client after min execution time: "
|
||||
+ getRunningJobNameLocked() + " because " + reason);
|
||||
mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
|
||||
sendStopMessageLocked(reason);
|
||||
} else {
|
||||
Slog.i(TAG, "Letting " + getRunningJobNameLocked()
|
||||
+ " continue to run past min execution time");
|
||||
scheduleOpTimeOutLocked();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Handling timeout for an invalid job state: "
|
||||
@@ -878,10 +906,16 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
final long timeoutMillis;
|
||||
switch (mVerb) {
|
||||
case VERB_EXECUTING:
|
||||
timeoutMillis = mRunningJob.shouldTreatAsExpeditedJob()
|
||||
&& mRunningJob.getStandbyBucket() == RESTRICTED_INDEX
|
||||
? DEFAULT_RESTRICTED_EXPEDITED_JOB_EXECUTING_TIMESLICE_MILLIS
|
||||
: DEFAULT_EXECUTING_TIMESLICE_MILLIS;
|
||||
final long earliestStopTimeElapsed =
|
||||
mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
|
||||
final long latestStopTimeElapsed =
|
||||
mExecutionStartTimeElapsed + mMaxExecutionTimeMillis;
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
if (nowElapsed < earliestStopTimeElapsed) {
|
||||
timeoutMillis = earliestStopTimeElapsed - nowElapsed;
|
||||
} else {
|
||||
timeoutMillis = latestStopTimeElapsed - nowElapsed;
|
||||
}
|
||||
break;
|
||||
|
||||
case VERB_BINDING:
|
||||
@@ -925,6 +959,13 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
pw.print(", timeout at: ");
|
||||
TimeUtils.formatDuration(mTimeoutElapsed - nowElapsed, pw);
|
||||
pw.println();
|
||||
pw.print("Remaining execution limits: [");
|
||||
TimeUtils.formatDuration(
|
||||
(mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis) - nowElapsed, pw);
|
||||
pw.print(", ");
|
||||
TimeUtils.formatDuration(
|
||||
(mExecutionStartTimeElapsed + mMaxExecutionTimeMillis) - nowElapsed, pw);
|
||||
pw.println("]");
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +325,8 @@ public final class ConnectivityController extends RestrictingController implemen
|
||||
*/
|
||||
private boolean isInsane(JobStatus jobStatus, Network network,
|
||||
NetworkCapabilities capabilities, Constants constants) {
|
||||
// Use the maximum possible time since it gives us an upper bound, even though the job
|
||||
// could end up stopping earlier.
|
||||
final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus);
|
||||
|
||||
final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
|
||||
|
||||
@@ -72,7 +72,6 @@ import com.android.server.LocalServices;
|
||||
import com.android.server.PowerAllowlistInternal;
|
||||
import com.android.server.job.ConstantsProto;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.JobServiceContext;
|
||||
import com.android.server.job.StateControllerProto;
|
||||
import com.android.server.usage.AppStandbyInternal;
|
||||
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
|
||||
@@ -770,18 +769,38 @@ public final class QuotaController extends StateController {
|
||||
|
||||
/** Returns the maximum amount of time this job could run for. */
|
||||
public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
|
||||
// If quota is currently "free", then the job can run for the full amount of time.
|
||||
if (mChargeTracker.isCharging()
|
||||
|| isTopStartedJobLocked(jobStatus)
|
||||
|| isUidInForeground(jobStatus.getSourceUid())) {
|
||||
return JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS;
|
||||
// Need to look at current proc state as well in the case where the job hasn't started yet.
|
||||
final boolean isTop = mActivityManagerInternal
|
||||
.getUidProcessState(jobStatus.getSourceUid()) <= ActivityManager.PROCESS_STATE_TOP;
|
||||
|
||||
if (!jobStatus.shouldTreatAsExpeditedJob()) {
|
||||
// If quota is currently "free", then the job can run for the full amount of time.
|
||||
if (mChargeTracker.isCharging()
|
||||
|| isTop
|
||||
|| isTopStartedJobLocked(jobStatus)
|
||||
|| isUidInForeground(jobStatus.getSourceUid())) {
|
||||
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
|
||||
}
|
||||
return getTimeUntilQuotaConsumedLocked(
|
||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
|
||||
}
|
||||
if (jobStatus.shouldTreatAsExpeditedJob()) {
|
||||
return jobStatus.getStandbyBucket() == RESTRICTED_INDEX
|
||||
? JobServiceContext.DEFAULT_RESTRICTED_EXPEDITED_JOB_EXECUTING_TIMESLICE_MILLIS
|
||||
: JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS;
|
||||
|
||||
// Expedited job.
|
||||
if (mChargeTracker.isCharging()) {
|
||||
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
|
||||
}
|
||||
return getRemainingExecutionTimeLocked(jobStatus);
|
||||
if (isTop || isTopStartedJobLocked(jobStatus)) {
|
||||
return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2,
|
||||
getTimeUntilEJQuotaConsumedLocked(
|
||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()));
|
||||
}
|
||||
if (isUidInForeground(jobStatus.getSourceUid())) {
|
||||
return Math.max(mEJLimitsMs[WORKING_INDEX] / 2,
|
||||
getTimeUntilEJQuotaConsumedLocked(
|
||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()));
|
||||
}
|
||||
return getTimeUntilEJQuotaConsumedLocked(
|
||||
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
|
||||
}
|
||||
|
||||
/** @return true if the job is within expedited job quota. */
|
||||
@@ -3577,8 +3596,8 @@ public final class QuotaController extends StateController {
|
||||
mEJLimitsMs[RARE_INDEX] = newRareLimitMs;
|
||||
mShouldReevaluateConstraints = true;
|
||||
}
|
||||
// The limit must be in the range [0 minutes, rare limit].
|
||||
long newRestrictedLimitMs = Math.max(0,
|
||||
// The limit must be in the range [5 minutes, rare limit].
|
||||
long newRestrictedLimitMs = Math.max(5 * MINUTE_IN_MILLIS,
|
||||
Math.min(newRareLimitMs, EJ_LIMIT_RESTRICTED_MS));
|
||||
if (mEJLimitsMs[RESTRICTED_INDEX] != newRestrictedLimitMs) {
|
||||
mEJLimitsMs[RESTRICTED_INDEX] = newRestrictedLimitMs;
|
||||
|
||||
@@ -63,7 +63,6 @@ import android.util.DataUnit;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.JobSchedulerService.Constants;
|
||||
import com.android.server.job.JobServiceContext;
|
||||
import com.android.server.net.NetworkPolicyManagerInternal;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -144,8 +143,7 @@ public class ConnectivityControllerTest {
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||
|
||||
final ConnectivityController controller = new ConnectivityController(mService);
|
||||
when(mService.getMaxJobExecutionTimeMs(any()))
|
||||
.thenReturn(JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS);
|
||||
when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L);
|
||||
|
||||
// Slow network is too slow
|
||||
assertFalse(controller.isSatisfied(createJobStatus(job), net,
|
||||
|
||||
@@ -82,8 +82,6 @@ import com.android.internal.util.ArrayUtils;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.PowerAllowlistInternal;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.JobSchedulerService.Constants;
|
||||
import com.android.server.job.JobServiceContext;
|
||||
import com.android.server.job.JobStore;
|
||||
import com.android.server.job.controllers.QuotaController.ExecutionStats;
|
||||
import com.android.server.job.controllers.QuotaController.QcConstants;
|
||||
@@ -123,6 +121,7 @@ public class QuotaControllerTest {
|
||||
private BroadcastReceiver mChargingReceiver;
|
||||
private QuotaController mQuotaController;
|
||||
private QuotaController.QcConstants mQcConstants;
|
||||
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
|
||||
private int mSourceUid;
|
||||
private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
|
||||
private IUidObserver mUidObserver;
|
||||
@@ -158,7 +157,7 @@ public class QuotaControllerTest {
|
||||
// Called in StateController constructor.
|
||||
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
|
||||
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
|
||||
when(mJobSchedulerService.getConstants()).thenReturn(mock(Constants.class));
|
||||
when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
|
||||
// Called in QuotaController constructor.
|
||||
IActivityManager activityManager = ActivityManager.getService();
|
||||
spyOn(activityManager);
|
||||
@@ -1282,23 +1281,23 @@ public class QuotaControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMaxJobExecutionTimeLocked() {
|
||||
public void testGetMaxJobExecutionTimeLocked_Regular() {
|
||||
mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
|
||||
createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
|
||||
3 * MINUTE_IN_MILLIS, 5), false);
|
||||
JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
|
||||
job.setStandbyBucket(RARE_INDEX);
|
||||
setStandbyBucket(RARE_INDEX, job);
|
||||
|
||||
setCharging();
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS,
|
||||
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
|
||||
}
|
||||
|
||||
setDischarging();
|
||||
setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS,
|
||||
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
|
||||
}
|
||||
|
||||
@@ -1310,7 +1309,7 @@ public class QuotaControllerTest {
|
||||
}
|
||||
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS,
|
||||
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
|
||||
mQuotaController.maybeStopTrackingJobLocked(job, null, false);
|
||||
}
|
||||
@@ -1322,6 +1321,81 @@ public class QuotaControllerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMaxJobExecutionTimeLocked_EJ() {
|
||||
final long timeUsedMs = 3 * MINUTE_IN_MILLIS;
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
|
||||
timeUsedMs, 5), true);
|
||||
JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0);
|
||||
setStandbyBucket(RARE_INDEX, job);
|
||||
mQuotaController.maybeStartTrackingJobLocked(job, null);
|
||||
|
||||
setCharging();
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
|
||||
}
|
||||
|
||||
setDischarging();
|
||||
setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
|
||||
}
|
||||
|
||||
// Top-started job
|
||||
setProcessState(ActivityManager.PROCESS_STATE_TOP);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.prepareForExecutionLocked(job);
|
||||
}
|
||||
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
|
||||
mQuotaController.maybeStopTrackingJobLocked(job, null, false);
|
||||
}
|
||||
|
||||
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
|
||||
}
|
||||
|
||||
// Test used quota rolling out of window.
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
|
||||
}
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
|
||||
timeUsedMs, 5), true);
|
||||
|
||||
setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
|
||||
}
|
||||
|
||||
// Top-started job
|
||||
setProcessState(ActivityManager.PROCESS_STATE_TOP);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStartTrackingJobLocked(job, null);
|
||||
mQuotaController.prepareForExecutionLocked(job);
|
||||
}
|
||||
setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
|
||||
mQuotaController.maybeStopTrackingJobLocked(job, null, false);
|
||||
}
|
||||
|
||||
setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
|
||||
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
|
||||
* window.
|
||||
@@ -2508,7 +2582,7 @@ public class QuotaControllerTest {
|
||||
assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
|
||||
assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
|
||||
assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
|
||||
assertEquals(0, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
|
||||
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
|
||||
assertEquals(0, mQuotaController.getEjLimitSpecialAdditionMs());
|
||||
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
|
||||
assertEquals(1, mQuotaController.getEJTopAppTimeChunkSizeMs());
|
||||
|
||||
Reference in New Issue
Block a user