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

Commit 0f53d325 authored by Kweku Adams's avatar Kweku Adams
Browse files

Add exemptions to flex policy.

Exempt DEFAULT+ priority jobs for BFGS+ and exempted apps from flex
policy. LOW and MIN priority jobs will continue to have the flex policy
applied.

Also make the run shell command more reliable for connectivity jobs.

Bug: 236261941
Bug: 299329948
Bug: 299346198
Bug: 316435418
Test: atest CtsJobSchedulerTestCases:FlexibilityConstraintTest
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atest
frameworks/base/services/tests/servicestests/src/com/android/server/job

Change-Id: Ic774f541b8bf64aecc814eba991008ebaeba3566
parent 67494dd1
Loading
Loading
Loading
Loading
+80 −13
Original line number Diff line number Diff line
@@ -159,6 +159,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;

@@ -301,6 +302,8 @@ public class JobSchedulerService extends com.android.server.SystemService
    private final ConnectivityController mConnectivityController;
    /** Need directly for sending uid state changes */
    private final DeviceIdleJobsController mDeviceIdleJobsController;
    /** Need directly for sending exempted bucket changes */
    private final FlexibilityController mFlexibilityController;
    /** Needed to get next estimated launch time. */
    private final PrefetchController mPrefetchController;
    /** Needed to get remaining quota time. */
