summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java13
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java19
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java43
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java2
9 files changed, 65 insertions, 32 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
index 34ba753b3daa..862d8b7cac50 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobCompletedListener.java
@@ -25,7 +25,9 @@ import com.android.server.job.controllers.JobStatus;
public interface JobCompletedListener {
/**
* Callback for when a job is completed.
+ *
+ * @param stopReason The stop reason provided to JobParameters.
* @param needsReschedule Whether the implementing class should reschedule this job.
*/
- void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule);
+ void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 8bb03e911528..515cb747a99e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1745,11 +1745,12 @@ public class JobSchedulerService extends com.android.server.SystemService
* A job just finished executing. We fetch the
* {@link com.android.server.job.controllers.JobStatus} from the store and depending on
* whether we want to reschedule we re-add it to the controllers.
- * @param jobStatus Completed job.
+ *
+ * @param jobStatus Completed job.
* @param needsReschedule Whether the implementing class should reschedule this job.
*/
@Override
- public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) {
+ public void onJobCompletedLocked(JobStatus jobStatus, int stopReason, boolean needsReschedule) {
if (DEBUG) {
Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
}
@@ -1767,6 +1768,11 @@ public class JobSchedulerService extends com.android.server.SystemService
// we stop it.
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus) : null;
+ if (rescheduledJob != null
+ && (stopReason == JobParameters.REASON_TIMEOUT
+ || stopReason == JobParameters.REASON_PREEMPT)) {
+ rescheduledJob.disallowRunInBatterySaverAndDoze();
+ }
// Do not write back immediately if this is a periodic job. The job may get lost if system
// shuts down before it is added back.
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 2a23d60d8af6..c40772fdc0bc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -379,8 +379,8 @@ public final class JobServiceContext implements ServiceConnection {
}
boolean isWithinExecutionGuaranteeTime() {
- return mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis
- < sElapsedRealtimeClock.millis();
+ return sElapsedRealtimeClock.millis()
+ < mExecutionStartTimeElapsed + mMinExecutionGuaranteeMillis;
}
@GuardedBy("mLock")
@@ -848,11 +848,12 @@ public final class JobServiceContext implements ServiceConnection {
}
applyStoppedReasonLocked(reason);
completedJob = mRunningJob;
- mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
+ final int stopReason = mParams.getStopReason();
+ mJobPackageTracker.noteInactive(completedJob, stopReason, reason);
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.getSourceUid(), null, completedJob.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
- mParams.getStopReason(), completedJob.getStandbyBucket(), completedJob.getJobId(),
+ stopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
completedJob.hasChargingConstraint(),
completedJob.hasBatteryNotLowConstraint(),
completedJob.hasStorageNotLowConstraint(),
@@ -863,7 +864,7 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.hasContentTriggerConstraint());
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
- mParams.getStopReason());
+ stopReason);
} catch (RemoteException e) {
// Whatever.
}
@@ -882,7 +883,7 @@ public final class JobServiceContext implements ServiceConnection {
service = null;
mAvailable = true;
removeOpTimeOutLocked();
- mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
+ mCompletedListener.onJobCompletedLocked(completedJob, stopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index eaf8f4d96331..aa8d98c01853 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -954,7 +954,7 @@ public final class JobStore {
appBucket, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
- (rtcIsGood) ? null : rtcRuntimes, internalFlags);
+ (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
return js;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 192f5e66255d..79ef321eaf07 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -64,7 +64,7 @@ public final class DeviceIdleJobsController extends StateController {
* when the app is temp whitelisted or in the foreground.
*/
private final ArraySet<JobStatus> mAllowInIdleJobs;
- private final SparseBooleanArray mForegroundUids;
+ private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
private final DeviceIdleJobsDelayHandler mHandler;
private final PowerManager mPowerManager;
@@ -77,7 +77,6 @@ public final class DeviceIdleJobsController extends StateController {
private int[] mDeviceIdleWhitelistAppIds;
private int[] mPowerSaveTempWhitelistAppIds;
- // onReceive
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -120,6 +119,10 @@ public final class DeviceIdleJobsController extends StateController {
}
};
+ /** Criteria for whether or not we should a job's rush evaluation when the device exits Doze. */
+ private final Predicate<JobStatus> mShouldRushEvaluation = (jobStatus) ->
+ jobStatus.isRequestedExpeditedJob() || mForegroundUids.get(jobStatus.getSourceUid());
+
public DeviceIdleJobsController(JobSchedulerService service) {
super(service);
@@ -133,7 +136,6 @@ public final class DeviceIdleJobsController extends StateController {
mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
mAllowInIdleJobs = new ArraySet<>();
- mForegroundUids = new SparseBooleanArray();
final IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -156,14 +158,9 @@ public final class DeviceIdleJobsController extends StateController {
mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
mService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
} else {
- // When coming out of doze, process all foreground uids immediately, while others
- // will be processed after a delay of 3 seconds.
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) {
- mService.getJobStore().forEachJobForSourceUid(
- mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
- }
- }
+ // When coming out of doze, process all foreground uids and EJs immediately,
+ // while others will be processed after a delay of 3 seconds.
+ mService.getJobStore().forEachJob(mShouldRushEvaluation, mDeviceIdleUpdateFunctor);
mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 5bdeb38a1424..bad8dc1ad1cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -91,6 +91,12 @@ public final class JobStatus {
static final int CONSTRAINT_WITHIN_EXPEDITED_QUOTA = 1 << 23; // Implicit constraint
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
+ // The following set of dynamic constraints are for specific use cases (as explained in their
+ // relative naming and comments). Right now, they apply different constraints, which is fine,
+ // but if in the future, we have overlapping dynamic constraint sets, removing one constraint
+ // set may accidentally remove a constraint applied by another dynamic set.
+ // TODO: properly handle overlapping dynamic constraint sets
+
/**
* The additional set of dynamic constraints that must be met if the job's effective bucket is
* {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't
@@ -103,6 +109,13 @@ public final class JobStatus {
| CONSTRAINT_IDLE;
/**
+ * The additional set of dynamic constraints that must be met if this is an expedited job that
+ * had a long enough run while the device was Dozing or in battery saver.
+ */
+ private static final int DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS =
+ CONSTRAINT_DEVICE_NOT_DOZING | CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
+
+ /**
* Standard media URIs that contain the media files that might be important to the user.
* @see #mHasMediaBackupExemption
*/
@@ -426,7 +439,8 @@ public final class JobStatus {
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, int standbyBucket, String tag, int numFailures,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
- long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
+ long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
+ int dynamicConstraints) {
this.job = job;
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
@@ -487,6 +501,7 @@ public final class JobStatus {
}
this.requiredConstraints = requiredConstraints;
mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
+ addDynamicConstraints(dynamicConstraints);
mReadyNotDozing = canRunInDoze();
if (standbyBucket == RESTRICTED_INDEX) {
addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
@@ -521,7 +536,7 @@ public final class JobStatus {
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
- jobStatus.getInternalFlags());
+ jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints);
mPersistedUtcTimes = jobStatus.mPersistedUtcTimes;
if (jobStatus.mPersistedUtcTimes != null) {
if (DEBUG) {
@@ -543,12 +558,12 @@ public final class JobStatus {
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime,
Pair<Long, Long> persistedExecutionTimesUTC,
- int innerFlags) {
+ int innerFlags, int dynamicConstraints) {
this(job, callingUid, sourcePkgName, sourceUserId,
standbyBucket,
sourceTag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
+ lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
// Only during initial inflation do we record the UTC-timebase execution bounds
// read from the persistent store. If we ever have to recreate the JobStatus on
@@ -572,7 +587,8 @@ public final class JobStatus {
rescheduling.getStandbyBucket(),
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
- lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
+ lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(),
+ rescheduling.mDynamicConstraints);
}
/**
@@ -609,7 +625,7 @@ public final class JobStatus {
standbyBucket, tag, 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
- /*innerFlags=*/ 0);
+ /*innerFlags=*/ 0, /* dynamicConstraints */ 0);
}
public void enqueueWorkLocked(JobWorkItem work) {
@@ -1083,12 +1099,15 @@ public final class JobStatus {
* in Doze.
*/
public boolean canRunInDoze() {
- return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0 || shouldTreatAsExpeditedJob();
+ return (getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0
+ || (shouldTreatAsExpeditedJob()
+ && (mDynamicConstraints & CONSTRAINT_DEVICE_NOT_DOZING) == 0);
}
boolean canRunInBatterySaver() {
return (getInternalFlags() & INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0
- || shouldTreatAsExpeditedJob();
+ || (shouldTreatAsExpeditedJob()
+ && (mDynamicConstraints & CONSTRAINT_BACKGROUND_NOT_RESTRICTED) == 0);
}
boolean shouldIgnoreNetworkBlocking() {
@@ -1245,6 +1264,14 @@ public final class JobStatus {
}
/**
+ * Add additional constraints to prevent this job from running when doze or battery saver are
+ * active.
+ */
+ public void disallowRunInBatterySaverAndDoze() {
+ addDynamicConstraints(DYNAMIC_EXPEDITED_DEFERRAL_CONSTRAINTS);
+ }
+
+ /**
* Indicates that this job cannot run without the specified constraints. This is evaluated
* separately from the job's explicitly requested constraints and MUST be satisfied before
* the job can run if the app doesn't have quota.
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 f7f592886473..3870b02ba37c 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
@@ -641,6 +641,6 @@ public class ConnectivityControllerTest {
private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
return new JobStatus(job.build(), uid, null, -1, 0, null,
- earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
+ earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 91b3cb7dbdd9..7925b69852ba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -685,7 +685,7 @@ public class JobStatusTest {
final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis,
- latestRunTimeElapsedMillis, 0, 0, null, 0);
+ latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
}
private static JobStatus createJobStatus(JobInfo job) {
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index deaeb46c4074..8b35af80e47f 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -276,7 +276,7 @@ public class JobStoreTest {
0 /* sourceUserId */, 0, "someTag",
invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
- persistedExecutionTimesUTC, 0 /* innerFlagg */);
+ persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */);
mTaskStoreUnderTest.add(js);
waitForPendingIo();