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(this.sourceUid, this.sourceUid))); builder.setRequiredNetwork(requestBuilder.build()); - job = builder.build(); + // Don't perform prefetch-deadline check at this point. We've already passed the + // initial validation check. + job = builder.build(false); } final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);