diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 930415fcd8fd0..b7a3f10831769 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -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. + *
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: *
* 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). * + *
* Expedited jobs may only set network, storage-not-low, and persistence constraints. * No other constraints are allowed. * + *
+ * 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 may 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. + * + *
* 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.
*
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 164781a250b7a..af9771553063d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -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 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);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 0aca2461b41c8..be91947b0445b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -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();
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index d249f2ae813c6..14484ff441ca8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -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();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index f2d10ac0f7d74..2196b16e0846a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -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;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 8099eda9a0af2..775276bd03f68 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -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,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index c4c9173536ad6..b72121f096ba0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -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());