Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +8 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +9 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); Loading apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +13 −1 Original line number Diff line number Diff line Loading @@ -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") Loading Loading @@ -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. Loading Loading @@ -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. Loading apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +176 −43 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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); Loading @@ -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; Loading @@ -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. */ Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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 Loading @@ -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); } } Loading @@ -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) { Loading @@ -308,5 +440,6 @@ public final class FlexibilityController extends StateController { pw.println(); mFlexibilityTracker.dump(pw, predicate); mFcConstants.dump(pw); } } apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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 Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +8 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +9 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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); Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +13 −1 Original line number Diff line number Diff line Loading @@ -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") Loading Loading @@ -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. Loading Loading @@ -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. Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +176 −43 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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); Loading @@ -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; Loading @@ -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. */ Loading @@ -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; } Loading @@ -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); } Loading Loading @@ -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); Loading @@ -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 Loading @@ -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); } } Loading @@ -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) { Loading @@ -308,5 +440,6 @@ public final class FlexibilityController extends StateController { pw.println(); mFlexibilityTracker.dump(pw, predicate); mFcConstants.dump(pw); } }
apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; } /** Loading Loading @@ -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