Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 85033f33 authored by Kweku Adams's avatar Kweku Adams
Browse files

Prevent prefetch jobs from having deadlines.

Now that we're trying to run prefetch jobs close to app launch, there's
no good reason for a prefetch job to have a deadline. Periodic jobs
don't run until the constraints are satisfied. Just wanting to run a
prefetch job by some time just for the sake of running it is not
something we want to allow. Gated by target SDK, updated apps will be
prevented from scheduling a prefetch job with a deadline.

Bug: 194532703
Test: adb shell am compat disable 194532703 android.jobscheduler.cts; atest android.jobscheduler.cts.JobInfoTest#testPrefetch
Test: adb shell am compat reset 194532703 android.jobscheduler.cts; atest android.jobscheduler.cts.JobInfoTest#testPrefetch
Change-Id: I92d91c89d01765f76064dd00725137f166ca483f
parent 638e4594
Loading
Loading
Loading
Loading
+43 −4
Original line number Original line Diff line number Diff line
@@ -29,6 +29,9 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
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.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.ComponentName;
@@ -63,6 +66,25 @@ import java.util.Objects;
public class JobInfo implements Parcelable {
public class JobInfo implements Parcelable {
    private static String TAG = "JobInfo";
    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 */
    /** @hide */
    @IntDef(prefix = { "NETWORK_TYPE_" }, value = {
    @IntDef(prefix = { "NETWORK_TYPE_" }, value = {
            NETWORK_TYPE_NONE,
            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
         * 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,
         * 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
         * Setting this function on the builder with {@link #setMinimumLatency(long)} or
         * {@link #setOverrideDeadline(long)} will result in an error.
         * {@link #setOverrideDeadline(long)} will result in an error.
         * @param intervalMillis Millisecond interval for which this job will repeat.
         * @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
         * the specific user of this device. For example, fetching top headlines
         * of interest to the current user.
         * of interest to the current user.
         * <p>
         * <p>
         * Starting with Android version {@link Build.VERSION_CODES#TIRAMISU}, prefetch jobs are
         * not allowed to have deadlines (set via {@link #setOverrideDeadline(long)}.
         * <p>
         * The system may use this signal to relax the network constraints you
         * The system may use this signal to relax the network constraints you
         * originally requested, such as allowing a
         * originally requested, such as allowing a
         * {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over a metered
         * {@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.
         * @return The job object to hand to the JobScheduler. This object is immutable.
         */
         */
        public JobInfo build() {
        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
            // This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
            // check that would ideally be phased out instead.
            // check that would ideally be phased out instead.
            if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
            if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
@@ -1683,7 +1715,7 @@ public class JobInfo implements Parcelable {
                        " setRequiresDeviceIdle is an error.");
                        " setRequiresDeviceIdle is an error.");
            }
            }
            JobInfo jobInfo = new JobInfo(this);
            JobInfo jobInfo = new JobInfo(this);
            jobInfo.enforceValidity();
            jobInfo.enforceValidity(disallowPrefetchDeadlines);
            return jobInfo;
            return jobInfo;
        }
        }


@@ -1701,7 +1733,7 @@ public class JobInfo implements Parcelable {
    /**
    /**
     * @hide
     * @hide
     */
     */
    public final void enforceValidity() {
    public final void enforceValidity(boolean disallowPrefetchDeadlines) {
        // Check that network estimates require network type and are reasonable values.
        // Check that network estimates require network type and are reasonable values.
        if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
        if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
                && networkRequest == null) {
                && networkRequest == null) {
@@ -1725,9 +1757,10 @@ public class JobInfo implements Parcelable {
            throw new IllegalArgumentException("Minimum chunk size must be positive");
            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.
        // Check that a deadline was not set on a periodic job.
        if (isPeriodic) {
        if (isPeriodic) {
            if (maxExecutionDelayMillis != 0L) {
            if (hasDeadline) {
                throw new IllegalArgumentException(
                throw new IllegalArgumentException(
                        "Can't call setOverrideDeadline() on a periodic job.");
                        "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) {
        if (isPersisted) {
            // We can't serialize network specifiers
            // We can't serialize network specifiers
            if (networkRequest != null
            if (networkRequest != null
+4 −1
Original line number Original line Diff line number Diff line
@@ -29,6 +29,7 @@ import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppGlobals;
import android.app.IUidObserver;
import android.app.IUidObserver;
import android.app.compat.CompatChanges;
import android.app.job.IJobScheduler;
import android.app.job.IJobScheduler;
import android.app.job.JobInfo;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobParameters;
@@ -2932,7 +2933,9 @@ public class JobSchedulerService extends com.android.server.SystemService
        }
        }


        private void validateJobFlags(JobInfo job, int callingUid) {
        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) {
            if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
                getContext().enforceCallingOrSelfPermission(
                getContext().enforceCallingOrSelfPermission(
                        android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
                        android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
+11 −6
Original line number Original line Diff line number Diff line
@@ -49,7 +49,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.BitUtils;
import com.android.internal.util.BitUtils;
import com.android.server.IoThread;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.JobStatus;


@@ -978,10 +977,17 @@ public final class JobStore {


            final JobInfo builtJob;
            final JobInfo builtJob;
            try {
            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) {
            } catch (Exception e) {
                Slog.w(TAG, "Unable to build job from XML, ignoring: "
                Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e);
                        + jobBuilder.summarize());
                return null;
                return null;
            }
            }


@@ -997,11 +1003,10 @@ public final class JobStore {
            }
            }


            // And now we're done
            // And now we're done
            JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
            final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
            final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
                    sourceUserId, elapsedNow);
                    sourceUserId, elapsedNow);
            JobStatus js = new JobStatus(
            JobStatus js = new JobStatus(
                    jobBuilder.build(), uid, sourcePackageName, sourceUserId,
                    builtJob, uid, sourcePackageName, sourceUserId,
                    appBucket, sourceTag,
                    appBucket, sourceTag,
                    elapsedRuntimes.first, elapsedRuntimes.second,
                    elapsedRuntimes.first, elapsedRuntimes.second,
                    lastSuccessfulRunTime, lastFailedRunTime,
                    lastSuccessfulRunTime, lastFailedRunTime,
+3 −1
Original line number Original line Diff line number Diff line
@@ -555,7 +555,9 @@ public final class JobStatus {
            requestBuilder.setUids(
            requestBuilder.setUids(
                    Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
                    Collections.singleton(new Range<Integer>(this.sourceUid, this.sourceUid)));
            builder.setRequiredNetwork(requestBuilder.build());
            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);
        final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);