diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 646a0279c7224..0fa40872099d0 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -29,6 +29,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ComponentName; @@ -63,6 +66,25 @@ import java.util.Objects; public class JobInfo implements Parcelable { private static String TAG = "JobInfo"; + /** + * Disallow setting a deadline (via {@link Builder#setOverrideDeadline(long)}) for prefetch + * jobs ({@link Builder#setPrefetch(boolean)}. Prefetch jobs are meant to run close to the next + * app launch, so there's no good reason to allow them to have deadlines. + * + * We don't drop or cancel any previously scheduled prefetch jobs with a deadline. + * There's no way for an app to keep a perpetually scheduled prefetch job with a deadline. + * Prefetch jobs with a deadline will run and apps under this restriction won't be able to + * schedule new prefetch jobs with a deadline. If a job is rescheduled (by providing + * {@code true} via {@link JobService#jobFinished(JobParameters, boolean)} or + * {@link JobService#onStopJob(JobParameters)}'s return value),the deadline is dropped. + * Periodic jobs require all constraints to be met, so there's no issue with their deadlines. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + public static final long DISALLOW_DEADLINES_FOR_PREFETCH_JOBS = 194532703L; + /** @hide */ @IntDef(prefix = { "NETWORK_TYPE_" }, value = { NETWORK_TYPE_NONE, @@ -1445,7 +1467,9 @@ public class JobInfo implements Parcelable { /** * Specify that this job should recur with the provided interval, not more than once per * period. You have no control over when within this interval this job will be executed, - * only the guarantee that it will be executed at most once within this interval. + * only the guarantee that it will be executed at most once within this interval, as long + * as the constraints are satisfied. If the constraints are not satisfied within this + * interval, the job will wait until the constraints are satisfied. * Setting this function on the builder with {@link #setMinimumLatency(long)} or * {@link #setOverrideDeadline(long)} will result in an error. * @param intervalMillis Millisecond interval for which this job will repeat. @@ -1641,6 +1665,9 @@ public class JobInfo implements Parcelable { * the specific user of this device. For example, fetching top headlines * of interest to the current user. *
+ * Starting with Android version {@link Build.VERSION_CODES#TIRAMISU}, prefetch jobs are + * not allowed to have deadlines (set via {@link #setOverrideDeadline(long)}. + *
* The system may use this signal to relax the network constraints you
* originally requested, such as allowing a
* {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered
@@ -1675,6 +1702,11 @@ public class JobInfo implements Parcelable {
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
+ return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS));
+ }
+
+ /** @hide */
+ public JobInfo build(boolean disallowPrefetchDeadlines) {
// This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
// check that would ideally be phased out instead.
if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
@@ -1683,7 +1715,7 @@ public class JobInfo implements Parcelable {
" setRequiresDeviceIdle is an error.");
}
JobInfo jobInfo = new JobInfo(this);
- jobInfo.enforceValidity();
+ jobInfo.enforceValidity(disallowPrefetchDeadlines);
return jobInfo;
}
@@ -1701,7 +1733,7 @@ public class JobInfo implements Parcelable {
/**
* @hide
*/
- public final void enforceValidity() {
+ public final void enforceValidity(boolean disallowPrefetchDeadlines) {
// Check that network estimates require network type and are reasonable values.
if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
&& networkRequest == null) {
@@ -1725,9 +1757,10 @@ public class JobInfo implements Parcelable {
throw new IllegalArgumentException("Minimum chunk size must be positive");
}
+ final boolean hasDeadline = maxExecutionDelayMillis != 0L;
// Check that a deadline was not set on a periodic job.
if (isPeriodic) {
- if (maxExecutionDelayMillis != 0L) {
+ if (hasDeadline) {
throw new IllegalArgumentException(
"Can't call setOverrideDeadline() on a periodic job.");
}
@@ -1741,6 +1774,12 @@ public class JobInfo implements Parcelable {
}
}
+ // Prefetch jobs should not have deadlines
+ if (disallowPrefetchDeadlines && hasDeadline && (flags & FLAG_PREFETCH) != 0) {
+ throw new IllegalArgumentException(
+ "Can't call setOverrideDeadline() on a prefetch job.");
+ }
+
if (isPersisted) {
// We can't serialize network specifiers
if (networkRequest != null
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 a23f6e1980f47..c4603127e00bf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -29,6 +29,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.IUidObserver;
+import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -2932,7 +2933,9 @@ public class JobSchedulerService extends com.android.server.SystemService
}
private void validateJobFlags(JobInfo job, int callingUid) {
- job.enforceValidity();
+ job.enforceValidity(
+ CompatChanges.isChangeEnabled(
+ JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid));
if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
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 d1afc8093f401..7c1e4c9fafcc0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -49,7 +49,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.server.IoThread;
-import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
@@ -978,10 +977,17 @@ public final class JobStore {
final JobInfo builtJob;
try {
- builtJob = jobBuilder.build();
+ // Don't perform prefetch-deadline check here. Apps targeting S- shouldn't have
+ // any prefetch-with-deadline jobs accidentally dropped. It's not worth doing
+ // target SDK version checks here for apps targeting T+. There's no way for an
+ // app to keep a perpetually scheduled prefetch job with a deadline. Prefetch jobs
+ // with a deadline would run and then any newly scheduled prefetch jobs wouldn't
+ // have a deadline. If a job is rescheduled (via jobFinished(true) or onStopJob()'s
+ // return value), the deadline is dropped. Periodic jobs require all constraints
+ // to be met, so there's no issue with their deadlines.
+ builtJob = jobBuilder.build(false);
} catch (Exception e) {
- Slog.w(TAG, "Unable to build job from XML, ignoring: "
- + jobBuilder.summarize());
+ Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e);
return null;
}
@@ -997,11 +1003,10 @@ public final class JobStore {
}
// And now we're done
- JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
sourceUserId, elapsedNow);
JobStatus js = new JobStatus(
- jobBuilder.build(), uid, sourcePackageName, sourceUserId,
+ builtJob, uid, sourcePackageName, sourceUserId,
appBucket, sourceTag,
elapsedRuntimes.first, elapsedRuntimes.second,
lastSuccessfulRunTime, lastFailedRunTime,
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 d35c03dbef53f..b4651a9b28ddf 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
@@ -555,7 +555,9 @@ public final class JobStatus {
requestBuilder.setUids(
Collections.singleton(new Range