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

Commit 5951fa21 authored by Kweku Adams's avatar Kweku Adams
Browse files

Fix RESTRICTED app handling.

1. Making sure dynamic constraints are added for every applicable
JobStatus, including when a new one is created.
2. Ensure NEVER jobs are never considered ready to run.

Bug: 148607342
Test: atest android.jobscheduler.cts.JobThrottlingTest#testJobsInNeverApp
Test: atest android.jobscheduler.cts.JobThrottlingTest#testJobsInRestrictedBucket_NoRequiredNetwork
Test: atest android.jobscheduler.cts.JobThrottlingTest#testJobsInRestrictedBucket_ParoleSession
Test: atest android.jobscheduler.cts.JobThrottlingTest#testJobsInRestrictedBucket_WithRequiredNetwork
Change-Id: Ic65aebb24bbf4861b4e8c1fc2c66fb9deb8f1939
parent f0962a49
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -1390,18 +1390,8 @@ public class JobSchedulerService extends com.android.server.SystemService
                    // Effective standby bucket can change after this in some situations so use
                    // the real bucket so that the job is tracked by the controllers.
                    if (js.getStandbyBucket() == RESTRICTED_INDEX) {
                        js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
                        js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
                        js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
                        js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE);

                        mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js);
                    } else {
                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW);
                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING);
                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY);
                        js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE);

                        mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js);
                    }
                }
+55 −20
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.server.job.controllers;

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

import android.app.AppGlobals;
@@ -63,25 +65,35 @@ import java.util.function.Predicate;
 * @hide
 */
public final class JobStatus {
    static final String TAG = "JobSchedulerService";
    private static final String TAG = "JobScheduler.JobStatus";
    static final boolean DEBUG = JobSchedulerService.DEBUG;

    public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
    public static final long NO_EARLIEST_RUNTIME = 0L;

    public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
    public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
    public static final int CONSTRAINT_BATTERY_NOT_LOW =
            JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
    static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
    static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
    static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
    static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
    static final int CONSTRAINT_TIMING_DELAY = 1<<31;
    static final int CONSTRAINT_DEADLINE = 1<<30;
    public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
    static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
    static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
    static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint
    static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24;      // Implicit constraint
    static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint

    /**
     * The additional set of dynamic constraints that must be met if the job's effective bucket is
     * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't
     * need network.
     */
    private static final int DYNAMIC_RESTRICTED_CONSTRAINTS =
            CONSTRAINT_BATTERY_NOT_LOW
                    | CONSTRAINT_CHARGING
                    | CONSTRAINT_CONNECTIVITY
                    | CONSTRAINT_IDLE;

    /**
     * The constraints that we want to log to statsd.
     *
@@ -419,7 +431,11 @@ public final class JobStatus {
        this.requiredConstraints = requiredConstraints;
        mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
        mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
        if (standbyBucket == RESTRICTED_INDEX) {
            addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
        } else {
            mReadyDynamicSatisfied = true;
        }

        mLastSuccessfulRunTime = lastSuccessfulRunTime;
        mLastFailedRunTime = lastFailedRunTime;
@@ -727,6 +743,14 @@ public final class JobStatus {
    }

    public void setStandbyBucket(int newBucket) {
        if (newBucket == RESTRICTED_INDEX) {
            // Adding to the bucket.
            addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
        } else if (standbyBucket == RESTRICTED_INDEX) {
            // Removing from the RESTRICTED bucket.
            removeDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
        }

        standbyBucket = newBucket;
    }

@@ -1054,6 +1078,11 @@ public final class JobStatus {
        if (old == state) {
            return false;
        }
        if (DEBUG) {
            Slog.v(TAG,
                    "Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for "
                            + toShortString());
        }
        satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0);
        mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST;
        mReadyDynamicSatisfied =
@@ -1086,38 +1115,40 @@ public final class JobStatus {
    }

    /**
     * Indicates that this job cannot run without the specified constraint. This is evaluated
     * Indicates that this job cannot run without the specified constraints. This is evaluated
     * separately from the job's explicitly requested constraints and MUST be satisfied before
     * the job can run if the app doesn't have quota.
     *
     */
    public void addDynamicConstraint(int constraint) {
        if (constraint == CONSTRAINT_WITHIN_QUOTA) {
    private void addDynamicConstraints(int constraints) {
        if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
            // Quota should never be used as a dynamic constraint.
            Slog.wtf(TAG, "Tried to set quota as a dynamic constraint");
            return;
            constraints &= ~CONSTRAINT_WITHIN_QUOTA;
        }

        // Connectivity and content trigger are special since they're only valid to add if the
        // job has requested network or specific content URIs. Adding these constraints to jobs
        // that don't need them doesn't make sense.
        if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint())
                || (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) {
            return;
        if (!hasConnectivityConstraint()) {
            constraints &= ~CONSTRAINT_CONNECTIVITY;
        }
        if (!hasContentTriggerConstraint()) {
            constraints &= ~CONSTRAINT_CONTENT_TRIGGER;
        }

        mDynamicConstraints |= constraint;
        mDynamicConstraints |= constraints;
        mReadyDynamicSatisfied =
                mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
    }

    /**
     * Removes a dynamic constraint from a job, meaning that the requirement is not required for
     * Removes dynamic constraints from a job, meaning that the requirements are not required for
     * the job to run (if the job itself hasn't requested the constraint. This is separate from
     * the job's explicitly requested constraints and does not remove those requested constraints.
     *
     */
    public void removeDynamicConstraint(int constraint) {
        mDynamicConstraints &= ~constraint;
    private void removeDynamicConstraints(int constraints) {
        mDynamicConstraints &= ~constraints;
        mReadyDynamicSatisfied =
                mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints);
    }
@@ -1193,7 +1224,11 @@ public final class JobStatus {

    private boolean isReady(int satisfiedConstraints) {
        // Quota and dynamic constraints trump all other constraints.
        if (!mReadyWithinQuota && !mReadyDynamicSatisfied) {
        // NEVER jobs are not supposed to run at all. Since we're using quota to allow parole
        // sessions (exempt from dynamic restrictions), we need the additional check to ensure
        // that NEVER jobs don't run.
        // TODO: cleanup quota and standby bucket management so we don't need the additional checks
        if ((!mReadyWithinQuota && !mReadyDynamicSatisfied) || standbyBucket == NEVER_INDEX) {
            return false;
        }
        // Deadline constraint trumps other constraints besides quota and dynamic (except for