1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.job.controllers; 18 19 import static android.text.format.DateUtils.DAY_IN_MILLIS; 20 import static android.text.format.DateUtils.HOUR_IN_MILLIS; 21 import static android.text.format.DateUtils.MINUTE_IN_MILLIS; 22 23 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 24 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; 25 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; 26 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; 27 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; 28 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; 29 30 import android.annotation.ElapsedRealtimeLong; 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.app.job.JobInfo; 34 import android.content.Context; 35 import android.content.pm.PackageManager; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.os.Message; 39 import android.os.UserHandle; 40 import android.provider.DeviceConfig; 41 import android.util.ArraySet; 42 import android.util.IndentingPrintWriter; 43 import android.util.Log; 44 import android.util.Slog; 45 import android.util.SparseArrayMap; 46 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.server.AppSchedulingModuleThread; 50 import com.android.server.job.JobSchedulerService; 51 import com.android.server.utils.AlarmQueue; 52 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 import java.util.function.Predicate; 56 57 /** 58 * Controller that tracks the number of flexible constraints being actively satisfied. 59 * Drops constraint for TOP apps and lowers number of required constraints with time. 60 */ 61 public final class FlexibilityController extends StateController { 62 private static final String TAG = "JobScheduler.Flex"; 63 private static final boolean DEBUG = JobSchedulerService.DEBUG 64 || Log.isLoggable(TAG, Log.DEBUG); 65 66 /** List of all system-wide flexible constraints whose satisfaction is independent of job. */ 67 static final int SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW 68 | CONSTRAINT_CHARGING 69 | CONSTRAINT_IDLE; 70 71 /** List of flexible constraints a job can opt into. */ 72 static final int OPTIONAL_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW 73 | CONSTRAINT_CHARGING 74 | CONSTRAINT_IDLE; 75 76 /** List of all job flexible constraints whose satisfaction is job specific. */ 77 private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY; 78 79 /** List of all flexible constraints. */ 80 private static final int FLEXIBLE_CONSTRAINTS = 81 JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; 82 83 private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = 84 Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS); 85 86 static final int NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS = 87 Integer.bitCount(OPTIONAL_FLEXIBLE_CONSTRAINTS); 88 89 static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = 90 Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS); 91 92 static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS); 93 94 private static final long NO_LIFECYCLE_END = Long.MAX_VALUE; 95 96 /** 97 * The default deadline that all flexible constraints should be dropped by if a job lacks 98 * a deadline. 99 */ 100 private long mFallbackFlexibilityDeadlineMs = 101 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; 102 103 private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; 104 private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; 105 106 @VisibleForTesting 107 @GuardedBy("mLock") 108 boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED; 109 110 private long mMinTimeBetweenFlexibilityAlarmsMs = 111 FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; 112 113 /** Hard cutoff to remove flexible constraints. */ 114 private long mDeadlineProximityLimitMs = 115 FcConfig.DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS; 116 117 /** 118 * The percent of a job's lifecycle to drop number of required constraints. 119 * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle, 120 * the controller should have i+1 constraints dropped. 121 */ 122 private int[] mPercentToDropConstraints; 123 124 @VisibleForTesting 125 boolean mDeviceSupportsFlexConstraints; 126 127 /** 128 * Keeps track of what flexible constraints are satisfied at the moment. 129 * Is updated by the other controllers. 130 */ 131 @VisibleForTesting 132 @GuardedBy("mLock") 133 int mSatisfiedFlexibleConstraints; 134 135 @VisibleForTesting 136 @GuardedBy("mLock") 137 final FlexibilityTracker mFlexibilityTracker; 138 @VisibleForTesting 139 @GuardedBy("mLock") 140 final FlexibilityAlarmQueue mFlexibilityAlarmQueue; 141 @VisibleForTesting 142 final FcConfig mFcConfig; 143 private final FcHandler mHandler; 144 @VisibleForTesting 145 final PrefetchController mPrefetchController; 146 147 /** 148 * Stores the beginning of prefetch jobs lifecycle per app as a maximum of 149 * the last time the app was used and the last time the launch time was updated. 150 */ 151 @VisibleForTesting 152 @GuardedBy("mLock") 153 final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>(); 154 155 @VisibleForTesting 156 final PrefetchController.PrefetchChangedListener mPrefetchChangedListener = 157 new PrefetchController.PrefetchChangedListener() { 158 @Override 159 public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, 160 String pkgName, long prevEstimatedLaunchTime, 161 long newEstimatedLaunchTime, long nowElapsed) { 162 synchronized (mLock) { 163 final long prefetchThreshold = 164 mPrefetchController.getLaunchTimeThresholdMs(); 165 boolean jobWasInPrefetchWindow = prevEstimatedLaunchTime 166 - prefetchThreshold < nowElapsed; 167 boolean jobIsInPrefetchWindow = newEstimatedLaunchTime 168 - prefetchThreshold < nowElapsed; 169 if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) { 170 // If the job was in the window previously then changing the start 171 // of the lifecycle to the current moment without a large change in the 172 // end would squeeze the window too tight fail to drop constraints. 173 mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed, 174 mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L))); 175 } 176 for (int i = 0; i < jobs.size(); i++) { 177 JobStatus js = jobs.valueAt(i); 178 if (!js.hasFlexibilityConstraint()) { 179 continue; 180 } 181 mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); 182 mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); 183 } 184 } 185 } 186 }; 187 188 private static final int MSG_UPDATE_JOBS = 0; 189 FlexibilityController( JobSchedulerService service, PrefetchController prefetchController)190 public FlexibilityController( 191 JobSchedulerService service, PrefetchController prefetchController) { 192 super(service); 193 mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper()); 194 mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature( 195 PackageManager.FEATURE_AUTOMOTIVE); 196 mFlexibilityEnabled &= mDeviceSupportsFlexConstraints; 197 mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS); 198 mFcConfig = new FcConfig(); 199 mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( 200 mContext, AppSchedulingModuleThread.get().getLooper()); 201 mPercentToDropConstraints = 202 mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; 203 mPrefetchController = prefetchController; 204 if (mFlexibilityEnabled) { 205 mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener); 206 } 207 } 208 209 /** 210 * StateController interface. 211 */ 212 @Override 213 @GuardedBy("mLock") maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob)214 public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) { 215 if (js.hasFlexibilityConstraint()) { 216 final long nowElapsed = sElapsedRealtimeClock.millis(); 217 if (!mDeviceSupportsFlexConstraints) { 218 js.setFlexibilityConstraintSatisfied(nowElapsed, true); 219 return; 220 } 221 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); 222 mFlexibilityTracker.add(js); 223 js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); 224 mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); 225 } 226 } 227 228 @Override 229 @GuardedBy("mLock") maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob)230 public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) { 231 if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) { 232 mFlexibilityAlarmQueue.removeAlarmForKey(js); 233 mFlexibilityTracker.remove(js); 234 } 235 } 236 237 @Override 238 @GuardedBy("mLock") onAppRemovedLocked(String packageName, int uid)239 public void onAppRemovedLocked(String packageName, int uid) { 240 final int userId = UserHandle.getUserId(uid); 241 mPrefetchLifeCycleStart.delete(userId, packageName); 242 } 243 244 @Override 245 @GuardedBy("mLock") onUserRemovedLocked(int userId)246 public void onUserRemovedLocked(int userId) { 247 mPrefetchLifeCycleStart.delete(userId); 248 } 249 250 /** Checks if the flexibility constraint is actively satisfied for a given job. */ 251 @GuardedBy("mLock") isFlexibilitySatisfiedLocked(JobStatus js)252 boolean isFlexibilitySatisfiedLocked(JobStatus js) { 253 return !mFlexibilityEnabled 254 || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP 255 || getNumSatisfiedFlexibleConstraintsLocked(js) 256 >= js.getNumRequiredFlexibleConstraints() 257 || mService.isCurrentlyRunningLocked(js); 258 } 259 260 @VisibleForTesting 261 @GuardedBy("mLock") getNumSatisfiedFlexibleConstraintsLocked(JobStatus js)262 int getNumSatisfiedFlexibleConstraintsLocked(JobStatus js) { 263 return Integer.bitCount(mSatisfiedFlexibleConstraints & js.getPreferredConstraintFlags()) 264 // Connectivity is job-specific, so must be handled separately. 265 + (js.getHasAccessToUnmetered() ? 1 : 0); 266 } 267 268 /** 269 * Sets the controller's constraint to a given state. 270 * Changes flexibility constraint satisfaction for affected jobs. 271 */ 272 @VisibleForTesting setConstraintSatisfied(int constraint, boolean state, long nowElapsed)273 void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) { 274 synchronized (mLock) { 275 final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; 276 if (old == state) { 277 return; 278 } 279 280 if (DEBUG) { 281 Slog.d(TAG, "setConstraintSatisfied: " 282 + " constraint: " + constraint + " state: " + state); 283 } 284 285 mSatisfiedFlexibleConstraints = 286 (mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0); 287 // Push the job update to the handler to avoid blocking other controllers and 288 // potentially batch back-to-back controller state updates together. 289 mHandler.obtainMessage(MSG_UPDATE_JOBS).sendToTarget(); 290 } 291 } 292 293 /** Checks if the given constraint is satisfied in the flexibility controller. */ 294 @VisibleForTesting isConstraintSatisfied(int constraint)295 boolean isConstraintSatisfied(int constraint) { 296 return (mSatisfiedFlexibleConstraints & constraint) != 0; 297 } 298 299 @VisibleForTesting 300 @GuardedBy("mLock") getLifeCycleBeginningElapsedLocked(JobStatus js)301 long getLifeCycleBeginningElapsedLocked(JobStatus js) { 302 if (js.getJob().isPrefetch()) { 303 final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime()); 304 final long estimatedLaunchTime = 305 mPrefetchController.getNextEstimatedLaunchTimeLocked(js); 306 long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault( 307 js.getSourceUserId(), js.getSourcePackageName(), 0L); 308 if (estimatedLaunchTime != Long.MAX_VALUE) { 309 prefetchWindowStart = Math.max(prefetchWindowStart, 310 estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs()); 311 } 312 return Math.max(prefetchWindowStart, earliestRuntime); 313 } 314 return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME 315 ? js.enqueueTime : js.getEarliestRunTime(); 316 } 317 318 @VisibleForTesting 319 @GuardedBy("mLock") getLifeCycleEndElapsedLocked(JobStatus js, long earliest)320 long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) { 321 if (js.getJob().isPrefetch()) { 322 final long estimatedLaunchTime = 323 mPrefetchController.getNextEstimatedLaunchTimeLocked(js); 324 // Prefetch jobs aren't supposed to have deadlines after T. 325 // But some legacy apps might still schedule them with deadlines. 326 if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) { 327 // If there is a deadline, the earliest time is the end of the lifecycle. 328 return Math.min( 329 estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS, 330 js.getLatestRunTimeElapsed()); 331 } 332 if (estimatedLaunchTime != Long.MAX_VALUE) { 333 return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS; 334 } 335 // There is no deadline and no estimated launch time. 336 return NO_LIFECYCLE_END; 337 } 338 // Increase the flex deadline for jobs rescheduled more than once. 339 if (js.getNumPreviousAttempts() > 1) { 340 return earliest + Math.min( 341 (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2), 342 mMaxRescheduledDeadline); 343 } 344 return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME 345 ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed(); 346 } 347 348 @VisibleForTesting 349 @GuardedBy("mLock") getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed)350 int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) { 351 final long earliest = getLifeCycleBeginningElapsedLocked(js); 352 final long latest = getLifeCycleEndElapsedLocked(js, earliest); 353 if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) { 354 return 0; 355 } 356 if (nowElapsed > latest || latest == earliest) { 357 return 100; 358 } 359 final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest)); 360 return percentInTime; 361 } 362 363 @VisibleForTesting 364 @ElapsedRealtimeLong 365 @GuardedBy("mLock") getNextConstraintDropTimeElapsedLocked(JobStatus js)366 long getNextConstraintDropTimeElapsedLocked(JobStatus js) { 367 final long earliest = getLifeCycleBeginningElapsedLocked(js); 368 final long latest = getLifeCycleEndElapsedLocked(js, earliest); 369 return getNextConstraintDropTimeElapsedLocked(js, earliest, latest); 370 } 371 372 /** The elapsed time that marks when the next constraint should be dropped. */ 373 @ElapsedRealtimeLong 374 @GuardedBy("mLock") getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest)375 long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) { 376 if (latest == NO_LIFECYCLE_END 377 || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) { 378 return NO_LIFECYCLE_END; 379 } 380 final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()]; 381 final long percentInTime = ((latest - earliest) * percent) / 100; 382 return earliest + percentInTime; 383 } 384 385 @Override 386 @GuardedBy("mLock") onUidBiasChangedLocked(int uid, int prevBias, int newBias)387 public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { 388 if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) { 389 return; 390 } 391 final long nowElapsed = sElapsedRealtimeClock.millis(); 392 ArraySet<JobStatus> jobsByUid = mService.getJobStore().getJobsBySourceUid(uid); 393 boolean hasPrefetch = false; 394 for (int i = 0; i < jobsByUid.size(); i++) { 395 JobStatus js = jobsByUid.valueAt(i); 396 if (js.hasFlexibilityConstraint()) { 397 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); 398 hasPrefetch |= js.getJob().isPrefetch(); 399 } 400 } 401 402 // Prefetch jobs can't run when the app is TOP, so it should not be included in their 403 // lifecycle, and marks the beginning of a new lifecycle. 404 if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) { 405 final int userId = UserHandle.getUserId(uid); 406 final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid); 407 if (pkgs == null) { 408 return; 409 } 410 for (int i = 0; i < pkgs.size(); i++) { 411 String pkg = pkgs.valueAt(i); 412 mPrefetchLifeCycleStart.add(userId, pkg, 413 Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L), 414 nowElapsed)); 415 } 416 } 417 } 418 419 @Override 420 @GuardedBy("mLock") onConstantsUpdatedLocked()421 public void onConstantsUpdatedLocked() { 422 if (mFcConfig.mShouldReevaluateConstraints) { 423 AppSchedulingModuleThread.getHandler().post(() -> { 424 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 425 synchronized (mLock) { 426 final long nowElapsed = sElapsedRealtimeClock.millis(); 427 for (int j = 0; j < mFlexibilityTracker.size(); j++) { 428 final ArraySet<JobStatus> jobs = mFlexibilityTracker 429 .getJobsByNumRequiredConstraints(j); 430 for (int i = 0; i < jobs.size(); i++) { 431 JobStatus js = jobs.valueAt(i); 432 mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); 433 mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); 434 if (js.setFlexibilityConstraintSatisfied( 435 nowElapsed, isFlexibilitySatisfiedLocked(js))) { 436 changedJobs.add(js); 437 } 438 } 439 } 440 } 441 if (changedJobs.size() > 0) { 442 mStateChangedListener.onControllerStateChanged(changedJobs); 443 } 444 }); 445 } 446 } 447 448 @Override 449 @GuardedBy("mLock") prepareForUpdatedConstantsLocked()450 public void prepareForUpdatedConstantsLocked() { 451 mFcConfig.mShouldReevaluateConstraints = false; 452 } 453 454 @Override 455 @GuardedBy("mLock") processConstantLocked(DeviceConfig.Properties properties, String key)456 public void processConstantLocked(DeviceConfig.Properties properties, String key) { 457 mFcConfig.processConstantLocked(properties, key); 458 } 459 460 @VisibleForTesting 461 class FlexibilityTracker { 462 final ArrayList<ArraySet<JobStatus>> mTrackedJobs; 463 FlexibilityTracker(int numFlexibleConstraints)464 FlexibilityTracker(int numFlexibleConstraints) { 465 mTrackedJobs = new ArrayList<>(); 466 for (int i = 0; i <= numFlexibleConstraints; i++) { 467 mTrackedJobs.add(new ArraySet<JobStatus>()); 468 } 469 } 470 471 /** Gets every tracked job with a given number of required constraints. */ 472 @Nullable getJobsByNumRequiredConstraints(int numRequired)473 public ArraySet<JobStatus> getJobsByNumRequiredConstraints(int numRequired) { 474 if (numRequired > mTrackedJobs.size()) { 475 Slog.wtfStack(TAG, "Asked for a larger number of constraints than exists."); 476 return null; 477 } 478 return mTrackedJobs.get(numRequired); 479 } 480 481 /** adds a JobStatus object based on number of required flexible constraints. */ add(JobStatus js)482 public void add(JobStatus js) { 483 if (js.getNumRequiredFlexibleConstraints() < 0) { 484 return; 485 } 486 mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).add(js); 487 } 488 489 /** Removes a JobStatus object. */ remove(JobStatus js)490 public void remove(JobStatus js) { 491 mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(js); 492 } 493 resetJobNumDroppedConstraints(JobStatus js, long nowElapsed)494 public void resetJobNumDroppedConstraints(JobStatus js, long nowElapsed) { 495 final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); 496 int toDrop = 0; 497 final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS 498 + (js.getPreferUnmetered() ? 1 : 0); 499 for (int i = 0; i < jsMaxFlexibleConstraints; i++) { 500 if (curPercent >= mPercentToDropConstraints[i]) { 501 toDrop++; 502 } 503 } 504 adjustJobsRequiredConstraints( 505 js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed); 506 } 507 508 /** Returns all tracked jobs. */ getArrayList()509 public ArrayList<ArraySet<JobStatus>> getArrayList() { 510 return mTrackedJobs; 511 } 512 513 /** 514 * Adjusts number of required flexible constraints and sorts it into the tracker. 515 * Returns false if the job status's number of flexible constraints is now 0. 516 */ adjustJobsRequiredConstraints(JobStatus js, int adjustBy, long nowElapsed)517 public boolean adjustJobsRequiredConstraints(JobStatus js, int adjustBy, long nowElapsed) { 518 if (adjustBy != 0) { 519 remove(js); 520 js.adjustNumRequiredFlexibleConstraints(adjustBy); 521 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); 522 add(js); 523 } 524 return js.getNumRequiredFlexibleConstraints() > 0; 525 } 526 size()527 public int size() { 528 return mTrackedJobs.size(); 529 } 530 dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate)531 public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { 532 for (int i = 0; i < mTrackedJobs.size(); i++) { 533 ArraySet<JobStatus> jobs = mTrackedJobs.get(i); 534 for (int j = 0; j < jobs.size(); j++) { 535 final JobStatus js = jobs.valueAt(j); 536 if (!predicate.test(js)) { 537 continue; 538 } 539 pw.print("#"); 540 js.printUniqueId(pw); 541 pw.print(" from "); 542 UserHandle.formatUid(pw, js.getSourceUid()); 543 pw.print(" Num Required Constraints: "); 544 pw.print(js.getNumRequiredFlexibleConstraints()); 545 pw.println(); 546 } 547 } 548 } 549 } 550 551 @VisibleForTesting 552 class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> { FlexibilityAlarmQueue(Context context, Looper looper)553 private FlexibilityAlarmQueue(Context context, Looper looper) { 554 super(context, looper, "*job.flexibility_check*", 555 "Flexible Constraint Check", true, 556 mMinTimeBetweenFlexibilityAlarmsMs); 557 } 558 559 @Override isForUser(@onNull JobStatus js, int userId)560 protected boolean isForUser(@NonNull JobStatus js, int userId) { 561 return js.getSourceUserId() == userId; 562 } 563 scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed)564 public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) { 565 synchronized (mLock) { 566 final long earliest = getLifeCycleBeginningElapsedLocked(js); 567 final long latest = getLifeCycleEndElapsedLocked(js, earliest); 568 final long nextTimeElapsed = 569 getNextConstraintDropTimeElapsedLocked(js, earliest, latest); 570 571 if (DEBUG) { 572 Slog.d(TAG, "scheduleDropNumConstraintsAlarm: " 573 + js.getSourcePackageName() + " " + js.getSourceUserId() 574 + " numRequired: " + js.getNumRequiredFlexibleConstraints() 575 + " numSatisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints) 576 + " curTime: " + nowElapsed 577 + " earliest: " + earliest 578 + " latest: " + latest 579 + " nextTime: " + nextTimeElapsed); 580 } 581 if (latest - nowElapsed < mDeadlineProximityLimitMs) { 582 if (DEBUG) { 583 Slog.d(TAG, "deadline proximity met: " + js); 584 } 585 mFlexibilityTracker.adjustJobsRequiredConstraints(js, 586 -js.getNumRequiredFlexibleConstraints(), nowElapsed); 587 return; 588 } 589 if (nextTimeElapsed == NO_LIFECYCLE_END) { 590 // There is no known or estimated next time to drop a constraint. 591 removeAlarmForKey(js); 592 return; 593 } 594 if (latest - nextTimeElapsed <= mDeadlineProximityLimitMs) { 595 if (DEBUG) { 596 Slog.d(TAG, "last alarm set: " + js); 597 } 598 addAlarm(js, latest - mDeadlineProximityLimitMs); 599 return; 600 } 601 addAlarm(js, nextTimeElapsed); 602 } 603 } 604 605 @Override processExpiredAlarms(@onNull ArraySet<JobStatus> expired)606 protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) { 607 synchronized (mLock) { 608 ArraySet<JobStatus> changedJobs = new ArraySet<>(); 609 final long nowElapsed = sElapsedRealtimeClock.millis(); 610 for (int i = 0; i < expired.size(); i++) { 611 JobStatus js = expired.valueAt(i); 612 boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE); 613 614 if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)) { 615 scheduleDropNumConstraintsAlarm(js, nowElapsed); 616 } 617 if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) { 618 changedJobs.add(js); 619 } 620 } 621 mStateChangedListener.onControllerStateChanged(changedJobs); 622 } 623 } 624 } 625 626 private class FcHandler extends Handler { FcHandler(Looper looper)627 FcHandler(Looper looper) { 628 super(looper); 629 } 630 631 @Override handleMessage(Message msg)632 public void handleMessage(Message msg) { 633 switch (msg.what) { 634 case MSG_UPDATE_JOBS: 635 removeMessages(MSG_UPDATE_JOBS); 636 637 synchronized (mLock) { 638 final long nowElapsed = sElapsedRealtimeClock.millis(); 639 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 640 641 for (int o = 0; o <= NUM_OPTIONAL_FLEXIBLE_CONSTRAINTS; ++o) { 642 final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker 643 .getJobsByNumRequiredConstraints(o); 644 645 if (jobsByNumConstraints != null) { 646 for (int i = 0; i < jobsByNumConstraints.size(); i++) { 647 final JobStatus js = jobsByNumConstraints.valueAt(i); 648 if (js.setFlexibilityConstraintSatisfied( 649 nowElapsed, isFlexibilitySatisfiedLocked(js))) { 650 changedJobs.add(js); 651 } 652 } 653 } 654 } 655 if (changedJobs.size() > 0) { 656 mStateChangedListener.onControllerStateChanged(changedJobs); 657 } 658 } 659 break; 660 } 661 } 662 } 663 664 @VisibleForTesting 665 class FcConfig { 666 private boolean mShouldReevaluateConstraints = false; 667 668 /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ 669 private static final String FC_CONFIG_PREFIX = "fc_"; 670 671 static final String KEY_FLEXIBILITY_ENABLED = FC_CONFIG_PREFIX + "enable_flexibility"; 672 static final String KEY_DEADLINE_PROXIMITY_LIMIT = 673 FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms"; 674 static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE = 675 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms"; 676 static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = 677 FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms"; 678 static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = 679 FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints"; 680 static final String KEY_MAX_RESCHEDULED_DEADLINE_MS = 681 FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms"; 682 static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = 683 FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; 684 685 private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; 686 @VisibleForTesting 687 static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; 688 @VisibleForTesting 689 static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS; 690 private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS; 691 @VisibleForTesting 692 final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80}; 693 private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS; 694 private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS; 695 696 /** 697 * If false the controller will not track new jobs 698 * and the flexibility constraint will always be satisfied. 699 */ 700 public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED; 701 /** How close to a jobs' deadline all flexible constraints will be dropped. */ 702 public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS; 703 /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */ 704 public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; 705 public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = 706 DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; 707 /** The percentages of a jobs' lifecycle to drop the number of required constraints. */ 708 public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = 709 DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; 710 /** Initial fallback flexible deadline for rescheduled jobs. */ 711 public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; 712 /** The max deadline for rescheduled jobs. */ 713 public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; 714 715 @GuardedBy("mLock") processConstantLocked(@onNull DeviceConfig.Properties properties, @NonNull String key)716 public void processConstantLocked(@NonNull DeviceConfig.Properties properties, 717 @NonNull String key) { 718 switch (key) { 719 case KEY_FLEXIBILITY_ENABLED: 720 FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED) 721 && mDeviceSupportsFlexConstraints; 722 if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) { 723 mFlexibilityEnabled = FLEXIBILITY_ENABLED; 724 mShouldReevaluateConstraints = true; 725 if (mFlexibilityEnabled) { 726 mPrefetchController 727 .registerPrefetchChangedListener(mPrefetchChangedListener); 728 } else { 729 mPrefetchController 730 .unRegisterPrefetchChangedListener(mPrefetchChangedListener); 731 } 732 } 733 break; 734 case KEY_RESCHEDULED_JOB_DEADLINE_MS: 735 RESCHEDULED_JOB_DEADLINE_MS = 736 properties.getLong(key, DEFAULT_RESCHEDULED_JOB_DEADLINE_MS); 737 if (mRescheduledJobDeadline != RESCHEDULED_JOB_DEADLINE_MS) { 738 mRescheduledJobDeadline = RESCHEDULED_JOB_DEADLINE_MS; 739 mShouldReevaluateConstraints = true; 740 } 741 break; 742 case KEY_MAX_RESCHEDULED_DEADLINE_MS: 743 MAX_RESCHEDULED_DEADLINE_MS = 744 properties.getLong(key, DEFAULT_MAX_RESCHEDULED_DEADLINE_MS); 745 if (mMaxRescheduledDeadline != MAX_RESCHEDULED_DEADLINE_MS) { 746 mMaxRescheduledDeadline = MAX_RESCHEDULED_DEADLINE_MS; 747 mShouldReevaluateConstraints = true; 748 } 749 break; 750 case KEY_DEADLINE_PROXIMITY_LIMIT: 751 DEADLINE_PROXIMITY_LIMIT_MS = 752 properties.getLong(key, DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS); 753 if (mDeadlineProximityLimitMs != DEADLINE_PROXIMITY_LIMIT_MS) { 754 mDeadlineProximityLimitMs = DEADLINE_PROXIMITY_LIMIT_MS; 755 mShouldReevaluateConstraints = true; 756 } 757 break; 758 case KEY_FALLBACK_FLEXIBILITY_DEADLINE: 759 FALLBACK_FLEXIBILITY_DEADLINE_MS = 760 properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS); 761 if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) { 762 mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS; 763 mShouldReevaluateConstraints = true; 764 } 765 break; 766 case KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS: 767 MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = 768 properties.getLong(key, DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS); 769 if (mMinTimeBetweenFlexibilityAlarmsMs 770 != MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS) { 771 mMinTimeBetweenFlexibilityAlarmsMs = MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; 772 mFlexibilityAlarmQueue 773 .setMinTimeBetweenAlarmsMs(MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS); 774 mShouldReevaluateConstraints = true; 775 } 776 break; 777 case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS: 778 String dropPercentString = properties.getString(key, ""); 779 PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS = 780 parsePercentToDropString(dropPercentString); 781 if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null 782 && !Arrays.equals(mPercentToDropConstraints, 783 PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) { 784 mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS; 785 mShouldReevaluateConstraints = true; 786 } 787 break; 788 } 789 } 790 parsePercentToDropString(String s)791 private int[] parsePercentToDropString(String s) { 792 String[] dropPercentString = s.split(","); 793 int[] dropPercentInt = new int[NUM_FLEXIBLE_CONSTRAINTS]; 794 if (dropPercentInt.length != dropPercentString.length) { 795 return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; 796 } 797 int prevPercent = 0; 798 for (int i = 0; i < dropPercentString.length; i++) { 799 try { 800 dropPercentInt[i] = 801 Integer.parseInt(dropPercentString[i]); 802 } catch (NumberFormatException ex) { 803 Slog.e(TAG, "Provided string was improperly formatted.", ex); 804 return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; 805 } 806 if (dropPercentInt[i] < prevPercent) { 807 Slog.wtf(TAG, "Percents to drop constraints were not in increasing order."); 808 return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; 809 } 810 prevPercent = dropPercentInt[i]; 811 } 812 813 return dropPercentInt; 814 } 815 dump(IndentingPrintWriter pw)816 private void dump(IndentingPrintWriter pw) { 817 pw.println(); 818 pw.print(FlexibilityController.class.getSimpleName()); 819 pw.println(":"); 820 pw.increaseIndent(); 821 822 pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println(); 823 pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println(); 824 pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println(); 825 pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS, 826 MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println(); 827 pw.print(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, 828 PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS).println(); 829 pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println(); 830 pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println(); 831 832 pw.decreaseIndent(); 833 } 834 } 835 836 @VisibleForTesting 837 @NonNull getFcConfig()838 FcConfig getFcConfig() { 839 return mFcConfig; 840 } 841 842 @Override 843 @GuardedBy("mLock") dumpConstants(IndentingPrintWriter pw)844 public void dumpConstants(IndentingPrintWriter pw) { 845 mFcConfig.dump(pw); 846 } 847 848 @Override 849 @GuardedBy("mLock") dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)850 public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { 851 pw.println("# Constraints Satisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints)); 852 pw.print("Satisfied Flexible Constraints: "); 853 JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints); 854 pw.println(); 855 pw.println(); 856 857 mFlexibilityTracker.dump(pw, predicate); 858 pw.println(); 859 mFlexibilityAlarmQueue.dump(pw); 860 } 861 } 862