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

Commit 64345146 authored by Kweku Adams's avatar Kweku Adams
Browse files

First version of HPJs.

This is the base implementation for HPJs. It allows scheduling and
running of HPJs with their own quota. Quota is regained when previous
sessions expire and when certain events take place.

A few things still aren't implemented yet. They'll be done in a separate
CL:
1. Free temp-allowlist jobs
2. Dedicated JobServiceContexts for HPJs
3. Additional quota for system-critical roles

Bug: 171305774
Test: atest --rerun-until-failure 5 com.android.server.job.controllers.QuotaControllerTest
Test: atest CtsJobSchedulerTestCases
Change-Id: I4b704cb0b406769f6de8b60bf762dd1f4eb10c68
parent 649ed103
Loading
Loading
Loading
Loading
+76 −1
Original line number Original line Diff line number Diff line
@@ -274,12 +274,19 @@ public class JobInfo implements Parcelable {


    /**
    /**
     * This job needs to be exempted from the app standby throttling. Only the system (UID 1000)
     * This job needs to be exempted from the app standby throttling. Only the system (UID 1000)
     * can set it. Jobs with a time constrant must not have it.
     * can set it. Jobs with a time constraint must not have it.
     *
     *
     * @hide
     * @hide
     */
     */
    public static final int FLAG_EXEMPT_FROM_APP_STANDBY = 1 << 3;
    public static final int FLAG_EXEMPT_FROM_APP_STANDBY = 1 << 3;


    /**
     * Whether it's a so-called "HPJ" or not.
     *
     * @hide
     */
    public static final int FLAG_FOREGROUND_JOB = 1 << 4;

    /**
    /**
     * @hide
     * @hide
     */
     */
@@ -571,12 +578,20 @@ public class JobInfo implements Parcelable {


    /**
    /**
     * Return the backoff policy of this job.
     * Return the backoff policy of this job.
     *
     * @see JobInfo.Builder#setBackoffCriteria(long, int)
     * @see JobInfo.Builder#setBackoffCriteria(long, int)
     */
     */
    public @BackoffPolicy int getBackoffPolicy() {
    public @BackoffPolicy int getBackoffPolicy() {
        return backoffPolicy;
        return backoffPolicy;
    }
    }


    /**
     * @see JobInfo.Builder#setForeground(boolean)
     */
    public boolean isForegroundJob() {
        return (flags & FLAG_FOREGROUND_JOB) != 0;
    }

    /**
    /**
     * @see JobInfo.Builder#setImportantWhileForeground(boolean)
     * @see JobInfo.Builder#setImportantWhileForeground(boolean)
     */
     */
@@ -1441,6 +1456,41 @@ public class JobInfo implements Parcelable {
            return this;
            return this;
        }
        }


        /**
         * Setting this to true indicates that this job is important and needs to run as soon as
         * possible with stronger guarantees than regular jobs. These "foreground" jobs will:
         * <ol>
         *     <li>Run as soon as possible</li>
         *     <li>Be exempted from Doze and battery saver restrictions</li>
         *     <li>Have network access</li>
         * </ol>
         *
         * Since these jobs have stronger guarantees than regular jobs, they will be subject to
         * stricter quotas. As long as an app has available foreground quota, jobs scheduled with
         * this set to true will run with these guarantees. If an app has run out of available
         * foreground quota, any pending foreground jobs will run as regular jobs.
         * {@link JobParameters#isForegroundJob()} can be used to know whether the executing job
         * has foreground guarantees or not. In addition, {@link JobScheduler#schedule(JobInfo)}
         * will immediately return {@link JobScheduler#RESULT_FAILURE} if the app does not have
         * available quota (and the job will not be successfully scheduled).
         *
         * Foreground jobs may only set network constraints. No other constraints are allowed.
         *
         * Note: Even though foreground jobs are meant to run as soon as possible, they may be
         * deferred if the system is under heavy load or the network constraint is satisfied
         *
         * @see JobInfo#isForegroundJob()
         */
        @NonNull
        public Builder setForeground(boolean foreground) {
            if (foreground) {
                mFlags |= FLAG_FOREGROUND_JOB;
            } else {
                mFlags &= (~FLAG_FOREGROUND_JOB);
            }
            return this;
        }

        /**
        /**
         * Setting this to true indicates that this job is important while the scheduling app
         * Setting this to true indicates that this job is important while the scheduling app
         * is in the foreground or on the temporary whitelist for background restrictions.
         * is in the foreground or on the temporary whitelist for background restrictions.
@@ -1456,7 +1506,9 @@ public class JobInfo implements Parcelable {
         * @param importantWhileForeground whether to relax doze restrictions for this job when the
         * @param importantWhileForeground whether to relax doze restrictions for this job when the
         *                                 app is in the foreground. False by default.
         *                                 app is in the foreground. False by default.
         * @see JobInfo#isImportantWhileForeground()
         * @see JobInfo#isImportantWhileForeground()
         * @deprecated Use {@link #setForeground(boolean)} instead.
         */
         */
        @Deprecated
        public Builder setImportantWhileForeground(boolean importantWhileForeground) {
        public Builder setImportantWhileForeground(boolean importantWhileForeground) {
            if (importantWhileForeground) {
            if (importantWhileForeground) {
                mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
                mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
@@ -1580,6 +1632,29 @@ public class JobInfo implements Parcelable {
            throw new IllegalArgumentException(
            throw new IllegalArgumentException(
                    "An important while foreground job cannot have a time delay");
                    "An important while foreground job cannot have a time delay");
        }
        }

        if ((flags & FLAG_FOREGROUND_JOB) != 0) {
            if (hasEarlyConstraint) {
                throw new IllegalArgumentException("A foreground job cannot have a time delay");
            }
            if (hasLateConstraint) {
                throw new IllegalArgumentException("A foreground job cannot have a deadline");
            }
            if (isPeriodic) {
                throw new IllegalArgumentException("A foreground job cannot be periodic");
            }
            if (isPersisted) {
                throw new IllegalArgumentException("A foreground job cannot be persisted");
            }
            if (constraintFlags != 0 || (flags & ~FLAG_FOREGROUND_JOB) != 0) {
                throw new IllegalArgumentException(
                        "A foreground job can only have network constraints");
            }
            if (triggerContentUris != null && triggerContentUris.length > 0) {
                throw new IllegalArgumentException(
                        "Can't call addTriggerContentUri() on a foreground job");
            }
        }
    }
    }


    /**
    /**
+18 −1
Original line number Original line Diff line number Diff line
@@ -111,6 +111,9 @@ public class JobParameters implements Parcelable {
    @UnsupportedAppUsage
    @UnsupportedAppUsage
    private final IBinder callback;
    private final IBinder callback;
    private final boolean overrideDeadlineExpired;
    private final boolean overrideDeadlineExpired;
    // HPJs = foreground jobs.
    // TODO(171305774): clean up naming
    private final boolean mIsHpj;
    private final Uri[] mTriggeredContentUris;
    private final Uri[] mTriggeredContentUris;
    private final String[] mTriggeredContentAuthorities;
    private final String[] mTriggeredContentAuthorities;
    private final Network network;
    private final Network network;
@@ -121,7 +124,7 @@ public class JobParameters implements Parcelable {
    /** @hide */
    /** @hide */
    public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
    public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
            Bundle transientExtras, ClipData clipData, int clipGrantFlags,
            Bundle transientExtras, ClipData clipData, int clipGrantFlags,
            boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
            boolean overrideDeadlineExpired, boolean isHpj, Uri[] triggeredContentUris,
            String[] triggeredContentAuthorities, Network network) {
            String[] triggeredContentAuthorities, Network network) {
        this.jobId = jobId;
        this.jobId = jobId;
        this.extras = extras;
        this.extras = extras;
@@ -130,6 +133,7 @@ public class JobParameters implements Parcelable {
        this.clipGrantFlags = clipGrantFlags;
        this.clipGrantFlags = clipGrantFlags;
        this.callback = callback;
        this.callback = callback;
        this.overrideDeadlineExpired = overrideDeadlineExpired;
        this.overrideDeadlineExpired = overrideDeadlineExpired;
        this.mIsHpj = isHpj;
        this.mTriggeredContentUris = triggeredContentUris;
        this.mTriggeredContentUris = triggeredContentUris;
        this.mTriggeredContentAuthorities = triggeredContentAuthorities;
        this.mTriggeredContentAuthorities = triggeredContentAuthorities;
        this.network = network;
        this.network = network;
@@ -194,6 +198,17 @@ public class JobParameters implements Parcelable {
        return clipGrantFlags;
        return clipGrantFlags;
    }
    }


    /**
     * @return Whether this job is running as a foreground job or not. A job is guaranteed to have
     * all foreground job guarantees for the duration of the job execution if this returns
     * {@code true}. This will return {@code false} if the job that wasn't requested to run as a
     * foreground job, or if it was requested to run as a foreground job but the app didn't have
     * any remaining foreground job quota at the time of execution.
     */
    public boolean isForegroundJob() {
        return mIsHpj;
    }

    /**
    /**
     * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this
     * For jobs with {@link android.app.job.JobInfo.Builder#setOverrideDeadline(long)} set, this
     * provides an easy way to tell whether the job is being executed due to the deadline
     * provides an easy way to tell whether the job is being executed due to the deadline
@@ -337,6 +352,7 @@ public class JobParameters implements Parcelable {
        }
        }
        callback = in.readStrongBinder();
        callback = in.readStrongBinder();
        overrideDeadlineExpired = in.readInt() == 1;
        overrideDeadlineExpired = in.readInt() == 1;
        mIsHpj = in.readBoolean();
        mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
        mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
        mTriggeredContentAuthorities = in.createStringArray();
        mTriggeredContentAuthorities = in.createStringArray();
        if (in.readInt() != 0) {
        if (in.readInt() != 0) {
@@ -373,6 +389,7 @@ public class JobParameters implements Parcelable {
        }
        }
        dest.writeStrongBinder(callback);
        dest.writeStrongBinder(callback);
        dest.writeInt(overrideDeadlineExpired ? 1 : 0);
        dest.writeInt(overrideDeadlineExpired ? 1 : 0);
        dest.writeBoolean(mIsHpj);
        dest.writeTypedArray(mTriggeredContentUris, flags);
        dest.writeTypedArray(mTriggeredContentUris, flags);
        dest.writeStringArray(mTriggeredContentAuthorities);
        dest.writeStringArray(mTriggeredContentAuthorities);
        if (network != null) {
        if (network != null) {
+5 −1
Original line number Original line Diff line number Diff line
@@ -205,7 +205,9 @@ class JobConcurrencyManager {
    }
    }


    private boolean isFgJob(JobStatus job) {
    private boolean isFgJob(JobStatus job) {
        return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP;
        // (It's super confusing PRIORITY_BOUND_FOREGROUND_SERVICE isn't FG here)
        return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
                || job.shouldTreatAsForegroundJob();
    }
    }


    @GuardedBy("mLock")
    @GuardedBy("mLock")
@@ -336,6 +338,8 @@ class JobConcurrencyManager {
                continue;
                continue;
            }
            }


            // TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them

            final boolean isPendingFg = isFgJob(nextPending);
            final boolean isPendingFg = isFgJob(nextPending);


            // Find an available slot for nextPending. The context should be available OR
            // Find an available slot for nextPending. The context should be available OR
+22 −5
Original line number Original line Diff line number Diff line
@@ -834,6 +834,13 @@ public class JobSchedulerService extends com.android.server.SystemService
            // Higher override state (OVERRIDE_FULL) should be before lower state (OVERRIDE_SOFT)
            // Higher override state (OVERRIDE_FULL) should be before lower state (OVERRIDE_SOFT)
            return o2.overrideState - o1.overrideState;
            return o2.overrideState - o1.overrideState;
        }
        }
        if (o1.getSourceUid() == o2.getSourceUid()) {
            final boolean o1FGJ = o1.isRequestedForegroundJob();
            if (o1FGJ != o2.isRequestedForegroundJob()) {
                // Attempt to run requested HPJs ahead of regular jobs, regardless of HPJ quota.
                return o1FGJ ? -1 : 1;
            }
        }
        if (o1.enqueueTime < o2.enqueueTime) {
        if (o1.enqueueTime < o2.enqueueTime) {
            return -1;
            return -1;
        }
        }
@@ -1136,6 +1143,12 @@ public class JobSchedulerService extends com.android.server.SystemService


            JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
            JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);


            // Return failure early if HPJ quota used up.
            if (jobStatus.isRequestedForegroundJob()
                    && !mQuotaController.isWithinHpjQuotaLocked(jobStatus)) {
                return JobScheduler.RESULT_FAILURE;
            }

            // Give exemption if the source is in the foreground just now.
            // Give exemption if the source is in the foreground just now.
            // Note if it's a sync job, this method is called on the handler so it's not exactly
            // Note if it's a sync job, this method is called on the handler so it's not exactly
            // the state when requestSync() was called, but that should be fine because of the
            // the state when requestSync() was called, but that should be fine because of the
@@ -1791,9 +1804,9 @@ public class JobSchedulerService extends com.android.server.SystemService
     * time of the job to be the time of completion (i.e. the time at which this function is
     * time of the job to be the time of completion (i.e. the time at which this function is
     * called).
     * called).
     * <p>This could be inaccurate b/c the job can run for as long as
     * <p>This could be inaccurate b/c the job can run for as long as
     * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
     * {@link com.android.server.job.JobServiceContext#DEFAULT_EXECUTING_TIMESLICE_MILLIS}, but
     * to underscheduling at least, rather than if we had taken the last execution time to be the
     * will lead to underscheduling at least, rather than if we had taken the last execution time
     * start of the execution.
     * to be the start of the execution.
     *
     *
     * @return A new job representing the execution criteria for this instantiation of the
     * @return A new job representing the execution criteria for this instantiation of the
     * recurring job.
     * recurring job.
@@ -1884,6 +1897,10 @@ public class JobSchedulerService extends com.android.server.SystemService
            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
        }
        }


        // Intentionally not checking HPJ quota here. An app can't find out if it's run out of quota
        // when it asks JS to reschedule an HPJ. Instead, the rescheduled HPJ will just be demoted
        // to a regular job if the app has no HPJ quota left.

        // If the job wants to be rescheduled, we first need to make the next upcoming
        // If the job wants to be rescheduled, we first need to make the next upcoming
        // job so we can transfer any appropriate state over from the previous job when
        // job so we can transfer any appropriate state over from the previous job when
        // we stop it.
        // we stop it.
@@ -2382,7 +2399,7 @@ public class JobSchedulerService extends com.android.server.SystemService
    public long getMaxJobExecutionTimeMs(JobStatus job) {
    public long getMaxJobExecutionTimeMs(JobStatus job) {
        synchronized (mLock) {
        synchronized (mLock) {
            return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
            return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
                    JobServiceContext.EXECUTING_TIMESLICE_MILLIS);
                    JobServiceContext.DEFAULT_EXECUTING_TIMESLICE_MILLIS);
        }
        }
    }
    }


@@ -2600,7 +2617,6 @@ public class JobSchedulerService extends com.android.server.SystemService
        // job that runs one of the app's services, as well as verifying that the
        // job that runs one of the app's services, as well as verifying that the
        // named service properly requires the BIND_JOB_SERVICE permission
        // named service properly requires the BIND_JOB_SERVICE permission
        private void enforceValidJobRequest(int uid, JobInfo job) {
        private void enforceValidJobRequest(int uid, JobInfo job) {
            job.enforceValidity();
            final PackageManager pm = getContext()
            final PackageManager pm = getContext()
                    .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
                    .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
                    .getPackageManager();
                    .getPackageManager();
@@ -2649,6 +2665,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        }
        }


        private void validateJobFlags(JobInfo job, int callingUid) {
        private void validateJobFlags(JobInfo job, int callingUid) {
            job.enforceValidity();
            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);
+26 −6
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package com.android.server.job;
package com.android.server.job;


import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;


import android.app.job.IJobCallback;
import android.app.job.IJobCallback;
@@ -73,7 +74,13 @@ public final class JobServiceContext implements ServiceConnection {


    private static final String TAG = "JobServiceContext";
    private static final String TAG = "JobServiceContext";
    /** Amount of time a job is allowed to execute for before being considered timed-out. */
    /** Amount of time a job is allowed to execute for before being considered timed-out. */
    public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
    public static final long DEFAULT_EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
    /**
     * Amount of time a RESTRICTED HPJ is allowed to execute for before being considered
     * timed-out.
     */
    public static final long DEFAULT_RESTRICTED_HPJ_EXECUTING_TIMESLICE_MILLIS =
            DEFAULT_EXECUTING_TIMESLICE_MILLIS / 2;
    /** Amount of time the JobScheduler waits for the initial service launch+bind. */
    /** Amount of time the JobScheduler waits for the initial service launch+bind. */
    private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
    private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
    /** Amount of time the JobScheduler will wait for a response from an app for a message. */
    /** Amount of time the JobScheduler will wait for a response from an app for a message. */
@@ -224,7 +231,8 @@ public final class JobServiceContext implements ServiceConnection {
            final JobInfo ji = job.getJob();
            final JobInfo ji = job.getJob();
            mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
            mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                    ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                    ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                    isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
                    isDeadlineExpired, job.shouldTreatAsForegroundJob(),
                    triggeredUris, triggeredAuthorities, job.network);
            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();


            final long whenDeferred = job.getWhenStandbyDeferred();
            final long whenDeferred = job.getWhenStandbyDeferred();
@@ -250,9 +258,18 @@ public final class JobServiceContext implements ServiceConnection {
            final Intent intent = new Intent().setComponent(job.getServiceComponent());
            final Intent intent = new Intent().setComponent(job.getServiceComponent());
            boolean binding = false;
            boolean binding = false;
            try {
            try {
                binding = mContext.bindServiceAsUser(intent, this,
                final int bindFlags;
                        Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
                if (job.shouldTreatAsForegroundJob()) {
                        | Context.BIND_NOT_PERCEPTIBLE,
                    // Add BIND_FOREGROUND_SERVICE to make it BFGS. Without it, it'll be
                    // PROCESS_STATE_IMPORTANT_FOREGROUND. Unclear which is better here.
                    // TODO(171305774): The job should run on the little cores. We'll probably need
                    // another binding flag for that.
                    bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
                } else {
                    bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
                            | Context.BIND_NOT_PERCEPTIBLE;
                }
                binding = mContext.bindServiceAsUser(intent, this, bindFlags,
                        UserHandle.of(job.getUserId()));
                        UserHandle.of(job.getUserId()));
            } catch (SecurityException e) {
            } catch (SecurityException e) {
                // Some permission policy, for example INTERACT_ACROSS_USERS and
                // Some permission policy, for example INTERACT_ACROSS_USERS and
@@ -848,7 +865,10 @@ public final class JobServiceContext implements ServiceConnection {
        final long timeoutMillis;
        final long timeoutMillis;
        switch (mVerb) {
        switch (mVerb) {
            case VERB_EXECUTING:
            case VERB_EXECUTING:
                timeoutMillis = EXECUTING_TIMESLICE_MILLIS;
                timeoutMillis = mRunningJob.shouldTreatAsForegroundJob()
                        && mRunningJob.getStandbyBucket() == RESTRICTED_INDEX
                        ? DEFAULT_RESTRICTED_HPJ_EXECUTING_TIMESLICE_MILLIS
                        : DEFAULT_EXECUTING_TIMESLICE_MILLIS;
                break;
                break;


            case VERB_BINDING:
            case VERB_BINDING:
Loading