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

Commit 953c2f9b authored by Alex Bianchi's avatar Alex Bianchi Committed by Android (Google) Code Review
Browse files

Merge "Connecting Device Controllers to Flexibility Controller"

parents 32c19c8e 33098a1c
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ import com.android.server.job.controllers.ComponentController;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.ContentObserverController;
import com.android.server.job.controllers.DeviceIdleJobsController;
import com.android.server.job.controllers.FlexibilityController;
import com.android.server.job.controllers.IdleController;
import com.android.server.job.controllers.JobStatus;
import com.android.server.job.controllers.PrefetchController;
@@ -1555,12 +1556,16 @@ public class JobSchedulerService extends com.android.server.SystemService

        // Create the controllers.
        mControllers = new ArrayList<StateController>();
        final ConnectivityController connectivityController = new ConnectivityController(this);
        final FlexibilityController flexibilityController = new FlexibilityController(this);
        mControllers.add(flexibilityController);
        final ConnectivityController connectivityController =
                new ConnectivityController(this, flexibilityController);
        mControllers.add(connectivityController);
        mControllers.add(new TimeController(this));
        final IdleController idleController = new IdleController(this);
        final IdleController idleController = new IdleController(this, flexibilityController);
        mControllers.add(idleController);
        final BatteryController batteryController = new BatteryController(this);
        final BatteryController batteryController =
                new BatteryController(this, flexibilityController);
        mControllers.add(batteryController);
        mStorageController = new StorageController(this);
        mControllers.add(mStorageController);
+9 −1
Original line number Diff line number Diff line
@@ -62,16 +62,19 @@ public final class BatteryController extends RestrictingController {

    private final PowerTracker mPowerTracker;

    private final FlexibilityController mFlexibilityController;
    /**
     * Helper set to avoid too much GC churn from frequent calls to
     * {@link #maybeReportNewChargingStateLocked()}.
     */
    private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>();

    public BatteryController(JobSchedulerService service) {
    public BatteryController(JobSchedulerService service,
            FlexibilityController flexibilityController) {
        super(service);
        mPowerTracker = new PowerTracker();
        mPowerTracker.startTracking();
        mFlexibilityController = flexibilityController;
    }

    @Override
@@ -173,6 +176,11 @@ public final class BatteryController extends RestrictingController {
            Slog.d(TAG, "maybeReportNewChargingStateLocked: "
                    + powerConnected + "/" + stablePower + "/" + batteryNotLow);
        }
        mFlexibilityController.setConstraintSatisfied(
                JobStatus.CONSTRAINT_CHARGING, mService.isBatteryCharging());
        mFlexibilityController
            .setConstraintSatisfied(JobStatus.CONSTRAINT_BATTERY_NOT_LOW, batteryNotLow);

        final long nowElapsed = sElapsedRealtimeClock.millis();
        for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
            final JobStatus ts = mTrackedTasks.valueAt(i);
+13 −1
Original line number Diff line number Diff line
@@ -106,6 +106,7 @@ public final class ConnectivityController extends RestrictingController implemen

    private final ConnectivityManager mConnManager;
    private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
    private final FlexibilityController mFlexibilityController;

    /** List of tracked jobs keyed by source UID. */
    @GuardedBy("mLock")
@@ -231,12 +232,14 @@ public final class ConnectivityController extends RestrictingController implemen

    private final Handler mHandler;

    public ConnectivityController(JobSchedulerService service) {
    public ConnectivityController(JobSchedulerService service,
            @NonNull FlexibilityController flexibilityController) {
        super(service);
        mHandler = new CcHandler(mContext.getMainLooper());

        mConnManager = mContext.getSystemService(ConnectivityManager.class);
        mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
        mFlexibilityController = flexibilityController;

        // We're interested in all network changes; internally we match these
        // network changes against the active network for each UID with jobs.
@@ -1058,6 +1061,15 @@ public final class ConnectivityController extends RestrictingController implemen

        final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied);

        if (jobStatus.getPreferUnmetered()) {
            jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null
                    && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED));

            jobStatus.setFlexibilityConstraintSatisfied(nowElapsed,
                    mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus));
        }


        // Pass along the evaluated network for job to use; prevents race
        // conditions as default routes change over time, and opens the door to
        // using non-default routes.
