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

Commit 00e2e631 authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Expose job priority API."

parents af7b29f2 d6ff7d2a
Loading
Loading
Loading
Loading
+202 −9
Original line number Diff line number Diff line
@@ -85,6 +85,17 @@ public class JobInfo implements Parcelable {
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    public static final long DISALLOW_DEADLINES_FOR_PREFETCH_JOBS = 194532703L;

    /**
     * Whether to throw an exception when an app provides an invalid priority value via
     * {@link Builder#setPriority(int)}. Legacy apps may be incorrectly using the API and
     * so the call will silently fail for them if they continue using the API.
     *
     * @hide
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    public static final long THROW_ON_INVALID_PRIORITY_VALUE = 140852299L;

    /** @hide */
    @IntDef(prefix = { "NETWORK_TYPE_" }, value = {
            NETWORK_TYPE_NONE,
@@ -206,6 +217,67 @@ public class JobInfo implements Parcelable {
     */
    public static final int DEFAULT_BACKOFF_POLICY = BACKOFF_POLICY_EXPONENTIAL;

    /**
     * Job has minimal value to the user. The user has absolutely no expectation
     * or knowledge of this task and it has no bearing on the user's perception of
     * the app whatsoever. JobScheduler <i>may</i> decide to defer these tasks while
     * there are higher priority tasks in order to ensure there is sufficient quota
     * available for the higher priority tasks.
     * A sample task of min priority: uploading analytics
     */
    public static final int PRIORITY_MIN = 100;

    /**
     * Low priority. The task provides some benefit to users, but is not critical
     * and is more of a nice-to-have. This is more important than minimum priority
     * jobs and will be prioritized ahead of them, but may still be deferred in lieu
     * of higher priority jobs. JobScheduler <i>may</i> decide to defer these tasks
     * while there are higher priority tasks in order to ensure there is sufficient
     * quota available for the higher priority tasks.
     * A sample task of low priority: prefetching data the user hasn't requested
     */
    public static final int PRIORITY_LOW = 200;

    /**
     * Default value for all regular jobs. As noted in {@link JobScheduler},
     * these jobs have a general maximum execution time of 10 minutes.
     * Receives the standard job management policy.
     */
    public static final int PRIORITY_DEFAULT = 300;

    /**
     * This task should be ordered ahead of most other tasks. It may be
     * deferred a little, but if it doesn't run at some point, the user may think
     * something is wrong. Assuming all constraints remain satisfied
     * (including ideal system load conditions), these jobs will have a maximum
     * execution time of at least 4 minutes. Setting all of your jobs to high
     * priority will not be beneficial to your app and in fact may hurt its
     * performance in the long run.
     */
    public static final int PRIORITY_HIGH = 400;

    /**
     * This task should be run ahead of all other tasks. Only Expedited Jobs
     * {@link Builder#setExpedited(boolean)} can have this priority and as such,
     * are subject to the same maximum execution time details noted in
     * {@link Builder#setExpedited(boolean)}.
     * A sample task of max priority: receiving a text message and processing it to
     * show a notification
     */
    public static final int PRIORITY_MAX = 500;

    /** @hide */
    @IntDef(prefix = {"PRIORITY_"}, value = {
            PRIORITY_MIN,
            PRIORITY_LOW,
            PRIORITY_DEFAULT,
            PRIORITY_HIGH,
            PRIORITY_MAX,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Priority {
    }

    /**
     * Default of {@link #getBias}.
     * @hide
@@ -359,6 +431,8 @@ public class JobInfo implements Parcelable {
    private final long initialBackoffMillis;
    private final int backoffPolicy;
    private final int mBias;
    @Priority
    private final int mPriority;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private final int flags;

@@ -410,6 +484,14 @@ public class JobInfo implements Parcelable {
        return mBias;
    }

    /**
     * @see JobInfo.Builder#setPriority(int)
     */
    @Priority
    public int getPriority() {
        return mPriority;
    }

    /** @hide */
    public int getFlags() {
        return flags;
@@ -746,6 +828,9 @@ public class JobInfo implements Parcelable {
        if (mBias != j.mBias) {
            return false;
        }
        if (mPriority != j.mPriority) {
            return false;
        }
        if (flags != j.flags) {
            return false;
        }
@@ -791,6 +876,7 @@ public class JobInfo implements Parcelable {
        hashCode = 31 * hashCode + Long.hashCode(initialBackoffMillis);
        hashCode = 31 * hashCode + backoffPolicy;
        hashCode = 31 * hashCode + mBias;
        hashCode = 31 * hashCode + mPriority;
        hashCode = 31 * hashCode + flags;
        return hashCode;
    }
@@ -830,6 +916,7 @@ public class JobInfo implements Parcelable {
        hasEarlyConstraint = in.readInt() == 1;
        hasLateConstraint = in.readInt() == 1;
        mBias = in.readInt();
        mPriority = in.readInt();
        flags = in.readInt();
    }

@@ -861,6 +948,7 @@ public class JobInfo implements Parcelable {
        hasEarlyConstraint = b.mHasEarlyConstraint;
        hasLateConstraint = b.mHasLateConstraint;
        mBias = b.mBias;
        mPriority = b.mPriority;
        flags = b.mFlags;
    }

@@ -906,6 +994,7 @@ public class JobInfo implements Parcelable {
        out.writeInt(hasEarlyConstraint ? 1 : 0);
        out.writeInt(hasLateConstraint ? 1 : 0);
        out.writeInt(mBias);
        out.writeInt(mPriority);
        out.writeInt(this.flags);
    }

@@ -1024,6 +1113,8 @@ public class JobInfo implements Parcelable {
        private ClipData mClipData;
        private int mClipGrantFlags;
        private int mBias = BIAS_DEFAULT;
        @Priority
        private int mPriority = PRIORITY_DEFAULT;
        private int mFlags;
        // Requirements.
        private int mConstraintFlags;
@@ -1100,6 +1191,7 @@ public class JobInfo implements Parcelable {
            // mBackoffPolicySet isn't set but it's fine since this is copying from an already valid
            // job.
            mBackoffPolicy = job.getBackoffPolicy();
            mPriority = job.getPriority();
        }

        /** @hide */
@@ -1109,11 +1201,36 @@ public class JobInfo implements Parcelable {
            return this;
        }

        /** @hide */
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        public Builder setPriority(int priority) {
            // No-op for invalid calls. This wasn't a supported API before Tiramisu, so anyone
            // calling this that isn't targeting T isn't guaranteed a behavior change.
        /**
         * Indicate the priority for this job. The priority set here will be used to sort jobs
         * for a single app and apply slightly different policies based on the priority.
         * The priority will <b>NOT</b> be used as a global sorting value to sort between
         * different app's jobs. Use this to inform the system about which jobs it should try
         * to run before other jobs. Giving the same priority to all of your jobs will result
         * in them all being treated the same. The priorities each have slightly different
         * behaviors, as noted in their relevant javadoc.
         *
         * <b>NOTE:</b> Setting all of your jobs to high priority will not be
         * beneficial to your app and in fact may hurt its performance in the
         * long run.
         *
         * In order to prevent starvation, repeatedly retried jobs (because of failures) will slowly
         * have their priorities lowered.
         *
         * @see JobInfo#getPriority()
         */
        @NonNull
        public Builder setPriority(@Priority int priority) {
            if (priority > PRIORITY_MAX || priority < PRIORITY_MIN) {
                if (Compatibility.isChangeEnabled(THROW_ON_INVALID_PRIORITY_VALUE)) {
                    throw new IllegalArgumentException("Invalid priority value");
                }
                // No-op for invalid calls of apps that are targeting S-. This was an unsupported
                // API before Tiramisu, so anyone calling this that isn't targeting T isn't
                // guaranteed a behavior change.
                return this;
            }
            mPriority = priority;
            return this;
        }

@@ -1637,7 +1754,17 @@ public class JobInfo implements Parcelable {
        public Builder setExpedited(boolean expedited) {
            if (expedited) {
                mFlags |= FLAG_EXPEDITED;
                if (mPriority == PRIORITY_DEFAULT) {
                    // The default priority for EJs is MAX, but only change this if .setPriority()
                    // hasn't been called yet.
                    mPriority = PRIORITY_MAX;
                }
            } else {
                if (mPriority == PRIORITY_MAX && (mFlags & FLAG_EXPEDITED) != 0) {
                    // Reset the priority for the job, but only change this if .setPriority()
                    // hasn't been called yet.
                    mPriority = PRIORITY_DEFAULT;
                }
                mFlags &= (~FLAG_EXPEDITED);
            }
            return this;
@@ -1664,7 +1791,18 @@ public class JobInfo implements Parcelable {
        public Builder setImportantWhileForeground(boolean importantWhileForeground) {
            if (importantWhileForeground) {
                mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
                if (mPriority == PRIORITY_DEFAULT) {
                    // The default priority for important-while-foreground is HIGH, but only change
                    // this if .setPriority() hasn't been called yet.
                    mPriority = PRIORITY_HIGH;
                }
            } else {
                if (mPriority == PRIORITY_HIGH
                        && (mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
                    // Reset the priority for the job, but only change this if .setPriority()
                    // hasn't been called yet.
                    mPriority = PRIORITY_DEFAULT;
                }
                mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND);
            }
            return this;
@@ -1812,12 +1950,42 @@ public class JobInfo implements Parcelable {
            }
        }

        if ((flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && hasEarlyConstraint) {
        if ((flags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
            if (hasEarlyConstraint) {
                throw new IllegalArgumentException(
                        "An important while foreground job cannot have a time delay");
            }
            if (mPriority != PRIORITY_HIGH && mPriority != PRIORITY_DEFAULT) {
                throw new IllegalArgumentException(
                        "An important while foreground job must be high or default priority."
                                + " Don't mark unimportant tasks as important while foreground.");
            }
        }

        final boolean isExpedited = (flags & FLAG_EXPEDITED) != 0;
        switch (mPriority) {
            case PRIORITY_MAX:
                if (!isExpedited) {
                    throw new IllegalArgumentException("Only expedited jobs can have max priority");
                }
                break;
            case PRIORITY_HIGH:
                if ((flags & FLAG_PREFETCH) != 0) {
                    throw new IllegalArgumentException("Prefetch jobs cannot be high priority");
                }
                if (isPeriodic) {
                    throw new IllegalArgumentException("Periodic jobs cannot be high priority");
                }
                break;
            case PRIORITY_DEFAULT:
            case PRIORITY_LOW:
            case PRIORITY_MIN:
                break;
            default:
                throw new IllegalArgumentException("Invalid priority level provided: " + mPriority);
        }

        if ((flags & FLAG_EXPEDITED) != 0) {
        if (isExpedited) {
            if (hasEarlyConstraint) {
                throw new IllegalArgumentException("An expedited job cannot have a time delay");
            }
@@ -1827,6 +1995,11 @@ public class JobInfo implements Parcelable {
            if (isPeriodic) {
                throw new IllegalArgumentException("An expedited job cannot be periodic");
            }
            if (mPriority != PRIORITY_MAX && mPriority != PRIORITY_HIGH) {
                throw new IllegalArgumentException(
                        "An expedited job must be high or max priority. Don't use expedited jobs"
                                + " for unimportant tasks.");
            }
            if ((constraintFlags & ~CONSTRAINT_FLAG_STORAGE_NOT_LOW) != 0
                    || (flags & ~(FLAG_EXPEDITED | FLAG_EXEMPT_FROM_APP_STANDBY)) != 0) {
                throw new IllegalArgumentException(
@@ -1863,4 +2036,24 @@ public class JobInfo implements Parcelable {
        }
        return bias + " [UNKNOWN]";
    }

    /**
     * Convert a priority integer into a human readable string for debugging.
     * @hide
     */
    public static String getPriorityString(@Priority int priority) {
        switch (priority) {
            case PRIORITY_MIN:
                return priority + " [MIN]";
            case PRIORITY_LOW:
                return priority + " [LOW]";
            case PRIORITY_DEFAULT:
                return priority + " [DEFAULT]";
            case PRIORITY_HIGH:
                return priority + " [HIGH]";
            case PRIORITY_MAX:
                return priority + " [MAX]";
        }
        return priority + " [UNKNOWN]";
    }
}
+148 −36

File changed.

Preview size limit exceeded, changes collapsed.

+30 −6
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.job;

import static android.app.job.JobInfo.getPriorityString;

import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

@@ -378,19 +380,41 @@ public final class JobServiceContext implements ServiceConnection {

    @EconomicPolicy.AppAction
    private static int getStartActionId(@NonNull JobStatus job) {
        if (job.startedAsExpeditedJob || job.shouldTreatAsExpeditedJob()) {
        switch (job.getEffectivePriority()) {
            case JobInfo.PRIORITY_MAX:
                return JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START;
        }
            case JobInfo.PRIORITY_HIGH:
                return JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START;
            case JobInfo.PRIORITY_LOW:
                return JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START;
            case JobInfo.PRIORITY_MIN:
                return JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START;
            default:
                Slog.wtf(TAG, "Unknown priority: " + getPriorityString(job.getEffectivePriority()));
                // Intentional fallthrough
            case JobInfo.PRIORITY_DEFAULT:
                return JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START;
        }
    }

    @EconomicPolicy.AppAction
    private static int getRunningActionId(@NonNull JobStatus job) {
        if (job.startedAsExpeditedJob || job.shouldTreatAsExpeditedJob()) {
        switch (job.getEffectivePriority()) {
            case JobInfo.PRIORITY_MAX:
                return JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING;
        }
            case JobInfo.PRIORITY_HIGH:
                return JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING;
            case JobInfo.PRIORITY_LOW:
                return JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING;
            case JobInfo.PRIORITY_MIN:
                return JobSchedulerEconomicPolicy.ACTION_JOB_MIN_RUNNING;
            default:
                Slog.wtf(TAG, "Unknown priority: " + getPriorityString(job.getEffectivePriority()));
                // Intentional fallthrough
            case JobInfo.PRIORITY_DEFAULT:
                return JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING;
        }
    }

    /**
     * Used externally to query the running job. Will return null if there is no job running.
+22 −11
Original line number Diff line number Diff line
@@ -335,7 +335,7 @@ public final class JobStore {
    }

    /** Version of the db schema. */
    private static final int JOBS_FILE_VERSION = 0;
    private static final int JOBS_FILE_VERSION = 1;
    /** Tag corresponds to constraints this job needs. */
    private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
    /** Tag corresponds to execution parameters. */
@@ -548,6 +548,7 @@ public final class JobStore {
            out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
            out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
            out.attribute(null, "bias", String.valueOf(jobStatus.getBias()));
            out.attribute(null, "priority", String.valueOf(jobStatus.getEffectivePriority()));
            out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
            if (jobStatus.getInternalFlags() != 0) {
                out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
@@ -771,10 +772,11 @@ public final class JobStore {
            String tagName = parser.getName();
            if ("job-info".equals(tagName)) {
                final List<JobStatus> jobs = new ArrayList<JobStatus>();
                final int version;
                // Read in version info.
                try {
                    int version = Integer.parseInt(parser.getAttributeValue(null, "version"));
                    if (version != JOBS_FILE_VERSION) {
                    version = Integer.parseInt(parser.getAttributeValue(null, "version"));
                    if (version > JOBS_FILE_VERSION || version < 0) {
                        Slog.d(TAG, "Invalid version number, aborting jobs file read.");
                        return null;
                    }
@@ -789,7 +791,7 @@ public final class JobStore {
                        tagName = parser.getName();
                        // Start reading job.
                        if ("job".equals(tagName)) {
                            JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser);
                            JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser, version);
                            if (persistedJob != null) {
                                if (DEBUG) {
                                    Slog.d(TAG, "Read out " + persistedJob);
@@ -812,8 +814,8 @@ public final class JobStore {
         *               will take the parser into the body of the job tag.
         * @return Newly instantiated job holding all the information we just read out of the xml tag.
         */
        private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser)
                throws XmlPullParserException, IOException {
        private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser,
                int schemaVersion) throws XmlPullParserException, IOException {
            JobInfo.Builder jobBuilder;
            int uid, sourceUserId;
            long lastSuccessfulRunTime;
@@ -826,13 +828,22 @@ public final class JobStore {
                jobBuilder.setPersisted(true);
                uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));

                String val = parser.getAttributeValue(null, "bias");
                if (val == null) {
                String val;
                if (schemaVersion == 0) {
                    val = parser.getAttributeValue(null, "priority");
                    if (val != null) {
                        jobBuilder.setBias(Integer.parseInt(val));
                    }
                } else if (schemaVersion >= 1) {
                    val = parser.getAttributeValue(null, "bias");
                    if (val != null) {
                        jobBuilder.setBias(Integer.parseInt(val));
                    }
                    val = parser.getAttributeValue(null, "priority");
                    if (val != null) {
                        jobBuilder.setPriority(Integer.parseInt(val));
                    }
                }
                val = parser.getAttributeValue(null, "flags");
                if (val != null) {
                    jobBuilder.setFlags(Integer.parseInt(val));
+3 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
            "name": "CtsJobSchedulerTestCases",
            "options": [
                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
                {"exclude-annotation": "androidx.test.filters.LargeTest"}
            ]
@@ -13,6 +14,7 @@
            "options": [
                {"include-filter": "com.android.server.job"},
                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
                {"exclude-annotation": "androidx.test.filters.FlakyTest"}
            ]
        },
@@ -21,6 +23,7 @@
            "options": [
                {"include-filter": "com.android.server.job"},
                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
                {"exclude-annotation": "androidx.test.filters.FlakyTest"}
            ]
        }
Loading