Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +4 −3 Original line number Original line Diff line number Diff line Loading @@ -1556,7 +1556,10 @@ public class JobSchedulerService extends com.android.server.SystemService // Create the controllers. // Create the controllers. mControllers = new ArrayList<StateController>(); mControllers = new ArrayList<StateController>(); final FlexibilityController flexibilityController = new FlexibilityController(this); mPrefetchController = new PrefetchController(this); mControllers.add(mPrefetchController); final FlexibilityController flexibilityController = new FlexibilityController(this, mPrefetchController); mControllers.add(flexibilityController); mControllers.add(flexibilityController); final ConnectivityController connectivityController = final ConnectivityController connectivityController = new ConnectivityController(this, flexibilityController); new ConnectivityController(this, flexibilityController); Loading @@ -1575,8 +1578,6 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(new ContentObserverController(this)); mControllers.add(new ContentObserverController(this)); mDeviceIdleJobsController = new DeviceIdleJobsController(this); mDeviceIdleJobsController = new DeviceIdleJobsController(this); mControllers.add(mDeviceIdleJobsController); mControllers.add(mDeviceIdleJobsController); mPrefetchController = new PrefetchController(this); mControllers.add(mPrefetchController); mQuotaController = mQuotaController = new QuotaController(this, backgroundJobsController, connectivityController); new QuotaController(this, backgroundJobsController, connectivityController); mControllers.add(mQuotaController); mControllers.add(mQuotaController); Loading apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +204 −21 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,7 @@ 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_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; 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_CONNECTIVITY; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import android.annotation.ElapsedRealtimeLong; import android.annotation.ElapsedRealtimeLong; Loading @@ -36,6 +37,7 @@ import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.Slog; import android.util.SparseArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading @@ -50,8 +52,6 @@ import java.util.function.Predicate; /** /** * Controller that tracks the number of flexible constraints being actively satisfied. * Controller that tracks the number of flexible constraints being actively satisfied. * Drops constraint for TOP apps and lowers number of required constraints with time. * Drops constraint for TOP apps and lowers number of required constraints with time. * * TODO(b/238887951): handle prefetch */ */ public final class FlexibilityController extends StateController { public final class FlexibilityController extends StateController { private static final String TAG = "JobScheduler.Flexibility"; private static final String TAG = "JobScheduler.Flexibility"; Loading @@ -78,6 +78,8 @@ public final class FlexibilityController extends StateController { @VisibleForTesting @VisibleForTesting static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS); static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS); private static final long NO_LIFECYCLE_END = Long.MAX_VALUE; /** Hard cutoff to remove flexible constraints. */ /** Hard cutoff to remove flexible constraints. */ private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; Loading @@ -85,7 +87,8 @@ public final class FlexibilityController extends StateController { * The default deadline that all flexible constraints should be dropped by if a job lacks * The default deadline that all flexible constraints should be dropped by if a job lacks * a deadline. * a deadline. */ */ private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS; @VisibleForTesting static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS; /** /** * Keeps track of what flexible constraints are satisfied at the moment. * Keeps track of what flexible constraints are satisfied at the moment. Loading @@ -102,6 +105,10 @@ public final class FlexibilityController extends StateController { final FlexibilityTracker mFlexibilityTracker; final FlexibilityTracker mFlexibilityTracker; private final FcConstants mFcConstants; private final FcConstants mFcConstants; @VisibleForTesting final PrefetchController mPrefetchController; @GuardedBy("mLock") private final FlexibilityAlarmQueue mFlexibilityAlarmQueue; private final FlexibilityAlarmQueue mFlexibilityAlarmQueue; private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS; private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS; Loading @@ -112,12 +119,58 @@ public final class FlexibilityController extends StateController { */ */ private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80}; private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80}; public FlexibilityController(JobSchedulerService service) { /** * Stores the beginning of prefetch jobs lifecycle per app as a maximum of * the last time the app was used and the last time the launch time was updated. */ @VisibleForTesting @GuardedBy("mLock") final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>(); @VisibleForTesting final PrefetchController.PrefetchChangedListener mPrefetchChangedListener = new PrefetchController.PrefetchChangedListener() { @Override public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) { synchronized (mLock) { final long nowElapsed = sElapsedRealtimeClock.millis(); final long prefetchThreshold = mPrefetchController.getLaunchTimeThresholdMs(); boolean jobWasInPrefetchWindow = prevEstimatedLaunchTime - prefetchThreshold < nowElapsed; boolean jobIsInPrefetchWindow = newEstimatedLaunchTime - prefetchThreshold < nowElapsed; if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) { // If the job was in the window previously then changing the start // of the lifecycle to the current moment without a large change in the // end would squeeze the window too tight fail to drop constraints. mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed, mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L))); } for (int i = 0; i < jobs.size(); i++) { JobStatus js = jobs.valueAt(i); if (!js.hasFlexibilityConstraint()) { continue; } mFlexibilityTracker.resetJobNumDroppedConstraints(js); mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); } } } }; public FlexibilityController( JobSchedulerService service, PrefetchController prefetchController) { super(service); super(service); mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS); mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS); mFcConstants = new FcConstants(); mFcConstants = new FcConstants(); mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( mContext, JobSchedulerBackgroundThread.get().getLooper()); mContext, JobSchedulerBackgroundThread.get().getLooper()); mPrefetchController = prefetchController; if (mFlexibilityEnabled) { mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener); } } } /** /** Loading @@ -131,7 +184,7 @@ public final class FlexibilityController extends StateController { js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); mFlexibilityAlarmQueue.addAlarm(js, getNextConstraintDropTimeElapsed(js)); mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); } } } } Loading @@ -144,6 +197,19 @@ public final class FlexibilityController extends StateController { } } } } @Override @GuardedBy("mLock") public void onAppRemovedLocked(String packageName, int uid) { final int userId = UserHandle.getUserId(uid); mPrefetchLifeCycleStart.delete(userId, packageName); } @Override @GuardedBy("mLock") public void onUserRemovedLocked(int userId) { mPrefetchLifeCycleStart.delete(userId); } /** Checks if the flexibility constraint is actively satisfied for a given job. */ /** Checks if the flexibility constraint is actively satisfied for a given job. */ @GuardedBy("mLock") @GuardedBy("mLock") boolean isFlexibilitySatisfiedLocked(JobStatus js) { boolean isFlexibilitySatisfiedLocked(JobStatus js) { Loading @@ -165,6 +231,7 @@ public final class FlexibilityController extends StateController { * Sets the controller's constraint to a given state. * Sets the controller's constraint to a given state. * Changes flexibility constraint satisfaction for affected jobs. * Changes flexibility constraint satisfaction for affected jobs. */ */ @VisibleForTesting void setConstraintSatisfied(int constraint, boolean state) { void setConstraintSatisfied(int constraint, boolean state) { synchronized (mLock) { synchronized (mLock) { final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; Loading @@ -187,21 +254,20 @@ public final class FlexibilityController extends StateController { // of satisfied system-wide constraints and iterate to the max number of potentially // of satisfied system-wide constraints and iterate to the max number of potentially // satisfied constraints, determined by how many job-specific constraints exist. // satisfied constraints, determined by how many job-specific constraints exist. for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) { for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) { final ArraySet<JobStatus> jobs = mFlexibilityTracker final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j); .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j); if (jobs == null) { if (jobsByNumConstraints == null) { // If there are no more jobs to iterate through we can just return. // If there are no more jobs to iterate through we can just return. return; return; } } for (int i = 0; i < jobs.size(); i++) { for (int i = 0; i < jobsByNumConstraints.size(); i++) { JobStatus js = jobs.valueAt(i); JobStatus js = jobsByNumConstraints.valueAt(i); js.setFlexibilityConstraintSatisfied( js.setFlexibilityConstraintSatisfied( nowElapsed, isFlexibilitySatisfiedLocked(js)); nowElapsed, isFlexibilitySatisfiedLocked(js)); } } } } } } } } Loading @@ -211,15 +277,77 @@ public final class FlexibilityController extends StateController { return (mSatisfiedFlexibleConstraints & constraint) != 0; return (mSatisfiedFlexibleConstraints & constraint) != 0; } } /** The elapsed time that marks when the next constraint should be dropped. */ @VisibleForTesting @VisibleForTesting @ElapsedRealtimeLong @GuardedBy("mLock") long getNextConstraintDropTimeElapsed(JobStatus js) { long getLifeCycleBeginningElapsedLocked(JobStatus js) { final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME if (js.getJob().isPrefetch()) { final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime()); final long estimatedLaunchTime = mPrefetchController.getNextEstimatedLaunchTimeLocked(js); long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault( js.getSourceUserId(), js.getSourcePackageName(), 0L); if (estimatedLaunchTime != Long.MAX_VALUE) { prefetchWindowStart = Math.max(prefetchWindowStart, estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs()); } return Math.max(prefetchWindowStart, earliestRuntime); } return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME ? js.enqueueTime : js.getEarliestRunTime(); ? js.enqueueTime : js.getEarliestRunTime(); final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME } @VisibleForTesting @GuardedBy("mLock") long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) { if (js.getJob().isPrefetch()) { final long estimatedLaunchTime = mPrefetchController.getNextEstimatedLaunchTimeLocked(js); // Prefetch jobs aren't supposed to have deadlines after T. // But some legacy apps might still schedule them with deadlines. if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) { // If there is a deadline, the earliest time is the end of the lifecycle. return Math.min( estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS, js.getLatestRunTimeElapsed()); } if (estimatedLaunchTime != Long.MAX_VALUE) { return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS; } // There is no deadline and no estimated launch time. return NO_LIFECYCLE_END; } return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME ? earliest + DEFAULT_FLEXIBILITY_DEADLINE ? earliest + DEFAULT_FLEXIBILITY_DEADLINE : js.getLatestRunTimeElapsed(); : js.getLatestRunTimeElapsed(); } @VisibleForTesting @GuardedBy("mLock") int getCurPercentOfLifecycleLocked(JobStatus js) { final long earliest = getLifeCycleBeginningElapsedLocked(js); final long latest = getLifeCycleEndElapsedLocked(js, earliest); final long nowElapsed = sElapsedRealtimeClock.millis(); if (latest == NO_LIFECYCLE_END || earliest > nowElapsed) { return 0; } if (nowElapsed > latest || latest == earliest) { return 100; } final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest)); return percentInTime; } /** The elapsed time that marks when the next constraint should be dropped. */ @VisibleForTesting @ElapsedRealtimeLong @GuardedBy("mLock") long getNextConstraintDropTimeElapsedLocked(JobStatus js) { final long earliest = getLifeCycleBeginningElapsedLocked(js); final long latest = getLifeCycleEndElapsedLocked(js, earliest); if (latest == NO_LIFECYCLE_END || js.getNumDroppedFlexibleConstraints() == PERCENT_TO_DROP_CONSTRAINTS.length) { return NO_LIFECYCLE_END; } final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()]; final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()]; final long percentInTime = ((latest - earliest) * percent) / 100; final long percentInTime = ((latest - earliest) * percent) / 100; return earliest + percentInTime; return earliest + percentInTime; Loading @@ -233,10 +361,28 @@ public final class FlexibilityController extends StateController { } } final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid); List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid); boolean hasPrefetch = false; for (int i = 0; i < jobsByUid.size(); i++) { for (int i = 0; i < jobsByUid.size(); i++) { JobStatus js = jobsByUid.get(i); JobStatus js = jobsByUid.get(i); if (js.hasFlexibilityConstraint()) { if (js.hasFlexibilityConstraint()) { js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); hasPrefetch |= js.getJob().isPrefetch(); } } // Prefetch jobs can't run when the app is TOP, so it should not be included in their // lifecycle, and marks the beginning of a new lifecycle. if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) { final int userId = UserHandle.getUserId(uid); final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid); if (pkgs == null) { return; } for (int i = 0; i < pkgs.size(); i++) { String pkg = pkgs.valueAt(i); mPrefetchLifeCycleStart.add(userId, pkg, Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L), nowElapsed)); } } } } } } Loading Loading @@ -281,7 +427,7 @@ public final class FlexibilityController extends StateController { FlexibilityTracker(int numFlexibleConstraints) { FlexibilityTracker(int numFlexibleConstraints) { mTrackedJobs = new ArrayList<>(); mTrackedJobs = new ArrayList<>(); for (int i = 0; i <= numFlexibleConstraints; i++) { for (int i = 0; i < numFlexibleConstraints; i++) { mTrackedJobs.add(new ArraySet<JobStatus>()); mTrackedJobs.add(new ArraySet<JobStatus>()); } } } } Loading Loading @@ -312,6 +458,17 @@ public final class FlexibilityController extends StateController { mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js); mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js); } } public void resetJobNumDroppedConstraints(JobStatus js) { final int curPercent = getCurPercentOfLifecycleLocked(js); int toDrop = 0; for (int i = 0; i < PERCENT_TO_DROP_CONSTRAINTS.length; i++) { if (curPercent >= PERCENT_TO_DROP_CONSTRAINTS[i]) { toDrop++; } } adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop); } /** Returns all tracked jobs. */ /** Returns all tracked jobs. */ public ArrayList<ArraySet<JobStatus>> getArrayList() { public ArrayList<ArraySet<JobStatus>> getArrayList() { return mTrackedJobs; return mTrackedJobs; Loading Loading @@ -369,20 +526,39 @@ public final class FlexibilityController extends StateController { return js.getSourceUserId() == userId; return js.getSourceUserId() == userId; } } protected void scheduleDropNumConstraintsAlarm(JobStatus js) { long nextTimeElapsed; synchronized (mLock) { nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js); if (nextTimeElapsed == NO_LIFECYCLE_END) { // There is no known or estimated next time to drop a constraint. removeAlarmForKey(js); return; } mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed); } } @Override @Override protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) { protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) { synchronized (mLock) { synchronized (mLock) { JobStatus js; ArraySet<JobStatus> changedJobs = new ArraySet<>(); for (int i = 0; i < expired.size(); i++) { for (int i = 0; i < expired.size(); i++) { js = expired.valueAt(i); JobStatus js = expired.valueAt(i); long time = getNextConstraintDropTimeElapsed(js); boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE); long time = getNextConstraintDropTimeElapsedLocked(js); int toDecrease = int toDecrease = js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS ? -js.getNumRequiredFlexibleConstraints() : -1; ? -js.getNumRequiredFlexibleConstraints() : -1; if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) { if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease) && time != NO_LIFECYCLE_END) { mFlexibilityAlarmQueue.addAlarm(js, time); mFlexibilityAlarmQueue.addAlarm(js, time); } } if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) { changedJobs.add(js); } } } mStateChangedListener.onControllerStateChanged(changedJobs); } } } } } } Loading Loading @@ -410,6 +586,13 @@ public final class FlexibilityController extends StateController { if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) { if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) { mFlexibilityEnabled = FLEXIBILITY_ENABLED; mFlexibilityEnabled = FLEXIBILITY_ENABLED; mShouldReevaluateConstraints = true; mShouldReevaluateConstraints = true; if (mFlexibilityEnabled) { mPrefetchController .registerPrefetchChangedListener(mPrefetchChangedListener); } else { mPrefetchController .unRegisterPrefetchChangedListener(mPrefetchChangedListener); } } } break; break; } } Loading apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +0 −1 Original line number Original line Diff line number Diff line Loading @@ -574,7 +574,6 @@ public final class JobStatus { if (!isRequestedExpeditedJob() if (!isRequestedExpeditedJob() && satisfiesMinWindowException && satisfiesMinWindowException && !job.isPrefetch() && lacksSomeFlexibleConstraints) { && lacksSomeFlexibleConstraints) { mNumRequiredFlexibleConstraints = mNumRequiredFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); Loading apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +26 −0 Original line number Original line Diff line number Diff line Loading @@ -81,6 +81,8 @@ public class PrefetchController extends StateController { */ */ @GuardedBy("mLock") @GuardedBy("mLock") private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>(); private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>(); @GuardedBy("mLock") private final ArraySet<PrefetchChangedListener> mPrefetchChangedListeners = new ArraySet<>(); private final ThresholdAlarmListener mThresholdAlarmListener; private final ThresholdAlarmListener mThresholdAlarmListener; /** /** Loading @@ -99,6 +101,13 @@ public class PrefetchController extends StateController { @GuardedBy("mLock") @GuardedBy("mLock") private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS; private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS; /** Called by Prefetch Controller after local cache has been updated */ public interface PrefetchChangedListener { /** Callback to inform listeners when estimated launch times change. */ void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime); } @SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal") private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener = private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener = new EstimatedLaunchTimeChangedListener() { new EstimatedLaunchTimeChangedListener() { Loading Loading @@ -291,12 +300,17 @@ public class PrefetchController extends StateController { // Don't bother caching the value unless the app has scheduled prefetch jobs // Don't bother caching the value unless the app has scheduled prefetch jobs // before. This is based on the assumption that if an app has scheduled a // before. This is based on the assumption that if an app has scheduled a // prefetch job before, then it will probably schedule another one again. // prefetch job before, then it will probably schedule another one again. final long prevEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName); mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime); mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime); if (!jobs.isEmpty()) { if (!jobs.isEmpty()) { final long now = sSystemClock.millis(); final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed); updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed); for (int i = 0; i < mPrefetchChangedListeners.size(); i++) { mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs, userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime); } if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) { if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) { mStateChangedListener.onControllerStateChanged(jobs); mStateChangedListener.onControllerStateChanged(jobs); } } Loading Loading @@ -448,6 +462,18 @@ public class PrefetchController extends StateController { } } } } void registerPrefetchChangedListener(PrefetchChangedListener listener) { synchronized (mLock) { mPrefetchChangedListeners.add(listener); } } void unRegisterPrefetchChangedListener(PrefetchChangedListener listener) { synchronized (mLock) { mPrefetchChangedListeners.remove(listener); } } private class PcHandler extends Handler { private class PcHandler extends Handler { PcHandler(Looper looper) { PcHandler(Looper looper) { super(looper); super(looper); Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -100,7 +100,9 @@ public class BatteryControllerTest { // Capture the listeners. // Capture the listeners. ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); ArgumentCaptor.forClass(BroadcastReceiver.class); mFlexibilityController = new FlexibilityController(mJobSchedulerService); mFlexibilityController = new FlexibilityController(mJobSchedulerService, mock(PrefetchController.class)); mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController); mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController); verify(mContext).registerReceiver(receiverCaptor.capture(), verify(mContext).registerReceiver(receiverCaptor.capture(), Loading Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +4 −3 Original line number Original line Diff line number Diff line Loading @@ -1556,7 +1556,10 @@ public class JobSchedulerService extends com.android.server.SystemService // Create the controllers. // Create the controllers. mControllers = new ArrayList<StateController>(); mControllers = new ArrayList<StateController>(); final FlexibilityController flexibilityController = new FlexibilityController(this); mPrefetchController = new PrefetchController(this); mControllers.add(mPrefetchController); final FlexibilityController flexibilityController = new FlexibilityController(this, mPrefetchController); mControllers.add(flexibilityController); mControllers.add(flexibilityController); final ConnectivityController connectivityController = final ConnectivityController connectivityController = new ConnectivityController(this, flexibilityController); new ConnectivityController(this, flexibilityController); Loading @@ -1575,8 +1578,6 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(new ContentObserverController(this)); mControllers.add(new ContentObserverController(this)); mDeviceIdleJobsController = new DeviceIdleJobsController(this); mDeviceIdleJobsController = new DeviceIdleJobsController(this); mControllers.add(mDeviceIdleJobsController); mControllers.add(mDeviceIdleJobsController); mPrefetchController = new PrefetchController(this); mControllers.add(mPrefetchController); mQuotaController = mQuotaController = new QuotaController(this, backgroundJobsController, connectivityController); new QuotaController(this, backgroundJobsController, connectivityController); mControllers.add(mQuotaController); mControllers.add(mQuotaController); Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +204 −21 Original line number Original line Diff line number Diff line Loading @@ -23,6 +23,7 @@ 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_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; 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_CONNECTIVITY; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import android.annotation.ElapsedRealtimeLong; import android.annotation.ElapsedRealtimeLong; Loading @@ -36,6 +37,7 @@ import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.Slog; import android.util.SparseArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting; Loading @@ -50,8 +52,6 @@ import java.util.function.Predicate; /** /** * Controller that tracks the number of flexible constraints being actively satisfied. * Controller that tracks the number of flexible constraints being actively satisfied. * Drops constraint for TOP apps and lowers number of required constraints with time. * Drops constraint for TOP apps and lowers number of required constraints with time. * * TODO(b/238887951): handle prefetch */ */ public final class FlexibilityController extends StateController { public final class FlexibilityController extends StateController { private static final String TAG = "JobScheduler.Flexibility"; private static final String TAG = "JobScheduler.Flexibility"; Loading @@ -78,6 +78,8 @@ public final class FlexibilityController extends StateController { @VisibleForTesting @VisibleForTesting static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS); static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS); private static final long NO_LIFECYCLE_END = Long.MAX_VALUE; /** Hard cutoff to remove flexible constraints. */ /** Hard cutoff to remove flexible constraints. */ private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; Loading @@ -85,7 +87,8 @@ public final class FlexibilityController extends StateController { * The default deadline that all flexible constraints should be dropped by if a job lacks * The default deadline that all flexible constraints should be dropped by if a job lacks * a deadline. * a deadline. */ */ private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS; @VisibleForTesting static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS; /** /** * Keeps track of what flexible constraints are satisfied at the moment. * Keeps track of what flexible constraints are satisfied at the moment. Loading @@ -102,6 +105,10 @@ public final class FlexibilityController extends StateController { final FlexibilityTracker mFlexibilityTracker; final FlexibilityTracker mFlexibilityTracker; private final FcConstants mFcConstants; private final FcConstants mFcConstants; @VisibleForTesting final PrefetchController mPrefetchController; @GuardedBy("mLock") private final FlexibilityAlarmQueue mFlexibilityAlarmQueue; private final FlexibilityAlarmQueue mFlexibilityAlarmQueue; private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS; private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS; Loading @@ -112,12 +119,58 @@ public final class FlexibilityController extends StateController { */ */ private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80}; private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80}; public FlexibilityController(JobSchedulerService service) { /** * Stores the beginning of prefetch jobs lifecycle per app as a maximum of * the last time the app was used and the last time the launch time was updated. */ @VisibleForTesting @GuardedBy("mLock") final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>(); @VisibleForTesting final PrefetchController.PrefetchChangedListener mPrefetchChangedListener = new PrefetchController.PrefetchChangedListener() { @Override public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) { synchronized (mLock) { final long nowElapsed = sElapsedRealtimeClock.millis(); final long prefetchThreshold = mPrefetchController.getLaunchTimeThresholdMs(); boolean jobWasInPrefetchWindow = prevEstimatedLaunchTime - prefetchThreshold < nowElapsed; boolean jobIsInPrefetchWindow = newEstimatedLaunchTime - prefetchThreshold < nowElapsed; if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) { // If the job was in the window previously then changing the start // of the lifecycle to the current moment without a large change in the // end would squeeze the window too tight fail to drop constraints. mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed, mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L))); } for (int i = 0; i < jobs.size(); i++) { JobStatus js = jobs.valueAt(i); if (!js.hasFlexibilityConstraint()) { continue; } mFlexibilityTracker.resetJobNumDroppedConstraints(js); mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); } } } }; public FlexibilityController( JobSchedulerService service, PrefetchController prefetchController) { super(service); super(service); mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS); mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS); mFcConstants = new FcConstants(); mFcConstants = new FcConstants(); mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( mContext, JobSchedulerBackgroundThread.get().getLooper()); mContext, JobSchedulerBackgroundThread.get().getLooper()); mPrefetchController = prefetchController; if (mFlexibilityEnabled) { mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener); } } } /** /** Loading @@ -131,7 +184,7 @@ public final class FlexibilityController extends StateController { js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); mFlexibilityAlarmQueue.addAlarm(js, getNextConstraintDropTimeElapsed(js)); mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js); } } } } Loading @@ -144,6 +197,19 @@ public final class FlexibilityController extends StateController { } } } } @Override @GuardedBy("mLock") public void onAppRemovedLocked(String packageName, int uid) { final int userId = UserHandle.getUserId(uid); mPrefetchLifeCycleStart.delete(userId, packageName); } @Override @GuardedBy("mLock") public void onUserRemovedLocked(int userId) { mPrefetchLifeCycleStart.delete(userId); } /** Checks if the flexibility constraint is actively satisfied for a given job. */ /** Checks if the flexibility constraint is actively satisfied for a given job. */ @GuardedBy("mLock") @GuardedBy("mLock") boolean isFlexibilitySatisfiedLocked(JobStatus js) { boolean isFlexibilitySatisfiedLocked(JobStatus js) { Loading @@ -165,6 +231,7 @@ public final class FlexibilityController extends StateController { * Sets the controller's constraint to a given state. * Sets the controller's constraint to a given state. * Changes flexibility constraint satisfaction for affected jobs. * Changes flexibility constraint satisfaction for affected jobs. */ */ @VisibleForTesting void setConstraintSatisfied(int constraint, boolean state) { void setConstraintSatisfied(int constraint, boolean state) { synchronized (mLock) { synchronized (mLock) { final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; Loading @@ -187,21 +254,20 @@ public final class FlexibilityController extends StateController { // of satisfied system-wide constraints and iterate to the max number of potentially // of satisfied system-wide constraints and iterate to the max number of potentially // satisfied constraints, determined by how many job-specific constraints exist. // satisfied constraints, determined by how many job-specific constraints exist. for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) { for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) { final ArraySet<JobStatus> jobs = mFlexibilityTracker final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j); .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j); if (jobs == null) { if (jobsByNumConstraints == null) { // If there are no more jobs to iterate through we can just return. // If there are no more jobs to iterate through we can just return. return; return; } } for (int i = 0; i < jobs.size(); i++) { for (int i = 0; i < jobsByNumConstraints.size(); i++) { JobStatus js = jobs.valueAt(i); JobStatus js = jobsByNumConstraints.valueAt(i); js.setFlexibilityConstraintSatisfied( js.setFlexibilityConstraintSatisfied( nowElapsed, isFlexibilitySatisfiedLocked(js)); nowElapsed, isFlexibilitySatisfiedLocked(js)); } } } } } } } } Loading @@ -211,15 +277,77 @@ public final class FlexibilityController extends StateController { return (mSatisfiedFlexibleConstraints & constraint) != 0; return (mSatisfiedFlexibleConstraints & constraint) != 0; } } /** The elapsed time that marks when the next constraint should be dropped. */ @VisibleForTesting @VisibleForTesting @ElapsedRealtimeLong @GuardedBy("mLock") long getNextConstraintDropTimeElapsed(JobStatus js) { long getLifeCycleBeginningElapsedLocked(JobStatus js) { final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME if (js.getJob().isPrefetch()) { final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime()); final long estimatedLaunchTime = mPrefetchController.getNextEstimatedLaunchTimeLocked(js); long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault( js.getSourceUserId(), js.getSourcePackageName(), 0L); if (estimatedLaunchTime != Long.MAX_VALUE) { prefetchWindowStart = Math.max(prefetchWindowStart, estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs()); } return Math.max(prefetchWindowStart, earliestRuntime); } return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME ? js.enqueueTime : js.getEarliestRunTime(); ? js.enqueueTime : js.getEarliestRunTime(); final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME } @VisibleForTesting @GuardedBy("mLock") long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) { if (js.getJob().isPrefetch()) { final long estimatedLaunchTime = mPrefetchController.getNextEstimatedLaunchTimeLocked(js); // Prefetch jobs aren't supposed to have deadlines after T. // But some legacy apps might still schedule them with deadlines. if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) { // If there is a deadline, the earliest time is the end of the lifecycle. return Math.min( estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS, js.getLatestRunTimeElapsed()); } if (estimatedLaunchTime != Long.MAX_VALUE) { return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS; } // There is no deadline and no estimated launch time. return NO_LIFECYCLE_END; } return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME ? earliest + DEFAULT_FLEXIBILITY_DEADLINE ? earliest + DEFAULT_FLEXIBILITY_DEADLINE : js.getLatestRunTimeElapsed(); : js.getLatestRunTimeElapsed(); } @VisibleForTesting @GuardedBy("mLock") int getCurPercentOfLifecycleLocked(JobStatus js) { final long earliest = getLifeCycleBeginningElapsedLocked(js); final long latest = getLifeCycleEndElapsedLocked(js, earliest); final long nowElapsed = sElapsedRealtimeClock.millis(); if (latest == NO_LIFECYCLE_END || earliest > nowElapsed) { return 0; } if (nowElapsed > latest || latest == earliest) { return 100; } final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest)); return percentInTime; } /** The elapsed time that marks when the next constraint should be dropped. */ @VisibleForTesting @ElapsedRealtimeLong @GuardedBy("mLock") long getNextConstraintDropTimeElapsedLocked(JobStatus js) { final long earliest = getLifeCycleBeginningElapsedLocked(js); final long latest = getLifeCycleEndElapsedLocked(js, earliest); if (latest == NO_LIFECYCLE_END || js.getNumDroppedFlexibleConstraints() == PERCENT_TO_DROP_CONSTRAINTS.length) { return NO_LIFECYCLE_END; } final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()]; final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()]; final long percentInTime = ((latest - earliest) * percent) / 100; final long percentInTime = ((latest - earliest) * percent) / 100; return earliest + percentInTime; return earliest + percentInTime; Loading @@ -233,10 +361,28 @@ public final class FlexibilityController extends StateController { } } final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid); List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid); boolean hasPrefetch = false; for (int i = 0; i < jobsByUid.size(); i++) { for (int i = 0; i < jobsByUid.size(); i++) { JobStatus js = jobsByUid.get(i); JobStatus js = jobsByUid.get(i); if (js.hasFlexibilityConstraint()) { if (js.hasFlexibilityConstraint()) { js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); hasPrefetch |= js.getJob().isPrefetch(); } } // Prefetch jobs can't run when the app is TOP, so it should not be included in their // lifecycle, and marks the beginning of a new lifecycle. if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) { final int userId = UserHandle.getUserId(uid); final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid); if (pkgs == null) { return; } for (int i = 0; i < pkgs.size(); i++) { String pkg = pkgs.valueAt(i); mPrefetchLifeCycleStart.add(userId, pkg, Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L), nowElapsed)); } } } } } } Loading Loading @@ -281,7 +427,7 @@ public final class FlexibilityController extends StateController { FlexibilityTracker(int numFlexibleConstraints) { FlexibilityTracker(int numFlexibleConstraints) { mTrackedJobs = new ArrayList<>(); mTrackedJobs = new ArrayList<>(); for (int i = 0; i <= numFlexibleConstraints; i++) { for (int i = 0; i < numFlexibleConstraints; i++) { mTrackedJobs.add(new ArraySet<JobStatus>()); mTrackedJobs.add(new ArraySet<JobStatus>()); } } } } Loading Loading @@ -312,6 +458,17 @@ public final class FlexibilityController extends StateController { mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js); mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js); } } public void resetJobNumDroppedConstraints(JobStatus js) { final int curPercent = getCurPercentOfLifecycleLocked(js); int toDrop = 0; for (int i = 0; i < PERCENT_TO_DROP_CONSTRAINTS.length; i++) { if (curPercent >= PERCENT_TO_DROP_CONSTRAINTS[i]) { toDrop++; } } adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop); } /** Returns all tracked jobs. */ /** Returns all tracked jobs. */ public ArrayList<ArraySet<JobStatus>> getArrayList() { public ArrayList<ArraySet<JobStatus>> getArrayList() { return mTrackedJobs; return mTrackedJobs; Loading Loading @@ -369,20 +526,39 @@ public final class FlexibilityController extends StateController { return js.getSourceUserId() == userId; return js.getSourceUserId() == userId; } } protected void scheduleDropNumConstraintsAlarm(JobStatus js) { long nextTimeElapsed; synchronized (mLock) { nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js); if (nextTimeElapsed == NO_LIFECYCLE_END) { // There is no known or estimated next time to drop a constraint. removeAlarmForKey(js); return; } mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed); } } @Override @Override protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) { protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) { synchronized (mLock) { synchronized (mLock) { JobStatus js; ArraySet<JobStatus> changedJobs = new ArraySet<>(); for (int i = 0; i < expired.size(); i++) { for (int i = 0; i < expired.size(); i++) { js = expired.valueAt(i); JobStatus js = expired.valueAt(i); long time = getNextConstraintDropTimeElapsed(js); boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE); long time = getNextConstraintDropTimeElapsedLocked(js); int toDecrease = int toDecrease = js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS ? -js.getNumRequiredFlexibleConstraints() : -1; ? -js.getNumRequiredFlexibleConstraints() : -1; if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) { if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease) && time != NO_LIFECYCLE_END) { mFlexibilityAlarmQueue.addAlarm(js, time); mFlexibilityAlarmQueue.addAlarm(js, time); } } if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) { changedJobs.add(js); } } } mStateChangedListener.onControllerStateChanged(changedJobs); } } } } } } Loading Loading @@ -410,6 +586,13 @@ public final class FlexibilityController extends StateController { if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) { if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) { mFlexibilityEnabled = FLEXIBILITY_ENABLED; mFlexibilityEnabled = FLEXIBILITY_ENABLED; mShouldReevaluateConstraints = true; mShouldReevaluateConstraints = true; if (mFlexibilityEnabled) { mPrefetchController .registerPrefetchChangedListener(mPrefetchChangedListener); } else { mPrefetchController .unRegisterPrefetchChangedListener(mPrefetchChangedListener); } } } break; break; } } Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +0 −1 Original line number Original line Diff line number Diff line Loading @@ -574,7 +574,6 @@ public final class JobStatus { if (!isRequestedExpeditedJob() if (!isRequestedExpeditedJob() && satisfiesMinWindowException && satisfiesMinWindowException && !job.isPrefetch() && lacksSomeFlexibleConstraints) { && lacksSomeFlexibleConstraints) { mNumRequiredFlexibleConstraints = mNumRequiredFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); Loading
apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +26 −0 Original line number Original line Diff line number Diff line Loading @@ -81,6 +81,8 @@ public class PrefetchController extends StateController { */ */ @GuardedBy("mLock") @GuardedBy("mLock") private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>(); private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>(); @GuardedBy("mLock") private final ArraySet<PrefetchChangedListener> mPrefetchChangedListeners = new ArraySet<>(); private final ThresholdAlarmListener mThresholdAlarmListener; private final ThresholdAlarmListener mThresholdAlarmListener; /** /** Loading @@ -99,6 +101,13 @@ public class PrefetchController extends StateController { @GuardedBy("mLock") @GuardedBy("mLock") private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS; private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS; /** Called by Prefetch Controller after local cache has been updated */ public interface PrefetchChangedListener { /** Callback to inform listeners when estimated launch times change. */ void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime); } @SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal") private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener = private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener = new EstimatedLaunchTimeChangedListener() { new EstimatedLaunchTimeChangedListener() { Loading Loading @@ -291,12 +300,17 @@ public class PrefetchController extends StateController { // Don't bother caching the value unless the app has scheduled prefetch jobs // Don't bother caching the value unless the app has scheduled prefetch jobs // before. This is based on the assumption that if an app has scheduled a // before. This is based on the assumption that if an app has scheduled a // prefetch job before, then it will probably schedule another one again. // prefetch job before, then it will probably schedule another one again. final long prevEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName); mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime); mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime); if (!jobs.isEmpty()) { if (!jobs.isEmpty()) { final long now = sSystemClock.millis(); final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed); updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed); for (int i = 0; i < mPrefetchChangedListeners.size(); i++) { mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs, userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime); } if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) { if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) { mStateChangedListener.onControllerStateChanged(jobs); mStateChangedListener.onControllerStateChanged(jobs); } } Loading Loading @@ -448,6 +462,18 @@ public class PrefetchController extends StateController { } } } } void registerPrefetchChangedListener(PrefetchChangedListener listener) { synchronized (mLock) { mPrefetchChangedListeners.add(listener); } } void unRegisterPrefetchChangedListener(PrefetchChangedListener listener) { synchronized (mLock) { mPrefetchChangedListeners.remove(listener); } } private class PcHandler extends Handler { private class PcHandler extends Handler { PcHandler(Looper looper) { PcHandler(Looper looper) { super(looper); super(looper); Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -100,7 +100,9 @@ public class BatteryControllerTest { // Capture the listeners. // Capture the listeners. ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); ArgumentCaptor.forClass(BroadcastReceiver.class); mFlexibilityController = new FlexibilityController(mJobSchedulerService); mFlexibilityController = new FlexibilityController(mJobSchedulerService, mock(PrefetchController.class)); mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController); mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController); verify(mContext).registerReceiver(receiverCaptor.capture(), verify(mContext).registerReceiver(receiverCaptor.capture(), Loading