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.app.job.JobInfo.PRIORITY_DEFAULT; 20 import static android.app.job.JobInfo.PRIORITY_HIGH; 21 import static android.app.job.JobInfo.PRIORITY_LOW; 22 import static android.app.job.JobInfo.PRIORITY_MAX; 23 import static android.app.job.JobInfo.PRIORITY_MIN; 24 import static android.text.format.DateUtils.DAY_IN_MILLIS; 25 import static android.text.format.DateUtils.HOUR_IN_MILLIS; 26 import static android.text.format.DateUtils.MINUTE_IN_MILLIS; 27 28 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 29 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; 30 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; 31 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; 32 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; 33 34 import android.annotation.ElapsedRealtimeLong; 35 import android.annotation.NonNull; 36 import android.annotation.Nullable; 37 import android.app.job.JobInfo; 38 import android.content.BroadcastReceiver; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.IntentFilter; 42 import android.content.pm.PackageManager; 43 import android.os.Handler; 44 import android.os.Looper; 45 import android.os.Message; 46 import android.os.PowerManager; 47 import android.os.UserHandle; 48 import android.provider.DeviceConfig; 49 import android.telephony.TelephonyManager; 50 import android.telephony.UiccSlotMapping; 51 import android.util.ArraySet; 52 import android.util.IndentingPrintWriter; 53 import android.util.IntArray; 54 import android.util.KeyValueListParser; 55 import android.util.Log; 56 import android.util.Slog; 57 import android.util.SparseArray; 58 import android.util.SparseArrayMap; 59 import android.util.SparseIntArray; 60 import android.util.SparseLongArray; 61 import android.util.SparseSetArray; 62 import android.util.TimeUtils; 63 64 import com.android.internal.annotations.GuardedBy; 65 import com.android.internal.annotations.VisibleForTesting; 66 import com.android.server.AppSchedulingModuleThread; 67 import com.android.server.DeviceIdleInternal; 68 import com.android.server.LocalServices; 69 import com.android.server.job.JobSchedulerService; 70 import com.android.server.utils.AlarmQueue; 71 72 import java.util.ArrayList; 73 import java.util.Arrays; 74 import java.util.Collection; 75 import java.util.Set; 76 import java.util.function.Predicate; 77 78 /** 79 * Controller that tracks the number of flexible constraints being actively satisfied. 80 * Drops constraint for TOP apps and lowers number of required constraints with time. 81 */ 82 public final class FlexibilityController extends StateController { 83 private static final String TAG = "JobScheduler.Flex"; 84 private static final boolean DEBUG = JobSchedulerService.DEBUG 85 || Log.isLoggable(TAG, Log.DEBUG); 86 87 /** List of all system-wide flexible constraints whose satisfaction is independent of job. */ 88 static final int SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW 89 | CONSTRAINT_CHARGING 90 | CONSTRAINT_IDLE; 91 92 /** List of all job flexible constraints whose satisfaction is job specific. */ 93 private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY; 94 95 /** List of all flexible constraints. */ 96 @VisibleForTesting 97 static final int FLEXIBLE_CONSTRAINTS = 98 JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; 99 100 private static final long NO_LIFECYCLE_END = Long.MAX_VALUE; 101 102 /** 103 * The default deadline that all flexible constraints should be dropped by if a job lacks 104 * a deadline. 105 */ 106 private long mFallbackFlexibilityDeadlineMs = 107 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; 108 /** 109 * The default deadline that all flexible constraints should be dropped by if a job lacks 110 * a deadline, keyed by job priority. 111 */ 112 private SparseLongArray mFallbackFlexibilityDeadlines = 113 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES; 114 /** 115 * The scores to use for each job, keyed by job priority. 116 */ 117 private SparseIntArray mFallbackFlexibilityDeadlineScores = 118 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES; 119 /** 120 * The amount of time to add (scaled by job run score) to the fallback flexibility deadline, 121 * keyed by job priority. 122 */ 123 private SparseLongArray mFallbackFlexibilityAdditionalScoreTimeFactors = 124 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS; 125 126 private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; 127 private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; 128 129 private long mUnseenConstraintGracePeriodMs = 130 FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; 131 132 /** Set of constraints supported on this device for flex scheduling. */ 133 private final int mSupportedFlexConstraints; 134 135 @GuardedBy("mLock") 136 private boolean mFlexibilityEnabled; 137 138 /** Set of constraints that will be used in the flex policy. */ 139 @GuardedBy("mLock") 140 private int mAppliedConstraints = FcConfig.DEFAULT_APPLIED_CONSTRAINTS; 141 142 private long mMinTimeBetweenFlexibilityAlarmsMs = 143 FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; 144 145 /** Hard cutoff to remove flexible constraints. */ 146 private long mDeadlineProximityLimitMs = 147 FcConfig.DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS; 148 149 /** 150 * The percent of a job's lifecycle to drop number of required constraints. 151 * mPercentsToDropConstraints[i] denotes that at x% of a Jobs lifecycle, 152 * the controller should have i+1 constraints dropped. Keyed by job priority. 153 */ 154 private SparseArray<int[]> mPercentsToDropConstraints; 155 156 /** 157 * Keeps track of what flexible constraints are satisfied at the moment. 158 * Is updated by the other controllers. 159 */ 160 @VisibleForTesting 161 @GuardedBy("mLock") 162 int mSatisfiedFlexibleConstraints; 163 164 @GuardedBy("mLock") 165 private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray(); 166 167 @VisibleForTesting 168 @GuardedBy("mLock") 169 final FlexibilityTracker mFlexibilityTracker; 170 @VisibleForTesting 171 @GuardedBy("mLock") 172 final FlexibilityAlarmQueue mFlexibilityAlarmQueue; 173 @VisibleForTesting 174 final FcConfig mFcConfig; 175 private final FcHandler mHandler; 176 @VisibleForTesting 177 final PrefetchController mPrefetchController; 178 private final SpecialAppTracker mSpecialAppTracker; 179 180 /** 181 * Stores the beginning of prefetch jobs lifecycle per app as a maximum of 182 * the last time the app was used and the last time the launch time was updated. 183 */ 184 @VisibleForTesting 185 @GuardedBy("mLock") 186 final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>(); 187 188 @VisibleForTesting 189 final PrefetchController.PrefetchChangedListener mPrefetchChangedListener = 190 new PrefetchController.PrefetchChangedListener() { 191 @Override 192 public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, 193 String pkgName, long prevEstimatedLaunchTime, 194 long newEstimatedLaunchTime, long nowElapsed) { 195 synchronized (mLock) { 196 final long prefetchThreshold = 197 mPrefetchController.getLaunchTimeThresholdMs(); 198 boolean jobWasInPrefetchWindow = prevEstimatedLaunchTime 199 - prefetchThreshold < nowElapsed; 200 boolean jobIsInPrefetchWindow = newEstimatedLaunchTime 201 - prefetchThreshold < nowElapsed; 202 if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) { 203 // If the job was in the window previously then changing the start 204 // of the lifecycle to the current moment without a large change in the 205 // end would squeeze the window too tight fail to drop constraints. 206 mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed, 207 mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L))); 208 } 209 for (int i = 0; i < jobs.size(); i++) { 210 JobStatus js = jobs.valueAt(i); 211 if (!js.hasFlexibilityConstraint()) { 212 continue; 213 } 214 mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); 215 mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); 216 } 217 } 218 } 219 }; 220 221 /** Helper object to track job run score for each app. */ 222 private static class JobScoreTracker { 223 private static class JobScoreBucket { 224 @ElapsedRealtimeLong 225 public long startTimeElapsed; 226 public int score; 227 reset()228 private void reset() { 229 startTimeElapsed = 0; 230 score = 0; 231 } 232 } 233 234 private static final int NUM_SCORE_BUCKETS = 24; 235 private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS; 236 private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS]; 237 private int mScoreBucketIndex = 0; 238 private long mCachedScoreExpirationTimeElapsed; 239 private int mCachedScore; 240 addScore(int add, long nowElapsed)241 public void addScore(int add, long nowElapsed) { 242 JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex]; 243 if (bucket == null) { 244 bucket = new JobScoreBucket(); 245 bucket.startTimeElapsed = nowElapsed; 246 mScoreBuckets[mScoreBucketIndex] = bucket; 247 // Brand new bucket, there's nothing to remove from the score, 248 // so just update the expiration time if needed. 249 mCachedScoreExpirationTimeElapsed = Math.min(mCachedScoreExpirationTimeElapsed, 250 nowElapsed + MAX_TIME_WINDOW_MS); 251 } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) { 252 // The bucket is too old. 253 bucket.reset(); 254 bucket.startTimeElapsed = nowElapsed; 255 // Force a recalculation of the cached score instead of just updating the cached 256 // value and time in case there are multiple stale buckets. 257 mCachedScoreExpirationTimeElapsed = nowElapsed; 258 } else if (bucket.startTimeElapsed 259 < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) { 260 // The current bucket's duration has completed. Move on to the next bucket. 261 mScoreBucketIndex = (mScoreBucketIndex + 1) % NUM_SCORE_BUCKETS; 262 addScore(add, nowElapsed); 263 return; 264 } 265 266 bucket.score += add; 267 mCachedScore += add; 268 } 269 getScore(long nowElapsed)270 public int getScore(long nowElapsed) { 271 if (nowElapsed < mCachedScoreExpirationTimeElapsed) { 272 return mCachedScore; 273 } 274 int score = 0; 275 final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS; 276 long earliestValidBucketTimeElapsed = Long.MAX_VALUE; 277 for (JobScoreBucket bucket : mScoreBuckets) { 278 if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) { 279 score += bucket.score; 280 if (earliestValidBucketTimeElapsed > bucket.startTimeElapsed) { 281 earliestValidBucketTimeElapsed = bucket.startTimeElapsed; 282 } 283 } 284 } 285 mCachedScore = score; 286 mCachedScoreExpirationTimeElapsed = earliestValidBucketTimeElapsed + MAX_TIME_WINDOW_MS; 287 return score; 288 } 289 dump(@onNull IndentingPrintWriter pw, long nowElapsed)290 public void dump(@NonNull IndentingPrintWriter pw, long nowElapsed) { 291 pw.print("{"); 292 293 boolean printed = false; 294 for (int x = 0; x < mScoreBuckets.length; ++x) { 295 final int idx = (mScoreBucketIndex + 1 + x) % mScoreBuckets.length; 296 final JobScoreBucket jsb = mScoreBuckets[idx]; 297 if (jsb == null || jsb.startTimeElapsed == 0) { 298 continue; 299 } 300 if (printed) { 301 pw.print(", "); 302 } 303 TimeUtils.formatDuration(jsb.startTimeElapsed, nowElapsed, pw); 304 pw.print("="); 305 pw.print(jsb.score); 306 printed = true; 307 } 308 309 pw.print("}"); 310 } 311 } 312 313 /** 314 * Set of {@link JobScoreTracker JobScoreTrackers} for each app. 315 * Keyed by source UID -> source package. 316 **/ 317 private final SparseArrayMap<String, JobScoreTracker> mJobScoreTrackers = 318 new SparseArrayMap<>(); 319 320 private static final int MSG_CHECK_ALL_JOBS = 0; 321 /** Check the jobs in {@link #mJobsToCheck} */ 322 private static final int MSG_CHECK_JOBS = 1; 323 /** Check the jobs of packages in {@link #mPackagesToCheck} */ 324 private static final int MSG_CHECK_PACKAGES = 2; 325 326 @GuardedBy("mLock") 327 private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>(); 328 @GuardedBy("mLock") 329 private final ArraySet<String> mPackagesToCheck = new ArraySet<>(); 330 331 @GuardedBy("mLock") 332 private boolean mLocalOverride; 333 FlexibilityController( JobSchedulerService service, PrefetchController prefetchController)334 public FlexibilityController( 335 JobSchedulerService service, PrefetchController prefetchController) { 336 super(service); 337 mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper()); 338 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 339 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) { 340 // Embedded devices have no user-installable apps. Assume all jobs are critical 341 // and can't be flexed. 342 mSupportedFlexConstraints = 0; 343 } else { 344 // TODO(236261941): handle devices without a battery 345 mSupportedFlexConstraints = FLEXIBLE_CONSTRAINTS; 346 } 347 mFlexibilityEnabled = (mAppliedConstraints & mSupportedFlexConstraints) != 0; 348 mFlexibilityTracker = new FlexibilityTracker(Integer.bitCount(mSupportedFlexConstraints)); 349 mFcConfig = new FcConfig(); 350 mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( 351 mContext, AppSchedulingModuleThread.get().getLooper()); 352 mPercentsToDropConstraints = 353 FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; 354 mPrefetchController = prefetchController; 355 mSpecialAppTracker = new SpecialAppTracker(); 356 357 if (mFlexibilityEnabled) { 358 mSpecialAppTracker.startTracking(); 359 } 360 } 361 362 @Override onSystemServicesReady()363 public void onSystemServicesReady() { 364 mSpecialAppTracker.onSystemServicesReady(); 365 } 366 367 @Override startTrackingLocked()368 public void startTrackingLocked() { 369 if (mFlexibilityEnabled) { 370 mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener); 371 } 372 } 373 374 /** 375 * StateController interface. 376 */ 377 @Override 378 @GuardedBy("mLock") maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob)379 public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) { 380 if (js.hasFlexibilityConstraint()) { 381 final long nowElapsed = sElapsedRealtimeClock.millis(); 382 if (mSupportedFlexConstraints == 0) { 383 js.setFlexibilityConstraintSatisfied(nowElapsed, true); 384 return; 385 } 386 js.setNumAppliedFlexibleConstraints( 387 Integer.bitCount(getRelevantAppliedConstraintsLocked(js))); 388 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); 389 mFlexibilityTracker.add(js); 390 js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); 391 mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); 392 } 393 } 394 395 @Override prepareForExecutionLocked(JobStatus jobStatus)396 public void prepareForExecutionLocked(JobStatus jobStatus) { 397 if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { 398 // Don't include jobs for the TOP app in the score calculation. 399 return; 400 } 401 // Use the job's requested priority to determine its score since that is what the developer 402 // selected and it will be stable across job runs. 403 final int priority = jobStatus.getJob().getPriority(); 404 final int score = mFallbackFlexibilityDeadlineScores.get(priority, 405 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES 406 .get(priority, priority / 100)); 407 JobScoreTracker jobScoreTracker = 408 mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName()); 409 if (jobScoreTracker == null) { 410 jobScoreTracker = new JobScoreTracker(); 411 mJobScoreTrackers.add(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), 412 jobScoreTracker); 413 } 414 jobScoreTracker.addScore(score, sElapsedRealtimeClock.millis()); 415 } 416 417 @Override unprepareFromExecutionLocked(JobStatus jobStatus)418 public void unprepareFromExecutionLocked(JobStatus jobStatus) { 419 if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) { 420 // Jobs for the TOP app are excluded from the score calculation. 421 return; 422 } 423 // The job didn't actually start. Undo the score increase. 424 JobScoreTracker jobScoreTracker = 425 mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName()); 426 if (jobScoreTracker == null) { 427 Slog.e(TAG, "Unprepared a job that didn't result in a score change"); 428 return; 429 } 430 final int priority = jobStatus.getJob().getPriority(); 431 final int score = mFallbackFlexibilityDeadlineScores.get(priority, 432 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES 433 .get(priority, priority / 100)); 434 jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis()); 435 } 436 437 @Override 438 @GuardedBy("mLock") maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob)439 public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) { 440 if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) { 441 mFlexibilityAlarmQueue.removeAlarmForKey(js); 442 mFlexibilityTracker.remove(js); 443 } 444 mJobsToCheck.remove(js); 445 } 446 447 @Override 448 @GuardedBy("mLock") onAppRemovedLocked(String packageName, int uid)449 public void onAppRemovedLocked(String packageName, int uid) { 450 final int userId = UserHandle.getUserId(uid); 451 mPrefetchLifeCycleStart.delete(userId, packageName); 452 mJobScoreTrackers.delete(uid, packageName); 453 mSpecialAppTracker.onAppRemoved(userId, packageName); 454 for (int i = mJobsToCheck.size() - 1; i >= 0; --i) { 455 final JobStatus js = mJobsToCheck.valueAt(i); 456 if ((js.getSourceUid() == uid && js.getSourcePackageName().equals(packageName)) 457 || (js.getUid() == uid && js.getCallingPackageName().equals(packageName))) { 458 mJobsToCheck.removeAt(i); 459 } 460 } 461 } 462 463 @Override 464 @GuardedBy("mLock") onUserRemovedLocked(int userId)465 public void onUserRemovedLocked(int userId) { 466 mPrefetchLifeCycleStart.delete(userId); 467 mSpecialAppTracker.onUserRemoved(userId); 468 for (int u = mJobScoreTrackers.numMaps() - 1; u >= 0; --u) { 469 final int uid = mJobScoreTrackers.keyAt(u); 470 if (UserHandle.getUserId(uid) == userId) { 471 mJobScoreTrackers.deleteAt(u); 472 } 473 } 474 for (int i = mJobsToCheck.size() - 1; i >= 0; --i) { 475 final JobStatus js = mJobsToCheck.valueAt(i); 476 if (UserHandle.getUserId(js.getSourceUid()) == userId 477 || UserHandle.getUserId(js.getUid()) == userId) { 478 mJobsToCheck.removeAt(i); 479 } 480 } 481 } 482 isEnabled()483 boolean isEnabled() { 484 synchronized (mLock) { 485 return mFlexibilityEnabled; 486 } 487 } 488 489 /** Checks if the flexibility constraint is actively satisfied for a given job. */ 490 @GuardedBy("mLock") isFlexibilitySatisfiedLocked(JobStatus js)491 boolean isFlexibilitySatisfiedLocked(JobStatus js) { 492 return !mFlexibilityEnabled 493 // Exclude all jobs of the TOP app 494 || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP 495 // Only exclude DEFAULT+ priority jobs for BFGS+ apps 496 || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE 497 && js.getEffectivePriority() >= PRIORITY_DEFAULT) 498 // For special/privileged apps, automatically exclude DEFAULT+ priority jobs. 499 || (js.getEffectivePriority() >= PRIORITY_DEFAULT 500 && mSpecialAppTracker.isSpecialApp( 501 js.getSourceUserId(), js.getSourcePackageName())) 502 || hasEnoughSatisfiedConstraintsLocked(js) 503 || mService.isCurrentlyRunningLocked(js); 504 } 505 506 @VisibleForTesting 507 @GuardedBy("mLock") getRelevantAppliedConstraintsLocked(@onNull JobStatus js)508 int getRelevantAppliedConstraintsLocked(@NonNull JobStatus js) { 509 final int relevantConstraints = SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS 510 | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0); 511 return mAppliedConstraints & relevantConstraints; 512 } 513 514 /** 515 * Returns whether there are enough constraints satisfied to allow running the job from flex's 516 * perspective. This takes into account unseen constraint combinations and expectations around 517 * whether additional constraints can ever be satisfied. 518 */ 519 @VisibleForTesting 520 @GuardedBy("mLock") hasEnoughSatisfiedConstraintsLocked(@onNull JobStatus js)521 boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) { 522 final int satisfiedConstraints = mSatisfiedFlexibleConstraints & mAppliedConstraints 523 & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS 524 | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0)); 525 final int numSatisfied = Integer.bitCount(satisfiedConstraints); 526 if (numSatisfied >= js.getNumRequiredFlexibleConstraints()) { 527 return true; 528 } 529 // We don't yet have the full number of required flex constraints. See if we should expect 530 // to be able to reach it. If not, then there's no point waiting anymore. 531 final long nowElapsed = sElapsedRealtimeClock.millis(); 532 if (nowElapsed < mUnseenConstraintGracePeriodMs) { 533 // Too soon after boot. Not enough time to start predicting. Wait longer. 534 return false; 535 } 536 537 // The intention is to not force jobs to wait for constraint combinations that have never 538 // been seen together in a while. The job may still be allowed to wait for other constraint 539 // combinations. Thus, the logic is: 540 // If all the constraint combinations that have a count higher than the current satisfied 541 // count have not been seen recently enough, then assume they won't be seen anytime soon, 542 // so don't force the job to wait longer. If any combinations with a higher count have been 543 // seen recently, then the job can potentially wait for those combinations. 544 final int irrelevantConstraints = ~getRelevantAppliedConstraintsLocked(js); 545 for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) { 546 final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i); 547 if ((constraints & irrelevantConstraints) != 0) { 548 // Ignore combinations that couldn't satisfy this job's needs. 549 continue; 550 } 551 final long lastSeenElapsed = mLastSeenConstraintTimesElapsed.valueAt(i); 552 final boolean seenRecently = 553 nowElapsed - lastSeenElapsed <= mUnseenConstraintGracePeriodMs; 554 if (Integer.bitCount(constraints) > numSatisfied && seenRecently) { 555 // We've seen a set of constraints with a higher count than what is currently 556 // satisfied recently enough, which means we can expect to see it again at some 557 // point. Keep waiting for now. 558 return false; 559 } 560 } 561 562 // We haven't seen any constraint set with more satisfied than the current satisfied count. 563 // There's no reason to expect additional constraints to be satisfied. Let the job run. 564 return true; 565 } 566 567 /** 568 * Sets the controller's constraint to a given state. 569 * Changes flexibility constraint satisfaction for affected jobs. 570 */ setConstraintSatisfied(int constraint, boolean state, long nowElapsed)571 void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) { 572 synchronized (mLock) { 573 final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0; 574 if (old == state) { 575 return; 576 } 577 578 if (DEBUG) { 579 Slog.d(TAG, "setConstraintSatisfied: " 580 + " constraint: " + constraint + " state: " + state); 581 } 582 583 // Mark now as the last time we saw this set of constraints. 584 mLastSeenConstraintTimesElapsed.put(mSatisfiedFlexibleConstraints, nowElapsed); 585 if (!state) { 586 // Mark now as the last time we saw this particular constraint. 587 // (Good for logging/dump purposes). 588 mLastSeenConstraintTimesElapsed.put(constraint, nowElapsed); 589 } 590 591 mSatisfiedFlexibleConstraints = 592 (mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0); 593 594 if ((JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS & constraint) != 0) { 595 // Job-specific constraint --> don't need to proceed with logic below that 596 // works with system-wide constraints. 597 return; 598 } 599 600 if (mFlexibilityEnabled) { 601 // Only attempt to update jobs if the flex logic is enabled. Otherwise, the status 602 // of the jobs won't change, so all the work will be a waste. 603 604 // Push the job update to the handler to avoid blocking other controllers and 605 // potentially batch back-to-back controller state updates together. 606 mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget(); 607 } 608 } 609 } 610 611 /** Checks if the given constraint is satisfied in the flexibility controller. */ 612 @VisibleForTesting isConstraintSatisfied(int constraint)613 boolean isConstraintSatisfied(int constraint) { 614 return (mSatisfiedFlexibleConstraints & constraint) != 0; 615 } 616 617 @VisibleForTesting 618 @GuardedBy("mLock") getLifeCycleBeginningElapsedLocked(JobStatus js)619 long getLifeCycleBeginningElapsedLocked(JobStatus js) { 620 long earliestRuntime = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME 621 ? js.enqueueTime : js.getEarliestRunTime(); 622 if (js.getJob().isPeriodic() && js.getNumPreviousAttempts() == 0) { 623 // Rescheduling periodic jobs (after a successful execution) may result in the job's 624 // start time being a little after the "true" periodic start time (to avoid jobs 625 // running back to back). See JobSchedulerService#getRescheduleJobForPeriodic for more 626 // details. Since rescheduled periodic jobs may already be delayed slightly by this 627 // policy, don't penalize them further by then enforcing the full set of applied 628 // flex constraints at the beginning of the newly determined start time. Let the flex 629 // constraint requirement start closer to the true periodic start time. 630 final long truePeriodicStartTimeElapsed = 631 js.getLatestRunTimeElapsed() - js.getJob().getFlexMillis(); 632 // For now, treat the lifecycle beginning as the midpoint between the true periodic 633 // start time and the adjusted start time. 634 earliestRuntime = (earliestRuntime + truePeriodicStartTimeElapsed) / 2; 635 } 636 if (js.getJob().isPrefetch()) { 637 final long estimatedLaunchTime = 638 mPrefetchController.getNextEstimatedLaunchTimeLocked(js); 639 long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault( 640 js.getSourceUserId(), js.getSourcePackageName(), 0L); 641 if (estimatedLaunchTime != Long.MAX_VALUE) { 642 prefetchWindowStart = Math.max(prefetchWindowStart, 643 estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs()); 644 } 645 return Math.max(prefetchWindowStart, earliestRuntime); 646 } 647 return earliestRuntime; 648 } 649 650 @VisibleForTesting 651 @GuardedBy("mLock") getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed)652 int getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed) { 653 final JobScoreTracker scoreTracker = mJobScoreTrackers.get(uid, pkgName); 654 return scoreTracker == null ? 0 : scoreTracker.getScore(nowElapsed); 655 } 656 657 @VisibleForTesting 658 @GuardedBy("mLock") getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest)659 long getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest) { 660 if (js.getJob().isPrefetch()) { 661 final long estimatedLaunchTime = 662 mPrefetchController.getNextEstimatedLaunchTimeLocked(js); 663 // Prefetch jobs aren't supposed to have deadlines after T. 664 // But some legacy apps might still schedule them with deadlines. 665 if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) { 666 // If there is a deadline, the earliest time is the end of the lifecycle. 667 return Math.min( 668 estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS, 669 js.getLatestRunTimeElapsed()); 670 } 671 if (estimatedLaunchTime != Long.MAX_VALUE) { 672 return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS; 673 } 674 // There is no deadline and no estimated launch time. 675 return NO_LIFECYCLE_END; 676 } 677 // Increase the flex deadline for jobs rescheduled more than once. 678 if (js.getNumPreviousAttempts() > 1) { 679 return earliest + Math.min( 680 (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2), 681 mMaxRescheduledDeadline); 682 } 683 684 // Intentionally use the effective priority here. If a job's priority was effectively 685 // lowered, it will be less likely to run quickly given other policies in JobScheduler. 686 // Thus, there's no need to further delay the job based on flex policy. 687 final int jobPriority = js.getEffectivePriority(); 688 final int jobScore = 689 getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed); 690 // Set an upper limit on the fallback deadline so that the delay doesn't become extreme. 691 final long fallbackDurationMs = Math.min(3 * mFallbackFlexibilityDeadlineMs, 692 mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs) 693 + mFallbackFlexibilityAdditionalScoreTimeFactors 694 .get(jobPriority, MINUTE_IN_MILLIS) * jobScore); 695 final long fallbackDeadlineMs = earliest + fallbackDurationMs; 696 697 if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) { 698 return fallbackDeadlineMs; 699 } 700 return Math.max(fallbackDeadlineMs, js.getLatestRunTimeElapsed()); 701 } 702 703 @VisibleForTesting 704 @GuardedBy("mLock") getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed)705 int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) { 706 final long earliest = getLifeCycleBeginningElapsedLocked(js); 707 final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest); 708 if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) { 709 return 0; 710 } 711 if (nowElapsed > latest || latest == earliest) { 712 return 100; 713 } 714 final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest)); 715 return percentInTime; 716 } 717 718 @VisibleForTesting 719 @ElapsedRealtimeLong 720 @GuardedBy("mLock") getNextConstraintDropTimeElapsedLocked(JobStatus js)721 long getNextConstraintDropTimeElapsedLocked(JobStatus js) { 722 final long earliest = getLifeCycleBeginningElapsedLocked(js); 723 final long latest = 724 getLifeCycleEndElapsedLocked(js, sElapsedRealtimeClock.millis(), earliest); 725 return getNextConstraintDropTimeElapsedLocked(js, earliest, latest); 726 } 727 728 /** The elapsed time that marks when the next constraint should be dropped. */ 729 @ElapsedRealtimeLong 730 @GuardedBy("mLock") getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest)731 long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) { 732 final int[] percentsToDropConstraints = 733 getPercentsToDropConstraints(js.getEffectivePriority()); 734 if (latest == NO_LIFECYCLE_END 735 || js.getNumDroppedFlexibleConstraints() == percentsToDropConstraints.length) { 736 return NO_LIFECYCLE_END; 737 } 738 final int percent = percentsToDropConstraints[js.getNumDroppedFlexibleConstraints()]; 739 final long percentInTime = ((latest - earliest) * percent) / 100; 740 return earliest + percentInTime; 741 } 742 743 @NonNull getPercentsToDropConstraints(int priority)744 private int[] getPercentsToDropConstraints(int priority) { 745 int[] percentsToDropConstraints = mPercentsToDropConstraints.get(priority); 746 if (percentsToDropConstraints == null) { 747 Slog.wtf(TAG, "No %-to-drop for priority " + JobInfo.getPriorityString(priority)); 748 return new int[]{50, 60, 70, 80}; 749 } 750 return percentsToDropConstraints; 751 } 752 753 @Override 754 @GuardedBy("mLock") onUidBiasChangedLocked(int uid, int prevBias, int newBias)755 public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) { 756 if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE 757 && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) { 758 // All changes are below BFGS. There's no significant change to care about. 759 return; 760 } 761 final long nowElapsed = sElapsedRealtimeClock.millis(); 762 ArraySet<JobStatus> jobsByUid = mService.getJobStore().getJobsBySourceUid(uid); 763 boolean hasPrefetch = false; 764 for (int i = 0; i < jobsByUid.size(); i++) { 765 JobStatus js = jobsByUid.valueAt(i); 766 if (js.hasFlexibilityConstraint()) { 767 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); 768 hasPrefetch |= js.getJob().isPrefetch(); 769 } 770 } 771 772 // Prefetch jobs can't run when the app is TOP, so it should not be included in their 773 // lifecycle, and marks the beginning of a new lifecycle. 774 if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) { 775 final int userId = UserHandle.getUserId(uid); 776 final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid); 777 if (pkgs == null) { 778 return; 779 } 780 for (int i = 0; i < pkgs.size(); i++) { 781 String pkg = pkgs.valueAt(i); 782 mPrefetchLifeCycleStart.add(userId, pkg, 783 Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L), 784 nowElapsed)); 785 } 786 } 787 } 788 789 @Override 790 @GuardedBy("mLock") onConstantsUpdatedLocked()791 public void onConstantsUpdatedLocked() { 792 if (mFcConfig.mShouldReevaluateConstraints) { 793 AppSchedulingModuleThread.getHandler().post(() -> { 794 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 795 synchronized (mLock) { 796 final long nowElapsed = sElapsedRealtimeClock.millis(); 797 for (int j = 0; j < mFlexibilityTracker.size(); j++) { 798 final ArraySet<JobStatus> jobs = mFlexibilityTracker 799 .getJobsByNumRequiredConstraints(j); 800 for (int i = jobs.size() - 1; i >= 0; --i) { 801 JobStatus js = jobs.valueAt(i); 802 mFlexibilityTracker.updateFlexibleConstraints(js, nowElapsed); 803 mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); 804 if (js.setFlexibilityConstraintSatisfied( 805 nowElapsed, isFlexibilitySatisfiedLocked(js))) { 806 changedJobs.add(js); 807 } 808 } 809 } 810 } 811 if (changedJobs.size() > 0) { 812 mStateChangedListener.onControllerStateChanged(changedJobs); 813 } 814 }); 815 } 816 } 817 818 @Override 819 @GuardedBy("mLock") prepareForUpdatedConstantsLocked()820 public void prepareForUpdatedConstantsLocked() { 821 mFcConfig.mShouldReevaluateConstraints = false; 822 } 823 824 @Override 825 @GuardedBy("mLock") processConstantLocked(DeviceConfig.Properties properties, String key)826 public void processConstantLocked(DeviceConfig.Properties properties, String key) { 827 mFcConfig.processConstantLocked(properties, key); 828 } 829 830 @VisibleForTesting 831 class FlexibilityTracker { 832 final ArrayList<ArraySet<JobStatus>> mTrackedJobs; 833 FlexibilityTracker(int numFlexibleConstraints)834 FlexibilityTracker(int numFlexibleConstraints) { 835 mTrackedJobs = new ArrayList<>(); 836 for (int i = 0; i <= numFlexibleConstraints; i++) { 837 mTrackedJobs.add(new ArraySet<JobStatus>()); 838 } 839 } 840 841 /** Gets every tracked job with a given number of required constraints. */ 842 @Nullable getJobsByNumRequiredConstraints(int numRequired)843 public ArraySet<JobStatus> getJobsByNumRequiredConstraints(int numRequired) { 844 if (numRequired > mTrackedJobs.size()) { 845 Slog.wtfStack(TAG, "Asked for a larger number of constraints than exists."); 846 return null; 847 } 848 return mTrackedJobs.get(numRequired); 849 } 850 851 /** adds a JobStatus object based on number of required flexible constraints. */ add(JobStatus js)852 public void add(JobStatus js) { 853 if (js.getNumRequiredFlexibleConstraints() < 0) { 854 return; 855 } 856 mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).add(js); 857 } 858 859 /** Removes a JobStatus object. */ remove(JobStatus js)860 public void remove(JobStatus js) { 861 mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(js); 862 } 863 864 /** 865 * Updates applied and dropped constraints for the job. 866 */ updateFlexibleConstraints(JobStatus js, long nowElapsed)867 public void updateFlexibleConstraints(JobStatus js, long nowElapsed) { 868 final int prevNumRequired = js.getNumRequiredFlexibleConstraints(); 869 870 final int numAppliedConstraints = 871 Integer.bitCount(getRelevantAppliedConstraintsLocked(js)); 872 js.setNumAppliedFlexibleConstraints(numAppliedConstraints); 873 874 final int[] percentsToDropConstraints = 875 getPercentsToDropConstraints(js.getEffectivePriority()); 876 final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); 877 int toDrop = 0; 878 for (int i = 0; i < numAppliedConstraints; i++) { 879 if (curPercent >= percentsToDropConstraints[i]) { 880 toDrop++; 881 } 882 } 883 js.setNumDroppedFlexibleConstraints(toDrop); 884 885 if (prevNumRequired == js.getNumRequiredFlexibleConstraints()) { 886 return; 887 } 888 mTrackedJobs.get(prevNumRequired).remove(js); 889 add(js); 890 } 891 892 /** 893 * Calculates the number of constraints that should be dropped for the job, based on how 894 * far along the job is into its lifecycle. 895 */ calculateNumDroppedConstraints(JobStatus js, long nowElapsed)896 public void calculateNumDroppedConstraints(JobStatus js, long nowElapsed) { 897 final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); 898 int toDrop = 0; 899 final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints(); 900 final int[] percentsToDropConstraints = 901 getPercentsToDropConstraints(js.getEffectivePriority()); 902 for (int i = 0; i < jsMaxFlexibleConstraints; i++) { 903 if (curPercent >= percentsToDropConstraints[i]) { 904 toDrop++; 905 } 906 } 907 setNumDroppedFlexibleConstraints(js, toDrop); 908 } 909 910 /** Returns all tracked jobs. */ getArrayList()911 public ArrayList<ArraySet<JobStatus>> getArrayList() { 912 return mTrackedJobs; 913 } 914 915 /** 916 * Updates the number of dropped flexible constraints and sorts it into the tracker. 917 */ setNumDroppedFlexibleConstraints(JobStatus js, int numDropped)918 public void setNumDroppedFlexibleConstraints(JobStatus js, int numDropped) { 919 if (numDropped != js.getNumDroppedFlexibleConstraints()) { 920 remove(js); 921 js.setNumDroppedFlexibleConstraints(numDropped); 922 add(js); 923 } 924 } 925 size()926 public int size() { 927 return mTrackedJobs.size(); 928 } 929 dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed)930 public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed) { 931 for (int i = 0; i < mTrackedJobs.size(); i++) { 932 ArraySet<JobStatus> jobs = mTrackedJobs.get(i); 933 for (int j = 0; j < jobs.size(); j++) { 934 final JobStatus js = jobs.valueAt(j); 935 if (!predicate.test(js)) { 936 continue; 937 } 938 js.printUniqueId(pw); 939 pw.print(" from "); 940 UserHandle.formatUid(pw, js.getSourceUid()); 941 pw.print("-> Num Required Constraints: "); 942 pw.print(js.getNumRequiredFlexibleConstraints()); 943 944 pw.print(", lifecycle=["); 945 final long earliest = getLifeCycleBeginningElapsedLocked(js); 946 pw.print(earliest); 947 pw.print(", ("); 948 pw.print(getCurPercentOfLifecycleLocked(js, nowElapsed)); 949 pw.print("%), "); 950 pw.print(getLifeCycleEndElapsedLocked(js, nowElapsed, earliest)); 951 pw.print("]"); 952 953 pw.println(); 954 } 955 } 956 } 957 } 958 959 @VisibleForTesting 960 class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> { FlexibilityAlarmQueue(Context context, Looper looper)961 private FlexibilityAlarmQueue(Context context, Looper looper) { 962 super(context, looper, "*job.flexibility_check*", 963 "Flexible Constraint Check", true, 964 mMinTimeBetweenFlexibilityAlarmsMs); 965 } 966 967 @Override isForUser(@onNull JobStatus js, int userId)968 protected boolean isForUser(@NonNull JobStatus js, int userId) { 969 return js.getSourceUserId() == userId; 970 } 971 scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed)972 public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) { 973 synchronized (mLock) { 974 final long earliest = getLifeCycleBeginningElapsedLocked(js); 975 final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest); 976 if (latest <= earliest) { 977 // Something has gone horribly wrong. This has only occurred on incorrectly 978 // configured tests, but add a check here for safety. 979 Slog.wtf(TAG, "Got invalid latest when scheduling alarm." 980 + " prefetch=" + js.getJob().isPrefetch() 981 + " periodic=" + js.getJob().isPeriodic()); 982 // Since things have gone wrong, the safest and most reliable thing to do is 983 // stop applying flex policy to the job. 984 mFlexibilityTracker.setNumDroppedFlexibleConstraints(js, 985 js.getNumAppliedFlexibleConstraints()); 986 mJobsToCheck.add(js); 987 mHandler.sendEmptyMessage(MSG_CHECK_JOBS); 988 return; 989 } 990 991 final long nextTimeElapsed = 992 getNextConstraintDropTimeElapsedLocked(js, earliest, latest); 993 994 if (DEBUG) { 995 Slog.d(TAG, "scheduleDropNumConstraintsAlarm: " 996 + js.toShortString() 997 + " numApplied: " + js.getNumAppliedFlexibleConstraints() 998 + " numRequired: " + js.getNumRequiredFlexibleConstraints() 999 + " numSatisfied: " + Integer.bitCount( 1000 mSatisfiedFlexibleConstraints & getRelevantAppliedConstraintsLocked(js)) 1001 + " curTime: " + nowElapsed 1002 + " earliest: " + earliest 1003 + " latest: " + latest 1004 + " nextTime: " + nextTimeElapsed); 1005 } 1006 if (latest - nowElapsed < mDeadlineProximityLimitMs) { 1007 if (DEBUG) { 1008 Slog.d(TAG, "deadline proximity met: " + js); 1009 } 1010 mFlexibilityTracker.setNumDroppedFlexibleConstraints(js, 1011 js.getNumAppliedFlexibleConstraints()); 1012 mJobsToCheck.add(js); 1013 mHandler.sendEmptyMessage(MSG_CHECK_JOBS); 1014 return; 1015 } 1016 if (nextTimeElapsed == NO_LIFECYCLE_END) { 1017 // There is no known or estimated next time to drop a constraint. 1018 removeAlarmForKey(js); 1019 return; 1020 } 1021 if (latest - nextTimeElapsed <= mDeadlineProximityLimitMs) { 1022 if (DEBUG) { 1023 Slog.d(TAG, "last alarm set: " + js); 1024 } 1025 addAlarm(js, latest - mDeadlineProximityLimitMs); 1026 return; 1027 } 1028 addAlarm(js, nextTimeElapsed); 1029 } 1030 } 1031 1032 @Override processExpiredAlarms(@onNull ArraySet<JobStatus> expired)1033 protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) { 1034 synchronized (mLock) { 1035 ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1036 final long nowElapsed = sElapsedRealtimeClock.millis(); 1037 for (int i = 0; i < expired.size(); i++) { 1038 JobStatus js = expired.valueAt(i); 1039 if (DEBUG) { 1040 Slog.d(TAG, "Alarm fired for " + js.toShortString()); 1041 } 1042 mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); 1043 if (js.getNumRequiredFlexibleConstraints() > 0) { 1044 scheduleDropNumConstraintsAlarm(js, nowElapsed); 1045 } 1046 if (js.setFlexibilityConstraintSatisfied(nowElapsed, 1047 isFlexibilitySatisfiedLocked(js))) { 1048 changedJobs.add(js); 1049 } 1050 } 1051 mStateChangedListener.onControllerStateChanged(changedJobs); 1052 } 1053 } 1054 } 1055 1056 private class FcHandler extends Handler { FcHandler(Looper looper)1057 FcHandler(Looper looper) { 1058 super(looper); 1059 } 1060 1061 @Override handleMessage(Message msg)1062 public void handleMessage(Message msg) { 1063 switch (msg.what) { 1064 case MSG_CHECK_ALL_JOBS: 1065 removeMessages(MSG_CHECK_ALL_JOBS); 1066 1067 synchronized (mLock) { 1068 mJobsToCheck.clear(); 1069 mPackagesToCheck.clear(); 1070 final long nowElapsed = sElapsedRealtimeClock.millis(); 1071 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1072 1073 final int numAppliedSystemWideConstraints = Integer.bitCount( 1074 mAppliedConstraints & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS); 1075 for (int o = 0; o <= numAppliedSystemWideConstraints; ++o) { 1076 final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker 1077 .getJobsByNumRequiredConstraints(o); 1078 1079 if (jobsByNumConstraints != null) { 1080 for (int i = 0; i < jobsByNumConstraints.size(); i++) { 1081 final JobStatus js = jobsByNumConstraints.valueAt(i); 1082 if (js.setFlexibilityConstraintSatisfied( 1083 nowElapsed, isFlexibilitySatisfiedLocked(js))) { 1084 changedJobs.add(js); 1085 } 1086 } 1087 } 1088 } 1089 if (changedJobs.size() > 0) { 1090 mStateChangedListener.onControllerStateChanged(changedJobs); 1091 } 1092 } 1093 break; 1094 1095 case MSG_CHECK_JOBS: 1096 synchronized (mLock) { 1097 final long nowElapsed = sElapsedRealtimeClock.millis(); 1098 ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1099 1100 for (int i = mJobsToCheck.size() - 1; i >= 0; --i) { 1101 final JobStatus js = mJobsToCheck.valueAt(i); 1102 if (DEBUG) { 1103 Slog.d(TAG, "Checking on " + js.toShortString()); 1104 } 1105 if (js.setFlexibilityConstraintSatisfied( 1106 nowElapsed, isFlexibilitySatisfiedLocked(js))) { 1107 changedJobs.add(js); 1108 } 1109 } 1110 1111 mJobsToCheck.clear(); 1112 if (changedJobs.size() > 0) { 1113 mStateChangedListener.onControllerStateChanged(changedJobs); 1114 } 1115 } 1116 break; 1117 1118 case MSG_CHECK_PACKAGES: 1119 synchronized (mLock) { 1120 final long nowElapsed = sElapsedRealtimeClock.millis(); 1121 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1122 1123 mService.getJobStore().forEachJob( 1124 (js) -> mPackagesToCheck.contains(js.getSourcePackageName()) 1125 || mPackagesToCheck.contains(js.getCallingPackageName()), 1126 (js) -> { 1127 if (DEBUG) { 1128 Slog.d(TAG, "Checking on " + js.toShortString()); 1129 } 1130 if (js.setFlexibilityConstraintSatisfied( 1131 nowElapsed, isFlexibilitySatisfiedLocked(js))) { 1132 changedJobs.add(js); 1133 } 1134 }); 1135 1136 mPackagesToCheck.clear(); 1137 if (changedJobs.size() > 0) { 1138 mStateChangedListener.onControllerStateChanged(changedJobs); 1139 } 1140 } 1141 break; 1142 } 1143 } 1144 } 1145 1146 class FcConfig { 1147 private boolean mShouldReevaluateConstraints = false; 1148 1149 /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ 1150 private static final String FC_CONFIG_PREFIX = "fc_"; 1151 1152 @VisibleForTesting 1153 static final String KEY_APPLIED_CONSTRAINTS = FC_CONFIG_PREFIX + "applied_constraints"; 1154 static final String KEY_DEADLINE_PROXIMITY_LIMIT = 1155 FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms"; 1156 static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE = 1157 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms"; 1158 static final String KEY_FALLBACK_FLEXIBILITY_DEADLINES = 1159 FC_CONFIG_PREFIX + "fallback_flexibility_deadlines"; 1160 static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES = 1161 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_scores"; 1162 static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS = 1163 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_additional_score_time_factors"; 1164 static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = 1165 FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms"; 1166 static final String KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = 1167 FC_CONFIG_PREFIX + "percents_to_drop_flexible_constraints"; 1168 static final String KEY_MAX_RESCHEDULED_DEADLINE_MS = 1169 FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms"; 1170 static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = 1171 FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; 1172 static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 1173 FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms"; 1174 1175 static final int DEFAULT_APPLIED_CONSTRAINTS = 0; 1176 @VisibleForTesting 1177 static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; 1178 @VisibleForTesting 1179 static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS; 1180 static final SparseLongArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray(); 1181 static final SparseIntArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES = 1182 new SparseIntArray(); 1183 static final SparseLongArray 1184 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS = 1185 new SparseLongArray(); 1186 @VisibleForTesting 1187 static final SparseArray<int[]> DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = 1188 new SparseArray<>(); 1189 1190 static { DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS)1191 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS)1192 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS)1193 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS)1194 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS)1195 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5)1196 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4)1197 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3)1198 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2)1199 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2); DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1)1200 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1); 1201 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS put(PRIORITY_MAX, 0)1202 .put(PRIORITY_MAX, 0); 1203 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS put(PRIORITY_HIGH, 3 * MINUTE_IN_MILLIS)1204 .put(PRIORITY_HIGH, 3 * MINUTE_IN_MILLIS); 1205 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS put(PRIORITY_DEFAULT, 2 * MINUTE_IN_MILLIS)1206 .put(PRIORITY_DEFAULT, 2 * MINUTE_IN_MILLIS); 1207 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS put(PRIORITY_LOW, 1 * MINUTE_IN_MILLIS)1208 .put(PRIORITY_LOW, 1 * MINUTE_IN_MILLIS); 1209 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS)1210 .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS); 1211 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS put(PRIORITY_MAX, new int[]{1, 2, 3, 4})1212 .put(PRIORITY_MAX, new int[]{1, 2, 3, 4}); 1213 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS put(PRIORITY_HIGH, new int[]{33, 50, 60, 75})1214 .put(PRIORITY_HIGH, new int[]{33, 50, 60, 75}); 1215 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80})1216 .put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80}); 1217 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS put(PRIORITY_LOW, new int[]{50, 60, 70, 80})1218 .put(PRIORITY_LOW, new int[]{50, 60, 70, 80}); 1219 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS put(PRIORITY_MIN, new int[]{55, 65, 75, 85})1220 .put(PRIORITY_MIN, new int[]{55, 65, 75, 85}); 1221 } 1222 1223 private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS; 1224 private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS; 1225 private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = DAY_IN_MILLIS; 1226 @VisibleForTesting 1227 static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS; 1228 1229 /** Which constraints to apply/consider in flex policy. */ 1230 public int APPLIED_CONSTRAINTS = DEFAULT_APPLIED_CONSTRAINTS; 1231 /** How close to a jobs' deadline all flexible constraints will be dropped. */ 1232 public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS; 1233 /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */ 1234 public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; 1235 public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = 1236 DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; 1237 /** 1238 * The percentages of a jobs' lifecycle to drop the number of required constraints. 1239 * Keyed by job priority. 1240 */ 1241 public SparseArray<int[]> PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = new SparseArray<>(); 1242 /** Initial fallback flexible deadline for rescheduled jobs. */ 1243 public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS; 1244 /** The max deadline for rescheduled jobs. */ 1245 public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS; 1246 /** 1247 * How long to wait after last seeing a constraint combination before no longer waiting for 1248 * it in order to run jobs. 1249 */ 1250 public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; 1251 /** 1252 * The base fallback deadlines to use if a job doesn't have its own deadline. Values are in 1253 * milliseconds and keyed by job priority. 1254 */ 1255 public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray(); 1256 /** 1257 * The score to ascribe to each job, keyed by job priority. 1258 */ 1259 public final SparseIntArray FALLBACK_FLEXIBILITY_DEADLINE_SCORES = new SparseIntArray(); 1260 /** 1261 * How much additional time to increase the fallback deadline by based on the app's current 1262 * job run score. Values are in 1263 * milliseconds and keyed by job priority. 1264 */ 1265 public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS = 1266 new SparseLongArray(); 1267 FcConfig()1268 FcConfig() { 1269 // Copy the values from the DEFAULT_* data structures to avoid accidentally modifying 1270 // the DEFAULT_* data structures in other parts of the code. 1271 for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.size(); ++i) { 1272 FALLBACK_FLEXIBILITY_DEADLINES.put( 1273 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.keyAt(i), 1274 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.valueAt(i)); 1275 } 1276 for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.size(); ++i) { 1277 FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put( 1278 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.keyAt(i), 1279 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.valueAt(i)); 1280 } 1281 for (int i = 0; 1282 i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.size(); 1283 ++i) { 1284 FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.put( 1285 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS 1286 .keyAt(i), 1287 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS 1288 .valueAt(i)); 1289 } 1290 for (int i = 0; i < DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.size(); ++i) { 1291 PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.put( 1292 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.keyAt(i), 1293 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.valueAt(i)); 1294 } 1295 } 1296 1297 @GuardedBy("mLock") processConstantLocked(@onNull DeviceConfig.Properties properties, @NonNull String key)1298 public void processConstantLocked(@NonNull DeviceConfig.Properties properties, 1299 @NonNull String key) { 1300 // TODO(257322915): add appropriate minimums and maximums to constants when parsing 1301 switch (key) { 1302 case KEY_APPLIED_CONSTRAINTS: 1303 APPLIED_CONSTRAINTS = 1304 properties.getInt(key, DEFAULT_APPLIED_CONSTRAINTS) 1305 & mSupportedFlexConstraints; 1306 if (mAppliedConstraints != APPLIED_CONSTRAINTS) { 1307 mAppliedConstraints = APPLIED_CONSTRAINTS; 1308 mShouldReevaluateConstraints = true; 1309 if (mAppliedConstraints != 0) { 1310 mFlexibilityEnabled = true; 1311 mPrefetchController 1312 .registerPrefetchChangedListener(mPrefetchChangedListener); 1313 mSpecialAppTracker.startTracking(); 1314 } else { 1315 mFlexibilityEnabled = false; 1316 mPrefetchController 1317 .unRegisterPrefetchChangedListener(mPrefetchChangedListener); 1318 mSpecialAppTracker.stopTracking(); 1319 } 1320 } 1321 break; 1322 case KEY_RESCHEDULED_JOB_DEADLINE_MS: 1323 RESCHEDULED_JOB_DEADLINE_MS = 1324 properties.getLong(key, DEFAULT_RESCHEDULED_JOB_DEADLINE_MS); 1325 if (mRescheduledJobDeadline != RESCHEDULED_JOB_DEADLINE_MS) { 1326 mRescheduledJobDeadline = RESCHEDULED_JOB_DEADLINE_MS; 1327 mShouldReevaluateConstraints = true; 1328 } 1329 break; 1330 case KEY_MAX_RESCHEDULED_DEADLINE_MS: 1331 MAX_RESCHEDULED_DEADLINE_MS = 1332 properties.getLong(key, DEFAULT_MAX_RESCHEDULED_DEADLINE_MS); 1333 if (mMaxRescheduledDeadline != MAX_RESCHEDULED_DEADLINE_MS) { 1334 mMaxRescheduledDeadline = MAX_RESCHEDULED_DEADLINE_MS; 1335 mShouldReevaluateConstraints = true; 1336 } 1337 break; 1338 case KEY_DEADLINE_PROXIMITY_LIMIT: 1339 DEADLINE_PROXIMITY_LIMIT_MS = 1340 properties.getLong(key, DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS); 1341 if (mDeadlineProximityLimitMs != DEADLINE_PROXIMITY_LIMIT_MS) { 1342 mDeadlineProximityLimitMs = DEADLINE_PROXIMITY_LIMIT_MS; 1343 mShouldReevaluateConstraints = true; 1344 } 1345 break; 1346 case KEY_FALLBACK_FLEXIBILITY_DEADLINE: 1347 FALLBACK_FLEXIBILITY_DEADLINE_MS = 1348 properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS); 1349 if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) { 1350 mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS; 1351 } 1352 break; 1353 case KEY_FALLBACK_FLEXIBILITY_DEADLINES: 1354 if (parsePriorityToLongKeyValueString( 1355 properties.getString(key, null), 1356 FALLBACK_FLEXIBILITY_DEADLINES, 1357 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES)) { 1358 mFallbackFlexibilityDeadlines = FALLBACK_FLEXIBILITY_DEADLINES; 1359 mShouldReevaluateConstraints = true; 1360 } 1361 break; 1362 case KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES: 1363 if (parsePriorityToIntKeyValueString( 1364 properties.getString(key, null), 1365 FALLBACK_FLEXIBILITY_DEADLINE_SCORES, 1366 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES)) { 1367 mFallbackFlexibilityDeadlineScores = FALLBACK_FLEXIBILITY_DEADLINE_SCORES; 1368 mShouldReevaluateConstraints = true; 1369 } 1370 break; 1371 case KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS: 1372 if (parsePriorityToLongKeyValueString( 1373 properties.getString(key, null), 1374 FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS, 1375 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS)) { 1376 mFallbackFlexibilityAdditionalScoreTimeFactors = 1377 FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS; 1378 mShouldReevaluateConstraints = true; 1379 } 1380 break; 1381 case KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS: 1382 MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = 1383 properties.getLong(key, DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS); 1384 if (mMinTimeBetweenFlexibilityAlarmsMs 1385 != MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS) { 1386 mMinTimeBetweenFlexibilityAlarmsMs = MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; 1387 mFlexibilityAlarmQueue 1388 .setMinTimeBetweenAlarmsMs(MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS); 1389 mShouldReevaluateConstraints = true; 1390 } 1391 break; 1392 case KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS: 1393 UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 1394 properties.getLong(key, DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS); 1395 if (mUnseenConstraintGracePeriodMs != UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) { 1396 mUnseenConstraintGracePeriodMs = UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; 1397 mShouldReevaluateConstraints = true; 1398 } 1399 break; 1400 case KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS: 1401 if (parsePercentToDropKeyValueString( 1402 properties.getString(key, null), 1403 PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, 1404 DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS)) { 1405 mPercentsToDropConstraints = PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS; 1406 mShouldReevaluateConstraints = true; 1407 } 1408 break; 1409 } 1410 } 1411 parsePercentToDropKeyValueString(@ullable String s, SparseArray<int[]> into, SparseArray<int[]> defaults)1412 private boolean parsePercentToDropKeyValueString(@Nullable String s, 1413 SparseArray<int[]> into, SparseArray<int[]> defaults) { 1414 final KeyValueListParser priorityParser = new KeyValueListParser(','); 1415 try { 1416 priorityParser.setString(s); 1417 } catch (IllegalArgumentException e) { 1418 Slog.wtf(TAG, "Bad percent to drop key value string given", e); 1419 // Clear the string and continue with the defaults. 1420 priorityParser.setString(null); 1421 } 1422 1423 final int[] oldMax = into.get(PRIORITY_MAX); 1424 final int[] oldHigh = into.get(PRIORITY_HIGH); 1425 final int[] oldDefault = into.get(PRIORITY_DEFAULT); 1426 final int[] oldLow = into.get(PRIORITY_LOW); 1427 final int[] oldMin = into.get(PRIORITY_MIN); 1428 1429 final int[] newMax = parsePercentToDropString(priorityParser.getString( 1430 String.valueOf(PRIORITY_MAX), null)); 1431 final int[] newHigh = parsePercentToDropString(priorityParser.getString( 1432 String.valueOf(PRIORITY_HIGH), null)); 1433 final int[] newDefault = parsePercentToDropString(priorityParser.getString( 1434 String.valueOf(PRIORITY_DEFAULT), null)); 1435 final int[] newLow = parsePercentToDropString(priorityParser.getString( 1436 String.valueOf(PRIORITY_LOW), null)); 1437 final int[] newMin = parsePercentToDropString(priorityParser.getString( 1438 String.valueOf(PRIORITY_MIN), null)); 1439 1440 into.put(PRIORITY_MAX, newMax == null ? defaults.get(PRIORITY_MAX) : newMax); 1441 into.put(PRIORITY_HIGH, newHigh == null ? defaults.get(PRIORITY_HIGH) : newHigh); 1442 into.put(PRIORITY_DEFAULT, 1443 newDefault == null ? defaults.get(PRIORITY_DEFAULT) : newDefault); 1444 into.put(PRIORITY_LOW, newLow == null ? defaults.get(PRIORITY_LOW) : newLow); 1445 into.put(PRIORITY_MIN, newMin == null ? defaults.get(PRIORITY_MIN) : newMin); 1446 1447 return !Arrays.equals(oldMax, into.get(PRIORITY_MAX)) 1448 || !Arrays.equals(oldHigh, into.get(PRIORITY_HIGH)) 1449 || !Arrays.equals(oldDefault, into.get(PRIORITY_DEFAULT)) 1450 || !Arrays.equals(oldLow, into.get(PRIORITY_LOW)) 1451 || !Arrays.equals(oldMin, into.get(PRIORITY_MIN)); 1452 } 1453 1454 @Nullable parsePercentToDropString(@ullable String s)1455 private int[] parsePercentToDropString(@Nullable String s) { 1456 if (s == null || s.isEmpty()) { 1457 return null; 1458 } 1459 final String[] dropPercentString = s.split("\\|"); 1460 int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)]; 1461 if (dropPercentInt.length != dropPercentString.length) { 1462 return null; 1463 } 1464 int prevPercent = 0; 1465 for (int i = 0; i < dropPercentString.length; i++) { 1466 try { 1467 dropPercentInt[i] = 1468 Integer.parseInt(dropPercentString[i]); 1469 } catch (NumberFormatException ex) { 1470 Slog.e(TAG, "Provided string was improperly formatted.", ex); 1471 return null; 1472 } 1473 if (dropPercentInt[i] < prevPercent) { 1474 Slog.wtf(TAG, "Percents to drop constraints were not in increasing order."); 1475 return null; 1476 } 1477 if (dropPercentInt[i] > 100) { 1478 Slog.e(TAG, "Found % over 100"); 1479 return null; 1480 } 1481 prevPercent = dropPercentInt[i]; 1482 } 1483 1484 return dropPercentInt; 1485 } 1486 1487 /** 1488 * Parses the input string, expecting it to a key-value string where the keys are job 1489 * priorities, and replaces everything in {@code into} with the values from the string, 1490 * or the default values if the string contains none. 1491 * 1492 * Returns true if any values changed. 1493 */ parsePriorityToIntKeyValueString(@ullable String s, SparseIntArray into, SparseIntArray defaults)1494 private boolean parsePriorityToIntKeyValueString(@Nullable String s, 1495 SparseIntArray into, SparseIntArray defaults) { 1496 final KeyValueListParser parser = new KeyValueListParser(','); 1497 try { 1498 parser.setString(s); 1499 } catch (IllegalArgumentException e) { 1500 Slog.wtf(TAG, "Bad string given", e); 1501 // Clear the string and continue with the defaults. 1502 parser.setString(null); 1503 } 1504 1505 final int oldMax = into.get(PRIORITY_MAX); 1506 final int oldHigh = into.get(PRIORITY_HIGH); 1507 final int oldDefault = into.get(PRIORITY_DEFAULT); 1508 final int oldLow = into.get(PRIORITY_LOW); 1509 final int oldMin = into.get(PRIORITY_MIN); 1510 1511 final int newMax = parser.getInt(String.valueOf(PRIORITY_MAX), 1512 defaults.get(PRIORITY_MAX)); 1513 final int newHigh = parser.getInt(String.valueOf(PRIORITY_HIGH), 1514 defaults.get(PRIORITY_HIGH)); 1515 final int newDefault = parser.getInt(String.valueOf(PRIORITY_DEFAULT), 1516 defaults.get(PRIORITY_DEFAULT)); 1517 final int newLow = parser.getInt(String.valueOf(PRIORITY_LOW), 1518 defaults.get(PRIORITY_LOW)); 1519 final int newMin = parser.getInt(String.valueOf(PRIORITY_MIN), 1520 defaults.get(PRIORITY_MIN)); 1521 1522 into.put(PRIORITY_MAX, newMax); 1523 into.put(PRIORITY_HIGH, newHigh); 1524 into.put(PRIORITY_DEFAULT, newDefault); 1525 into.put(PRIORITY_LOW, newLow); 1526 into.put(PRIORITY_MIN, newMin); 1527 1528 return oldMax != newMax 1529 || oldHigh != newHigh 1530 || oldDefault != newDefault 1531 || oldLow != newLow 1532 || oldMin != newMin; 1533 } 1534 1535 /** 1536 * Parses the input string, expecting it to a key-value string where the keys are job 1537 * priorities, and replaces everything in {@code into} with the values from the string, 1538 * or the default values if the string contains none. 1539 * 1540 * Returns true if any values changed. 1541 */ parsePriorityToLongKeyValueString(@ullable String s, SparseLongArray into, SparseLongArray defaults)1542 private boolean parsePriorityToLongKeyValueString(@Nullable String s, 1543 SparseLongArray into, SparseLongArray defaults) { 1544 final KeyValueListParser parser = new KeyValueListParser(','); 1545 try { 1546 parser.setString(s); 1547 } catch (IllegalArgumentException e) { 1548 Slog.wtf(TAG, "Bad string given", e); 1549 // Clear the string and continue with the defaults. 1550 parser.setString(null); 1551 } 1552 1553 final long oldMax = into.get(PRIORITY_MAX); 1554 final long oldHigh = into.get(PRIORITY_HIGH); 1555 final long oldDefault = into.get(PRIORITY_DEFAULT); 1556 final long oldLow = into.get(PRIORITY_LOW); 1557 final long oldMin = into.get(PRIORITY_MIN); 1558 1559 final long newMax = parser.getLong(String.valueOf(PRIORITY_MAX), 1560 defaults.get(PRIORITY_MAX)); 1561 final long newHigh = parser.getLong(String.valueOf(PRIORITY_HIGH), 1562 defaults.get(PRIORITY_HIGH)); 1563 final long newDefault = parser.getLong(String.valueOf(PRIORITY_DEFAULT), 1564 defaults.get(PRIORITY_DEFAULT)); 1565 final long newLow = parser.getLong(String.valueOf(PRIORITY_LOW), 1566 defaults.get(PRIORITY_LOW)); 1567 final long newMin = parser.getLong(String.valueOf(PRIORITY_MIN), 1568 defaults.get(PRIORITY_MIN)); 1569 1570 into.put(PRIORITY_MAX, newMax); 1571 into.put(PRIORITY_HIGH, newHigh); 1572 into.put(PRIORITY_DEFAULT, newDefault); 1573 into.put(PRIORITY_LOW, newLow); 1574 into.put(PRIORITY_MIN, newMin); 1575 1576 return oldMax != newMax 1577 || oldHigh != newHigh 1578 || oldDefault != newDefault 1579 || oldLow != newLow 1580 || oldMin != newMin; 1581 } 1582 dump(IndentingPrintWriter pw)1583 private void dump(IndentingPrintWriter pw) { 1584 pw.println(); 1585 pw.print(FlexibilityController.class.getSimpleName()); 1586 pw.println(":"); 1587 pw.increaseIndent(); 1588 1589 pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS); 1590 pw.print("("); 1591 if (APPLIED_CONSTRAINTS != 0) { 1592 JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS); 1593 } else { 1594 pw.print("nothing"); 1595 } 1596 pw.println(")"); 1597 pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println(); 1598 pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println(); 1599 pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINES, FALLBACK_FLEXIBILITY_DEADLINES).println(); 1600 pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES, 1601 FALLBACK_FLEXIBILITY_DEADLINE_SCORES).println(); 1602 pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS, 1603 FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS).println(); 1604 pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS, 1605 MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println(); 1606 pw.print(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS, 1607 PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS).println(); 1608 pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println(); 1609 pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println(); 1610 pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) 1611 .println(); 1612 1613 pw.decreaseIndent(); 1614 } 1615 } 1616 1617 @VisibleForTesting 1618 @NonNull getFcConfig()1619 FcConfig getFcConfig() { 1620 return mFcConfig; 1621 } 1622 1623 private class SpecialAppTracker { 1624 /** 1625 * Lock for objects inside this class. This should never be held when attempting to acquire 1626 * {@link #mLock}. It is fine to acquire this if already holding {@link #mLock}. 1627 */ 1628 private final Object mSatLock = new Object(); 1629 1630 private DeviceIdleInternal mDeviceIdleInternal; 1631 private TelephonyManager mTelephonyManager; 1632 1633 private final boolean mHasFeatureTelephonySubscription; 1634 1635 /** Set of all apps that have been deemed special, keyed by user ID. */ 1636 private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>(); 1637 /** 1638 * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged 1639 * for. 1640 */ 1641 @GuardedBy("mSatLock") 1642 private final SparseSetArray<String> mCarrierPrivilegedApps = new SparseSetArray<>(); 1643 @GuardedBy("mSatLock") 1644 private final SparseArray<LogicalIndexCarrierPrivilegesCallback> 1645 mCarrierPrivilegedCallbacks = new SparseArray<>(); 1646 @GuardedBy("mSatLock") 1647 private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>(); 1648 1649 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1650 @Override 1651 public void onReceive(Context context, Intent intent) { 1652 switch (intent.getAction()) { 1653 case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED: 1654 updateCarrierPrivilegedCallbackRegistration(); 1655 break; 1656 1657 case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: 1658 mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache); 1659 break; 1660 } 1661 } 1662 }; 1663 SpecialAppTracker()1664 SpecialAppTracker() { 1665 mHasFeatureTelephonySubscription = mContext.getPackageManager() 1666 .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION); 1667 } 1668 isSpecialApp(final int userId, @NonNull String packageName)1669 public boolean isSpecialApp(final int userId, @NonNull String packageName) { 1670 synchronized (mSatLock) { 1671 if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) { 1672 return true; 1673 } 1674 if (mSpecialApps.contains(userId, packageName)) { 1675 return true; 1676 } 1677 } 1678 return false; 1679 } 1680 isSpecialAppInternal(final int userId, @NonNull String packageName)1681 private boolean isSpecialAppInternal(final int userId, @NonNull String packageName) { 1682 synchronized (mSatLock) { 1683 if (mPowerAllowlistedApps.contains(packageName)) { 1684 return true; 1685 } 1686 for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) { 1687 if (mCarrierPrivilegedApps.contains( 1688 mCarrierPrivilegedApps.keyAt(l), packageName)) { 1689 return true; 1690 } 1691 } 1692 } 1693 return false; 1694 } 1695 onAppRemoved(final int userId, String packageName)1696 private void onAppRemoved(final int userId, String packageName) { 1697 synchronized (mSatLock) { 1698 // Don't touch the USER_ALL set here. If the app is completely removed from the 1699 // device, any list that affects USER_ALL should update and this would eventually 1700 // be updated with those lists no longer containing the app. 1701 mSpecialApps.remove(userId, packageName); 1702 } 1703 } 1704 onSystemServicesReady()1705 private void onSystemServicesReady() { 1706 mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class); 1707 mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 1708 1709 synchronized (mLock) { 1710 if (mFlexibilityEnabled) { 1711 mHandler.post( 1712 SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration); 1713 mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache); 1714 } 1715 } 1716 } 1717 onUserRemoved(final int userId)1718 private void onUserRemoved(final int userId) { 1719 synchronized (mSatLock) { 1720 mSpecialApps.remove(userId); 1721 } 1722 } 1723 startTracking()1724 private void startTracking() { 1725 IntentFilter filter = new IntentFilter( 1726 PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); 1727 1728 if (mHasFeatureTelephonySubscription) { 1729 filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); 1730 1731 updateCarrierPrivilegedCallbackRegistration(); 1732 } 1733 1734 mContext.registerReceiver(mBroadcastReceiver, filter); 1735 1736 updatePowerAllowlistCache(); 1737 } 1738 stopTracking()1739 private void stopTracking() { 1740 mContext.unregisterReceiver(mBroadcastReceiver); 1741 1742 synchronized (mSatLock) { 1743 mCarrierPrivilegedApps.clear(); 1744 mPowerAllowlistedApps.clear(); 1745 mSpecialApps.clear(); 1746 1747 for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) { 1748 mTelephonyManager.unregisterCarrierPrivilegesCallback( 1749 mCarrierPrivilegedCallbacks.valueAt(i)); 1750 } 1751 mCarrierPrivilegedCallbacks.clear(); 1752 } 1753 } 1754 updateCarrierPrivilegedCallbackRegistration()1755 private void updateCarrierPrivilegedCallbackRegistration() { 1756 if (mTelephonyManager == null) { 1757 return; 1758 } 1759 if (!mHasFeatureTelephonySubscription) { 1760 return; 1761 } 1762 1763 Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping(); 1764 final ArraySet<String> changedPkgs = new ArraySet<>(); 1765 synchronized (mSatLock) { 1766 final IntArray callbacksToRemove = new IntArray(); 1767 for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) { 1768 callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i)); 1769 } 1770 for (UiccSlotMapping mapping : simSlotMapping) { 1771 final int logicalIndex = mapping.getLogicalSlotIndex(); 1772 if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) { 1773 // Callback already exists. No need to create a new one or remove it. 1774 for (int i = callbacksToRemove.size() - 1; i >= 0; i--) { 1775 if (callbacksToRemove.get(i) == logicalIndex) { 1776 callbacksToRemove.remove(i); 1777 break; 1778 } 1779 } 1780 1781 continue; 1782 } 1783 final LogicalIndexCarrierPrivilegesCallback callback = 1784 new LogicalIndexCarrierPrivilegesCallback(logicalIndex); 1785 mCarrierPrivilegedCallbacks.put(logicalIndex, callback); 1786 // Upon registration, the callbacks will be called with the current list of 1787 // apps, so there's no need to query the app list synchronously. 1788 mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex, 1789 AppSchedulingModuleThread.getExecutor(), callback); 1790 } 1791 1792 for (int i = callbacksToRemove.size() - 1; i >= 0; --i) { 1793 final int logicalIndex = callbacksToRemove.get(i); 1794 final LogicalIndexCarrierPrivilegesCallback callback = 1795 mCarrierPrivilegedCallbacks.get(logicalIndex); 1796 mTelephonyManager.unregisterCarrierPrivilegesCallback(callback); 1797 mCarrierPrivilegedCallbacks.remove(logicalIndex); 1798 changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex)); 1799 mCarrierPrivilegedApps.remove(logicalIndex); 1800 } 1801 } 1802 1803 updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); 1804 } 1805 1806 /** 1807 * Update the processed special app set for the specified user ID, only looking at the 1808 * specified set of apps. This method must <b>NEVER</b> be called while holding 1809 * {@link #mSatLock}. 1810 */ updateSpecialAppSetUnlocked(final int userId, @NonNull ArraySet<String> pkgs)1811 private void updateSpecialAppSetUnlocked(final int userId, @NonNull ArraySet<String> pkgs) { 1812 // This method may need to acquire mLock, so ensure that mSatLock isn't held to avoid 1813 // lock inversion. 1814 if (Thread.holdsLock(mSatLock)) { 1815 throw new IllegalStateException("Must never hold local mSatLock"); 1816 } 1817 if (pkgs.size() == 0) { 1818 return; 1819 } 1820 final ArraySet<String> changedPkgs = new ArraySet<>(); 1821 1822 synchronized (mSatLock) { 1823 for (int i = pkgs.size() - 1; i >= 0; --i) { 1824 final String pkgName = pkgs.valueAt(i); 1825 if (isSpecialAppInternal(userId, pkgName)) { 1826 if (mSpecialApps.add(userId, pkgName)) { 1827 changedPkgs.add(pkgName); 1828 } 1829 } else if (mSpecialApps.remove(userId, pkgName)) { 1830 changedPkgs.add(pkgName); 1831 } 1832 } 1833 } 1834 1835 if (changedPkgs.size() > 0) { 1836 synchronized (mLock) { 1837 mPackagesToCheck.addAll(changedPkgs); 1838 mHandler.sendEmptyMessage(MSG_CHECK_PACKAGES); 1839 } 1840 } 1841 } 1842 updatePowerAllowlistCache()1843 private void updatePowerAllowlistCache() { 1844 if (mDeviceIdleInternal == null) { 1845 return; 1846 } 1847 1848 // Don't call out to DeviceIdleController with the lock held. 1849 final String[] allowlistedPkgs = mDeviceIdleInternal.getFullPowerWhitelistExceptIdle(); 1850 final ArraySet<String> changedPkgs = new ArraySet<>(); 1851 synchronized (mSatLock) { 1852 changedPkgs.addAll(mPowerAllowlistedApps); 1853 mPowerAllowlistedApps.clear(); 1854 for (String pkgName : allowlistedPkgs) { 1855 mPowerAllowlistedApps.add(pkgName); 1856 if (!changedPkgs.remove(pkgName)) { 1857 // The package wasn't in the previous set of allowlisted apps. Add it 1858 // since its state has changed. 1859 changedPkgs.add(pkgName); 1860 } 1861 } 1862 } 1863 1864 // The full allowlist is currently user-agnostic, so use USER_ALL for these packages. 1865 updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); 1866 } 1867 1868 class LogicalIndexCarrierPrivilegesCallback implements 1869 TelephonyManager.CarrierPrivilegesCallback { 1870 public final int logicalIndex; 1871 LogicalIndexCarrierPrivilegesCallback(int logicalIndex)1872 LogicalIndexCarrierPrivilegesCallback(int logicalIndex) { 1873 this.logicalIndex = logicalIndex; 1874 } 1875 1876 @Override onCarrierPrivilegesChanged(@onNull Set<String> privilegedPackageNames, @NonNull Set<Integer> privilegedUids)1877 public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames, 1878 @NonNull Set<Integer> privilegedUids) { 1879 final ArraySet<String> changedPkgs = new ArraySet<>(); 1880 synchronized (mSatLock) { 1881 final ArraySet<String> oldPrivilegedSet = 1882 mCarrierPrivilegedApps.get(logicalIndex); 1883 if (oldPrivilegedSet != null) { 1884 changedPkgs.addAll(oldPrivilegedSet); 1885 mCarrierPrivilegedApps.remove(logicalIndex); 1886 } 1887 for (String pkgName : privilegedPackageNames) { 1888 mCarrierPrivilegedApps.add(logicalIndex, pkgName); 1889 if (!changedPkgs.remove(pkgName)) { 1890 // The package wasn't in the previous set of privileged apps. Add it 1891 // since its state has changed. 1892 changedPkgs.add(pkgName); 1893 } 1894 } 1895 } 1896 1897 // The carrier privileged list doesn't provide a simple userId correlation, 1898 // so for now, use USER_ALL for these packages. 1899 // TODO(141645789): use the UID list to narrow down to specific userIds 1900 updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); 1901 } 1902 } 1903 dump(@onNull IndentingPrintWriter pw)1904 public void dump(@NonNull IndentingPrintWriter pw) { 1905 pw.println("Special apps:"); 1906 pw.increaseIndent(); 1907 1908 synchronized (mSatLock) { 1909 for (int u = 0; u < mSpecialApps.size(); ++u) { 1910 pw.print("User "); 1911 pw.print(mSpecialApps.keyAt(u)); 1912 pw.print(": "); 1913 pw.println(mSpecialApps.valuesAt(u)); 1914 } 1915 1916 pw.println(); 1917 pw.println("Carrier privileged packages:"); 1918 pw.increaseIndent(); 1919 for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) { 1920 pw.print(mCarrierPrivilegedApps.keyAt(i)); 1921 pw.print(": "); 1922 pw.println(mCarrierPrivilegedApps.valuesAt(i)); 1923 } 1924 pw.decreaseIndent(); 1925 1926 pw.println(); 1927 pw.print("Power allowlisted packages: "); 1928 pw.println(mPowerAllowlistedApps); 1929 } 1930 1931 pw.decreaseIndent(); 1932 } 1933 } 1934 1935 /** 1936 * If {@code override} is true, uses {@code appliedConstraints} for flex policy evaluation, 1937 * overriding anything else that was set. If {@code override} is false, any previous calls 1938 * will be discarded and the policy will be reset to the normal default policy. 1939 */ setLocalPolicyForTesting(boolean override, int appliedConstraints)1940 public void setLocalPolicyForTesting(boolean override, int appliedConstraints) { 1941 synchronized (mLock) { 1942 final boolean recheckJobs = mLocalOverride != override 1943 || mAppliedConstraints != appliedConstraints; 1944 mLocalOverride = override; 1945 if (mLocalOverride) { 1946 mAppliedConstraints = appliedConstraints; 1947 } else { 1948 mAppliedConstraints = mFcConfig.APPLIED_CONSTRAINTS; 1949 } 1950 if (recheckJobs) { 1951 mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget(); 1952 } 1953 } 1954 } 1955 1956 @Override 1957 @GuardedBy("mLock") dumpConstants(IndentingPrintWriter pw)1958 public void dumpConstants(IndentingPrintWriter pw) { 1959 mFcConfig.dump(pw); 1960 } 1961 1962 @Override 1963 @GuardedBy("mLock") dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)1964 public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { 1965 if (mLocalOverride) { 1966 pw.println("Local override active"); 1967 } 1968 pw.print("Applied Flexible Constraints:"); 1969 JobStatus.dumpConstraints(pw, mAppliedConstraints); 1970 pw.println(); 1971 pw.print("Satisfied Flexible Constraints:"); 1972 JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints); 1973 pw.println(); 1974 pw.println(); 1975 1976 final long nowElapsed = sElapsedRealtimeClock.millis(); 1977 pw.println("Time since constraint combos last seen:"); 1978 pw.increaseIndent(); 1979 for (int i = 0; i < mLastSeenConstraintTimesElapsed.size(); ++i) { 1980 final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i); 1981 if (constraints == mSatisfiedFlexibleConstraints) { 1982 pw.print("0ms"); 1983 } else { 1984 TimeUtils.formatDuration( 1985 mLastSeenConstraintTimesElapsed.valueAt(i), nowElapsed, pw); 1986 } 1987 pw.print(":"); 1988 if (constraints != 0) { 1989 // dumpConstraints prepends with a space, so no need to add a space after the : 1990 JobStatus.dumpConstraints(pw, constraints); 1991 } else { 1992 pw.print(" none"); 1993 } 1994 pw.println(); 1995 } 1996 pw.decreaseIndent(); 1997 1998 pw.println(); 1999 mSpecialAppTracker.dump(pw); 2000 2001 pw.println(); 2002 mFlexibilityTracker.dump(pw, predicate, nowElapsed); 2003 2004 pw.println(); 2005 pw.println("Job scores:"); 2006 pw.increaseIndent(); 2007 mJobScoreTrackers.forEach((uid, pkgName, jobScoreTracker) -> { 2008 pw.print(uid); 2009 pw.print("/"); 2010 pw.print(pkgName); 2011 pw.print(": "); 2012 jobScoreTracker.dump(pw, nowElapsed); 2013 pw.println(); 2014 }); 2015 pw.decreaseIndent(); 2016 2017 pw.println(); 2018 mFlexibilityAlarmQueue.dump(pw); 2019 } 2020 } 2021