@@ -513,6 +516,10 @@ public class JobSchedulerService extends com.android.server.SystemService
                    if (name == null) {
                        continue;
                    }
                    if (DEBUG) {
                        Slog.d(TAG, "DeviceConfig " + name
                                + " changed to " + properties.getString(name, null));
                    }
                    switch (name) {
                        case Constants.KEY_ENABLE_API_QUOTAS:
                        case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
@@ -2528,17 +2535,17 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers = new ArrayList<StateController>();
        mPrefetchController = new PrefetchController(this);
        mControllers.add(mPrefetchController);
        final FlexibilityController flexibilityController =
        mFlexibilityController =
                new FlexibilityController(this, mPrefetchController);
        mControllers.add(flexibilityController);
        mControllers.add(mFlexibilityController);
        mConnectivityController =
                new ConnectivityController(this, flexibilityController);
                new ConnectivityController(this, mFlexibilityController);
        mControllers.add(mConnectivityController);
        mControllers.add(new TimeController(this));
        final IdleController idleController = new IdleController(this, flexibilityController);
        final IdleController idleController = new IdleController(this, mFlexibilityController);
        mControllers.add(idleController);
        final BatteryController batteryController =
                new BatteryController(this, flexibilityController);
                new BatteryController(this, mFlexibilityController);
        mControllers.add(batteryController);
        mStorageController = new StorageController(this);
        mControllers.add(mStorageController);
@@ -3186,6 +3193,13 @@ public class JobSchedulerService extends com.android.server.SystemService
        }
    }

    @Override
    public void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> changedJobs) {
        if (changedJobs.size() > 0) {
            mFlexibilityController.onExemptedBucketChanged(changedJobs);
        }
    }

    @Override
    public void onRestrictionStateChanged(@NonNull JobRestriction restriction,
            boolean stopOvertimeJobs) {
@@ -3493,7 +3507,10 @@ public class JobSchedulerService extends com.android.server.SystemService
                }

                final boolean shouldForceBatchJob;
                if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
                if (job.overrideState > JobStatus.OVERRIDE_NONE) {
                    // The job should run for some test. Don't force batch it.
                    shouldForceBatchJob = false;
                } else if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
                    // Never batch expedited or user-initiated jobs, even for RESTRICTED apps.
                    shouldForceBatchJob = false;
                } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) {
@@ -4946,6 +4963,8 @@ public class JobSchedulerService extends com.android.server.SystemService
        Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + namespace + "/" + userId
                + " " + jobId + " s=" + satisfied + " f=" + force);

        final CountDownLatch delayLatch = new CountDownLatch(1);
        final JobStatus js;
        try {
            final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                    userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4954,7 +4973,7 @@ public class JobSchedulerService extends com.android.server.SystemService
            }

            synchronized (mLock) {
                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                if (js == null) {
                    return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
                }
@@ -4965,23 +4984,71 @@ public class JobSchedulerService extends com.android.server.SystemService
                // Re-evaluate constraints after the override is set in case one of the overridden
                // constraints was preventing another constraint from thinking it needed to update.
                for (int c = mControllers.size() - 1; c >= 0; --c) {
                    mControllers.get(c).reevaluateStateLocked(uid);
                    mControllers.get(c).evaluateStateLocked(js);
                }

                if (!js.isConstraintsSatisfied()) {
                    if (js.hasConnectivityConstraint()
                            && !js.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)
                            && js.wouldBeReadyWithConstraint(JobStatus.CONSTRAINT_CONNECTIVITY)) {
                        // Because of how asynchronous the connectivity signals are, JobScheduler
                        // may not get the connectivity satisfaction signal immediately. In this
                        // case, wait a few seconds to see if it comes in before saying the
                        // connectivity constraint isn't satisfied.
                        mHandler.postDelayed(
                                checkConstraintRunnableForTesting(
                                        mHandler, js, delayLatch, 5, 1000),
                                1000);
                    } else {
                        // There's no asynchronous signal to wait for. We can immediately say the
                        // job's constraints aren't satisfied and return.
                        js.overrideState = JobStatus.OVERRIDE_NONE;
                        return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
                    }

                queueReadyJobsForExecutionLocked();
                maybeRunPendingJobsLocked();
                } else {
                    delayLatch.countDown();
                }
            }
        } catch (RemoteException e) {
            // can't happen
            return 0;
        }

        // Choose to block the return until we're sure about the state of the connectivity job
        // so that tests can expect a reliable state after calling the run command.
        try {
            delayLatch.await(7L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Slog.e(TAG, "Couldn't wait for asynchronous constraint change", e);
        }

        synchronized (mLock) {
            if (!js.isConstraintsSatisfied()) {
                js.overrideState = JobStatus.OVERRIDE_NONE;
                return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
            }

            queueReadyJobsForExecutionLocked();
            maybeRunPendingJobsLocked();
        }
        return 0;
    }

    private static Runnable checkConstraintRunnableForTesting(@NonNull final Handler handler,
            @NonNull final JobStatus js, @NonNull final CountDownLatch latch,
            final int remainingAttempts, final long delayMs) {
        return () -> {
            if (remainingAttempts <= 0 || js.isConstraintsSatisfied()) {
                latch.countDown();
                return;
            }
            handler.postDelayed(
                    checkConstraintRunnableForTesting(
                            handler, js, latch, remainingAttempts - 1, delayMs),
                    delayMs);
        };
    }

    // Shell command infrastructure: immediately timeout currently executing jobs
    int executeStopCommand(PrintWriter pw, String pkgName, int userId,
            @Nullable String namespace, boolean hasJobId, int jobId,
+6 −0
Original line number Diff line number Diff line
@@ -60,6 +60,12 @@ public interface StateChangedListener {

    void onNetworkChanged(JobStatus jobStatus, Network newNetwork);

    /**
     * Called when these jobs are added or removed from the
     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_EXEMPTED} bucket.
     */
    void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> jobs);

    /**
     * Called when these jobs are added or removed from the
     * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket.
+52 −19
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -180,8 +181,12 @@ public final class FlexibilityController extends StateController {
                }
            };

    private static final int MSG_UPDATE_JOBS = 0;
    private static final int MSG_UPDATE_JOB = 1;
    private static final int MSG_CHECK_ALL_JOBS = 0;
    /** Check the jobs in {@link #mJobsToCheck} */
    private static final int MSG_CHECK_JOBS = 1;

    @GuardedBy("mLock")
    private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>();

    public FlexibilityController(
            JobSchedulerService service, PrefetchController prefetchController) {
@@ -266,7 +271,14 @@ public final class FlexibilityController extends StateController {
    @GuardedBy("mLock")
    boolean isFlexibilitySatisfiedLocked(JobStatus js) {
        return !mFlexibilityEnabled
                // Exclude all jobs of the TOP app
                || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
                // Only exclude DEFAULT+ priority jobs for BFGS+ apps
                || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
                        && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
                // Only exclude DEFAULT+ priority jobs for EXEMPTED apps
                || (js.getStandbyBucket() == EXEMPTED_INDEX
                        && js.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT)
                || hasEnoughSatisfiedConstraintsLocked(js)
                || mService.isCurrentlyRunningLocked(js);
    }
@@ -371,8 +383,16 @@ public final class FlexibilityController extends StateController {

                // Push the job update to the handler to avoid blocking other controllers and
                // potentially batch back-to-back controller state updates together.
                mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget();
                mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
            }
        }
    }

    /** Called with a set of apps who have been added to or removed from the exempted bucket. */
    public void onExemptedBucketChanged(@NonNull ArraySet<JobStatus> changedJobs) {
        synchronized (mLock) {
            mJobsToCheck.addAll(changedJobs);
            mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
        }
    }

@@ -485,7 +505,9 @@ public final class FlexibilityController extends StateController {
    @Override
    @GuardedBy("mLock")
    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
        if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
        if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
                && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
            // All changes are below BFGS. There's no significant change to care about.
            return;
        }
        final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -710,7 +732,8 @@ public final class FlexibilityController extends StateController {
                    }
                    mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
                            js.getNumAppliedFlexibleConstraints());
                    mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget();
                    mJobsToCheck.add(js);
                    mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
                    return;
                }
                if (nextTimeElapsed == NO_LIFECYCLE_END) {
@@ -761,10 +784,11 @@ public final class FlexibilityController extends StateController {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_UPDATE_JOBS:
                    removeMessages(MSG_UPDATE_JOBS);
                case MSG_CHECK_ALL_JOBS:
                    removeMessages(MSG_CHECK_ALL_JOBS);

                    synchronized (mLock) {
                        mJobsToCheck.clear();
                        final long nowElapsed = sElapsedRealtimeClock.millis();
                        final ArraySet<JobStatus> changedJobs = new ArraySet<>();

@@ -790,19 +814,25 @@ public final class FlexibilityController extends StateController {
                    }
                    break;

                case MSG_UPDATE_JOB:
                case MSG_CHECK_JOBS:
                    synchronized (mLock) {
                        final JobStatus js = (JobStatus) msg.obj;
                        final long nowElapsed = sElapsedRealtimeClock.millis();
                        ArraySet<JobStatus> changedJobs = new ArraySet<>();

                        for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
                            final JobStatus js = mJobsToCheck.valueAt(i);
                            if (DEBUG) {
                            Slog.d("blah", "Checking on " + js.toShortString());
                                Slog.d(TAG, "Checking on " + js.toShortString());
                            }
                        final long nowElapsed = sElapsedRealtimeClock.millis();
                            if (js.setFlexibilityConstraintSatisfied(
                                    nowElapsed, isFlexibilitySatisfiedLocked(js))) {
                            // TODO(141645789): add method that will take a single job
                            ArraySet<JobStatus> changedJob = new ArraySet<>();
                            changedJob.add(js);
                            mStateChangedListener.onControllerStateChanged(changedJob);
                                changedJobs.add(js);
                            }
                        }

                        mJobsToCheck.clear();
                        if (changedJobs.size() > 0) {
                            mStateChangedListener.onControllerStateChanged(changedJobs);
                        }
                    }
                    break;
@@ -985,7 +1015,10 @@ public final class FlexibilityController extends StateController {
            pw.println(":");
            pw.increaseIndent();

            pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println();
            pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS);
            pw.print("(");
            JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS);
            pw.println(")");
            pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
            pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
            pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
+1 −4
Original line number Diff line number Diff line
@@ -106,11 +106,8 @@ public final class JobStatus {
    public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
    public static final long NO_EARLIEST_RUNTIME = 0L;

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public static final int CONSTRAINT_BATTERY_NOT_LOW =
            JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -2194,7 +2191,7 @@ public final class JobStatus {
     * @return Whether or not this job would be ready to run if it had the specified constraint
     * granted, based on its requirements.
     */
    boolean wouldBeReadyWithConstraint(int constraint) {
    public boolean wouldBeReadyWithConstraint(int constraint) {
        return readinessStatusWithConstraint(constraint, true);
    }

+8 −0
Original line number Diff line number Diff line
@@ -2511,6 +2511,7 @@ public final class QuotaController extends StateController {
                    + " to bucketIndex " + bucketIndex);
        }
        List<JobStatus> restrictedChanges = new ArrayList<>();
        ArraySet<JobStatus> exemptedChanges = new ArraySet<>();
        synchronized (mLock) {
            ShrinkableDebits debits = mEJStats.get(userId, packageName);
            if (debits != null) {
@@ -2530,6 +2531,10 @@ public final class QuotaController extends StateController {
                        && bucketIndex != js.getStandbyBucket()) {
                    restrictedChanges.add(js);
                }
                if ((bucketIndex == EXEMPTED_INDEX || js.getStandbyBucket() == EXEMPTED_INDEX)
                        && bucketIndex != js.getStandbyBucket()) {
                    exemptedChanges.add(js);
                }
                js.setStandbyBucket(bucketIndex);
            }
            Timer timer = mPkgTimers.get(userId, packageName);
@@ -2544,6 +2549,9 @@ public final class QuotaController extends StateController {
                    maybeUpdateConstraintForPkgLocked(
                            sElapsedRealtimeClock.millis(), userId, packageName));
        }
        if (exemptedChanges.size() > 0) {
            mStateChangedListener.onExemptedBucketChanged(exemptedChanges);
        }
        if (restrictedChanges.size() > 0) {
            mStateChangedListener.onRestrictedBucketChanged(restrictedChanges);
        }
Loading