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