+176 −43
Original line number Diff line number Diff line
@@ -20,15 +20,22 @@ 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.sElapsedRealtimeClock;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;

import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.content.Context;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -44,55 +51,80 @@ import java.util.function.Predicate;
 * Controller that tracks the number of flexible constraints being actively satisfied.
 * Drops constraint for TOP apps and lowers number of required constraints with time.
 *
 * TODO: Plug in to other controllers (b/239047584), handle prefetch (b/238887951)
 * TODO(b/238887951): handle prefetch
 */
public final class FlexibilityController extends StateController {
    /**
     * List of all potential flexible constraints
     */
    private static final String TAG = "JobScheduler.Flexibility";

    /** List of all system-wide flexible constraints whose satisfaction is independent of job. */
    static final int SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW
            | CONSTRAINT_CHARGING
            | CONSTRAINT_IDLE;

    /** List of all job flexible constraints whose satisfaction is job specific. */
    private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;

    /** List of all flexible constraints. */
    private static final int FLEXIBLE_CONSTRAINTS =
            JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;

    @VisibleForTesting
    static final int FLEXIBLE_CONSTRAINTS = JobStatus.CONSTRAINT_BATTERY_NOT_LOW
            | JobStatus.CONSTRAINT_CHARGING
            | JobStatus.CONSTRAINT_CONNECTIVITY
            | JobStatus.CONSTRAINT_IDLE;
    static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
            Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);

    static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
            Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);

    /** Hard cutoff to remove flexible constraints */
    @VisibleForTesting
    static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);

    /** Hard cutoff to remove flexible constraints. */
    private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;

    /**
     * The default deadline that all flexible constraints should be dropped by if a job lacks
     * a deadline.
     */
    private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;

    /**
     * Keeps track of what flexible constraints are satisfied at the moment.
     * Is updated by the other controllers.
     */
    private int mSatisfiedFlexibleConstraints;
    @VisibleForTesting
    @GuardedBy("mLock")
    int mSatisfiedFlexibleConstraints;
    @GuardedBy("mLock")
    private boolean mFlexibilityEnabled = FcConstants.DEFAULT_FLEXIBILITY_ENABLED;

    @VisibleForTesting
    @GuardedBy("mLock")
    final FlexibilityTracker mFlexibilityTracker;
    private final FcConstants mFcConstants;

    private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
    private final long mMinTimeBetweenAlarmsMs = MINUTE_IN_MILLIS;
    private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;

    /**
     * The percent of a Jobs lifecycle to drop number of required constraints.
     * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
     * PERCENT_TO_DROP_CONSTRAINTS[i] denotes that at x% of a Jobs lifecycle,
     * the controller should have i+1 constraints dropped.
     */
    private final int[] mPercentToDropConstraints = {50, 60, 70, 80};

    /** The default deadline that all flexible constraints should be dropped by. */
    private final long mDefaultFlexibleDeadline = 72 * HOUR_IN_MILLIS;
    private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};

    public FlexibilityController(JobSchedulerService service) {
        super(service);
        mFlexibilityTracker = new FlexibilityTracker(FLEXIBLE_CONSTRAINTS);
        mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
        mFcConstants = new FcConstants();
        mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
                mContext, JobSchedulerBackgroundThread.get().getLooper());
    }

    /**
     * StateController interface
     * StateController interface.
     */
    @Override
    @GuardedBy("mLock")
    public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) {
        if (js.hasFlexibilityConstraint()) {
            mFlexibilityTracker.add(js);
@@ -104,6 +136,7 @@ public final class FlexibilityController extends StateController {
    }

    @Override
    @GuardedBy("mLock")
    public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob, boolean forUpdate) {
        if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
            mFlexibilityAlarmQueue.removeAlarmForKey(js);
@@ -112,27 +145,26 @@ public final class FlexibilityController extends StateController {
    }

    /** Checks if the flexibility constraint is actively satisfied for a given job. */
    @VisibleForTesting
    @GuardedBy("mLock")
    boolean isFlexibilitySatisfiedLocked(JobStatus js) {
        synchronized (mLock) {
            return mService.getUidBias(js.getUid()) == JobInfo.BIAS_TOP_APP
        return !mFlexibilityEnabled
                || mService.getUidBias(js.getUid()) == JobInfo.BIAS_TOP_APP
                || mService.isCurrentlyRunningLocked(js)
                || getNumSatisfiedRequiredConstraintsLocked(js)
                >= js.getNumRequiredFlexibleConstraints();
    }
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) {
        return Integer.bitCount(js.getFlexibleConstraints() & mSatisfiedFlexibleConstraints);
        return Integer.bitCount(mSatisfiedFlexibleConstraints)
                + (js.getHasAccessToUnmetered() ? 1 : 0);
    }

    /**
     * Sets the controller's constraint to a given state.
     * Changes flexibility constraint satisfaction for affected jobs.
     */
    @VisibleForTesting
    void setConstraintSatisfied(int constraint, boolean state) {
        synchronized (mLock) {
            final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
@@ -149,15 +181,28 @@ public final class FlexibilityController extends StateController {
            // The rest did not have a change in state and are still satisfied or unsatisfied.
            final int numConstraintsToUpdate = Math.max(curSatisfied, prevSatisfied);

            final ArraySet<JobStatus> jobs = mFlexibilityTracker.getJobsByNumRequiredConstraints(
                    numConstraintsToUpdate);
            final long nowElapsed = sElapsedRealtimeClock.millis();

            // In order to get the range of all potentially satisfied jobs, we start at the number
            // of satisfied system-wide constraints and iterate to the max number of potentially
            // satisfied constraints, determined by how many job-specific constraints exist.
            for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
                final ArraySet<JobStatus> jobs = mFlexibilityTracker
                        .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);

                if (jobs == null) {
                    // If there are no more jobs to iterate through we can just return.
                    return;
                }

                for (int i = 0; i < jobs.size(); i++) {
                    JobStatus js = jobs.valueAt(i);
                js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
                    js.setFlexibilityConstraintSatisfied(
                            nowElapsed, isFlexibilitySatisfiedLocked(js));
                }
            }

        }
    }

    /** Checks if the given constraint is satisfied in the flexibility controller. */
@@ -173,9 +218,9 @@ public final class FlexibilityController extends StateController {
        final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
                ? js.enqueueTime : js.getEarliestRunTime();
        final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
                ? earliest + mDefaultFlexibleDeadline
                ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
                : js.getLatestRunTimeElapsed();
        final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
        final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
        final long percentInTime = ((latest - earliest) * percent) / 100;
        return earliest + percentInTime;
    }
@@ -196,20 +241,58 @@ public final class FlexibilityController extends StateController {
        }
    }

    @Override
    @GuardedBy("mLock")
    public void onConstantsUpdatedLocked() {
        if (mFcConstants.mShouldReevaluateConstraints) {
            // Update job bookkeeping out of band.
            JobSchedulerBackgroundThread.getHandler().post(() -> {
                final ArraySet<JobStatus> changedJobs = new ArraySet<>();
                synchronized (mLock) {
                    final long nowElapsed = sElapsedRealtimeClock.millis();
                    for (int j = 1; j <= mFlexibilityTracker.size(); j++) {
                        final ArraySet<JobStatus> jobs = mFlexibilityTracker
                                .getJobsByNumRequiredConstraints(j);
                        for (int i = 0; i < jobs.size(); i++) {
                            JobStatus js = jobs.valueAt(i);
                            if (js.setFlexibilityConstraintSatisfied(
                                    nowElapsed, isFlexibilitySatisfiedLocked(js))) {
                                changedJobs.add(js);
                            }
                        }
                    }
                }
                if (changedJobs.size() > 0) {
                    mStateChangedListener.onControllerStateChanged(changedJobs);
                }
            });
        }
    }

    @Override
    @GuardedBy("mLock")
    public void prepareForUpdatedConstantsLocked() {
        mFcConstants.mShouldReevaluateConstraints = false;
    }

    @VisibleForTesting
    class FlexibilityTracker {
        final ArrayList<ArraySet<JobStatus>> mTrackedJobs;

        FlexibilityTracker(int flexibleConstraints) {
        FlexibilityTracker(int numFlexibleConstraints) {
            mTrackedJobs = new ArrayList<>();
            int numFlexibleConstraints = Integer.bitCount(flexibleConstraints);
            for (int i = 0; i <= numFlexibleConstraints; i++) {
                mTrackedJobs.add(new ArraySet<JobStatus>());
            }
        }

        /** Gets every tracked job with a given number of required constraints. */
        @Nullable
        public ArraySet<JobStatus> getJobsByNumRequiredConstraints(int numRequired) {
            if (numRequired > mTrackedJobs.size()) {
                Slog.wtfStack(TAG, "Asked for a larger number of constraints than exists.");
                return null;
            }
            return mTrackedJobs.get(numRequired - 1);
        }

@@ -252,6 +335,10 @@ public final class FlexibilityController extends StateController {
            return true;
        }

        public int size() {
            return mTrackedJobs.size();
        }

        public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
            for (int i = 0; i < mTrackedJobs.size(); i++) {
                ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
@@ -273,7 +360,8 @@ public final class FlexibilityController extends StateController {
    private class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
        private FlexibilityAlarmQueue(Context context, Looper looper) {
            super(context, looper, "*job.flexibility_check*",
                    "Flexible Constraint Check", false, mMinTimeBetweenAlarmsMs);
                    "Flexible Constraint Check", false,
                    MIN_TIME_BETWEEN_ALARMS_MS);
        }

        @Override
@@ -288,12 +376,10 @@ public final class FlexibilityController extends StateController {
                for (int i = 0; i < expired.size(); i++) {
                    js = expired.valueAt(i);
                    long time = getNextConstraintDropTimeElapsed(js);
                    if (js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS) {
                        mFlexibilityTracker.adjustJobsRequiredConstraints(js,
                                -js.getNumRequiredFlexibleConstraints());
                        continue;
                    }
                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1)) {
                    int toDecrease =
                            js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
                            ? -js.getNumRequiredFlexibleConstraints() : -1;
                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) {
                        mFlexibilityAlarmQueue.addAlarm(js, time);
                    }
                }
@@ -301,6 +387,52 @@ public final class FlexibilityController extends StateController {
        }
    }

    @VisibleForTesting
    class FcConstants {
        private boolean mShouldReevaluateConstraints = false;

        private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;

        public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;

        /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
        private static final String FC_CONSTANT_PREFIX = "fc_";

        static final String KEY_FLEXIBILITY_ENABLED = FC_CONSTANT_PREFIX + "enable_flexibility";

        // TODO(b/239925946): properly handle DeviceConfig and changing variables
        @GuardedBy("mLock")
        public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
                @NonNull String key) {
            switch (key) {
                case KEY_FLEXIBILITY_ENABLED:
                    FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED);
                    if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
                        mFlexibilityEnabled = FLEXIBILITY_ENABLED;
                        mShouldReevaluateConstraints = true;
                    }
                    break;
            }
        }

        private void dump(IndentingPrintWriter pw) {
            pw.println();
            pw.print(FlexibilityController.class.getSimpleName());
            pw.println(":");
            pw.increaseIndent();

            pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println();

            pw.decreaseIndent();
        }
    }

    @VisibleForTesting
    @NonNull
    FcConstants getFcConstants() {
        return mFcConstants;
    }

    @Override
    @GuardedBy("mLock")
    public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -308,5 +440,6 @@ public final class FlexibilityController extends StateController {
        pw.println();

        mFlexibilityTracker.dump(pw, predicate);
        mFcConstants.dump(pw);
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -48,10 +48,13 @@ public final class IdleController extends RestrictingController implements Idlen
    // screen off or dreaming or wireless charging dock idle for at least this long
    final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
    IdlenessTracker mIdleTracker;
    private final FlexibilityController mFlexibilityController;

    public IdleController(JobSchedulerService service) {
    public IdleController(JobSchedulerService service,
            FlexibilityController flexibilityController) {
        super(service);
        initIdleStateTracking(mContext);
        mFlexibilityController = flexibilityController;
    }

    /**
@@ -92,6 +95,7 @@ public final class IdleController extends RestrictingController implements Idlen
     */
    @Override
    public void reportNewIdleState(boolean isIdle) {
        mFlexibilityController.setConstraintSatisfied(JobStatus.CONSTRAINT_IDLE, isIdle);
        synchronized (mLock) {
            final long nowElapsed = sElapsedRealtimeClock.millis();
            for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
Loading