1 /* 2 * Copyright (C) 2018 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.HOUR_IN_MILLIS; 20 import static android.text.format.DateUtils.MINUTE_IN_MILLIS; 21 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 22 23 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; 24 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; 25 import static com.android.server.job.JobSchedulerService.NEVER_INDEX; 26 import static com.android.server.job.JobSchedulerService.RARE_INDEX; 27 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; 28 import static com.android.server.job.JobSchedulerService.WORKING_INDEX; 29 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 30 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.annotation.UserIdInt; 34 import android.app.ActivityManager; 35 import android.app.ActivityManagerInternal; 36 import android.app.AlarmManager; 37 import android.app.AppGlobals; 38 import android.app.IUidObserver; 39 import android.content.BroadcastReceiver; 40 import android.content.ContentResolver; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.IntentFilter; 44 import android.database.ContentObserver; 45 import android.net.Uri; 46 import android.os.BatteryManager; 47 import android.os.BatteryManagerInternal; 48 import android.os.Handler; 49 import android.os.Looper; 50 import android.os.Message; 51 import android.os.RemoteException; 52 import android.os.UserHandle; 53 import android.provider.Settings; 54 import android.util.ArraySet; 55 import android.util.KeyValueListParser; 56 import android.util.Log; 57 import android.util.Pair; 58 import android.util.Slog; 59 import android.util.SparseArrayMap; 60 import android.util.SparseBooleanArray; 61 import android.util.SparseSetArray; 62 import android.util.proto.ProtoOutputStream; 63 64 import com.android.internal.annotations.GuardedBy; 65 import com.android.internal.annotations.VisibleForTesting; 66 import com.android.internal.os.BackgroundThread; 67 import com.android.internal.util.IndentingPrintWriter; 68 import com.android.server.LocalServices; 69 import com.android.server.job.ConstantsProto; 70 import com.android.server.job.JobSchedulerService; 71 import com.android.server.job.JobServiceContext; 72 import com.android.server.job.StateControllerProto; 73 import com.android.server.usage.AppStandbyInternal; 74 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; 75 76 import java.util.ArrayList; 77 import java.util.List; 78 import java.util.Objects; 79 import java.util.PriorityQueue; 80 import java.util.function.Consumer; 81 import java.util.function.Predicate; 82 83 /** 84 * Controller that tracks whether an app has exceeded its standby bucket quota. 85 * 86 * With initial defaults, each app in each bucket is given 10 minutes to run within its respective 87 * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a 88 * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run 89 * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some 90 * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new 91 * quota is immediately applied to it. 92 * 93 * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on 94 * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will 95 * not be allowed to run more than 20 jobs within the past 10 minutes. 96 * 97 * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run 98 * freely when an app enters the foreground state and are restricted when the app leaves the 99 * foreground state. However, jobs that are started while the app is in the TOP state do not count 100 * towards any quota and are not restricted regardless of the app's state change. 101 * 102 * Jobs will not be throttled when the device is charging. The device is considered to be charging 103 * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast. 104 * 105 * Note: all limits are enforced per bucket window unless explicitly stated otherwise. 106 * All stated values are configurable and subject to change. See {@link QcConstants} for current 107 * defaults. 108 * 109 * Test: atest com.android.server.job.controllers.QuotaControllerTest 110 */ 111 public final class QuotaController extends StateController { 112 private static final String TAG = "JobScheduler.Quota"; 113 private static final boolean DEBUG = JobSchedulerService.DEBUG 114 || Log.isLoggable(TAG, Log.DEBUG); 115 116 private static final String ALARM_TAG_CLEANUP = "*job.cleanup*"; 117 private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*"; 118 119 /** 120 * Standardize the output of userId-packageName combo. 121 */ string(int userId, String packageName)122 private static String string(int userId, String packageName) { 123 return "<" + userId + ">" + packageName; 124 } 125 126 private static final class Package { 127 public final String packageName; 128 public final int userId; 129 Package(int userId, String packageName)130 Package(int userId, String packageName) { 131 this.userId = userId; 132 this.packageName = packageName; 133 } 134 135 @Override toString()136 public String toString() { 137 return string(userId, packageName); 138 } 139 dumpDebug(ProtoOutputStream proto, long fieldId)140 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 141 final long token = proto.start(fieldId); 142 143 proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId); 144 proto.write(StateControllerProto.QuotaController.Package.NAME, packageName); 145 146 proto.end(token); 147 } 148 149 @Override equals(Object obj)150 public boolean equals(Object obj) { 151 if (obj instanceof Package) { 152 Package other = (Package) obj; 153 return userId == other.userId && Objects.equals(packageName, other.packageName); 154 } else { 155 return false; 156 } 157 } 158 159 @Override hashCode()160 public int hashCode() { 161 return packageName.hashCode() + userId; 162 } 163 } 164 hashLong(long val)165 private static int hashLong(long val) { 166 return (int) (val ^ (val >>> 32)); 167 } 168 169 @VisibleForTesting 170 static class ExecutionStats { 171 /** 172 * The time after which this record should be considered invalid (out of date), in the 173 * elapsed realtime timebase. 174 */ 175 public long expirationTimeElapsed; 176 177 public long windowSizeMs; 178 public int jobCountLimit; 179 public int sessionCountLimit; 180 181 /** The total amount of time the app ran in its respective bucket window size. */ 182 public long executionTimeInWindowMs; 183 public int bgJobCountInWindow; 184 185 /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */ 186 public long executionTimeInMaxPeriodMs; 187 public int bgJobCountInMaxPeriod; 188 189 /** 190 * The number of {@link TimingSession}s within the bucket window size. This will include 191 * sessions that started before the window as long as they end within the window. 192 */ 193 public int sessionCountInWindow; 194 195 /** 196 * The time after which the app will be under the bucket quota and can start running jobs 197 * again. This is only valid if 198 * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs}, 199 * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs}, 200 * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or 201 * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}. 202 */ 203 public long inQuotaTimeElapsed; 204 205 /** 206 * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid, 207 * in the elapsed realtime timebase. 208 */ 209 public long jobRateLimitExpirationTimeElapsed; 210 211 /** 212 * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}. 213 * It may contain a few stale entries since cleanup won't happen exactly every 214 * {@link #mRateLimitingWindowMs}. 215 */ 216 public int jobCountInRateLimitingWindow; 217 218 /** 219 * The time after which {@link #sessionCountInRateLimitingWindow} should be considered 220 * invalid, in the elapsed realtime timebase. 221 */ 222 public long sessionRateLimitExpirationTimeElapsed; 223 224 /** 225 * The number of {@link TimingSession}s that ran in at least the last 226 * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't 227 * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered 228 * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}. 229 */ 230 public int sessionCountInRateLimitingWindow; 231 232 @Override toString()233 public String toString() { 234 return "expirationTime=" + expirationTimeElapsed + ", " 235 + "windowSizeMs=" + windowSizeMs + ", " 236 + "jobCountLimit=" + jobCountLimit + ", " 237 + "sessionCountLimit=" + sessionCountLimit + ", " 238 + "executionTimeInWindow=" + executionTimeInWindowMs + ", " 239 + "bgJobCountInWindow=" + bgJobCountInWindow + ", " 240 + "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", " 241 + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", " 242 + "sessionCountInWindow=" + sessionCountInWindow + ", " 243 + "inQuotaTime=" + inQuotaTimeElapsed + ", " 244 + "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", " 245 + "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", " 246 + "rateLimitSessionCountExpirationTime=" 247 + sessionRateLimitExpirationTimeElapsed + ", " 248 + "rateLimitSessionCountWindow=" + sessionCountInRateLimitingWindow; 249 } 250 251 @Override equals(Object obj)252 public boolean equals(Object obj) { 253 if (obj instanceof ExecutionStats) { 254 ExecutionStats other = (ExecutionStats) obj; 255 return this.expirationTimeElapsed == other.expirationTimeElapsed 256 && this.windowSizeMs == other.windowSizeMs 257 && this.jobCountLimit == other.jobCountLimit 258 && this.sessionCountLimit == other.sessionCountLimit 259 && this.executionTimeInWindowMs == other.executionTimeInWindowMs 260 && this.bgJobCountInWindow == other.bgJobCountInWindow 261 && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs 262 && this.sessionCountInWindow == other.sessionCountInWindow 263 && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod 264 && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed 265 && this.jobRateLimitExpirationTimeElapsed 266 == other.jobRateLimitExpirationTimeElapsed 267 && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow 268 && this.sessionRateLimitExpirationTimeElapsed 269 == other.sessionRateLimitExpirationTimeElapsed 270 && this.sessionCountInRateLimitingWindow 271 == other.sessionCountInRateLimitingWindow; 272 } else { 273 return false; 274 } 275 } 276 277 @Override hashCode()278 public int hashCode() { 279 int result = 0; 280 result = 31 * result + hashLong(expirationTimeElapsed); 281 result = 31 * result + hashLong(windowSizeMs); 282 result = 31 * result + hashLong(jobCountLimit); 283 result = 31 * result + hashLong(sessionCountLimit); 284 result = 31 * result + hashLong(executionTimeInWindowMs); 285 result = 31 * result + bgJobCountInWindow; 286 result = 31 * result + hashLong(executionTimeInMaxPeriodMs); 287 result = 31 * result + bgJobCountInMaxPeriod; 288 result = 31 * result + sessionCountInWindow; 289 result = 31 * result + hashLong(inQuotaTimeElapsed); 290 result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed); 291 result = 31 * result + jobCountInRateLimitingWindow; 292 result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed); 293 result = 31 * result + sessionCountInRateLimitingWindow; 294 return result; 295 } 296 } 297 298 /** List of all tracked jobs keyed by source package-userId combo. */ 299 private final SparseArrayMap<ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>(); 300 301 /** Timer for each package-userId combo. */ 302 private final SparseArrayMap<Timer> mPkgTimers = new SparseArrayMap<>(); 303 304 /** List of all timing sessions for a package-userId combo, in chronological order. */ 305 private final SparseArrayMap<List<TimingSession>> mTimingSessions = new SparseArrayMap<>(); 306 307 /** 308 * Listener to track and manage when each package comes back within quota. 309 */ 310 @GuardedBy("mLock") 311 private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener(); 312 313 /** Cached calculation results for each app, with the standby buckets as the array indices. */ 314 private final SparseArrayMap<ExecutionStats[]> mExecutionStatsCache = new SparseArrayMap<>(); 315 316 /** List of UIDs currently in the foreground. */ 317 private final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); 318 319 /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */ 320 private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>(); 321 322 /** 323 * List of jobs that started while the UID was in the TOP state. There will be no more than 324 * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is 325 * fine. 326 */ 327 private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>(); 328 329 private final ActivityManagerInternal mActivityManagerInternal; 330 private final AlarmManager mAlarmManager; 331 private final ChargingTracker mChargeTracker; 332 private final Handler mHandler; 333 private final QcConstants mQcConstants; 334 335 /** How much time each app will have to run jobs within their standby bucket window. */ 336 private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; 337 338 /** 339 * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS} 340 * window. 341 */ 342 private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS; 343 344 /** 345 * How much time the app should have before transitioning from out-of-quota to in-quota. 346 * This should not affect processing if the app is already in-quota. 347 */ 348 private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS; 349 350 /** 351 * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine 352 * when an app will have enough quota to transition from out-of-quota to in-quota. 353 */ 354 private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; 355 356 /** 357 * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an 358 * app will have enough quota to transition from out-of-quota to in-quota. 359 */ 360 private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; 361 362 /** The period of time used to rate limit recently run jobs. */ 363 private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS; 364 365 /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */ 366 private int mMaxJobCountPerRateLimitingWindow = 367 QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 368 369 /** 370 * The maximum number of {@link TimingSession}s that can run within the past {@link 371 * #mRateLimitingWindowMs}. 372 */ 373 private int mMaxSessionCountPerRateLimitingWindow = 374 QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; 375 376 private long mNextCleanupTimeElapsed = 0; 377 private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener = 378 new AlarmManager.OnAlarmListener() { 379 @Override 380 public void onAlarm() { 381 mHandler.obtainMessage(MSG_CLEAN_UP_SESSIONS).sendToTarget(); 382 } 383 }; 384 385 private final IUidObserver mUidObserver = new IUidObserver.Stub() { 386 @Override 387 public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { 388 mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget(); 389 } 390 391 @Override 392 public void onUidGone(int uid, boolean disabled) { 393 } 394 395 @Override 396 public void onUidActive(int uid) { 397 } 398 399 @Override 400 public void onUidIdle(int uid, boolean disabled) { 401 } 402 403 @Override 404 public void onUidCachedChanged(int uid, boolean cached) { 405 } 406 }; 407 408 private final BroadcastReceiver mPackageAddedReceiver = new BroadcastReceiver() { 409 @Override 410 public void onReceive(Context context, Intent intent) { 411 if (intent == null) { 412 return; 413 } 414 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 415 return; 416 } 417 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 418 synchronized (mLock) { 419 mUidToPackageCache.remove(uid); 420 } 421 } 422 }; 423 424 /** 425 * The rolling window size for each standby bucket. Within each window, an app will have 10 426 * minutes to run its jobs. 427 */ 428 private final long[] mBucketPeriodsMs = new long[]{ 429 QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS, 430 QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS, 431 QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS, 432 QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS, 433 0, // NEVER 434 QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS 435 }; 436 437 /** The maximum period any bucket can have. */ 438 private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS; 439 440 /** 441 * The maximum number of jobs based on its standby bucket. For each max value count in the 442 * array, the app will not be allowed to run more than that many number of jobs within the 443 * latest time interval of its rolling window size. 444 * 445 * @see #mBucketPeriodsMs 446 */ 447 private final int[] mMaxBucketJobCounts = new int[]{ 448 QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE, 449 QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING, 450 QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT, 451 QcConstants.DEFAULT_MAX_JOB_COUNT_RARE, 452 0, // NEVER 453 QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED 454 }; 455 456 /** 457 * The maximum number of {@link TimingSession}s based on its standby bucket. For each max value 458 * count in the array, the app will not be allowed to have more than that many number of 459 * {@link TimingSession}s within the latest time interval of its rolling window size. 460 * 461 * @see #mBucketPeriodsMs 462 */ 463 private final int[] mMaxBucketSessionCounts = new int[]{ 464 QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE, 465 QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING, 466 QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT, 467 QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE, 468 0, // NEVER 469 QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED, 470 }; 471 472 /** 473 * Treat two distinct {@link TimingSession}s as the same if they start and end within this 474 * amount of time of each other. 475 */ 476 private long mTimingSessionCoalescingDurationMs = 477 QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS; 478 479 /** An app has reached its quota. The message should contain a {@link Package} object. */ 480 private static final int MSG_REACHED_QUOTA = 0; 481 /** Drop any old timing sessions. */ 482 private static final int MSG_CLEAN_UP_SESSIONS = 1; 483 /** Check if a package is now within its quota. */ 484 private static final int MSG_CHECK_PACKAGE = 2; 485 /** Process state for a UID has changed. */ 486 private static final int MSG_UID_PROCESS_STATE_CHANGED = 3; 487 QuotaController(JobSchedulerService service)488 public QuotaController(JobSchedulerService service) { 489 super(service); 490 mHandler = new QcHandler(mContext.getMainLooper()); 491 mChargeTracker = new ChargingTracker(); 492 mChargeTracker.startTracking(); 493 mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); 494 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 495 mQcConstants = new QcConstants(mHandler); 496 497 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 498 mContext.registerReceiverAsUser(mPackageAddedReceiver, UserHandle.ALL, filter, null, null); 499 500 // Set up the app standby bucketing tracker 501 AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); 502 appStandby.addListener(new StandbyTracker()); 503 504 try { 505 ActivityManager.getService().registerUidObserver(mUidObserver, 506 ActivityManager.UID_OBSERVER_PROCSTATE, 507 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null); 508 } catch (RemoteException e) { 509 // ignored; both services live in system_server 510 } 511 } 512 513 @Override onSystemServicesReady()514 public void onSystemServicesReady() { 515 mQcConstants.start(mContext.getContentResolver()); 516 } 517 518 @Override maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)519 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 520 final int userId = jobStatus.getSourceUserId(); 521 final String pkgName = jobStatus.getSourcePackageName(); 522 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); 523 if (jobs == null) { 524 jobs = new ArraySet<>(); 525 mTrackedJobs.add(userId, pkgName, jobs); 526 } 527 jobs.add(jobStatus); 528 jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA); 529 final boolean isWithinQuota = isWithinQuotaLocked(jobStatus); 530 setConstraintSatisfied(jobStatus, isWithinQuota); 531 if (!isWithinQuota) { 532 maybeScheduleStartAlarmLocked(userId, pkgName, jobStatus.getEffectiveStandbyBucket()); 533 } 534 } 535 536 @Override prepareForExecutionLocked(JobStatus jobStatus)537 public void prepareForExecutionLocked(JobStatus jobStatus) { 538 if (DEBUG) { 539 Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); 540 } 541 542 final int uid = jobStatus.getSourceUid(); 543 if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) { 544 if (DEBUG) { 545 Slog.d(TAG, jobStatus.toShortString() + " is top started job"); 546 } 547 mTopStartedJobs.add(jobStatus); 548 // Top jobs won't count towards quota so there's no need to involve the Timer. 549 return; 550 } 551 552 final int userId = jobStatus.getSourceUserId(); 553 final String packageName = jobStatus.getSourcePackageName(); 554 Timer timer = mPkgTimers.get(userId, packageName); 555 if (timer == null) { 556 timer = new Timer(uid, userId, packageName); 557 mPkgTimers.add(userId, packageName, timer); 558 } 559 timer.startTrackingJobLocked(jobStatus); 560 } 561 562 @Override maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate)563 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, 564 boolean forUpdate) { 565 if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) { 566 Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(), 567 jobStatus.getSourcePackageName()); 568 if (timer != null) { 569 timer.stopTrackingJob(jobStatus); 570 } 571 ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(), 572 jobStatus.getSourcePackageName()); 573 if (jobs != null) { 574 jobs.remove(jobStatus); 575 } 576 mTopStartedJobs.remove(jobStatus); 577 } 578 } 579 580 @Override onAppRemovedLocked(String packageName, int uid)581 public void onAppRemovedLocked(String packageName, int uid) { 582 if (packageName == null) { 583 Slog.wtf(TAG, "Told app removed but given null package name."); 584 return; 585 } 586 clearAppStats(UserHandle.getUserId(uid), packageName); 587 mForegroundUids.delete(uid); 588 mUidToPackageCache.remove(uid); 589 } 590 591 @Override onUserRemovedLocked(int userId)592 public void onUserRemovedLocked(int userId) { 593 mTrackedJobs.delete(userId); 594 mPkgTimers.delete(userId); 595 mTimingSessions.delete(userId); 596 mInQuotaAlarmListener.removeAlarmsLocked(userId); 597 mExecutionStatsCache.delete(userId); 598 mUidToPackageCache.clear(); 599 } 600 601 /** Drop all historical stats and stop tracking any active sessions for the specified app. */ clearAppStats(int userId, @NonNull String packageName)602 public void clearAppStats(int userId, @NonNull String packageName) { 603 mTrackedJobs.delete(userId, packageName); 604 Timer timer = mPkgTimers.get(userId, packageName); 605 if (timer != null) { 606 if (timer.isActive()) { 607 Slog.e(TAG, "clearAppStats called before Timer turned off."); 608 timer.dropEverythingLocked(); 609 } 610 mPkgTimers.delete(userId, packageName); 611 } 612 mTimingSessions.delete(userId, packageName); 613 mInQuotaAlarmListener.removeAlarmLocked(userId, packageName); 614 mExecutionStatsCache.delete(userId, packageName); 615 } 616 isUidInForeground(int uid)617 private boolean isUidInForeground(int uid) { 618 if (UserHandle.isCore(uid)) { 619 return true; 620 } 621 synchronized (mLock) { 622 return mForegroundUids.get(uid); 623 } 624 } 625 626 /** @return true if the job was started while the app was in the TOP state. */ isTopStartedJobLocked(@onNull final JobStatus jobStatus)627 private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { 628 return mTopStartedJobs.contains(jobStatus); 629 } 630 631 /** Returns the maximum amount of time this job could run for. */ getMaxJobExecutionTimeMsLocked(@onNull final JobStatus jobStatus)632 public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) { 633 // If quota is currently "free", then the job can run for the full amount of time. 634 if (mChargeTracker.isCharging() 635 || isTopStartedJobLocked(jobStatus) 636 || isUidInForeground(jobStatus.getSourceUid())) { 637 return JobServiceContext.EXECUTING_TIMESLICE_MILLIS; 638 } 639 return getRemainingExecutionTimeLocked(jobStatus); 640 } 641 642 @VisibleForTesting isWithinQuotaLocked(@onNull final JobStatus jobStatus)643 boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { 644 final int standbyBucket = jobStatus.getEffectiveStandbyBucket(); 645 // A job is within quota if one of the following is true: 646 // 1. it was started while the app was in the TOP state 647 // 2. the app is currently in the foreground 648 // 3. the app overall is within its quota 649 return isTopStartedJobLocked(jobStatus) 650 || isUidInForeground(jobStatus.getSourceUid()) 651 || isWithinQuotaLocked( 652 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); 653 } 654 655 @VisibleForTesting isWithinQuotaLocked(final int userId, @NonNull final String packageName, final int standbyBucket)656 boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, 657 final int standbyBucket) { 658 if (standbyBucket == NEVER_INDEX) return false; 659 660 // Quota constraint is not enforced while charging. 661 if (mChargeTracker.isCharging()) { 662 // Restricted jobs require additional constraints when charging, so don't immediately 663 // mark quota as free when charging. 664 if (standbyBucket != RESTRICTED_INDEX) { 665 return true; 666 } 667 } 668 669 ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 670 return getRemainingExecutionTimeLocked(stats) > 0 671 && isUnderJobCountQuotaLocked(stats, standbyBucket) 672 && isUnderSessionCountQuotaLocked(stats, standbyBucket); 673 } 674 isUnderJobCountQuotaLocked(@onNull ExecutionStats stats, final int standbyBucket)675 private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats, 676 final int standbyBucket) { 677 final long now = sElapsedRealtimeClock.millis(); 678 final boolean isUnderAllowedTimeQuota = 679 (stats.jobRateLimitExpirationTimeElapsed <= now 680 || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow); 681 return isUnderAllowedTimeQuota 682 && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]); 683 } 684 isUnderSessionCountQuotaLocked(@onNull ExecutionStats stats, final int standbyBucket)685 private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats, 686 final int standbyBucket) { 687 final long now = sElapsedRealtimeClock.millis(); 688 final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now 689 || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow); 690 return isUnderAllowedTimeQuota 691 && stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket]; 692 } 693 694 @VisibleForTesting getRemainingExecutionTimeLocked(@onNull final JobStatus jobStatus)695 long getRemainingExecutionTimeLocked(@NonNull final JobStatus jobStatus) { 696 return getRemainingExecutionTimeLocked(jobStatus.getSourceUserId(), 697 jobStatus.getSourcePackageName(), 698 jobStatus.getEffectiveStandbyBucket()); 699 } 700 701 @VisibleForTesting getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName)702 long getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName) { 703 final int standbyBucket = JobSchedulerService.standbyBucketForPackage(packageName, 704 userId, sElapsedRealtimeClock.millis()); 705 return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket); 706 } 707 708 /** 709 * Returns the amount of time, in milliseconds, that this job has remaining to run based on its 710 * current standby bucket. Time remaining could be negative if the app was moved from a less 711 * restricted to a more restricted bucket. 712 */ getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName, final int standbyBucket)713 private long getRemainingExecutionTimeLocked(final int userId, 714 @NonNull final String packageName, final int standbyBucket) { 715 if (standbyBucket == NEVER_INDEX) { 716 return 0; 717 } 718 return getRemainingExecutionTimeLocked( 719 getExecutionStatsLocked(userId, packageName, standbyBucket)); 720 } 721 getRemainingExecutionTimeLocked(@onNull ExecutionStats stats)722 private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) { 723 return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs, 724 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); 725 } 726 727 /** 728 * Returns the amount of time, in milliseconds, until the package would have reached its 729 * duration quota, assuming it has a job counting towards its quota the entire time. This takes 730 * into account any {@link TimingSession}s that may roll out of the window as the job is 731 * running. 732 */ 733 @VisibleForTesting getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName)734 long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) { 735 final long nowElapsed = sElapsedRealtimeClock.millis(); 736 final int standbyBucket = JobSchedulerService.standbyBucketForPackage( 737 packageName, userId, nowElapsed); 738 if (standbyBucket == NEVER_INDEX) { 739 return 0; 740 } 741 List<TimingSession> sessions = mTimingSessions.get(userId, packageName); 742 if (sessions == null || sessions.size() == 0) { 743 return mAllowedTimePerPeriodMs; 744 } 745 746 final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 747 final long startWindowElapsed = nowElapsed - stats.windowSizeMs; 748 final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; 749 final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs; 750 final long maxExecutionTimeRemainingMs = 751 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; 752 753 // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can 754 // essentially run until they reach the maximum limit. 755 if (stats.windowSizeMs == mAllowedTimePerPeriodMs) { 756 return calculateTimeUntilQuotaConsumedLocked( 757 sessions, startMaxElapsed, maxExecutionTimeRemainingMs); 758 } 759 760 // Need to check both max time and period time in case one is less than the other. 761 // For example, max time remaining could be less than bucket time remaining, but sessions 762 // contributing to the max time remaining could phase out enough that we'd want to use the 763 // bucket value. 764 return Math.min( 765 calculateTimeUntilQuotaConsumedLocked( 766 sessions, startMaxElapsed, maxExecutionTimeRemainingMs), 767 calculateTimeUntilQuotaConsumedLocked( 768 sessions, startWindowElapsed, allowedTimeRemainingMs)); 769 } 770 771 /** 772 * Calculates how much time it will take, in milliseconds, until the quota is fully consumed. 773 * 774 * @param windowStartElapsed The start of the window, in the elapsed realtime timebase. 775 * @param deadSpaceMs How much time can be allowed to count towards the quota 776 */ calculateTimeUntilQuotaConsumedLocked(@onNull List<TimingSession> sessions, final long windowStartElapsed, long deadSpaceMs)777 private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions, 778 final long windowStartElapsed, long deadSpaceMs) { 779 long timeUntilQuotaConsumedMs = 0; 780 long start = windowStartElapsed; 781 for (int i = 0; i < sessions.size(); ++i) { 782 TimingSession session = sessions.get(i); 783 784 if (session.endTimeElapsed < windowStartElapsed) { 785 // Outside of window. Ignore. 786 continue; 787 } else if (session.startTimeElapsed <= windowStartElapsed) { 788 // Overlapping session. Can extend time by portion of session in window. 789 timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed; 790 start = session.endTimeElapsed; 791 } else { 792 // Completely within the window. Can only consider if there's enough dead space 793 // to get to the start of the session. 794 long diff = session.startTimeElapsed - start; 795 if (diff > deadSpaceMs) { 796 break; 797 } 798 timeUntilQuotaConsumedMs += diff 799 + (session.endTimeElapsed - session.startTimeElapsed); 800 deadSpaceMs -= diff; 801 start = session.endTimeElapsed; 802 } 803 } 804 // Will be non-zero if the loop didn't look at any sessions. 805 timeUntilQuotaConsumedMs += deadSpaceMs; 806 if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) { 807 Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs); 808 } 809 return timeUntilQuotaConsumedMs; 810 } 811 812 /** Returns the execution stats of the app in the most recent window. */ 813 @VisibleForTesting 814 @NonNull getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket)815 ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, 816 final int standbyBucket) { 817 return getExecutionStatsLocked(userId, packageName, standbyBucket, true); 818 } 819 820 @NonNull getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket, final boolean refreshStatsIfOld)821 private ExecutionStats getExecutionStatsLocked(final int userId, 822 @NonNull final String packageName, final int standbyBucket, 823 final boolean refreshStatsIfOld) { 824 if (standbyBucket == NEVER_INDEX) { 825 Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app."); 826 return new ExecutionStats(); 827 } 828 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 829 if (appStats == null) { 830 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 831 mExecutionStatsCache.add(userId, packageName, appStats); 832 } 833 ExecutionStats stats = appStats[standbyBucket]; 834 if (stats == null) { 835 stats = new ExecutionStats(); 836 appStats[standbyBucket] = stats; 837 } 838 if (refreshStatsIfOld) { 839 final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; 840 final int jobCountLimit = mMaxBucketJobCounts[standbyBucket]; 841 final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket]; 842 Timer timer = mPkgTimers.get(userId, packageName); 843 if ((timer != null && timer.isActive()) 844 || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis() 845 || stats.windowSizeMs != bucketWindowSizeMs 846 || stats.jobCountLimit != jobCountLimit 847 || stats.sessionCountLimit != sessionCountLimit) { 848 // The stats are no longer valid. 849 stats.windowSizeMs = bucketWindowSizeMs; 850 stats.jobCountLimit = jobCountLimit; 851 stats.sessionCountLimit = sessionCountLimit; 852 updateExecutionStatsLocked(userId, packageName, stats); 853 } 854 } 855 856 return stats; 857 } 858 859 @VisibleForTesting updateExecutionStatsLocked(final int userId, @NonNull final String packageName, @NonNull ExecutionStats stats)860 void updateExecutionStatsLocked(final int userId, @NonNull final String packageName, 861 @NonNull ExecutionStats stats) { 862 stats.executionTimeInWindowMs = 0; 863 stats.bgJobCountInWindow = 0; 864 stats.executionTimeInMaxPeriodMs = 0; 865 stats.bgJobCountInMaxPeriod = 0; 866 stats.sessionCountInWindow = 0; 867 if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) { 868 // App won't be in quota until configuration changes. 869 stats.inQuotaTimeElapsed = Long.MAX_VALUE; 870 } else { 871 stats.inQuotaTimeElapsed = 0; 872 } 873 874 Timer timer = mPkgTimers.get(userId, packageName); 875 final long nowElapsed = sElapsedRealtimeClock.millis(); 876 stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; 877 if (timer != null && timer.isActive()) { 878 // Exclude active sessions from the session count so that new jobs aren't prevented 879 // from starting due to an app hitting the session limit. 880 stats.executionTimeInWindowMs = 881 stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); 882 stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); 883 // If the timer is active, the value will be stale at the next method call, so 884 // invalidate now. 885 stats.expirationTimeElapsed = nowElapsed; 886 if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { 887 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 888 nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs); 889 } 890 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 891 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 892 nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); 893 } 894 if (stats.bgJobCountInWindow >= stats.jobCountLimit) { 895 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 896 nowElapsed + stats.windowSizeMs); 897 } 898 } 899 900 List<TimingSession> sessions = mTimingSessions.get(userId, packageName); 901 if (sessions == null || sessions.size() == 0) { 902 return; 903 } 904 905 final long startWindowElapsed = nowElapsed - stats.windowSizeMs; 906 final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; 907 int sessionCountInWindow = 0; 908 // The minimum time between the start time and the beginning of the sessions that were 909 // looked at --> how much time the stats will be valid for. 910 long emptyTimeMs = Long.MAX_VALUE; 911 // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get 912 // the most recent ones. 913 final int loopStart = sessions.size() - 1; 914 for (int i = loopStart; i >= 0; --i) { 915 TimingSession session = sessions.get(i); 916 917 // Window management. 918 if (startWindowElapsed < session.endTimeElapsed) { 919 final long start; 920 if (startWindowElapsed < session.startTimeElapsed) { 921 start = session.startTimeElapsed; 922 emptyTimeMs = 923 Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed); 924 } else { 925 // The session started before the window but ended within the window. Only 926 // include the portion that was within the window. 927 start = startWindowElapsed; 928 emptyTimeMs = 0; 929 } 930 931 stats.executionTimeInWindowMs += session.endTimeElapsed - start; 932 stats.bgJobCountInWindow += session.bgJobCount; 933 if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) { 934 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 935 start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs 936 + stats.windowSizeMs); 937 } 938 if (stats.bgJobCountInWindow >= stats.jobCountLimit) { 939 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 940 session.endTimeElapsed + stats.windowSizeMs); 941 } 942 if (i == loopStart 943 || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed) 944 > mTimingSessionCoalescingDurationMs) { 945 // Coalesce sessions if they are very close to each other in time 946 sessionCountInWindow++; 947 948 if (sessionCountInWindow >= stats.sessionCountLimit) { 949 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 950 session.endTimeElapsed + stats.windowSizeMs); 951 } 952 } 953 } 954 955 // Max period check. 956 if (startMaxElapsed < session.startTimeElapsed) { 957 stats.executionTimeInMaxPeriodMs += 958 session.endTimeElapsed - session.startTimeElapsed; 959 stats.bgJobCountInMaxPeriod += session.bgJobCount; 960 emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed); 961 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 962 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 963 session.startTimeElapsed + stats.executionTimeInMaxPeriodMs 964 - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); 965 } 966 } else if (startMaxElapsed < session.endTimeElapsed) { 967 // The session started before the window but ended within the window. Only include 968 // the portion that was within the window. 969 stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed; 970 stats.bgJobCountInMaxPeriod += session.bgJobCount; 971 emptyTimeMs = 0; 972 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 973 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 974 startMaxElapsed + stats.executionTimeInMaxPeriodMs 975 - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); 976 } 977 } else { 978 // This session ended before the window. No point in going any further. 979 break; 980 } 981 } 982 stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; 983 stats.sessionCountInWindow = sessionCountInWindow; 984 } 985 986 /** Invalidate ExecutionStats for all apps. */ 987 @VisibleForTesting invalidateAllExecutionStatsLocked()988 void invalidateAllExecutionStatsLocked() { 989 final long nowElapsed = sElapsedRealtimeClock.millis(); 990 mExecutionStatsCache.forEach((appStats) -> { 991 if (appStats != null) { 992 for (int i = 0; i < appStats.length; ++i) { 993 ExecutionStats stats = appStats[i]; 994 if (stats != null) { 995 stats.expirationTimeElapsed = nowElapsed; 996 } 997 } 998 } 999 }); 1000 } 1001 1002 @VisibleForTesting invalidateAllExecutionStatsLocked(final int userId, @NonNull final String packageName)1003 void invalidateAllExecutionStatsLocked(final int userId, 1004 @NonNull final String packageName) { 1005 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1006 if (appStats != null) { 1007 final long nowElapsed = sElapsedRealtimeClock.millis(); 1008 for (int i = 0; i < appStats.length; ++i) { 1009 ExecutionStats stats = appStats[i]; 1010 if (stats != null) { 1011 stats.expirationTimeElapsed = nowElapsed; 1012 } 1013 } 1014 } 1015 } 1016 1017 @VisibleForTesting incrementJobCount(final int userId, @NonNull final String packageName, int count)1018 void incrementJobCount(final int userId, @NonNull final String packageName, int count) { 1019 final long now = sElapsedRealtimeClock.millis(); 1020 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1021 if (appStats == null) { 1022 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1023 mExecutionStatsCache.add(userId, packageName, appStats); 1024 } 1025 for (int i = 0; i < appStats.length; ++i) { 1026 ExecutionStats stats = appStats[i]; 1027 if (stats == null) { 1028 stats = new ExecutionStats(); 1029 appStats[i] = stats; 1030 } 1031 if (stats.jobRateLimitExpirationTimeElapsed <= now) { 1032 stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs; 1033 stats.jobCountInRateLimitingWindow = 0; 1034 } 1035 stats.jobCountInRateLimitingWindow += count; 1036 } 1037 } 1038 incrementTimingSessionCount(final int userId, @NonNull final String packageName)1039 private void incrementTimingSessionCount(final int userId, @NonNull final String packageName) { 1040 final long now = sElapsedRealtimeClock.millis(); 1041 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1042 if (appStats == null) { 1043 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1044 mExecutionStatsCache.add(userId, packageName, appStats); 1045 } 1046 for (int i = 0; i < appStats.length; ++i) { 1047 ExecutionStats stats = appStats[i]; 1048 if (stats == null) { 1049 stats = new ExecutionStats(); 1050 appStats[i] = stats; 1051 } 1052 if (stats.sessionRateLimitExpirationTimeElapsed <= now) { 1053 stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs; 1054 stats.sessionCountInRateLimitingWindow = 0; 1055 } 1056 stats.sessionCountInRateLimitingWindow++; 1057 } 1058 } 1059 1060 @VisibleForTesting saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session)1061 void saveTimingSession(final int userId, @NonNull final String packageName, 1062 @NonNull final TimingSession session) { 1063 synchronized (mLock) { 1064 List<TimingSession> sessions = mTimingSessions.get(userId, packageName); 1065 if (sessions == null) { 1066 sessions = new ArrayList<>(); 1067 mTimingSessions.add(userId, packageName, sessions); 1068 } 1069 sessions.add(session); 1070 // Adding a new session means that the current stats are now incorrect. 1071 invalidateAllExecutionStatsLocked(userId, packageName); 1072 1073 maybeScheduleCleanupAlarmLocked(); 1074 } 1075 } 1076 1077 private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> { 1078 public long earliestEndElapsed = Long.MAX_VALUE; 1079 1080 @Override accept(List<TimingSession> sessions)1081 public void accept(List<TimingSession> sessions) { 1082 if (sessions != null && sessions.size() > 0) { 1083 earliestEndElapsed = Math.min(earliestEndElapsed, sessions.get(0).endTimeElapsed); 1084 } 1085 } 1086 reset()1087 void reset() { 1088 earliestEndElapsed = Long.MAX_VALUE; 1089 } 1090 } 1091 1092 private final EarliestEndTimeFunctor mEarliestEndTimeFunctor = new EarliestEndTimeFunctor(); 1093 1094 /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */ 1095 @VisibleForTesting maybeScheduleCleanupAlarmLocked()1096 void maybeScheduleCleanupAlarmLocked() { 1097 if (mNextCleanupTimeElapsed > sElapsedRealtimeClock.millis()) { 1098 // There's already an alarm scheduled. Just stick with that one. There's no way we'll 1099 // end up scheduling an earlier alarm. 1100 if (DEBUG) { 1101 Slog.v(TAG, "Not scheduling cleanup since there's already one at " 1102 + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed 1103 - sElapsedRealtimeClock.millis()) + "ms)"); 1104 } 1105 return; 1106 } 1107 mEarliestEndTimeFunctor.reset(); 1108 mTimingSessions.forEach(mEarliestEndTimeFunctor); 1109 final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed; 1110 if (earliestEndElapsed == Long.MAX_VALUE) { 1111 // Couldn't find a good time to clean up. Maybe this was called after we deleted all 1112 // timing sessions. 1113 if (DEBUG) { 1114 Slog.d(TAG, "Didn't find a time to schedule cleanup"); 1115 } 1116 return; 1117 } 1118 // Need to keep sessions for all apps up to the max period, regardless of their current 1119 // standby bucket. 1120 long nextCleanupElapsed = earliestEndElapsed + MAX_PERIOD_MS; 1121 if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) { 1122 // No need to clean up too often. Delay the alarm if the next cleanup would be too soon 1123 // after it. 1124 nextCleanupElapsed += 10 * MINUTE_IN_MILLIS; 1125 } 1126 mNextCleanupTimeElapsed = nextCleanupElapsed; 1127 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP, 1128 mSessionCleanupAlarmListener, mHandler); 1129 if (DEBUG) { 1130 Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed); 1131 } 1132 } 1133 1134 private class TimerChargingUpdateFunctor implements Consumer<Timer> { 1135 private long mNowElapsed; 1136 private boolean mIsCharging; 1137 setStatus(long nowElapsed, boolean isCharging)1138 private void setStatus(long nowElapsed, boolean isCharging) { 1139 mNowElapsed = nowElapsed; 1140 mIsCharging = isCharging; 1141 } 1142 1143 @Override accept(Timer timer)1144 public void accept(Timer timer) { 1145 if (JobSchedulerService.standbyBucketForPackage(timer.mPkg.packageName, 1146 timer.mPkg.userId, mNowElapsed) != RESTRICTED_INDEX) { 1147 // Restricted jobs need additional constraints even when charging, so don't 1148 // immediately say that quota is free. 1149 timer.onStateChangedLocked(mNowElapsed, mIsCharging); 1150 } 1151 } 1152 } 1153 1154 private final TimerChargingUpdateFunctor 1155 mTimerChargingUpdateFunctor = new TimerChargingUpdateFunctor(); 1156 handleNewChargingStateLocked()1157 private void handleNewChargingStateLocked() { 1158 mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(), 1159 mChargeTracker.isCharging()); 1160 if (DEBUG) { 1161 Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isCharging()); 1162 } 1163 // Deal with Timers first. 1164 mPkgTimers.forEach(mTimerChargingUpdateFunctor); 1165 // Now update jobs. 1166 maybeUpdateAllConstraintsLocked(); 1167 } 1168 maybeUpdateAllConstraintsLocked()1169 private void maybeUpdateAllConstraintsLocked() { 1170 boolean changed = false; 1171 for (int u = 0; u < mTrackedJobs.numMaps(); ++u) { 1172 final int userId = mTrackedJobs.keyAt(u); 1173 for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) { 1174 final String packageName = mTrackedJobs.keyAt(u, p); 1175 changed |= maybeUpdateConstraintForPkgLocked(userId, packageName); 1176 } 1177 } 1178 if (changed) { 1179 mStateChangedListener.onControllerStateChanged(); 1180 } 1181 } 1182 1183 /** 1184 * Update the CONSTRAINT_WITHIN_QUOTA bit for all of the Jobs for a given package. 1185 * 1186 * @return true if at least one job had its bit changed 1187 */ maybeUpdateConstraintForPkgLocked(final int userId, @NonNull final String packageName)1188 private boolean maybeUpdateConstraintForPkgLocked(final int userId, 1189 @NonNull final String packageName) { 1190 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 1191 if (jobs == null || jobs.size() == 0) { 1192 return false; 1193 } 1194 1195 // Quota is the same for all jobs within a package. 1196 final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket(); 1197 final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket); 1198 boolean changed = false; 1199 for (int i = jobs.size() - 1; i >= 0; --i) { 1200 final JobStatus js = jobs.valueAt(i); 1201 if (isTopStartedJobLocked(js)) { 1202 // Job was started while the app was in the TOP state so we should allow it to 1203 // finish. 1204 changed |= js.setQuotaConstraintSatisfied(true); 1205 } else if (realStandbyBucket != ACTIVE_INDEX 1206 && realStandbyBucket == js.getEffectiveStandbyBucket()) { 1207 // An app in the ACTIVE bucket may be out of quota while the job could be in quota 1208 // for some reason. Therefore, avoid setting the real value here and check each job 1209 // individually. 1210 changed |= setConstraintSatisfied(js, realInQuota); 1211 } else { 1212 // This job is somehow exempted. Need to determine its own quota status. 1213 changed |= setConstraintSatisfied(js, isWithinQuotaLocked(js)); 1214 } 1215 } 1216 if (!realInQuota) { 1217 // Don't want to use the effective standby bucket here since that bump the bucket to 1218 // ACTIVE for one of the jobs, which doesn't help with other jobs that aren't 1219 // exempted. 1220 maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket); 1221 } else { 1222 mInQuotaAlarmListener.removeAlarmLocked(userId, packageName); 1223 } 1224 return changed; 1225 } 1226 1227 private class UidConstraintUpdater implements Consumer<JobStatus> { 1228 private final SparseArrayMap<Integer> mToScheduleStartAlarms = new SparseArrayMap<>(); 1229 public boolean wasJobChanged; 1230 1231 @Override accept(JobStatus jobStatus)1232 public void accept(JobStatus jobStatus) { 1233 wasJobChanged |= setConstraintSatisfied(jobStatus, isWithinQuotaLocked(jobStatus)); 1234 final int userId = jobStatus.getSourceUserId(); 1235 final String packageName = jobStatus.getSourcePackageName(); 1236 final int realStandbyBucket = jobStatus.getStandbyBucket(); 1237 if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) { 1238 mInQuotaAlarmListener.removeAlarmLocked(userId, packageName); 1239 } else { 1240 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket); 1241 } 1242 } 1243 postProcess()1244 void postProcess() { 1245 for (int u = 0; u < mToScheduleStartAlarms.numMaps(); ++u) { 1246 final int userId = mToScheduleStartAlarms.keyAt(u); 1247 for (int p = 0; p < mToScheduleStartAlarms.numElementsForKey(userId); ++p) { 1248 final String packageName = mToScheduleStartAlarms.keyAt(u, p); 1249 final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName); 1250 maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket); 1251 } 1252 } 1253 } 1254 reset()1255 void reset() { 1256 wasJobChanged = false; 1257 mToScheduleStartAlarms.clear(); 1258 } 1259 } 1260 1261 private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater(); 1262 maybeUpdateConstraintForUidLocked(final int uid)1263 private boolean maybeUpdateConstraintForUidLocked(final int uid) { 1264 mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints); 1265 1266 mUpdateUidConstraints.postProcess(); 1267 boolean changed = mUpdateUidConstraints.wasJobChanged; 1268 mUpdateUidConstraints.reset(); 1269 return changed; 1270 } 1271 1272 /** 1273 * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run 1274 * again. This should only be called if the package is already out of quota. 1275 */ 1276 @VisibleForTesting maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName, final int standbyBucket)1277 void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName, 1278 final int standbyBucket) { 1279 if (standbyBucket == NEVER_INDEX) { 1280 return; 1281 } 1282 1283 final String pkgString = string(userId, packageName); 1284 ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 1285 final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); 1286 final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats, 1287 standbyBucket); 1288 1289 if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs 1290 && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs 1291 && isUnderJobCountQuota 1292 && isUnderTimingSessionCountQuota) { 1293 // Already in quota. Why was this method called? 1294 if (DEBUG) { 1295 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString 1296 + " even though it already has " 1297 + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) 1298 + "ms in its quota."); 1299 } 1300 mInQuotaAlarmListener.removeAlarmLocked(userId, packageName); 1301 mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); 1302 return; 1303 } 1304 1305 // The time this app will have quota again. 1306 long inQuotaTimeElapsed = stats.inQuotaTimeElapsed; 1307 if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) { 1308 // App hit the rate limit. 1309 inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, 1310 stats.jobRateLimitExpirationTimeElapsed); 1311 } 1312 if (!isUnderTimingSessionCountQuota 1313 && stats.sessionCountInWindow < stats.sessionCountLimit) { 1314 // App hit the rate limit. 1315 inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed, 1316 stats.sessionRateLimitExpirationTimeElapsed); 1317 } 1318 if (inQuotaTimeElapsed <= sElapsedRealtimeClock.millis()) { 1319 final long nowElapsed = sElapsedRealtimeClock.millis(); 1320 Slog.wtf(TAG, 1321 "In quota time is " + (nowElapsed - inQuotaTimeElapsed) + "ms old. Now=" 1322 + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats); 1323 inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS; 1324 } 1325 mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed); 1326 } 1327 setConstraintSatisfied(@onNull JobStatus jobStatus, boolean isWithinQuota)1328 private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) { 1329 if (!isWithinQuota && jobStatus.getWhenStandbyDeferred() == 0) { 1330 // Mark that the job is being deferred due to buckets. 1331 jobStatus.setWhenStandbyDeferred(sElapsedRealtimeClock.millis()); 1332 } 1333 return jobStatus.setQuotaConstraintSatisfied(isWithinQuota); 1334 } 1335 1336 private final class ChargingTracker extends BroadcastReceiver { 1337 /** 1338 * Track whether we're charging. This has a slightly different definition than that of 1339 * BatteryController. 1340 */ 1341 private boolean mCharging; 1342 ChargingTracker()1343 ChargingTracker() { 1344 } 1345 startTracking()1346 public void startTracking() { 1347 IntentFilter filter = new IntentFilter(); 1348 1349 // Charging/not charging. 1350 filter.addAction(BatteryManager.ACTION_CHARGING); 1351 filter.addAction(BatteryManager.ACTION_DISCHARGING); 1352 mContext.registerReceiver(this, filter); 1353 1354 // Initialise tracker state. 1355 BatteryManagerInternal batteryManagerInternal = 1356 LocalServices.getService(BatteryManagerInternal.class); 1357 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); 1358 } 1359 isCharging()1360 public boolean isCharging() { 1361 return mCharging; 1362 } 1363 1364 @Override onReceive(Context context, Intent intent)1365 public void onReceive(Context context, Intent intent) { 1366 synchronized (mLock) { 1367 final String action = intent.getAction(); 1368 if (BatteryManager.ACTION_CHARGING.equals(action)) { 1369 if (DEBUG) { 1370 Slog.d(TAG, "Received charging intent, fired @ " 1371 + sElapsedRealtimeClock.millis()); 1372 } 1373 mCharging = true; 1374 handleNewChargingStateLocked(); 1375 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { 1376 if (DEBUG) { 1377 Slog.d(TAG, "Disconnected from power."); 1378 } 1379 mCharging = false; 1380 handleNewChargingStateLocked(); 1381 } 1382 } 1383 } 1384 } 1385 1386 @VisibleForTesting 1387 static final class TimingSession { 1388 // Start timestamp in elapsed realtime timebase. 1389 public final long startTimeElapsed; 1390 // End timestamp in elapsed realtime timebase. 1391 public final long endTimeElapsed; 1392 // How many background jobs ran during this session. 1393 public final int bgJobCount; 1394 1395 private final int mHashCode; 1396 TimingSession(long startElapsed, long endElapsed, int bgJobCount)1397 TimingSession(long startElapsed, long endElapsed, int bgJobCount) { 1398 this.startTimeElapsed = startElapsed; 1399 this.endTimeElapsed = endElapsed; 1400 this.bgJobCount = bgJobCount; 1401 1402 int hashCode = 0; 1403 hashCode = 31 * hashCode + hashLong(startTimeElapsed); 1404 hashCode = 31 * hashCode + hashLong(endTimeElapsed); 1405 hashCode = 31 * hashCode + bgJobCount; 1406 mHashCode = hashCode; 1407 } 1408 1409 @Override toString()1410 public String toString() { 1411 return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount 1412 + "}"; 1413 } 1414 1415 @Override equals(Object obj)1416 public boolean equals(Object obj) { 1417 if (obj instanceof TimingSession) { 1418 TimingSession other = (TimingSession) obj; 1419 return startTimeElapsed == other.startTimeElapsed 1420 && endTimeElapsed == other.endTimeElapsed 1421 && bgJobCount == other.bgJobCount; 1422 } else { 1423 return false; 1424 } 1425 } 1426 1427 @Override hashCode()1428 public int hashCode() { 1429 return mHashCode; 1430 } 1431 dump(IndentingPrintWriter pw)1432 public void dump(IndentingPrintWriter pw) { 1433 pw.print(startTimeElapsed); 1434 pw.print(" -> "); 1435 pw.print(endTimeElapsed); 1436 pw.print(" ("); 1437 pw.print(endTimeElapsed - startTimeElapsed); 1438 pw.print("), "); 1439 pw.print(bgJobCount); 1440 pw.print(" bg jobs."); 1441 pw.println(); 1442 } 1443 dump(@onNull ProtoOutputStream proto, long fieldId)1444 public void dump(@NonNull ProtoOutputStream proto, long fieldId) { 1445 final long token = proto.start(fieldId); 1446 1447 proto.write(StateControllerProto.QuotaController.TimingSession.START_TIME_ELAPSED, 1448 startTimeElapsed); 1449 proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED, 1450 endTimeElapsed); 1451 proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT, 1452 bgJobCount); 1453 1454 proto.end(token); 1455 } 1456 } 1457 1458 private final class Timer { 1459 private final Package mPkg; 1460 private final int mUid; 1461 1462 // List of jobs currently running for this app that started when the app wasn't in the 1463 // foreground. 1464 private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); 1465 private long mStartTimeElapsed; 1466 private int mBgJobCount; 1467 Timer(int uid, int userId, String packageName)1468 Timer(int uid, int userId, String packageName) { 1469 mPkg = new Package(userId, packageName); 1470 mUid = uid; 1471 } 1472 startTrackingJobLocked(@onNull JobStatus jobStatus)1473 void startTrackingJobLocked(@NonNull JobStatus jobStatus) { 1474 if (isTopStartedJobLocked(jobStatus)) { 1475 // We intentionally don't pay attention to fg state changes after a TOP job has 1476 // started. 1477 if (DEBUG) { 1478 Slog.v(TAG, 1479 "Timer ignoring " + jobStatus.toShortString() + " because isTop"); 1480 } 1481 return; 1482 } 1483 if (DEBUG) { 1484 Slog.v(TAG, "Starting to track " + jobStatus.toShortString()); 1485 } 1486 // Always track jobs, even when charging. 1487 mRunningBgJobs.add(jobStatus); 1488 if (shouldTrackLocked()) { 1489 mBgJobCount++; 1490 incrementJobCount(mPkg.userId, mPkg.packageName, 1); 1491 if (mRunningBgJobs.size() == 1) { 1492 // Started tracking the first job. 1493 mStartTimeElapsed = sElapsedRealtimeClock.millis(); 1494 // Starting the timer means that all cached execution stats are now incorrect. 1495 invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); 1496 scheduleCutoff(); 1497 } 1498 } 1499 } 1500 stopTrackingJob(@onNull JobStatus jobStatus)1501 void stopTrackingJob(@NonNull JobStatus jobStatus) { 1502 if (DEBUG) { 1503 Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString()); 1504 } 1505 synchronized (mLock) { 1506 if (mRunningBgJobs.size() == 0) { 1507 // maybeStopTrackingJobLocked can be called when an app cancels a job, so a 1508 // timer may not be running when it's asked to stop tracking a job. 1509 if (DEBUG) { 1510 Slog.d(TAG, "Timer isn't tracking any jobs but still told to stop"); 1511 } 1512 return; 1513 } 1514 if (mRunningBgJobs.remove(jobStatus) 1515 && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) { 1516 emitSessionLocked(sElapsedRealtimeClock.millis()); 1517 cancelCutoff(); 1518 } 1519 } 1520 } 1521 1522 /** 1523 * Stops tracking all jobs and cancels any pending alarms. This should only be called if 1524 * the Timer is not going to be used anymore. 1525 */ dropEverythingLocked()1526 void dropEverythingLocked() { 1527 mRunningBgJobs.clear(); 1528 cancelCutoff(); 1529 } 1530 emitSessionLocked(long nowElapsed)1531 private void emitSessionLocked(long nowElapsed) { 1532 if (mBgJobCount <= 0) { 1533 // Nothing to emit. 1534 return; 1535 } 1536 TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount); 1537 saveTimingSession(mPkg.userId, mPkg.packageName, ts); 1538 mBgJobCount = 0; 1539 // Don't reset the tracked jobs list as we need to keep tracking the current number 1540 // of jobs. 1541 // However, cancel the currently scheduled cutoff since it's not currently useful. 1542 cancelCutoff(); 1543 incrementTimingSessionCount(mPkg.userId, mPkg.packageName); 1544 } 1545 1546 /** 1547 * Returns true if the Timer is actively tracking, as opposed to passively ref counting 1548 * during charging. 1549 */ isActive()1550 public boolean isActive() { 1551 synchronized (mLock) { 1552 return mBgJobCount > 0; 1553 } 1554 } 1555 isRunning(JobStatus jobStatus)1556 boolean isRunning(JobStatus jobStatus) { 1557 return mRunningBgJobs.contains(jobStatus); 1558 } 1559 getCurrentDuration(long nowElapsed)1560 long getCurrentDuration(long nowElapsed) { 1561 synchronized (mLock) { 1562 return !isActive() ? 0 : nowElapsed - mStartTimeElapsed; 1563 } 1564 } 1565 getBgJobCount()1566 int getBgJobCount() { 1567 synchronized (mLock) { 1568 return mBgJobCount; 1569 } 1570 } 1571 shouldTrackLocked()1572 private boolean shouldTrackLocked() { 1573 final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName, 1574 mPkg.userId, sElapsedRealtimeClock.millis()); 1575 return (standbyBucket == RESTRICTED_INDEX || !mChargeTracker.isCharging()) 1576 && !mForegroundUids.get(mUid); 1577 } 1578 onStateChangedLocked(long nowElapsed, boolean isQuotaFree)1579 void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) { 1580 if (isQuotaFree) { 1581 emitSessionLocked(nowElapsed); 1582 } else if (!isActive() && shouldTrackLocked()) { 1583 // Start timing from unplug. 1584 if (mRunningBgJobs.size() > 0) { 1585 mStartTimeElapsed = nowElapsed; 1586 // NOTE: this does have the unfortunate consequence that if the device is 1587 // repeatedly plugged in and unplugged, or an app changes foreground state 1588 // very frequently, the job count for a package may be artificially high. 1589 mBgJobCount = mRunningBgJobs.size(); 1590 incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount); 1591 // Starting the timer means that all cached execution stats are now 1592 // incorrect. 1593 invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); 1594 // Schedule cutoff since we're now actively tracking for quotas again. 1595 scheduleCutoff(); 1596 } 1597 } 1598 } 1599 rescheduleCutoff()1600 void rescheduleCutoff() { 1601 cancelCutoff(); 1602 scheduleCutoff(); 1603 } 1604 scheduleCutoff()1605 private void scheduleCutoff() { 1606 // Each package can only be in one standby bucket, so we only need to have one 1607 // message per timer. We only need to reschedule when restarting timer or when 1608 // standby bucket changes. 1609 synchronized (mLock) { 1610 if (!isActive()) { 1611 return; 1612 } 1613 Message msg = mHandler.obtainMessage(MSG_REACHED_QUOTA, mPkg); 1614 final long timeRemainingMs = getTimeUntilQuotaConsumedLocked(mPkg.userId, 1615 mPkg.packageName); 1616 if (DEBUG) { 1617 Slog.i(TAG, "Job for " + mPkg + " has " + timeRemainingMs + "ms left."); 1618 } 1619 // If the job was running the entire time, then the system would be up, so it's 1620 // fine to use uptime millis for these messages. 1621 mHandler.sendMessageDelayed(msg, timeRemainingMs); 1622 } 1623 } 1624 cancelCutoff()1625 private void cancelCutoff() { 1626 mHandler.removeMessages(MSG_REACHED_QUOTA, mPkg); 1627 } 1628 dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate)1629 public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { 1630 pw.print("Timer{"); 1631 pw.print(mPkg); 1632 pw.print("} "); 1633 if (isActive()) { 1634 pw.print("started at "); 1635 pw.print(mStartTimeElapsed); 1636 pw.print(" ("); 1637 pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed); 1638 pw.print("ms ago)"); 1639 } else { 1640 pw.print("NOT active"); 1641 } 1642 pw.print(", "); 1643 pw.print(mBgJobCount); 1644 pw.print(" running bg jobs"); 1645 pw.println(); 1646 pw.increaseIndent(); 1647 for (int i = 0; i < mRunningBgJobs.size(); i++) { 1648 JobStatus js = mRunningBgJobs.valueAt(i); 1649 if (predicate.test(js)) { 1650 pw.println(js.toShortString()); 1651 } 1652 } 1653 pw.decreaseIndent(); 1654 } 1655 dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)1656 public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) { 1657 final long token = proto.start(fieldId); 1658 1659 mPkg.dumpDebug(proto, StateControllerProto.QuotaController.Timer.PKG); 1660 proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive()); 1661 proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED, 1662 mStartTimeElapsed); 1663 proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount); 1664 for (int i = 0; i < mRunningBgJobs.size(); i++) { 1665 JobStatus js = mRunningBgJobs.valueAt(i); 1666 if (predicate.test(js)) { 1667 js.writeToShortProto(proto, 1668 StateControllerProto.QuotaController.Timer.RUNNING_JOBS); 1669 } 1670 } 1671 1672 proto.end(token); 1673 } 1674 } 1675 1676 /** 1677 * Tracking of app assignments to standby buckets 1678 */ 1679 final class StandbyTracker extends AppIdleStateChangeListener { 1680 1681 @Override onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, boolean idle, int bucket, int reason)1682 public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, 1683 boolean idle, int bucket, int reason) { 1684 // Update job bookkeeping out of band. 1685 BackgroundThread.getHandler().post(() -> { 1686 final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket); 1687 if (DEBUG) { 1688 Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex " 1689 + bucketIndex); 1690 } 1691 List<JobStatus> restrictedChanges = new ArrayList<>(); 1692 synchronized (mLock) { 1693 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 1694 if (jobs == null || jobs.size() == 0) { 1695 return; 1696 } 1697 for (int i = jobs.size() - 1; i >= 0; i--) { 1698 JobStatus js = jobs.valueAt(i); 1699 // Effective standby bucket can change after this in some situations so 1700 // use the real bucket so that the job is tracked by the controllers. 1701 if ((bucketIndex == RESTRICTED_INDEX 1702 || js.getStandbyBucket() == RESTRICTED_INDEX) 1703 && bucketIndex != js.getStandbyBucket()) { 1704 restrictedChanges.add(js); 1705 } 1706 js.setStandbyBucket(bucketIndex); 1707 } 1708 Timer timer = mPkgTimers.get(userId, packageName); 1709 if (timer != null && timer.isActive()) { 1710 timer.rescheduleCutoff(); 1711 } 1712 if (maybeUpdateConstraintForPkgLocked(userId, packageName)) { 1713 mStateChangedListener.onControllerStateChanged(); 1714 } 1715 } 1716 if (restrictedChanges.size() > 0) { 1717 mStateChangedListener.onRestrictedBucketChanged(restrictedChanges); 1718 } 1719 }); 1720 } 1721 } 1722 1723 private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> { 1724 private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() { 1725 public boolean test(TimingSession ts) { 1726 return ts.endTimeElapsed <= sElapsedRealtimeClock.millis() - MAX_PERIOD_MS; 1727 } 1728 }; 1729 1730 @Override accept(List<TimingSession> sessions)1731 public void accept(List<TimingSession> sessions) { 1732 if (sessions != null) { 1733 // Remove everything older than MAX_PERIOD_MS time ago. 1734 sessions.removeIf(mTooOld); 1735 } 1736 } 1737 } 1738 1739 private final DeleteTimingSessionsFunctor mDeleteOldSessionsFunctor = 1740 new DeleteTimingSessionsFunctor(); 1741 1742 @VisibleForTesting deleteObsoleteSessionsLocked()1743 void deleteObsoleteSessionsLocked() { 1744 mTimingSessions.forEach(mDeleteOldSessionsFunctor); 1745 } 1746 1747 private class QcHandler extends Handler { QcHandler(Looper looper)1748 QcHandler(Looper looper) { 1749 super(looper); 1750 } 1751 1752 @Override handleMessage(Message msg)1753 public void handleMessage(Message msg) { 1754 synchronized (mLock) { 1755 switch (msg.what) { 1756 case MSG_REACHED_QUOTA: { 1757 Package pkg = (Package) msg.obj; 1758 if (DEBUG) { 1759 Slog.d(TAG, "Checking if " + pkg + " has reached its quota."); 1760 } 1761 1762 long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId, 1763 pkg.packageName); 1764 if (timeRemainingMs <= 50) { 1765 // Less than 50 milliseconds left. Start process of shutting down jobs. 1766 if (DEBUG) Slog.d(TAG, pkg + " has reached its quota."); 1767 if (maybeUpdateConstraintForPkgLocked(pkg.userId, pkg.packageName)) { 1768 mStateChangedListener.onControllerStateChanged(); 1769 } 1770 } else { 1771 // This could potentially happen if an old session phases out while a 1772 // job is currently running. 1773 // Reschedule message 1774 Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); 1775 timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, 1776 pkg.packageName); 1777 if (DEBUG) { 1778 Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left."); 1779 } 1780 sendMessageDelayed(rescheduleMsg, timeRemainingMs); 1781 } 1782 break; 1783 } 1784 case MSG_CLEAN_UP_SESSIONS: 1785 if (DEBUG) { 1786 Slog.d(TAG, "Cleaning up timing sessions."); 1787 } 1788 deleteObsoleteSessionsLocked(); 1789 maybeScheduleCleanupAlarmLocked(); 1790 1791 break; 1792 case MSG_CHECK_PACKAGE: { 1793 String packageName = (String) msg.obj; 1794 int userId = msg.arg1; 1795 if (DEBUG) { 1796 Slog.d(TAG, "Checking pkg " + string(userId, packageName)); 1797 } 1798 if (maybeUpdateConstraintForPkgLocked(userId, packageName)) { 1799 mStateChangedListener.onControllerStateChanged(); 1800 } 1801 break; 1802 } 1803 case MSG_UID_PROCESS_STATE_CHANGED: { 1804 final int uid = msg.arg1; 1805 final int procState = msg.arg2; 1806 final int userId = UserHandle.getUserId(uid); 1807 final long nowElapsed = sElapsedRealtimeClock.millis(); 1808 1809 synchronized (mLock) { 1810 boolean isQuotaFree; 1811 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 1812 mForegroundUids.put(uid, true); 1813 isQuotaFree = true; 1814 } else { 1815 mForegroundUids.delete(uid); 1816 isQuotaFree = false; 1817 } 1818 // Update Timers first. 1819 if (mPkgTimers.indexOfKey(userId) >= 0) { 1820 ArraySet<String> packages = mUidToPackageCache.get(uid); 1821 if (packages == null) { 1822 try { 1823 String[] pkgs = AppGlobals.getPackageManager() 1824 .getPackagesForUid(uid); 1825 if (pkgs != null) { 1826 for (String pkg : pkgs) { 1827 mUidToPackageCache.add(uid, pkg); 1828 } 1829 packages = mUidToPackageCache.get(uid); 1830 } 1831 } catch (RemoteException e) { 1832 Slog.wtf(TAG, "Failed to get package list", e); 1833 } 1834 } 1835 if (packages != null) { 1836 for (int i = packages.size() - 1; i >= 0; --i) { 1837 Timer t = mPkgTimers.get(userId, packages.valueAt(i)); 1838 if (t != null) { 1839 t.onStateChangedLocked(nowElapsed, isQuotaFree); 1840 } 1841 } 1842 } 1843 } 1844 if (maybeUpdateConstraintForUidLocked(uid)) { 1845 mStateChangedListener.onControllerStateChanged(); 1846 } 1847 } 1848 break; 1849 } 1850 } 1851 } 1852 } 1853 } 1854 1855 static class AlarmQueue extends PriorityQueue<Pair<Package, Long>> { AlarmQueue()1856 AlarmQueue() { 1857 super(1, (o1, o2) -> (int) (o1.second - o2.second)); 1858 } 1859 1860 /** 1861 * Remove any instances of the Package from the queue. 1862 * 1863 * @return true if an instance was removed, false otherwise. 1864 */ remove(@onNull Package pkg)1865 boolean remove(@NonNull Package pkg) { 1866 boolean removed = false; 1867 Pair[] alarms = toArray(new Pair[size()]); 1868 for (int i = alarms.length - 1; i >= 0; --i) { 1869 if (pkg.equals(alarms[i].first)) { 1870 remove(alarms[i]); 1871 removed = true; 1872 } 1873 } 1874 return removed; 1875 } 1876 } 1877 1878 /** Track when UPTCs are expected to come back into quota. */ 1879 private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener { 1880 @GuardedBy("mLock") 1881 private final AlarmQueue mAlarmQueue = new AlarmQueue(); 1882 /** The next time the alarm is set to go off, in the elapsed realtime timebase. */ 1883 @GuardedBy("mLock") 1884 private long mTriggerTimeElapsed = 0; 1885 /** The minimum amount of time between quota check alarms. */ 1886 @GuardedBy("mLock") 1887 private long mMinQuotaCheckDelayMs = QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS; 1888 1889 @GuardedBy("mLock") addAlarmLocked(int userId, @NonNull String pkgName, long inQuotaTimeElapsed)1890 void addAlarmLocked(int userId, @NonNull String pkgName, long inQuotaTimeElapsed) { 1891 final Package pkg = new Package(userId, pkgName); 1892 mAlarmQueue.remove(pkg); 1893 mAlarmQueue.offer(new Pair<>(pkg, inQuotaTimeElapsed)); 1894 setNextAlarmLocked(); 1895 } 1896 1897 @GuardedBy("mLock") setMinQuotaCheckDelayMs(long minDelayMs)1898 void setMinQuotaCheckDelayMs(long minDelayMs) { 1899 mMinQuotaCheckDelayMs = minDelayMs; 1900 } 1901 1902 @GuardedBy("mLock") removeAlarmLocked(@onNull Package pkg)1903 void removeAlarmLocked(@NonNull Package pkg) { 1904 if (mAlarmQueue.remove(pkg)) { 1905 setNextAlarmLocked(); 1906 } 1907 } 1908 1909 @GuardedBy("mLock") removeAlarmLocked(int userId, @NonNull String packageName)1910 void removeAlarmLocked(int userId, @NonNull String packageName) { 1911 removeAlarmLocked(new Package(userId, packageName)); 1912 } 1913 1914 @GuardedBy("mLock") removeAlarmsLocked(int userId)1915 void removeAlarmsLocked(int userId) { 1916 boolean removed = false; 1917 Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]); 1918 for (int i = alarms.length - 1; i >= 0; --i) { 1919 final Package pkg = (Package) alarms[i].first; 1920 if (userId == pkg.userId) { 1921 mAlarmQueue.remove(alarms[i]); 1922 removed = true; 1923 } 1924 } 1925 if (removed) { 1926 setNextAlarmLocked(); 1927 } 1928 } 1929 1930 @GuardedBy("mLock") setNextAlarmLocked()1931 private void setNextAlarmLocked() { 1932 setNextAlarmLocked(sElapsedRealtimeClock.millis()); 1933 } 1934 1935 @GuardedBy("mLock") setNextAlarmLocked(long earliestTriggerElapsed)1936 private void setNextAlarmLocked(long earliestTriggerElapsed) { 1937 if (mAlarmQueue.size() > 0) { 1938 final Pair<Package, Long> alarm = mAlarmQueue.peek(); 1939 final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.second); 1940 // Only schedule the alarm if one of the following is true: 1941 // 1. There isn't one currently scheduled 1942 // 2. The new alarm is significantly earlier than the previous alarm. If it's 1943 // earlier but not significantly so, then we essentially delay the job a few extra 1944 // minutes. 1945 // 3. The alarm is after the current alarm. 1946 if (mTriggerTimeElapsed == 0 1947 || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS 1948 || mTriggerTimeElapsed < nextTriggerTimeElapsed) { 1949 if (DEBUG) { 1950 Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed 1951 + " for app " + alarm.first); 1952 } 1953 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed, 1954 ALARM_TAG_QUOTA_CHECK, this, mHandler); 1955 mTriggerTimeElapsed = nextTriggerTimeElapsed; 1956 } 1957 } else { 1958 mAlarmManager.cancel(this); 1959 mTriggerTimeElapsed = 0; 1960 } 1961 } 1962 1963 @Override onAlarm()1964 public void onAlarm() { 1965 synchronized (mLock) { 1966 while (mAlarmQueue.size() > 0) { 1967 final Pair<Package, Long> alarm = mAlarmQueue.peek(); 1968 if (alarm.second <= sElapsedRealtimeClock.millis()) { 1969 mHandler.obtainMessage(MSG_CHECK_PACKAGE, alarm.first.userId, 0, 1970 alarm.first.packageName).sendToTarget(); 1971 mAlarmQueue.remove(alarm); 1972 } else { 1973 break; 1974 } 1975 } 1976 setNextAlarmLocked(sElapsedRealtimeClock.millis() + mMinQuotaCheckDelayMs); 1977 } 1978 } 1979 1980 @GuardedBy("mLock") dumpLocked(IndentingPrintWriter pw)1981 void dumpLocked(IndentingPrintWriter pw) { 1982 pw.println("In quota alarms:"); 1983 pw.increaseIndent(); 1984 1985 if (mAlarmQueue.size() == 0) { 1986 pw.println("NOT WAITING"); 1987 } else { 1988 Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]); 1989 for (int i = 0; i < alarms.length; ++i) { 1990 final Package pkg = (Package) alarms[i].first; 1991 pw.print(pkg); 1992 pw.print(": "); 1993 pw.print(alarms[i].second); 1994 pw.println(); 1995 } 1996 } 1997 1998 pw.decreaseIndent(); 1999 } 2000 2001 @GuardedBy("mLock") dumpLocked(ProtoOutputStream proto, long fieldId)2002 void dumpLocked(ProtoOutputStream proto, long fieldId) { 2003 final long token = proto.start(fieldId); 2004 2005 proto.write( 2006 StateControllerProto.QuotaController.InQuotaAlarmListener.TRIGGER_TIME_ELAPSED, 2007 mTriggerTimeElapsed); 2008 2009 Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]); 2010 for (int i = 0; i < alarms.length; ++i) { 2011 final long aToken = proto.start( 2012 StateControllerProto.QuotaController.InQuotaAlarmListener.ALARMS); 2013 2014 final Package pkg = (Package) alarms[i].first; 2015 pkg.dumpDebug(proto, 2016 StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.PKG); 2017 proto.write( 2018 StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED, 2019 (Long) alarms[i].second); 2020 2021 proto.end(aToken); 2022 } 2023 2024 proto.end(token); 2025 } 2026 } 2027 2028 @VisibleForTesting 2029 class QcConstants extends ContentObserver { 2030 private ContentResolver mResolver; 2031 private final KeyValueListParser mParser = new KeyValueListParser(','); 2032 2033 private static final String KEY_ALLOWED_TIME_PER_PERIOD_MS = "allowed_time_per_period_ms"; 2034 private static final String KEY_IN_QUOTA_BUFFER_MS = "in_quota_buffer_ms"; 2035 private static final String KEY_WINDOW_SIZE_ACTIVE_MS = "window_size_active_ms"; 2036 private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms"; 2037 private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms"; 2038 private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms"; 2039 private static final String KEY_WINDOW_SIZE_RESTRICTED_MS = "window_size_restricted_ms"; 2040 private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms"; 2041 private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active"; 2042 private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working"; 2043 private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent"; 2044 private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare"; 2045 private static final String KEY_MAX_JOB_COUNT_RESTRICTED = "max_job_count_restricted"; 2046 private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms"; 2047 private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 2048 "max_job_count_per_rate_limiting_window"; 2049 private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active"; 2050 private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working"; 2051 private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent"; 2052 private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare"; 2053 private static final String KEY_MAX_SESSION_COUNT_RESTRICTED = 2054 "max_session_count_restricted"; 2055 private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 2056 "max_session_count_per_rate_limiting_window"; 2057 private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS = 2058 "timing_session_coalescing_duration_ms"; 2059 private static final String KEY_MIN_QUOTA_CHECK_DELAY_MS = "min_quota_check_delay_ms"; 2060 2061 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS = 2062 10 * 60 * 1000L; // 10 minutes 2063 private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 2064 30 * 1000L; // 30 seconds 2065 private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS = 2066 DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time 2067 private static final long DEFAULT_WINDOW_SIZE_WORKING_MS = 2068 2 * 60 * 60 * 1000L; // 2 hours 2069 private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS = 2070 8 * 60 * 60 * 1000L; // 8 hours 2071 private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 2072 24 * 60 * 60 * 1000L; // 24 hours 2073 private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = 2074 24 * 60 * 60 * 1000L; // 24 hours 2075 private static final long DEFAULT_MAX_EXECUTION_TIME_MS = 2076 4 * HOUR_IN_MILLIS; 2077 private static final long DEFAULT_RATE_LIMITING_WINDOW_MS = 2078 MINUTE_IN_MILLIS; 2079 private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20; 2080 private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = 2081 75; // 75/window = 450/hr = 1/session 2082 private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session 2083 (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS); 2084 private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session 2085 (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS); 2086 private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session 2087 (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS); 2088 private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10; 2089 private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = 2090 75; // 450/hr 2091 private static final int DEFAULT_MAX_SESSION_COUNT_WORKING = 2092 10; // 5/hr 2093 private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT = 2094 8; // 1/hr 2095 private static final int DEFAULT_MAX_SESSION_COUNT_RARE = 2096 3; // .125/hr 2097 private static final int DEFAULT_MAX_SESSION_COUNT_RESTRICTED = 1; // 1/day 2098 private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20; 2099 private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds 2100 private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS; 2101 2102 /** How much time each app will have to run jobs within their standby bucket window. */ 2103 public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; 2104 2105 /** 2106 * How much time the package should have before transitioning from out-of-quota to in-quota. 2107 * This should not affect processing if the package is already in-quota. 2108 */ 2109 public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS; 2110 2111 /** 2112 * The quota window size of the particular standby bucket. Apps in this standby bucket are 2113 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past 2114 * WINDOW_SIZE_MS. 2115 */ 2116 public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS; 2117 2118 /** 2119 * The quota window size of the particular standby bucket. Apps in this standby bucket are 2120 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past 2121 * WINDOW_SIZE_MS. 2122 */ 2123 public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS; 2124 2125 /** 2126 * The quota window size of the particular standby bucket. Apps in this standby bucket are 2127 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past 2128 * WINDOW_SIZE_MS. 2129 */ 2130 public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS; 2131 2132 /** 2133 * The quota window size of the particular standby bucket. Apps in this standby bucket are 2134 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past 2135 * WINDOW_SIZE_MS. 2136 */ 2137 public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS; 2138 2139 /** 2140 * The quota window size of the particular standby bucket. Apps in this standby bucket are 2141 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past 2142 * WINDOW_SIZE_MS. 2143 */ 2144 public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS; 2145 2146 /** 2147 * The maximum amount of time an app can have its jobs running within a 24 hour window. 2148 */ 2149 public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS; 2150 2151 /** 2152 * The maximum number of jobs an app can run within this particular standby bucket's 2153 * window size. 2154 */ 2155 public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE; 2156 2157 /** 2158 * The maximum number of jobs an app can run within this particular standby bucket's 2159 * window size. 2160 */ 2161 public int MAX_JOB_COUNT_WORKING = DEFAULT_MAX_JOB_COUNT_WORKING; 2162 2163 /** 2164 * The maximum number of jobs an app can run within this particular standby bucket's 2165 * window size. 2166 */ 2167 public int MAX_JOB_COUNT_FREQUENT = DEFAULT_MAX_JOB_COUNT_FREQUENT; 2168 2169 /** 2170 * The maximum number of jobs an app can run within this particular standby bucket's 2171 * window size. 2172 */ 2173 public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE; 2174 2175 /** 2176 * The maximum number of jobs an app can run within this particular standby bucket's 2177 * window size. 2178 */ 2179 public int MAX_JOB_COUNT_RESTRICTED = DEFAULT_MAX_JOB_COUNT_RESTRICTED; 2180 2181 /** The period of time used to rate limit recently run jobs. */ 2182 public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS; 2183 2184 /** 2185 * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}. 2186 */ 2187 public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 2188 DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 2189 2190 /** 2191 * The maximum number of {@link TimingSession}s an app can run within this particular 2192 * standby bucket's window size. 2193 */ 2194 public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE; 2195 2196 /** 2197 * The maximum number of {@link TimingSession}s an app can run within this particular 2198 * standby bucket's window size. 2199 */ 2200 public int MAX_SESSION_COUNT_WORKING = DEFAULT_MAX_SESSION_COUNT_WORKING; 2201 2202 /** 2203 * The maximum number of {@link TimingSession}s an app can run within this particular 2204 * standby bucket's window size. 2205 */ 2206 public int MAX_SESSION_COUNT_FREQUENT = DEFAULT_MAX_SESSION_COUNT_FREQUENT; 2207 2208 /** 2209 * The maximum number of {@link TimingSession}s an app can run within this particular 2210 * standby bucket's window size. 2211 */ 2212 public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE; 2213 2214 /** 2215 * The maximum number of {@link TimingSession}s an app can run within this particular 2216 * standby bucket's window size. 2217 */ 2218 public int MAX_SESSION_COUNT_RESTRICTED = DEFAULT_MAX_SESSION_COUNT_RESTRICTED; 2219 2220 /** 2221 * The maximum number of {@link TimingSession}s that can run within the past 2222 * {@link #ALLOWED_TIME_PER_PERIOD_MS}. 2223 */ 2224 public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 2225 DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; 2226 2227 /** 2228 * Treat two distinct {@link TimingSession}s as the same if they start and end within this 2229 * amount of time of each other. 2230 */ 2231 public long TIMING_SESSION_COALESCING_DURATION_MS = 2232 DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS; 2233 2234 /** The minimum amount of time between quota check alarms. */ 2235 public long MIN_QUOTA_CHECK_DELAY_MS = DEFAULT_MIN_QUOTA_CHECK_DELAY_MS; 2236 2237 // Safeguards 2238 2239 /** The minimum number of jobs that any bucket will be allowed to run within its window. */ 2240 private static final int MIN_BUCKET_JOB_COUNT = 10; 2241 2242 /** 2243 * The minimum number of {@link TimingSession}s that any bucket will be allowed to run 2244 * within its window. 2245 */ 2246 private static final int MIN_BUCKET_SESSION_COUNT = 1; 2247 2248 /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */ 2249 private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS; 2250 2251 /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */ 2252 private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10; 2253 2254 /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */ 2255 private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10; 2256 2257 /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */ 2258 private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS; 2259 QcConstants(Handler handler)2260 QcConstants(Handler handler) { 2261 super(handler); 2262 } 2263 start(ContentResolver resolver)2264 private void start(ContentResolver resolver) { 2265 mResolver = resolver; 2266 mResolver.registerContentObserver(Settings.Global.getUriFor( 2267 Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS), false, this); 2268 onChange(true, null); 2269 } 2270 2271 @Override onChange(boolean selfChange, Uri uri)2272 public void onChange(boolean selfChange, Uri uri) { 2273 final String constants = Settings.Global.getString( 2274 mResolver, Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS); 2275 2276 try { 2277 mParser.setString(constants); 2278 } catch (Exception e) { 2279 // Failed to parse the settings string, log this and move on with defaults. 2280 Slog.e(TAG, "Bad jobscheduler quota controller settings", e); 2281 } 2282 2283 ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis( 2284 KEY_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_MS); 2285 IN_QUOTA_BUFFER_MS = mParser.getDurationMillis( 2286 KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS); 2287 WINDOW_SIZE_ACTIVE_MS = mParser.getDurationMillis( 2288 KEY_WINDOW_SIZE_ACTIVE_MS, DEFAULT_WINDOW_SIZE_ACTIVE_MS); 2289 WINDOW_SIZE_WORKING_MS = mParser.getDurationMillis( 2290 KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS); 2291 WINDOW_SIZE_FREQUENT_MS = mParser.getDurationMillis( 2292 KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS); 2293 WINDOW_SIZE_RARE_MS = mParser.getDurationMillis( 2294 KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS); 2295 WINDOW_SIZE_RESTRICTED_MS = mParser.getDurationMillis( 2296 KEY_WINDOW_SIZE_RESTRICTED_MS, DEFAULT_WINDOW_SIZE_RESTRICTED_MS); 2297 MAX_EXECUTION_TIME_MS = mParser.getDurationMillis( 2298 KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); 2299 MAX_JOB_COUNT_ACTIVE = mParser.getInt( 2300 KEY_MAX_JOB_COUNT_ACTIVE, DEFAULT_MAX_JOB_COUNT_ACTIVE); 2301 MAX_JOB_COUNT_WORKING = mParser.getInt( 2302 KEY_MAX_JOB_COUNT_WORKING, DEFAULT_MAX_JOB_COUNT_WORKING); 2303 MAX_JOB_COUNT_FREQUENT = mParser.getInt( 2304 KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT); 2305 MAX_JOB_COUNT_RARE = mParser.getInt( 2306 KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE); 2307 MAX_JOB_COUNT_RESTRICTED = mParser.getInt( 2308 KEY_MAX_JOB_COUNT_RESTRICTED, DEFAULT_MAX_JOB_COUNT_RESTRICTED); 2309 RATE_LIMITING_WINDOW_MS = mParser.getLong( 2310 KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS); 2311 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt( 2312 KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 2313 DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW); 2314 MAX_SESSION_COUNT_ACTIVE = mParser.getInt( 2315 KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE); 2316 MAX_SESSION_COUNT_WORKING = mParser.getInt( 2317 KEY_MAX_SESSION_COUNT_WORKING, DEFAULT_MAX_SESSION_COUNT_WORKING); 2318 MAX_SESSION_COUNT_FREQUENT = mParser.getInt( 2319 KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT); 2320 MAX_SESSION_COUNT_RARE = mParser.getInt( 2321 KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE); 2322 MAX_SESSION_COUNT_RESTRICTED = mParser.getInt( 2323 KEY_MAX_SESSION_COUNT_RESTRICTED, DEFAULT_MAX_SESSION_COUNT_RESTRICTED); 2324 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt( 2325 KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 2326 DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); 2327 TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong( 2328 KEY_TIMING_SESSION_COALESCING_DURATION_MS, 2329 DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS); 2330 MIN_QUOTA_CHECK_DELAY_MS = mParser.getDurationMillis(KEY_MIN_QUOTA_CHECK_DELAY_MS, 2331 DEFAULT_MIN_QUOTA_CHECK_DELAY_MS); 2332 2333 updateConstants(); 2334 } 2335 2336 @VisibleForTesting updateConstants()2337 void updateConstants() { 2338 synchronized (mLock) { 2339 boolean changed = false; 2340 2341 long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS, 2342 Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS)); 2343 if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) { 2344 mMaxExecutionTimeMs = newMaxExecutionTimeMs; 2345 mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; 2346 changed = true; 2347 } 2348 long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs, 2349 Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS)); 2350 if (mAllowedTimePerPeriodMs != newAllowedTimeMs) { 2351 mAllowedTimePerPeriodMs = newAllowedTimeMs; 2352 mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; 2353 changed = true; 2354 } 2355 // Make sure quota buffer is non-negative, not greater than allowed time per period, 2356 // and no more than 5 minutes. 2357 long newQuotaBufferMs = Math.max(0, Math.min(mAllowedTimePerPeriodMs, 2358 Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS))); 2359 if (mQuotaBufferMs != newQuotaBufferMs) { 2360 mQuotaBufferMs = newQuotaBufferMs; 2361 mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs; 2362 mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; 2363 changed = true; 2364 } 2365 long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs, 2366 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS)); 2367 if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) { 2368 mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs; 2369 changed = true; 2370 } 2371 long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs, 2372 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS)); 2373 if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) { 2374 mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs; 2375 changed = true; 2376 } 2377 long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs, 2378 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); 2379 if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) { 2380 mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs; 2381 changed = true; 2382 } 2383 long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs, 2384 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS)); 2385 if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) { 2386 mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs; 2387 changed = true; 2388 } 2389 // Fit in the range [allowed time (10 mins), 1 week]. 2390 long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs, 2391 Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS)); 2392 if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) { 2393 mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs; 2394 changed = true; 2395 } 2396 long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS, 2397 Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS)); 2398 if (mRateLimitingWindowMs != newRateLimitingWindowMs) { 2399 mRateLimitingWindowMs = newRateLimitingWindowMs; 2400 changed = true; 2401 } 2402 int newMaxJobCountPerRateLimitingWindow = Math.max( 2403 MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 2404 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW); 2405 if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) { 2406 mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow; 2407 changed = true; 2408 } 2409 int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE); 2410 if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) { 2411 mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount; 2412 changed = true; 2413 } 2414 int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING); 2415 if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) { 2416 mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount; 2417 changed = true; 2418 } 2419 int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT); 2420 if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) { 2421 mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount; 2422 changed = true; 2423 } 2424 int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE); 2425 if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) { 2426 mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount; 2427 changed = true; 2428 } 2429 int newRestrictedMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, 2430 MAX_JOB_COUNT_RESTRICTED); 2431 if (mMaxBucketJobCounts[RESTRICTED_INDEX] != newRestrictedMaxJobCount) { 2432 mMaxBucketJobCounts[RESTRICTED_INDEX] = newRestrictedMaxJobCount; 2433 changed = true; 2434 } 2435 int newMaxSessionCountPerRateLimitPeriod = Math.max( 2436 MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 2437 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); 2438 if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) { 2439 mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod; 2440 changed = true; 2441 } 2442 int newActiveMaxSessionCount = 2443 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_ACTIVE); 2444 if (mMaxBucketSessionCounts[ACTIVE_INDEX] != newActiveMaxSessionCount) { 2445 mMaxBucketSessionCounts[ACTIVE_INDEX] = newActiveMaxSessionCount; 2446 changed = true; 2447 } 2448 int newWorkingMaxSessionCount = 2449 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_WORKING); 2450 if (mMaxBucketSessionCounts[WORKING_INDEX] != newWorkingMaxSessionCount) { 2451 mMaxBucketSessionCounts[WORKING_INDEX] = newWorkingMaxSessionCount; 2452 changed = true; 2453 } 2454 int newFrequentMaxSessionCount = 2455 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_FREQUENT); 2456 if (mMaxBucketSessionCounts[FREQUENT_INDEX] != newFrequentMaxSessionCount) { 2457 mMaxBucketSessionCounts[FREQUENT_INDEX] = newFrequentMaxSessionCount; 2458 changed = true; 2459 } 2460 int newRareMaxSessionCount = 2461 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_RARE); 2462 if (mMaxBucketSessionCounts[RARE_INDEX] != newRareMaxSessionCount) { 2463 mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount; 2464 changed = true; 2465 } 2466 int newRestrictedMaxSessionCount = Math.max(0, MAX_SESSION_COUNT_RESTRICTED); 2467 if (mMaxBucketSessionCounts[RESTRICTED_INDEX] != newRestrictedMaxSessionCount) { 2468 mMaxBucketSessionCounts[RESTRICTED_INDEX] = newRestrictedMaxSessionCount; 2469 changed = true; 2470 } 2471 long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS, 2472 Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS)); 2473 if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) { 2474 mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs; 2475 changed = true; 2476 } 2477 // Don't set changed to true for this one since we don't need to re-evaluate 2478 // execution stats or constraint status. Limit the delay to the range [0, 15] 2479 // minutes. 2480 mInQuotaAlarmListener.setMinQuotaCheckDelayMs( 2481 Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS))); 2482 2483 if (changed) { 2484 // Update job bookkeeping out of band. 2485 BackgroundThread.getHandler().post(() -> { 2486 synchronized (mLock) { 2487 invalidateAllExecutionStatsLocked(); 2488 maybeUpdateAllConstraintsLocked(); 2489 } 2490 }); 2491 } 2492 } 2493 } 2494 dump(IndentingPrintWriter pw)2495 private void dump(IndentingPrintWriter pw) { 2496 pw.println(); 2497 pw.println("QuotaController:"); 2498 pw.increaseIndent(); 2499 pw.printPair(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println(); 2500 pw.printPair(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println(); 2501 pw.printPair(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println(); 2502 pw.printPair(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println(); 2503 pw.printPair(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println(); 2504 pw.printPair(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println(); 2505 pw.printPair(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println(); 2506 pw.printPair(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println(); 2507 pw.printPair(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println(); 2508 pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println(); 2509 pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println(); 2510 pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println(); 2511 pw.printPair(KEY_MAX_JOB_COUNT_RESTRICTED, MAX_JOB_COUNT_RESTRICTED).println(); 2512 pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println(); 2513 pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 2514 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println(); 2515 pw.printPair(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println(); 2516 pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println(); 2517 pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println(); 2518 pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println(); 2519 pw.printPair(KEY_MAX_SESSION_COUNT_RESTRICTED, MAX_SESSION_COUNT_RESTRICTED).println(); 2520 pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 2521 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println(); 2522 pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS, 2523 TIMING_SESSION_COALESCING_DURATION_MS).println(); 2524 pw.printPair(KEY_MIN_QUOTA_CHECK_DELAY_MS, MIN_QUOTA_CHECK_DELAY_MS).println(); 2525 pw.decreaseIndent(); 2526 } 2527 dump(ProtoOutputStream proto)2528 private void dump(ProtoOutputStream proto) { 2529 final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER); 2530 proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS, 2531 ALLOWED_TIME_PER_PERIOD_MS); 2532 proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS); 2533 proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS, 2534 WINDOW_SIZE_ACTIVE_MS); 2535 proto.write(ConstantsProto.QuotaController.WORKING_WINDOW_SIZE_MS, 2536 WINDOW_SIZE_WORKING_MS); 2537 proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS, 2538 WINDOW_SIZE_FREQUENT_MS); 2539 proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS); 2540 proto.write(ConstantsProto.QuotaController.RESTRICTED_WINDOW_SIZE_MS, 2541 WINDOW_SIZE_RESTRICTED_MS); 2542 proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS, 2543 MAX_EXECUTION_TIME_MS); 2544 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE); 2545 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING, 2546 MAX_JOB_COUNT_WORKING); 2547 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT, 2548 MAX_JOB_COUNT_FREQUENT); 2549 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE); 2550 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RESTRICTED, 2551 MAX_JOB_COUNT_RESTRICTED); 2552 proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS, 2553 RATE_LIMITING_WINDOW_MS); 2554 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 2555 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW); 2556 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE, 2557 MAX_SESSION_COUNT_ACTIVE); 2558 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING, 2559 MAX_SESSION_COUNT_WORKING); 2560 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_FREQUENT, 2561 MAX_SESSION_COUNT_FREQUENT); 2562 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE, 2563 MAX_SESSION_COUNT_RARE); 2564 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RESTRICTED, 2565 MAX_SESSION_COUNT_RESTRICTED); 2566 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 2567 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); 2568 proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS, 2569 TIMING_SESSION_COALESCING_DURATION_MS); 2570 proto.write(ConstantsProto.QuotaController.MIN_QUOTA_CHECK_DELAY_MS, 2571 MIN_QUOTA_CHECK_DELAY_MS); 2572 proto.end(qcToken); 2573 } 2574 } 2575 2576 //////////////////////// TESTING HELPERS ///////////////////////////// 2577 2578 @VisibleForTesting getAllowedTimePerPeriodMs()2579 long getAllowedTimePerPeriodMs() { 2580 return mAllowedTimePerPeriodMs; 2581 } 2582 2583 @VisibleForTesting 2584 @NonNull getBucketMaxJobCounts()2585 int[] getBucketMaxJobCounts() { 2586 return mMaxBucketJobCounts; 2587 } 2588 2589 @VisibleForTesting 2590 @NonNull getBucketMaxSessionCounts()2591 int[] getBucketMaxSessionCounts() { 2592 return mMaxBucketSessionCounts; 2593 } 2594 2595 @VisibleForTesting 2596 @NonNull getBucketWindowSizes()2597 long[] getBucketWindowSizes() { 2598 return mBucketPeriodsMs; 2599 } 2600 2601 @VisibleForTesting 2602 @NonNull getForegroundUids()2603 SparseBooleanArray getForegroundUids() { 2604 return mForegroundUids; 2605 } 2606 2607 @VisibleForTesting 2608 @NonNull getHandler()2609 Handler getHandler() { 2610 return mHandler; 2611 } 2612 2613 @VisibleForTesting getInQuotaBufferMs()2614 long getInQuotaBufferMs() { 2615 return mQuotaBufferMs; 2616 } 2617 2618 @VisibleForTesting getMaxExecutionTimeMs()2619 long getMaxExecutionTimeMs() { 2620 return mMaxExecutionTimeMs; 2621 } 2622 2623 @VisibleForTesting getMaxJobCountPerRateLimitingWindow()2624 int getMaxJobCountPerRateLimitingWindow() { 2625 return mMaxJobCountPerRateLimitingWindow; 2626 } 2627 2628 @VisibleForTesting getMaxSessionCountPerRateLimitingWindow()2629 int getMaxSessionCountPerRateLimitingWindow() { 2630 return mMaxSessionCountPerRateLimitingWindow; 2631 } 2632 2633 @VisibleForTesting getRateLimitingWindowMs()2634 long getRateLimitingWindowMs() { 2635 return mRateLimitingWindowMs; 2636 } 2637 2638 @VisibleForTesting getTimingSessionCoalescingDurationMs()2639 long getTimingSessionCoalescingDurationMs() { 2640 return mTimingSessionCoalescingDurationMs; 2641 } 2642 2643 @VisibleForTesting 2644 @Nullable getTimingSessions(int userId, String packageName)2645 List<TimingSession> getTimingSessions(int userId, String packageName) { 2646 return mTimingSessions.get(userId, packageName); 2647 } 2648 2649 @VisibleForTesting 2650 @NonNull getQcConstants()2651 QcConstants getQcConstants() { 2652 return mQcConstants; 2653 } 2654 2655 //////////////////////////// DATA DUMP ////////////////////////////// 2656 2657 @Override dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate)2658 public void dumpControllerStateLocked(final IndentingPrintWriter pw, 2659 final Predicate<JobStatus> predicate) { 2660 pw.println("Is charging: " + mChargeTracker.isCharging()); 2661 pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis()); 2662 pw.println(); 2663 2664 pw.print("Foreground UIDs: "); 2665 pw.println(mForegroundUids.toString()); 2666 pw.println(); 2667 2668 pw.println("Cached UID->package map:"); 2669 pw.increaseIndent(); 2670 for (int i = 0; i < mUidToPackageCache.size(); ++i) { 2671 final int uid = mUidToPackageCache.keyAt(i); 2672 pw.print(uid); 2673 pw.print(": "); 2674 pw.println(mUidToPackageCache.get(uid)); 2675 } 2676 pw.decreaseIndent(); 2677 pw.println(); 2678 2679 mTrackedJobs.forEach((jobs) -> { 2680 for (int j = 0; j < jobs.size(); j++) { 2681 final JobStatus js = jobs.valueAt(j); 2682 if (!predicate.test(js)) { 2683 continue; 2684 } 2685 pw.print("#"); 2686 js.printUniqueId(pw); 2687 pw.print(" from "); 2688 UserHandle.formatUid(pw, js.getSourceUid()); 2689 if (mTopStartedJobs.contains(js)) { 2690 pw.print(" (TOP)"); 2691 } 2692 pw.println(); 2693 2694 pw.increaseIndent(); 2695 pw.print(JobStatus.bucketName(js.getEffectiveStandbyBucket())); 2696 pw.print(", "); 2697 if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { 2698 pw.print("within quota"); 2699 } else { 2700 pw.print("not within quota"); 2701 } 2702 pw.print(", "); 2703 pw.print(getRemainingExecutionTimeLocked(js)); 2704 pw.print("ms remaining in quota"); 2705 pw.decreaseIndent(); 2706 pw.println(); 2707 } 2708 }); 2709 2710 pw.println(); 2711 for (int u = 0; u < mPkgTimers.numMaps(); ++u) { 2712 final int userId = mPkgTimers.keyAt(u); 2713 for (int p = 0; p < mPkgTimers.numElementsForKey(userId); ++p) { 2714 final String pkgName = mPkgTimers.keyAt(u, p); 2715 mPkgTimers.valueAt(u, p).dump(pw, predicate); 2716 pw.println(); 2717 List<TimingSession> sessions = mTimingSessions.get(userId, pkgName); 2718 if (sessions != null) { 2719 pw.increaseIndent(); 2720 pw.println("Saved sessions:"); 2721 pw.increaseIndent(); 2722 for (int j = sessions.size() - 1; j >= 0; j--) { 2723 TimingSession session = sessions.get(j); 2724 session.dump(pw); 2725 } 2726 pw.decreaseIndent(); 2727 pw.decreaseIndent(); 2728 pw.println(); 2729 } 2730 } 2731 } 2732 2733 pw.println("Cached execution stats:"); 2734 pw.increaseIndent(); 2735 for (int u = 0; u < mExecutionStatsCache.numMaps(); ++u) { 2736 final int userId = mExecutionStatsCache.keyAt(u); 2737 for (int p = 0; p < mExecutionStatsCache.numElementsForKey(userId); ++p) { 2738 final String pkgName = mExecutionStatsCache.keyAt(u, p); 2739 ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p); 2740 2741 pw.println(string(userId, pkgName)); 2742 pw.increaseIndent(); 2743 for (int i = 0; i < stats.length; ++i) { 2744 ExecutionStats executionStats = stats[i]; 2745 if (executionStats != null) { 2746 pw.print(JobStatus.bucketName(i)); 2747 pw.print(": "); 2748 pw.println(executionStats); 2749 } 2750 } 2751 pw.decreaseIndent(); 2752 } 2753 } 2754 pw.decreaseIndent(); 2755 2756 pw.println(); 2757 mInQuotaAlarmListener.dumpLocked(pw); 2758 pw.decreaseIndent(); 2759 } 2760 2761 @Override dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)2762 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, 2763 Predicate<JobStatus> predicate) { 2764 final long token = proto.start(fieldId); 2765 final long mToken = proto.start(StateControllerProto.QUOTA); 2766 2767 proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging()); 2768 proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME, 2769 sElapsedRealtimeClock.millis()); 2770 2771 for (int i = 0; i < mForegroundUids.size(); ++i) { 2772 proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS, 2773 mForegroundUids.keyAt(i)); 2774 } 2775 2776 for (int i = 0; i < mUidToPackageCache.size(); ++i) { 2777 final long upToken = proto.start( 2778 StateControllerProto.QuotaController.UID_TO_PACKAGE_CACHE); 2779 2780 final int uid = mUidToPackageCache.keyAt(i); 2781 ArraySet<String> packages = mUidToPackageCache.get(uid); 2782 2783 proto.write(StateControllerProto.QuotaController.UidPackageMapping.UID, uid); 2784 for (int j = 0; j < packages.size(); ++j) { 2785 proto.write(StateControllerProto.QuotaController.UidPackageMapping.PACKAGE_NAMES, 2786 packages.valueAt(j)); 2787 } 2788 2789 proto.end(upToken); 2790 } 2791 2792 mTrackedJobs.forEach((jobs) -> { 2793 for (int j = 0; j < jobs.size(); j++) { 2794 final JobStatus js = jobs.valueAt(j); 2795 if (!predicate.test(js)) { 2796 continue; 2797 } 2798 final long jsToken = proto.start(StateControllerProto.QuotaController.TRACKED_JOBS); 2799 js.writeToShortProto(proto, StateControllerProto.QuotaController.TrackedJob.INFO); 2800 proto.write(StateControllerProto.QuotaController.TrackedJob.SOURCE_UID, 2801 js.getSourceUid()); 2802 proto.write( 2803 StateControllerProto.QuotaController.TrackedJob.EFFECTIVE_STANDBY_BUCKET, 2804 js.getEffectiveStandbyBucket()); 2805 proto.write(StateControllerProto.QuotaController.TrackedJob.IS_TOP_STARTED_JOB, 2806 mTopStartedJobs.contains(js)); 2807 proto.write(StateControllerProto.QuotaController.TrackedJob.HAS_QUOTA, 2808 js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2809 proto.write(StateControllerProto.QuotaController.TrackedJob.REMAINING_QUOTA_MS, 2810 getRemainingExecutionTimeLocked(js)); 2811 proto.end(jsToken); 2812 } 2813 }); 2814 2815 for (int u = 0; u < mPkgTimers.numMaps(); ++u) { 2816 final int userId = mPkgTimers.keyAt(u); 2817 for (int p = 0; p < mPkgTimers.numElementsForKey(userId); ++p) { 2818 final String pkgName = mPkgTimers.keyAt(u, p); 2819 final long psToken = proto.start( 2820 StateControllerProto.QuotaController.PACKAGE_STATS); 2821 mPkgTimers.valueAt(u, p).dump(proto, 2822 StateControllerProto.QuotaController.PackageStats.TIMER, predicate); 2823 2824 List<TimingSession> sessions = mTimingSessions.get(userId, pkgName); 2825 if (sessions != null) { 2826 for (int j = sessions.size() - 1; j >= 0; j--) { 2827 TimingSession session = sessions.get(j); 2828 session.dump(proto, 2829 StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS); 2830 } 2831 } 2832 2833 ExecutionStats[] stats = mExecutionStatsCache.get(userId, pkgName); 2834 if (stats != null) { 2835 for (int bucketIndex = 0; bucketIndex < stats.length; ++bucketIndex) { 2836 ExecutionStats es = stats[bucketIndex]; 2837 if (es == null) { 2838 continue; 2839 } 2840 final long esToken = proto.start( 2841 StateControllerProto.QuotaController.PackageStats.EXECUTION_STATS); 2842 proto.write( 2843 StateControllerProto.QuotaController.ExecutionStats.STANDBY_BUCKET, 2844 bucketIndex); 2845 proto.write( 2846 StateControllerProto.QuotaController.ExecutionStats.EXPIRATION_TIME_ELAPSED, 2847 es.expirationTimeElapsed); 2848 proto.write( 2849 StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS, 2850 es.windowSizeMs); 2851 proto.write( 2852 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT, 2853 es.jobCountLimit); 2854 proto.write( 2855 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT, 2856 es.sessionCountLimit); 2857 proto.write( 2858 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS, 2859 es.executionTimeInWindowMs); 2860 proto.write( 2861 StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_WINDOW, 2862 es.bgJobCountInWindow); 2863 proto.write( 2864 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_MAX_PERIOD_MS, 2865 es.executionTimeInMaxPeriodMs); 2866 proto.write( 2867 StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_MAX_PERIOD, 2868 es.bgJobCountInMaxPeriod); 2869 proto.write( 2870 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW, 2871 es.sessionCountInWindow); 2872 proto.write( 2873 StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED, 2874 es.inQuotaTimeElapsed); 2875 proto.write( 2876 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED, 2877 es.jobRateLimitExpirationTimeElapsed); 2878 proto.write( 2879 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW, 2880 es.jobCountInRateLimitingWindow); 2881 proto.write( 2882 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED, 2883 es.sessionRateLimitExpirationTimeElapsed); 2884 proto.write( 2885 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW, 2886 es.sessionCountInRateLimitingWindow); 2887 proto.end(esToken); 2888 } 2889 } 2890 2891 proto.end(psToken); 2892 } 2893 } 2894 2895 mInQuotaAlarmListener.dumpLocked(proto, 2896 StateControllerProto.QuotaController.IN_QUOTA_ALARM_LISTENER); 2897 2898 proto.end(mToken); 2899 proto.end(token); 2900 } 2901 2902 @Override dumpConstants(IndentingPrintWriter pw)2903 public void dumpConstants(IndentingPrintWriter pw) { 2904 mQcConstants.dump(pw); 2905 } 2906 2907 @Override dumpConstants(ProtoOutputStream proto)2908 public void dumpConstants(ProtoOutputStream proto) { 2909 mQcConstants.dump(proto); 2910 } 2911 } 2912