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.EXEMPTED_INDEX; 25 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; 26 import static com.android.server.job.JobSchedulerService.NEVER_INDEX; 27 import static com.android.server.job.JobSchedulerService.RARE_INDEX; 28 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; 29 import static com.android.server.job.JobSchedulerService.WORKING_INDEX; 30 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 31 32 import android.Manifest; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.annotation.UserIdInt; 36 import android.app.ActivityManager; 37 import android.app.AlarmManager; 38 import android.app.UidObserver; 39 import android.app.job.JobInfo; 40 import android.app.usage.UsageEvents; 41 import android.app.usage.UsageStatsManagerInternal; 42 import android.app.usage.UsageStatsManagerInternal.UsageEventListener; 43 import android.content.Context; 44 import android.content.pm.ApplicationInfo; 45 import android.content.pm.PackageInfo; 46 import android.content.pm.PackageManager; 47 import android.content.pm.UserPackage; 48 import android.os.BatteryManager; 49 import android.os.Handler; 50 import android.os.Looper; 51 import android.os.Message; 52 import android.os.RemoteException; 53 import android.os.Trace; 54 import android.os.UserHandle; 55 import android.provider.DeviceConfig; 56 import android.util.ArraySet; 57 import android.util.IndentingPrintWriter; 58 import android.util.Log; 59 import android.util.Slog; 60 import android.util.SparseArray; 61 import android.util.SparseArrayMap; 62 import android.util.SparseBooleanArray; 63 import android.util.SparseLongArray; 64 import android.util.SparseSetArray; 65 import android.util.proto.ProtoOutputStream; 66 67 import com.android.internal.annotations.GuardedBy; 68 import com.android.internal.annotations.VisibleForTesting; 69 import com.android.internal.util.ArrayUtils; 70 import com.android.server.AppSchedulingModuleThread; 71 import com.android.server.LocalServices; 72 import com.android.server.PowerAllowlistInternal; 73 import com.android.server.job.ConstantsProto; 74 import com.android.server.job.Flags; 75 import com.android.server.job.JobSchedulerService; 76 import com.android.server.job.StateControllerProto; 77 import com.android.server.usage.AppStandbyInternal; 78 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; 79 import com.android.server.utils.AlarmQueue; 80 81 import dalvik.annotation.optimization.NeverCompile; 82 83 import java.util.ArrayList; 84 import java.util.List; 85 import java.util.function.Consumer; 86 import java.util.function.Predicate; 87 88 /** 89 * Controller that tracks whether an app has exceeded its standby bucket quota. 90 * 91 * With initial defaults, each app in each bucket is given 10 minutes to run within its respective 92 * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a 93 * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run 94 * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some 95 * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new 96 * quota is immediately applied to it. 97 * 98 * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on 99 * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will 100 * not be allowed to run more than 20 jobs within the past 10 minutes. 101 * 102 * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run 103 * freely when an app enters the foreground state and are restricted when the app leaves the 104 * foreground state. However, jobs that are started while the app is in the TOP state do not count 105 * towards any quota and are not restricted regardless of the app's state change. 106 * 107 * Jobs will not be throttled when the device is charging. The device is considered to be charging 108 * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast. 109 * 110 * Note: all limits are enforced per bucket window unless explicitly stated otherwise. 111 * All stated values are configurable and subject to change. See {@link QcConstants} for current 112 * defaults. 113 * 114 * Test: atest com.android.server.job.controllers.QuotaControllerTest 115 */ 116 public final class QuotaController extends StateController { 117 private static final String TAG = "JobScheduler.Quota"; 118 private static final boolean DEBUG = JobSchedulerService.DEBUG 119 || Log.isLoggable(TAG, Log.DEBUG); 120 121 private static final String ALARM_TAG_CLEANUP = "*job.cleanup*"; 122 private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*"; 123 124 private static final int SYSTEM_APP_CHECK_FLAGS = 125 PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 126 | PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES; 127 hashLong(long val)128 private static int hashLong(long val) { 129 return (int) (val ^ (val >>> 32)); 130 } 131 132 @VisibleForTesting 133 static class ExecutionStats { 134 /** 135 * The time after which this record should be considered invalid (out of date), in the 136 * elapsed realtime timebase. 137 */ 138 public long expirationTimeElapsed; 139 140 public long allowedTimePerPeriodMs; 141 public long windowSizeMs; 142 public int jobCountLimit; 143 public int sessionCountLimit; 144 145 /** The total amount of time the app ran in its respective bucket window size. */ 146 public long executionTimeInWindowMs; 147 public int bgJobCountInWindow; 148 149 /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */ 150 public long executionTimeInMaxPeriodMs; 151 public int bgJobCountInMaxPeriod; 152 153 /** 154 * The number of {@link TimingSession TimingSessions} within the bucket window size. 155 * This will include sessions that started before the window as long as they end within 156 * the window. 157 */ 158 public int sessionCountInWindow; 159 160 /** 161 * The time after which the app will be under the bucket quota and can start running jobs 162 * again. This is only valid if 163 * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs}, 164 * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs}, 165 * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or 166 * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}. 167 */ 168 public long inQuotaTimeElapsed; 169 170 /** 171 * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid, 172 * in the elapsed realtime timebase. 173 */ 174 public long jobRateLimitExpirationTimeElapsed; 175 176 /** 177 * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}. 178 * It may contain a few stale entries since cleanup won't happen exactly every 179 * {@link #mRateLimitingWindowMs}. 180 */ 181 public int jobCountInRateLimitingWindow; 182 183 /** 184 * The time after which {@link #sessionCountInRateLimitingWindow} should be considered 185 * invalid, in the elapsed realtime timebase. 186 */ 187 public long sessionRateLimitExpirationTimeElapsed; 188 189 /** 190 * The number of {@link TimingSession TimingSessions} that ran in at least the last 191 * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't 192 * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered 193 * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}. 194 */ 195 public int sessionCountInRateLimitingWindow; 196 197 @Override toString()198 public String toString() { 199 return "expirationTime=" + expirationTimeElapsed + ", " 200 + "allowedTimePerPeriodMs=" + allowedTimePerPeriodMs + ", " 201 + "windowSizeMs=" + windowSizeMs + ", " 202 + "jobCountLimit=" + jobCountLimit + ", " 203 + "sessionCountLimit=" + sessionCountLimit + ", " 204 + "executionTimeInWindow=" + executionTimeInWindowMs + ", " 205 + "bgJobCountInWindow=" + bgJobCountInWindow + ", " 206 + "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", " 207 + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", " 208 + "sessionCountInWindow=" + sessionCountInWindow + ", " 209 + "inQuotaTime=" + inQuotaTimeElapsed + ", " 210 + "rateLimitJobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", " 211 + "rateLimitJobCountWindow=" + jobCountInRateLimitingWindow + ", " 212 + "rateLimitSessionCountExpirationTime=" 213 + sessionRateLimitExpirationTimeElapsed + ", " 214 + "rateLimitSessionCountWindow=" + sessionCountInRateLimitingWindow; 215 } 216 217 @Override equals(Object obj)218 public boolean equals(Object obj) { 219 if (obj instanceof ExecutionStats) { 220 ExecutionStats other = (ExecutionStats) obj; 221 return this.expirationTimeElapsed == other.expirationTimeElapsed 222 && this.allowedTimePerPeriodMs == other.allowedTimePerPeriodMs 223 && this.windowSizeMs == other.windowSizeMs 224 && this.jobCountLimit == other.jobCountLimit 225 && this.sessionCountLimit == other.sessionCountLimit 226 && this.executionTimeInWindowMs == other.executionTimeInWindowMs 227 && this.bgJobCountInWindow == other.bgJobCountInWindow 228 && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs 229 && this.sessionCountInWindow == other.sessionCountInWindow 230 && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod 231 && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed 232 && this.jobRateLimitExpirationTimeElapsed 233 == other.jobRateLimitExpirationTimeElapsed 234 && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow 235 && this.sessionRateLimitExpirationTimeElapsed 236 == other.sessionRateLimitExpirationTimeElapsed 237 && this.sessionCountInRateLimitingWindow 238 == other.sessionCountInRateLimitingWindow; 239 } else { 240 return false; 241 } 242 } 243 244 @Override hashCode()245 public int hashCode() { 246 int result = 0; 247 result = 31 * result + hashLong(expirationTimeElapsed); 248 result = 31 * result + hashLong(allowedTimePerPeriodMs); 249 result = 31 * result + hashLong(windowSizeMs); 250 result = 31 * result + hashLong(jobCountLimit); 251 result = 31 * result + hashLong(sessionCountLimit); 252 result = 31 * result + hashLong(executionTimeInWindowMs); 253 result = 31 * result + bgJobCountInWindow; 254 result = 31 * result + hashLong(executionTimeInMaxPeriodMs); 255 result = 31 * result + bgJobCountInMaxPeriod; 256 result = 31 * result + sessionCountInWindow; 257 result = 31 * result + hashLong(inQuotaTimeElapsed); 258 result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed); 259 result = 31 * result + jobCountInRateLimitingWindow; 260 result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed); 261 result = 31 * result + sessionCountInRateLimitingWindow; 262 return result; 263 } 264 } 265 266 /** List of all tracked jobs keyed by source package-userId combo. */ 267 private final SparseArrayMap<String, ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>(); 268 269 /** Timer for each package-userId combo. */ 270 private final SparseArrayMap<String, Timer> mPkgTimers = new SparseArrayMap<>(); 271 272 /** Timer for expedited jobs for each package-userId combo. */ 273 private final SparseArrayMap<String, Timer> mEJPkgTimers = new SparseArrayMap<>(); 274 275 /** List of all regular timing sessions for a package-userId combo, in chronological order. */ 276 private final SparseArrayMap<String, List<TimedEvent>> mTimingEvents = new SparseArrayMap<>(); 277 278 /** 279 * List of all expedited job timing sessions for a package-userId combo, in chronological order. 280 */ 281 private final SparseArrayMap<String, List<TimedEvent>> mEJTimingSessions = 282 new SparseArrayMap<>(); 283 284 /** 285 * Queue to track and manage when each package comes back within quota. 286 */ 287 @GuardedBy("mLock") 288 private final InQuotaAlarmQueue mInQuotaAlarmQueue; 289 290 /** Cached calculation results for each app, with the standby buckets as the array indices. */ 291 private final SparseArrayMap<String, ExecutionStats[]> mExecutionStatsCache = 292 new SparseArrayMap<>(); 293 294 private final SparseArrayMap<String, ShrinkableDebits> mEJStats = new SparseArrayMap<>(); 295 296 private final SparseArrayMap<String, TopAppTimer> mTopAppTrackers = new SparseArrayMap<>(); 297 298 /** List of UIDs currently in the foreground. */ 299 private final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); 300 301 /** 302 * List of jobs that started while the UID was in the TOP state. There will usually be no more 303 * than {@value JobConcurrencyManager#MAX_STANDARD_JOB_CONCURRENCY} running at once, so an 304 * ArraySet is fine. 305 */ 306 private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>(); 307 308 /** Current set of UIDs on the temp allowlist. */ 309 private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray(); 310 311 /** 312 * Mapping of UIDs to when their temp allowlist grace period ends (in the elapsed 313 * realtime timebase). 314 */ 315 private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray(); 316 317 /** Current set of UIDs in the {@link ActivityManager#PROCESS_STATE_TOP} state. */ 318 private final SparseBooleanArray mTopAppCache = new SparseBooleanArray(); 319 320 /** 321 * Mapping of UIDs to the when their top app grace period ends (in the elapsed realtime 322 * timebase). 323 */ 324 private final SparseLongArray mTopAppGraceCache = new SparseLongArray(); 325 326 private final AlarmManager mAlarmManager; 327 private final QcHandler mHandler; 328 private final QcConstants mQcConstants; 329 330 private final BackgroundJobsController mBackgroundJobsController; 331 private final ConnectivityController mConnectivityController; 332 333 /** How much time each app will have to run jobs within their standby bucket window. */ 334 private final long[] mAllowedTimePerPeriodMs = new long[]{ 335 QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 336 QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 337 QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 338 QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS, 339 0, // NEVER 340 QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 341 QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS 342 }; 343 344 /** 345 * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS} 346 * window. 347 */ 348 private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS; 349 350 /** 351 * How much time the app should have before transitioning from out-of-quota to in-quota. 352 * This should not affect processing if the app is already in-quota. 353 */ 354 private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS; 355 356 /** 357 * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an 358 * app will have enough quota to transition from out-of-quota to in-quota. 359 */ 360 private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; 361 362 /** The period of time used to rate limit recently run jobs. */ 363 private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS; 364 365 /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */ 366 private int mMaxJobCountPerRateLimitingWindow = 367 QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 368 369 /** 370 * The maximum number of {@link TimingSession TimingSessions} that can run within the past 371 * {@link #mRateLimitingWindowMs}. 372 */ 373 private int mMaxSessionCountPerRateLimitingWindow = 374 QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; 375 376 private long mNextCleanupTimeElapsed = 0; 377 private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener = 378 new AlarmManager.OnAlarmListener() { 379 @Override 380 public void onAlarm() { 381 mHandler.obtainMessage(MSG_CLEAN_UP_SESSIONS).sendToTarget(); 382 } 383 }; 384 385 private class QcUidObserver extends UidObserver { 386 @Override onUidStateChanged(int uid, int procState, long procStateSeq, int capability)387 public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { 388 mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget(); 389 } 390 } 391 392 /** 393 * The rolling window size for each standby bucket. Within each window, an app will have 10 394 * minutes to run its jobs. 395 */ 396 private final long[] mBucketPeriodsMs = new long[]{ 397 QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS, 398 QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS, 399 QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS, 400 QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS, 401 0, // NEVER 402 QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS, 403 QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS 404 }; 405 406 /** The maximum period any bucket can have. */ 407 private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS; 408 409 /** 410 * The maximum number of jobs based on its standby bucket. For each max value count in the 411 * array, the app will not be allowed to run more than that many number of jobs within the 412 * latest time interval of its rolling window size. 413 * 414 * @see #mBucketPeriodsMs 415 */ 416 private final int[] mMaxBucketJobCounts = new int[]{ 417 QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE, 418 QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING, 419 QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT, 420 QcConstants.DEFAULT_MAX_JOB_COUNT_RARE, 421 0, // NEVER 422 QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED, 423 QcConstants.DEFAULT_MAX_JOB_COUNT_EXEMPTED 424 }; 425 426 /** 427 * The maximum number of {@link TimingSession TimingSessions} based on its standby bucket. 428 * For each max value count in the array, the app will not be allowed to have more than that 429 * many number of {@link TimingSession TimingSessions} within the latest time interval of its 430 * rolling window size. 431 * 432 * @see #mBucketPeriodsMs 433 */ 434 private final int[] mMaxBucketSessionCounts = new int[]{ 435 QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE, 436 QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING, 437 QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT, 438 QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE, 439 0, // NEVER 440 QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED, 441 QcConstants.DEFAULT_MAX_SESSION_COUNT_EXEMPTED, 442 }; 443 444 /** 445 * Treat two distinct {@link TimingSession TimingSessions} as the same if they start and end 446 * within this amount of time of each other. 447 */ 448 private long mTimingSessionCoalescingDurationMs = 449 QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS; 450 451 /** 452 * The rolling window size for each standby bucket. Within each window, an app will have 10 453 * minutes to run its jobs. 454 */ 455 private final long[] mEJLimitsMs = new long[]{ 456 QcConstants.DEFAULT_EJ_LIMIT_ACTIVE_MS, 457 QcConstants.DEFAULT_EJ_LIMIT_WORKING_MS, 458 QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS, 459 QcConstants.DEFAULT_EJ_LIMIT_RARE_MS, 460 0, // NEVER 461 QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS, 462 QcConstants.DEFAULT_EJ_LIMIT_EXEMPTED_MS 463 }; 464 465 private long mEjLimitAdditionInstallerMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS; 466 467 private long mEjLimitAdditionSpecialMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS; 468 469 /** 470 * The period of time used to calculate expedited job sessions. Apps can only have expedited job 471 * sessions totalling {@link #mEJLimitsMs}[bucket within this period of time (without factoring 472 * in any rewards or free EJs). 473 */ 474 private long mEJLimitWindowSizeMs = QcConstants.DEFAULT_EJ_WINDOW_SIZE_MS; 475 476 /** 477 * Length of time used to split an app's top time into chunks. 478 */ 479 private long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; 480 481 /** 482 * How much EJ quota to give back to an app based on the number of top app time chunks it had. 483 */ 484 private long mEJRewardTopAppMs = QcConstants.DEFAULT_EJ_REWARD_TOP_APP_MS; 485 486 /** 487 * How much EJ quota to give back to an app based on each non-top user interaction. 488 */ 489 private long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS; 490 491 /** 492 * How much EJ quota to give back to an app based on each notification seen event. 493 */ 494 private long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS; 495 496 private long mEJGracePeriodTempAllowlistMs = 497 QcConstants.DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS; 498 499 private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; 500 501 private long mQuotaBumpAdditionalDurationMs = 502 QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS; 503 private int mQuotaBumpAdditionalJobCount = QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT; 504 private int mQuotaBumpAdditionalSessionCount = 505 QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT; 506 private long mQuotaBumpWindowSizeMs = QcConstants.DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS; 507 private int mQuotaBumpLimit = QcConstants.DEFAULT_QUOTA_BUMP_LIMIT; 508 509 /** 510 * List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission 511 * granted for each user. 512 */ 513 private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>(); 514 515 /** An app has reached its quota. The message should contain a {@link UserPackage} object. */ 516 @VisibleForTesting 517 static final int MSG_REACHED_TIME_QUOTA = 0; 518 /** Drop any old timing sessions. */ 519 private static final int MSG_CLEAN_UP_SESSIONS = 1; 520 /** Check if a package is now within its quota. */ 521 private static final int MSG_CHECK_PACKAGE = 2; 522 /** Process state for a UID has changed. */ 523 private static final int MSG_UID_PROCESS_STATE_CHANGED = 3; 524 /** 525 * An app has reached its expedited job quota. The message should contain a {@link UserPackage} 526 * object. 527 */ 528 @VisibleForTesting 529 static final int MSG_REACHED_EJ_TIME_QUOTA = 4; 530 /** 531 * Process a new {@link UsageEvents.Event}. The event will be the message's object and the 532 * userId will the first arg. 533 */ 534 private static final int MSG_PROCESS_USAGE_EVENT = 5; 535 /** A UID's free quota grace period has ended. */ 536 @VisibleForTesting 537 static final int MSG_END_GRACE_PERIOD = 6; 538 /** 539 * An app has reached its job count quota. The message should contain a {@link UserPackage} 540 * object. 541 */ 542 static final int MSG_REACHED_COUNT_QUOTA = 7; 543 QuotaController(@onNull JobSchedulerService service, @NonNull BackgroundJobsController backgroundJobsController, @NonNull ConnectivityController connectivityController)544 public QuotaController(@NonNull JobSchedulerService service, 545 @NonNull BackgroundJobsController backgroundJobsController, 546 @NonNull ConnectivityController connectivityController) { 547 super(service); 548 mHandler = new QcHandler(AppSchedulingModuleThread.get().getLooper()); 549 mAlarmManager = mContext.getSystemService(AlarmManager.class); 550 mQcConstants = new QcConstants(); 551 mBackgroundJobsController = backgroundJobsController; 552 mConnectivityController = connectivityController; 553 mInQuotaAlarmQueue = 554 new InQuotaAlarmQueue(mContext, AppSchedulingModuleThread.get().getLooper()); 555 556 // Set up the app standby bucketing tracker 557 AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); 558 appStandby.addListener(new StandbyTracker()); 559 560 UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class); 561 usmi.registerListener(new UsageEventTracker()); 562 563 PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class); 564 pai.registerTempAllowlistChangeListener(new TempAllowlistTracker()); 565 566 try { 567 ActivityManager.getService().registerUidObserver(new QcUidObserver(), 568 ActivityManager.UID_OBSERVER_PROCSTATE, 569 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null); 570 ActivityManager.getService().registerUidObserver(new QcUidObserver(), 571 ActivityManager.UID_OBSERVER_PROCSTATE, 572 ActivityManager.PROCESS_STATE_TOP, null); 573 } catch (RemoteException e) { 574 // ignored; both services live in system_server 575 } 576 } 577 578 @Override onSystemServicesReady()579 public void onSystemServicesReady() { 580 synchronized (mLock) { 581 cacheInstallerPackagesLocked(UserHandle.USER_SYSTEM); 582 } 583 } 584 585 @Override 586 @GuardedBy("mLock") maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)587 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 588 final long nowElapsed = sElapsedRealtimeClock.millis(); 589 final int userId = jobStatus.getSourceUserId(); 590 final String pkgName = jobStatus.getSourcePackageName(); 591 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); 592 if (jobs == null) { 593 jobs = new ArraySet<>(); 594 mTrackedJobs.add(userId, pkgName, jobs); 595 } 596 jobs.add(jobStatus); 597 jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA); 598 final boolean isWithinQuota = isWithinQuotaLocked(jobStatus); 599 final boolean isWithinEJQuota = 600 jobStatus.isRequestedExpeditedJob() && isWithinEJQuotaLocked(jobStatus); 601 setConstraintSatisfied(jobStatus, nowElapsed, isWithinQuota, isWithinEJQuota); 602 final boolean outOfEJQuota; 603 if (jobStatus.isRequestedExpeditedJob()) { 604 setExpeditedQuotaApproved(jobStatus, nowElapsed, isWithinEJQuota); 605 outOfEJQuota = !isWithinEJQuota; 606 } else { 607 outOfEJQuota = false; 608 } 609 if (!isWithinQuota || outOfEJQuota) { 610 maybeScheduleStartAlarmLocked(userId, pkgName, jobStatus.getEffectiveStandbyBucket()); 611 } 612 } 613 614 @Override 615 @GuardedBy("mLock") prepareForExecutionLocked(JobStatus jobStatus)616 public void prepareForExecutionLocked(JobStatus jobStatus) { 617 if (DEBUG) { 618 Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); 619 } 620 621 final int uid = jobStatus.getSourceUid(); 622 if (mTopAppCache.get(uid)) { 623 if (DEBUG) { 624 Slog.d(TAG, jobStatus.toShortString() + " is top started job"); 625 } 626 mTopStartedJobs.add(jobStatus); 627 // Top jobs won't count towards quota so there's no need to involve the Timer. 628 return; 629 } else if (jobStatus.shouldTreatAsUserInitiatedJob()) { 630 // User-initiated jobs won't count towards quota. 631 return; 632 } 633 634 final int userId = jobStatus.getSourceUserId(); 635 final String packageName = jobStatus.getSourcePackageName(); 636 final SparseArrayMap<String, Timer> timerMap = 637 jobStatus.shouldTreatAsExpeditedJob() ? mEJPkgTimers : mPkgTimers; 638 Timer timer = timerMap.get(userId, packageName); 639 if (timer == null) { 640 timer = new Timer(uid, userId, packageName, !jobStatus.shouldTreatAsExpeditedJob()); 641 timerMap.add(userId, packageName, timer); 642 } 643 timer.startTrackingJobLocked(jobStatus); 644 } 645 646 @Override 647 @GuardedBy("mLock") unprepareFromExecutionLocked(JobStatus jobStatus)648 public void unprepareFromExecutionLocked(JobStatus jobStatus) { 649 Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 650 if (timer != null) { 651 timer.stopTrackingJob(jobStatus); 652 } 653 if (jobStatus.isRequestedExpeditedJob()) { 654 timer = mEJPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 655 if (timer != null) { 656 timer.stopTrackingJob(jobStatus); 657 } 658 } 659 mTopStartedJobs.remove(jobStatus); 660 } 661 662 @Override 663 @GuardedBy("mLock") maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob)664 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { 665 if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) { 666 unprepareFromExecutionLocked(jobStatus); 667 final int userId = jobStatus.getSourceUserId(); 668 final String pkgName = jobStatus.getSourcePackageName(); 669 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); 670 if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) { 671 mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName)); 672 } 673 } 674 } 675 676 @Override onAppRemovedLocked(String packageName, int uid)677 public void onAppRemovedLocked(String packageName, int uid) { 678 if (packageName == null) { 679 Slog.wtf(TAG, "Told app removed but given null package name."); 680 return; 681 } 682 clearAppStatsLocked(UserHandle.getUserId(uid), packageName); 683 if (mService.getPackagesForUidLocked(uid) == null) { 684 // All packages in the UID have been removed. It's safe to remove things based on 685 // UID alone. 686 mForegroundUids.delete(uid); 687 mTempAllowlistCache.delete(uid); 688 mTempAllowlistGraceCache.delete(uid); 689 mTopAppCache.delete(uid); 690 mTopAppGraceCache.delete(uid); 691 } 692 } 693 694 @Override onUserAddedLocked(int userId)695 public void onUserAddedLocked(int userId) { 696 cacheInstallerPackagesLocked(userId); 697 } 698 699 @Override onUserRemovedLocked(int userId)700 public void onUserRemovedLocked(int userId) { 701 mTrackedJobs.delete(userId); 702 mPkgTimers.delete(userId); 703 mEJPkgTimers.delete(userId); 704 mTimingEvents.delete(userId); 705 mEJTimingSessions.delete(userId); 706 mInQuotaAlarmQueue.removeAlarmsForUserId(userId); 707 mExecutionStatsCache.delete(userId); 708 mEJStats.delete(userId); 709 mSystemInstallers.remove(userId); 710 mTopAppTrackers.delete(userId); 711 } 712 713 @Override onBatteryStateChangedLocked()714 public void onBatteryStateChangedLocked() { 715 handleNewChargingStateLocked(); 716 } 717 718 /** Drop all historical stats and stop tracking any active sessions for the specified app. */ clearAppStatsLocked(int userId, @NonNull String packageName)719 public void clearAppStatsLocked(int userId, @NonNull String packageName) { 720 mTrackedJobs.delete(userId, packageName); 721 Timer timer = mPkgTimers.delete(userId, packageName); 722 if (timer != null) { 723 if (timer.isActive()) { 724 Slog.e(TAG, "clearAppStats called before Timer turned off."); 725 timer.dropEverythingLocked(); 726 } 727 } 728 timer = mEJPkgTimers.delete(userId, packageName); 729 if (timer != null) { 730 if (timer.isActive()) { 731 Slog.e(TAG, "clearAppStats called before EJ Timer turned off."); 732 timer.dropEverythingLocked(); 733 } 734 } 735 mTimingEvents.delete(userId, packageName); 736 mEJTimingSessions.delete(userId, packageName); 737 mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); 738 mExecutionStatsCache.delete(userId, packageName); 739 mEJStats.delete(userId, packageName); 740 mTopAppTrackers.delete(userId, packageName); 741 } 742 cacheInstallerPackagesLocked(int userId)743 private void cacheInstallerPackagesLocked(int userId) { 744 final List<PackageInfo> packages = mContext.getPackageManager() 745 .getInstalledPackagesAsUser(SYSTEM_APP_CHECK_FLAGS, userId); 746 for (int i = packages.size() - 1; i >= 0; --i) { 747 final PackageInfo pi = packages.get(i); 748 final ApplicationInfo ai = pi.applicationInfo; 749 final int idx = ArrayUtils.indexOf( 750 pi.requestedPermissions, Manifest.permission.INSTALL_PACKAGES); 751 752 if (idx >= 0 && ai != null && PackageManager.PERMISSION_GRANTED 753 == mContext.checkPermission(Manifest.permission.INSTALL_PACKAGES, -1, ai.uid)) { 754 mSystemInstallers.add(UserHandle.getUserId(ai.uid), pi.packageName); 755 } 756 } 757 } 758 isUidInForeground(int uid)759 private boolean isUidInForeground(int uid) { 760 if (UserHandle.isCore(uid)) { 761 return true; 762 } 763 synchronized (mLock) { 764 return mForegroundUids.get(uid); 765 } 766 } 767 768 /** @return true if the job was started while the app was in the TOP state. */ isTopStartedJobLocked(@onNull final JobStatus jobStatus)769 private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { 770 return mTopStartedJobs.contains(jobStatus); 771 } 772 773 /** Returns the maximum amount of time this job could run for. */ 774 @GuardedBy("mLock") getMaxJobExecutionTimeMsLocked(@onNull final JobStatus jobStatus)775 public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) { 776 if (!jobStatus.shouldTreatAsExpeditedJob()) { 777 // If quota is currently "free", then the job can run for the full amount of time, 778 // regardless of bucket (hence using charging instead of isQuotaFreeLocked()). 779 if (mService.isBatteryCharging()) { 780 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; 781 } 782 // The top and foreground cases here were added because apps in those states 783 // aren't really restricted and the work could be something the user is 784 // waiting for. Now that user-initiated jobs are a defined concept, we may 785 // not need these exemptions as much. However, UIJs are currently limited 786 // (as of UDC) to data transfer work. There may be other work that could 787 // rely on this exception. Once we add more UIJ types, we can re-evaluate 788 // the need for these exceptions. 789 // TODO: re-evaluate the need for these exceptions 790 final boolean isInPrivilegedState = mTopAppCache.get(jobStatus.getSourceUid()) 791 || isTopStartedJobLocked(jobStatus) 792 || isUidInForeground(jobStatus.getSourceUid()); 793 final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH 794 || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0; 795 if (isInPrivilegedState && isJobImportant) { 796 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; 797 } 798 return getTimeUntilQuotaConsumedLocked( 799 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 800 } 801 802 // Expedited job. 803 if (mService.isBatteryCharging()) { 804 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; 805 } 806 if (jobStatus.getEffectiveStandbyBucket() == EXEMPTED_INDEX) { 807 return Math.max(mEJLimitsMs[EXEMPTED_INDEX] / 2, 808 getTimeUntilEJQuotaConsumedLocked( 809 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); 810 } 811 if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) { 812 return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2, 813 getTimeUntilEJQuotaConsumedLocked( 814 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); 815 } 816 if (isUidInForeground(jobStatus.getSourceUid())) { 817 return Math.max(mEJLimitsMs[WORKING_INDEX] / 2, 818 getTimeUntilEJQuotaConsumedLocked( 819 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); 820 } 821 return getTimeUntilEJQuotaConsumedLocked( 822 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 823 } 824 hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket, long nowElapsed)825 private boolean hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket, 826 long nowElapsed) { 827 if (standbyBucket == RESTRICTED_INDEX || standbyBucket == NEVER_INDEX) { 828 // Don't let RESTRICTED apps get free quota from the temp allowlist. 829 // TODO: consider granting the exemption to RESTRICTED apps if the temp allowlist allows 830 // them to start FGS 831 return false; 832 } 833 final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(sourceUid); 834 return mTempAllowlistCache.get(sourceUid) 835 || nowElapsed < tempAllowlistGracePeriodEndElapsed; 836 } 837 838 /** @return true if the job is within expedited job quota. */ 839 @GuardedBy("mLock") isWithinEJQuotaLocked(@onNull final JobStatus jobStatus)840 public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) { 841 if (isQuotaFreeLocked(jobStatus.getEffectiveStandbyBucket())) { 842 return true; 843 } 844 // A job is within quota if one of the following is true: 845 // 1. the app is currently in the foreground 846 // 2. the app overall is within its quota 847 // 3. It's on the temp allowlist (or within the grace period) 848 if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { 849 return true; 850 } 851 852 final long nowElapsed = sElapsedRealtimeClock.millis(); 853 if (hasTempAllowlistExemptionLocked(jobStatus.getSourceUid(), 854 jobStatus.getEffectiveStandbyBucket(), nowElapsed)) { 855 return true; 856 } 857 858 final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(jobStatus.getSourceUid()); 859 final boolean hasTopAppExemption = mTopAppCache.get(jobStatus.getSourceUid()) 860 || nowElapsed < topAppGracePeriodEndElapsed; 861 if (hasTopAppExemption) { 862 return true; 863 } 864 865 return 0 < getRemainingEJExecutionTimeLocked( 866 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 867 } 868 869 @NonNull 870 @VisibleForTesting 871 ShrinkableDebits getEJDebitsLocked(final int userId, @NonNull final String packageName) { 872 ShrinkableDebits debits = mEJStats.get(userId, packageName); 873 if (debits == null) { 874 debits = new ShrinkableDebits( 875 JobSchedulerService.standbyBucketForPackage( 876 packageName, userId, sElapsedRealtimeClock.millis()) 877 ); 878 mEJStats.add(userId, packageName, debits); 879 } 880 return debits; 881 } 882 883 @VisibleForTesting 884 @GuardedBy("mLock") 885 boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { 886 final int standbyBucket = jobStatus.getEffectiveStandbyBucket(); 887 // A job is within quota if one of the following is true: 888 // 1. it was started while the app was in the TOP state 889 // 2. the app is currently in the foreground 890 // 3. the app overall is within its quota 891 if (!Flags.countQuotaFix()) { 892 return jobStatus.shouldTreatAsUserInitiatedJob() 893 || isTopStartedJobLocked(jobStatus) 894 || isUidInForeground(jobStatus.getSourceUid()) 895 || isWithinQuotaLocked( 896 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); 897 } 898 899 if (jobStatus.shouldTreatAsUserInitiatedJob() 900 || isTopStartedJobLocked(jobStatus) 901 || isUidInForeground(jobStatus.getSourceUid())) { 902 return true; 903 } 904 905 if (standbyBucket == NEVER_INDEX) return false; 906 907 if (isQuotaFreeLocked(standbyBucket)) return true; 908 909 final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(), 910 jobStatus.getSourcePackageName(), standbyBucket); 911 if (!(getRemainingExecutionTimeLocked(stats) > 0)) { 912 // Out of execution time quota. 913 return false; 914 } 915 916 if (standbyBucket != RESTRICTED_INDEX && mService.isCurrentlyRunningLocked(jobStatus)) { 917 // Running job is considered as within quota except for the restricted one, which 918 // requires additional constraints. 919 return true; 920 } 921 922 // Check if the app is within job count quota. 923 return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats); 924 } 925 926 @GuardedBy("mLock") 927 private boolean isQuotaFreeLocked(final int standbyBucket) { 928 // Quota constraint is not enforced while charging. 929 if (mService.isBatteryCharging()) { 930 // Restricted jobs require additional constraints when charging, so don't immediately 931 // mark quota as free when charging. 932 return standbyBucket != RESTRICTED_INDEX; 933 } 934 return false; 935 } 936 937 @VisibleForTesting 938 @GuardedBy("mLock") 939 boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, 940 final int standbyBucket) { 941 if (standbyBucket == NEVER_INDEX) return false; 942 943 if (isQuotaFreeLocked(standbyBucket)) return true; 944 945 ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 946 // TODO: use a higher minimum remaining time for jobs with MINIMUM priority 947 return getRemainingExecutionTimeLocked(stats) > 0 948 && isUnderJobCountQuotaLocked(stats) 949 && isUnderSessionCountQuotaLocked(stats); 950 } 951 isUnderJobCountQuotaLocked(@onNull ExecutionStats stats)952 private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) { 953 final long now = sElapsedRealtimeClock.millis(); 954 final boolean isUnderAllowedTimeQuota = 955 (stats.jobRateLimitExpirationTimeElapsed <= now 956 || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow); 957 return isUnderAllowedTimeQuota 958 && stats.bgJobCountInWindow < stats.jobCountLimit; 959 } 960 isUnderSessionCountQuotaLocked(@onNull ExecutionStats stats)961 private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) { 962 final long now = sElapsedRealtimeClock.millis(); 963 final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now 964 || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow); 965 return isUnderAllowedTimeQuota 966 && stats.sessionCountInWindow < stats.sessionCountLimit; 967 } 968 969 @VisibleForTesting getRemainingExecutionTimeLocked(@onNull final JobStatus jobStatus)970 long getRemainingExecutionTimeLocked(@NonNull final JobStatus jobStatus) { 971 return getRemainingExecutionTimeLocked(jobStatus.getSourceUserId(), 972 jobStatus.getSourcePackageName(), 973 jobStatus.getEffectiveStandbyBucket()); 974 } 975 976 @VisibleForTesting getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName)977 long getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName) { 978 final int standbyBucket = JobSchedulerService.standbyBucketForPackage(packageName, 979 userId, sElapsedRealtimeClock.millis()); 980 return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket); 981 } 982 983 /** 984 * Returns the amount of time, in milliseconds, that this job has remaining to run based on its 985 * current standby bucket. Time remaining could be negative if the app was moved from a less 986 * restricted to a more restricted bucket. 987 */ getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName, final int standbyBucket)988 private long getRemainingExecutionTimeLocked(final int userId, 989 @NonNull final String packageName, final int standbyBucket) { 990 if (standbyBucket == NEVER_INDEX) { 991 return 0; 992 } 993 return getRemainingExecutionTimeLocked( 994 getExecutionStatsLocked(userId, packageName, standbyBucket)); 995 } 996 getRemainingExecutionTimeLocked(@onNull ExecutionStats stats)997 private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) { 998 return Math.min(stats.allowedTimePerPeriodMs - stats.executionTimeInWindowMs, 999 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); 1000 } 1001 1002 @VisibleForTesting getRemainingEJExecutionTimeLocked(final int userId, @NonNull final String packageName)1003 long getRemainingEJExecutionTimeLocked(final int userId, @NonNull final String packageName) { 1004 ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 1005 if (quota.getStandbyBucketLocked() == NEVER_INDEX) { 1006 return 0; 1007 } 1008 final long limitMs = 1009 getEJLimitMsLocked(userId, packageName, quota.getStandbyBucketLocked()); 1010 long remainingMs = limitMs - quota.getTallyLocked(); 1011 1012 // Stale sessions may still be factored into tally. Make sure they're removed. 1013 List<TimedEvent> timingSessions = mEJTimingSessions.get(userId, packageName); 1014 final long nowElapsed = sElapsedRealtimeClock.millis(); 1015 final long windowStartTimeElapsed = nowElapsed - mEJLimitWindowSizeMs; 1016 if (timingSessions != null) { 1017 while (timingSessions.size() > 0) { 1018 TimingSession ts = (TimingSession) timingSessions.get(0); 1019 if (ts.endTimeElapsed < windowStartTimeElapsed) { 1020 final long duration = ts.endTimeElapsed - ts.startTimeElapsed; 1021 remainingMs += duration; 1022 quota.transactLocked(-duration); 1023 timingSessions.remove(0); 1024 } else if (ts.startTimeElapsed < windowStartTimeElapsed) { 1025 remainingMs += windowStartTimeElapsed - ts.startTimeElapsed; 1026 break; 1027 } else { 1028 // Fully within the window. 1029 break; 1030 } 1031 } 1032 } 1033 1034 TopAppTimer topAppTimer = mTopAppTrackers.get(userId, packageName); 1035 if (topAppTimer != null && topAppTimer.isActive()) { 1036 remainingMs += topAppTimer.getPendingReward(nowElapsed); 1037 } 1038 1039 Timer timer = mEJPkgTimers.get(userId, packageName); 1040 if (timer == null) { 1041 return remainingMs; 1042 } 1043 1044 return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis()); 1045 } 1046 getEJLimitMsLocked(final int userId, @NonNull final String packageName, final int standbyBucket)1047 private long getEJLimitMsLocked(final int userId, @NonNull final String packageName, 1048 final int standbyBucket) { 1049 final long baseLimitMs = mEJLimitsMs[standbyBucket]; 1050 if (mSystemInstallers.contains(userId, packageName)) { 1051 return baseLimitMs + mEjLimitAdditionInstallerMs; 1052 } 1053 return baseLimitMs; 1054 } 1055 1056 /** 1057 * Returns the amount of time, in milliseconds, until the package would have reached its 1058 * duration quota, assuming it has a job counting towards its quota the entire time. This takes 1059 * into account any {@link TimingSession TimingSessions} that may roll out of the window as the 1060 * job is running. 1061 */ 1062 @VisibleForTesting getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName)1063 long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) { 1064 final long nowElapsed = sElapsedRealtimeClock.millis(); 1065 final int standbyBucket = JobSchedulerService.standbyBucketForPackage( 1066 packageName, userId, nowElapsed); 1067 if (standbyBucket == NEVER_INDEX) { 1068 return 0; 1069 } 1070 1071 List<TimedEvent> events = mTimingEvents.get(userId, packageName); 1072 final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 1073 if (events == null || events.size() == 0) { 1074 // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can 1075 // essentially run until they reach the maximum limit. 1076 if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { 1077 return mMaxExecutionTimeMs; 1078 } 1079 return mAllowedTimePerPeriodMs[standbyBucket]; 1080 } 1081 1082 final long startWindowElapsed = nowElapsed - stats.windowSizeMs; 1083 final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; 1084 final long allowedTimePerPeriodMs = mAllowedTimePerPeriodMs[standbyBucket]; 1085 final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs; 1086 final long maxExecutionTimeRemainingMs = 1087 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; 1088 1089 // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can 1090 // essentially run until they reach the maximum limit. 1091 if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { 1092 return calculateTimeUntilQuotaConsumedLocked( 1093 events, startMaxElapsed, maxExecutionTimeRemainingMs, false); 1094 } 1095 1096 // Need to check both max time and period time in case one is less than the other. 1097 // For example, max time remaining could be less than bucket time remaining, but sessions 1098 // contributing to the max time remaining could phase out enough that we'd want to use the 1099 // bucket value. 1100 return Math.min( 1101 calculateTimeUntilQuotaConsumedLocked( 1102 events, startMaxElapsed, maxExecutionTimeRemainingMs, false), 1103 calculateTimeUntilQuotaConsumedLocked( 1104 events, startWindowElapsed, allowedTimeRemainingMs, true)); 1105 } 1106 1107 /** 1108 * Calculates how much time it will take, in milliseconds, until the quota is fully consumed. 1109 * 1110 * @param windowStartElapsed The start of the window, in the elapsed realtime timebase. 1111 * @param deadSpaceMs How much time can be allowed to count towards the quota 1112 */ calculateTimeUntilQuotaConsumedLocked(@onNull List<TimedEvent> sessions, final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps)1113 private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimedEvent> sessions, 1114 final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps) { 1115 long timeUntilQuotaConsumedMs = 0; 1116 long start = windowStartElapsed; 1117 int numQuotaBumps = 0; 1118 final long quotaBumpWindowStartElapsed = 1119 sElapsedRealtimeClock.millis() - mQuotaBumpWindowSizeMs; 1120 final int numSessions = sessions.size(); 1121 if (allowQuotaBumps) { 1122 for (int i = numSessions - 1; i >= 0; --i) { 1123 TimedEvent event = sessions.get(i); 1124 1125 if (event instanceof QuotaBump) { 1126 if (event.getEndTimeElapsed() >= quotaBumpWindowStartElapsed 1127 && numQuotaBumps++ < mQuotaBumpLimit) { 1128 deadSpaceMs += mQuotaBumpAdditionalDurationMs; 1129 } else { 1130 break; 1131 } 1132 } 1133 } 1134 } 1135 for (int i = 0; i < numSessions; ++i) { 1136 TimedEvent event = sessions.get(i); 1137 1138 if (event instanceof QuotaBump) { 1139 continue; 1140 } 1141 1142 TimingSession session = (TimingSession) event; 1143 1144 if (session.endTimeElapsed < windowStartElapsed) { 1145 // Outside of window. Ignore. 1146 continue; 1147 } else if (session.startTimeElapsed <= windowStartElapsed) { 1148 // Overlapping session. Can extend time by portion of session in window. 1149 timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed; 1150 start = session.endTimeElapsed; 1151 } else { 1152 // Completely within the window. Can only consider if there's enough dead space 1153 // to get to the start of the session. 1154 long diff = session.startTimeElapsed - start; 1155 if (diff > deadSpaceMs) { 1156 break; 1157 } 1158 timeUntilQuotaConsumedMs += diff 1159 + (session.endTimeElapsed - session.startTimeElapsed); 1160 deadSpaceMs -= diff; 1161 start = session.endTimeElapsed; 1162 } 1163 } 1164 // Will be non-zero if the loop didn't look at any sessions. 1165 timeUntilQuotaConsumedMs += deadSpaceMs; 1166 if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) { 1167 Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs); 1168 } 1169 return timeUntilQuotaConsumedMs; 1170 } 1171 1172 /** 1173 * Returns the amount of time, in milliseconds, until the package would have reached its 1174 * expedited job quota, assuming it has a job counting towards the quota the entire time and 1175 * the quota isn't replenished at all in that time. 1176 */ 1177 @VisibleForTesting getTimeUntilEJQuotaConsumedLocked(final int userId, @NonNull final String packageName)1178 long getTimeUntilEJQuotaConsumedLocked(final int userId, @NonNull final String packageName) { 1179 final long remainingExecutionTimeMs = 1180 getRemainingEJExecutionTimeLocked(userId, packageName); 1181 1182 List<TimedEvent> sessions = mEJTimingSessions.get(userId, packageName); 1183 if (sessions == null || sessions.size() == 0) { 1184 return remainingExecutionTimeMs; 1185 } 1186 1187 final long nowElapsed = sElapsedRealtimeClock.millis(); 1188 ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 1189 final long limitMs = 1190 getEJLimitMsLocked(userId, packageName, quota.getStandbyBucketLocked()); 1191 final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs); 1192 long remainingDeadSpaceMs = remainingExecutionTimeMs; 1193 // Total time looked at where a session wouldn't be phasing out. 1194 long deadSpaceMs = 0; 1195 // Time regained from sessions phasing out 1196 long phasedOutSessionTimeMs = 0; 1197 1198 for (int i = 0; i < sessions.size(); ++i) { 1199 TimingSession session = (TimingSession) sessions.get(i); 1200 if (session.endTimeElapsed < startWindowElapsed) { 1201 // Edge case where a session became stale in the time between the call to 1202 // getRemainingEJExecutionTimeLocked and this line. 1203 remainingDeadSpaceMs += session.endTimeElapsed - session.startTimeElapsed; 1204 sessions.remove(i); 1205 i--; 1206 } else if (session.startTimeElapsed < startWindowElapsed) { 1207 // Session straddles start of window 1208 phasedOutSessionTimeMs = session.endTimeElapsed - startWindowElapsed; 1209 } else { 1210 // Session fully inside window 1211 final long timeBetweenSessions = session.startTimeElapsed 1212 - (i == 0 ? startWindowElapsed : sessions.get(i - 1).getEndTimeElapsed()); 1213 final long usedDeadSpaceMs = Math.min(remainingDeadSpaceMs, timeBetweenSessions); 1214 deadSpaceMs += usedDeadSpaceMs; 1215 if (usedDeadSpaceMs == timeBetweenSessions) { 1216 phasedOutSessionTimeMs += session.endTimeElapsed - session.startTimeElapsed; 1217 } 1218 remainingDeadSpaceMs -= usedDeadSpaceMs; 1219 if (remainingDeadSpaceMs <= 0) { 1220 break; 1221 } 1222 } 1223 } 1224 1225 return Math.min(limitMs, deadSpaceMs + phasedOutSessionTimeMs + remainingDeadSpaceMs); 1226 } 1227 1228 /** Returns the execution stats of the app in the most recent window. */ 1229 @VisibleForTesting 1230 @NonNull getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket)1231 ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, 1232 final int standbyBucket) { 1233 return getExecutionStatsLocked(userId, packageName, standbyBucket, true); 1234 } 1235 1236 @NonNull getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket, final boolean refreshStatsIfOld)1237 private ExecutionStats getExecutionStatsLocked(final int userId, 1238 @NonNull final String packageName, final int standbyBucket, 1239 final boolean refreshStatsIfOld) { 1240 if (standbyBucket == NEVER_INDEX) { 1241 Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app."); 1242 return new ExecutionStats(); 1243 } 1244 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1245 if (appStats == null) { 1246 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1247 mExecutionStatsCache.add(userId, packageName, appStats); 1248 } 1249 ExecutionStats stats = appStats[standbyBucket]; 1250 if (stats == null) { 1251 stats = new ExecutionStats(); 1252 appStats[standbyBucket] = stats; 1253 } 1254 if (refreshStatsIfOld) { 1255 final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket]; 1256 final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; 1257 final int jobCountLimit = mMaxBucketJobCounts[standbyBucket]; 1258 final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket]; 1259 Timer timer = mPkgTimers.get(userId, packageName); 1260 if ((timer != null && timer.isActive()) 1261 || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis() 1262 || stats.allowedTimePerPeriodMs != bucketAllowedTimeMs 1263 || stats.windowSizeMs != bucketWindowSizeMs 1264 || stats.jobCountLimit != jobCountLimit 1265 || stats.sessionCountLimit != sessionCountLimit) { 1266 // The stats are no longer valid. 1267 stats.allowedTimePerPeriodMs = bucketAllowedTimeMs; 1268 stats.windowSizeMs = bucketWindowSizeMs; 1269 stats.jobCountLimit = jobCountLimit; 1270 stats.sessionCountLimit = sessionCountLimit; 1271 updateExecutionStatsLocked(userId, packageName, stats); 1272 } 1273 } 1274 1275 return stats; 1276 } 1277 1278 @VisibleForTesting updateExecutionStatsLocked(final int userId, @NonNull final String packageName, @NonNull ExecutionStats stats)1279 void updateExecutionStatsLocked(final int userId, @NonNull final String packageName, 1280 @NonNull ExecutionStats stats) { 1281 stats.executionTimeInWindowMs = 0; 1282 stats.bgJobCountInWindow = 0; 1283 stats.executionTimeInMaxPeriodMs = 0; 1284 stats.bgJobCountInMaxPeriod = 0; 1285 stats.sessionCountInWindow = 0; 1286 if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) { 1287 // App won't be in quota until configuration changes. 1288 stats.inQuotaTimeElapsed = Long.MAX_VALUE; 1289 } else { 1290 stats.inQuotaTimeElapsed = 0; 1291 } 1292 final long allowedTimeIntoQuotaMs = stats.allowedTimePerPeriodMs - mQuotaBufferMs; 1293 1294 Timer timer = mPkgTimers.get(userId, packageName); 1295 final long nowElapsed = sElapsedRealtimeClock.millis(); 1296 stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; 1297 if (timer != null && timer.isActive()) { 1298 // Exclude active sessions from the session count so that new jobs aren't prevented 1299 // from starting due to an app hitting the session limit. 1300 stats.executionTimeInWindowMs = 1301 stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); 1302 stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); 1303 // If the timer is active, the value will be stale at the next method call, so 1304 // invalidate now. 1305 stats.expirationTimeElapsed = nowElapsed; 1306 if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { 1307 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1308 nowElapsed - allowedTimeIntoQuotaMs + stats.windowSizeMs); 1309 } 1310 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 1311 final long inQuotaTime = nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS; 1312 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1313 } 1314 if (stats.bgJobCountInWindow >= stats.jobCountLimit) { 1315 final long inQuotaTime = nowElapsed + stats.windowSizeMs; 1316 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1317 } 1318 } 1319 1320 List<TimedEvent> events = mTimingEvents.get(userId, packageName); 1321 if (events == null || events.size() == 0) { 1322 return; 1323 } 1324 1325 final long startWindowElapsed = nowElapsed - stats.windowSizeMs; 1326 final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; 1327 int sessionCountInWindow = 0; 1328 int numQuotaBumps = 0; 1329 final long quotaBumpWindowStartElapsed = nowElapsed - mQuotaBumpWindowSizeMs; 1330 // The minimum time between the start time and the beginning of the events that were 1331 // looked at --> how much time the stats will be valid for. 1332 long emptyTimeMs = Long.MAX_VALUE; 1333 // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get 1334 // the most recent ones. 1335 final int loopStart = events.size() - 1; 1336 // Process QuotaBumps first to ensure the limits are properly adjusted. 1337 for (int i = loopStart; i >= 0; --i) { 1338 TimedEvent event = events.get(i); 1339 1340 if (event.getEndTimeElapsed() < quotaBumpWindowStartElapsed 1341 || numQuotaBumps >= mQuotaBumpLimit) { 1342 break; 1343 } 1344 1345 if (event instanceof QuotaBump) { 1346 stats.allowedTimePerPeriodMs += mQuotaBumpAdditionalDurationMs; 1347 stats.jobCountLimit += mQuotaBumpAdditionalJobCount; 1348 stats.sessionCountLimit += mQuotaBumpAdditionalSessionCount; 1349 emptyTimeMs = Math.min(emptyTimeMs, 1350 event.getEndTimeElapsed() - quotaBumpWindowStartElapsed); 1351 numQuotaBumps++; 1352 } 1353 } 1354 TimingSession lastSeenTimingSession = null; 1355 for (int i = loopStart; i >= 0; --i) { 1356 TimedEvent event = events.get(i); 1357 1358 if (event instanceof QuotaBump) { 1359 continue; 1360 } 1361 1362 TimingSession session = (TimingSession) event; 1363 1364 // Window management. 1365 if (startWindowElapsed < session.endTimeElapsed) { 1366 final long start; 1367 if (startWindowElapsed < session.startTimeElapsed) { 1368 start = session.startTimeElapsed; 1369 emptyTimeMs = 1370 Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed); 1371 } else { 1372 // The session started before the window but ended within the window. Only 1373 // include the portion that was within the window. 1374 start = startWindowElapsed; 1375 emptyTimeMs = 0; 1376 } 1377 1378 stats.executionTimeInWindowMs += session.endTimeElapsed - start; 1379 stats.bgJobCountInWindow += session.bgJobCount; 1380 if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { 1381 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1382 start + stats.executionTimeInWindowMs - allowedTimeIntoQuotaMs 1383 + stats.windowSizeMs); 1384 } 1385 if (stats.bgJobCountInWindow >= stats.jobCountLimit) { 1386 final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs; 1387 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1388 } 1389 // Coalesce sessions if they are very close to each other in time 1390 boolean shouldCoalesce = lastSeenTimingSession != null 1391 && lastSeenTimingSession.startTimeElapsed - session.endTimeElapsed 1392 <= mTimingSessionCoalescingDurationMs; 1393 if (!shouldCoalesce) { 1394 sessionCountInWindow++; 1395 1396 if (sessionCountInWindow >= stats.sessionCountLimit) { 1397 final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs; 1398 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1399 } 1400 } 1401 } 1402 1403 // Max period check. 1404 if (startMaxElapsed < session.startTimeElapsed) { 1405 stats.executionTimeInMaxPeriodMs += 1406 session.endTimeElapsed - session.startTimeElapsed; 1407 stats.bgJobCountInMaxPeriod += session.bgJobCount; 1408 emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed); 1409 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 1410 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1411 session.startTimeElapsed + stats.executionTimeInMaxPeriodMs 1412 - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); 1413 } 1414 } else if (startMaxElapsed < session.endTimeElapsed) { 1415 // The session started before the window but ended within the window. Only include 1416 // the portion that was within the window. 1417 stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed; 1418 stats.bgJobCountInMaxPeriod += session.bgJobCount; 1419 emptyTimeMs = 0; 1420 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 1421 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1422 startMaxElapsed + stats.executionTimeInMaxPeriodMs 1423 - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); 1424 } 1425 } else { 1426 // This session ended before the window. No point in going any further. 1427 break; 1428 } 1429 1430 lastSeenTimingSession = session; 1431 } 1432 stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; 1433 stats.sessionCountInWindow = sessionCountInWindow; 1434 } 1435 1436 /** Invalidate ExecutionStats for all apps. */ 1437 @VisibleForTesting invalidateAllExecutionStatsLocked()1438 void invalidateAllExecutionStatsLocked() { 1439 final long nowElapsed = sElapsedRealtimeClock.millis(); 1440 mExecutionStatsCache.forEach((appStats) -> { 1441 if (appStats != null) { 1442 for (int i = 0; i < appStats.length; ++i) { 1443 ExecutionStats stats = appStats[i]; 1444 if (stats != null) { 1445 stats.expirationTimeElapsed = nowElapsed; 1446 } 1447 } 1448 } 1449 }); 1450 } 1451 1452 @VisibleForTesting invalidateAllExecutionStatsLocked(final int userId, @NonNull final String packageName)1453 void invalidateAllExecutionStatsLocked(final int userId, 1454 @NonNull final String packageName) { 1455 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1456 if (appStats != null) { 1457 final long nowElapsed = sElapsedRealtimeClock.millis(); 1458 for (int i = 0; i < appStats.length; ++i) { 1459 ExecutionStats stats = appStats[i]; 1460 if (stats != null) { 1461 stats.expirationTimeElapsed = nowElapsed; 1462 } 1463 } 1464 } 1465 } 1466 1467 @VisibleForTesting incrementJobCountLocked(final int userId, @NonNull final String packageName, int count)1468 void incrementJobCountLocked(final int userId, @NonNull final String packageName, int count) { 1469 final long now = sElapsedRealtimeClock.millis(); 1470 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1471 if (appStats == null) { 1472 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1473 mExecutionStatsCache.add(userId, packageName, appStats); 1474 } 1475 for (int i = 0; i < appStats.length; ++i) { 1476 ExecutionStats stats = appStats[i]; 1477 if (stats == null) { 1478 stats = new ExecutionStats(); 1479 appStats[i] = stats; 1480 } 1481 if (stats.jobRateLimitExpirationTimeElapsed <= now) { 1482 stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs; 1483 stats.jobCountInRateLimitingWindow = 0; 1484 } 1485 stats.jobCountInRateLimitingWindow += count; 1486 if (Flags.countQuotaFix()) { 1487 stats.bgJobCountInWindow += count; 1488 } 1489 } 1490 } 1491 incrementTimingSessionCountLocked(final int userId, @NonNull final String packageName)1492 private void incrementTimingSessionCountLocked(final int userId, 1493 @NonNull final String packageName) { 1494 final long now = sElapsedRealtimeClock.millis(); 1495 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1496 if (appStats == null) { 1497 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1498 mExecutionStatsCache.add(userId, packageName, appStats); 1499 } 1500 for (int i = 0; i < appStats.length; ++i) { 1501 ExecutionStats stats = appStats[i]; 1502 if (stats == null) { 1503 stats = new ExecutionStats(); 1504 appStats[i] = stats; 1505 } 1506 if (stats.sessionRateLimitExpirationTimeElapsed <= now) { 1507 stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs; 1508 stats.sessionCountInRateLimitingWindow = 0; 1509 } 1510 stats.sessionCountInRateLimitingWindow++; 1511 } 1512 } 1513 1514 @VisibleForTesting saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session, boolean isExpedited)1515 void saveTimingSession(final int userId, @NonNull final String packageName, 1516 @NonNull final TimingSession session, boolean isExpedited) { 1517 saveTimingSession(userId, packageName, session, isExpedited, 0); 1518 } 1519 saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session, boolean isExpedited, long debitAdjustment)1520 private void saveTimingSession(final int userId, @NonNull final String packageName, 1521 @NonNull final TimingSession session, boolean isExpedited, long debitAdjustment) { 1522 synchronized (mLock) { 1523 final SparseArrayMap<String, List<TimedEvent>> sessionMap = 1524 isExpedited ? mEJTimingSessions : mTimingEvents; 1525 List<TimedEvent> sessions = sessionMap.get(userId, packageName); 1526 if (sessions == null) { 1527 sessions = new ArrayList<>(); 1528 sessionMap.add(userId, packageName, sessions); 1529 } 1530 sessions.add(session); 1531 if (isExpedited) { 1532 final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 1533 quota.transactLocked(session.endTimeElapsed - session.startTimeElapsed 1534 + debitAdjustment); 1535 } else { 1536 // Adding a new session means that the current stats are now incorrect. 1537 invalidateAllExecutionStatsLocked(userId, packageName); 1538 1539 maybeScheduleCleanupAlarmLocked(); 1540 } 1541 } 1542 } 1543 grantRewardForInstantEvent( final int userId, @NonNull final String packageName, final long credit)1544 private void grantRewardForInstantEvent( 1545 final int userId, @NonNull final String packageName, final long credit) { 1546 if (credit == 0) { 1547 return; 1548 } 1549 synchronized (mLock) { 1550 final long nowElapsed = sElapsedRealtimeClock.millis(); 1551 final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 1552 if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)) { 1553 mStateChangedListener.onControllerStateChanged( 1554 maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)); 1555 } 1556 } 1557 } 1558 transactQuotaLocked(final int userId, @NonNull final String packageName, final long nowElapsed, @NonNull ShrinkableDebits debits, final long credit)1559 private boolean transactQuotaLocked(final int userId, @NonNull final String packageName, 1560 final long nowElapsed, @NonNull ShrinkableDebits debits, final long credit) { 1561 final long oldTally = debits.getTallyLocked(); 1562 final long leftover = debits.transactLocked(-credit); 1563 if (DEBUG) { 1564 Slog.d(TAG, "debits overflowed by " + leftover); 1565 } 1566 boolean changed = oldTally != debits.getTallyLocked(); 1567 if (leftover != 0) { 1568 // Only adjust timer if its active. 1569 final Timer ejTimer = mEJPkgTimers.get(userId, packageName); 1570 if (ejTimer != null && ejTimer.isActive()) { 1571 ejTimer.updateDebitAdjustment(nowElapsed, leftover); 1572 changed = true; 1573 } 1574 } 1575 return changed; 1576 } 1577 1578 private final class EarliestEndTimeFunctor implements Consumer<List<TimedEvent>> { 1579 public long earliestEndElapsed = Long.MAX_VALUE; 1580 1581 @Override accept(List<TimedEvent> events)1582 public void accept(List<TimedEvent> events) { 1583 if (events != null && events.size() > 0) { 1584 earliestEndElapsed = 1585 Math.min(earliestEndElapsed, events.get(0).getEndTimeElapsed()); 1586 } 1587 } 1588 reset()1589 void reset() { 1590 earliestEndElapsed = Long.MAX_VALUE; 1591 } 1592 } 1593 1594 private final EarliestEndTimeFunctor mEarliestEndTimeFunctor = new EarliestEndTimeFunctor(); 1595 1596 /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */ 1597 @VisibleForTesting maybeScheduleCleanupAlarmLocked()1598 void maybeScheduleCleanupAlarmLocked() { 1599 final long nowElapsed = sElapsedRealtimeClock.millis(); 1600 if (mNextCleanupTimeElapsed > nowElapsed) { 1601 // There's already an alarm scheduled. Just stick with that one. There's no way we'll 1602 // end up scheduling an earlier alarm. 1603 if (DEBUG) { 1604 Slog.v(TAG, "Not scheduling cleanup since there's already one at " 1605 + mNextCleanupTimeElapsed 1606 + " (in " + (mNextCleanupTimeElapsed - nowElapsed) + "ms)"); 1607 } 1608 return; 1609 } 1610 mEarliestEndTimeFunctor.reset(); 1611 mTimingEvents.forEach(mEarliestEndTimeFunctor); 1612 mEJTimingSessions.forEach(mEarliestEndTimeFunctor); 1613 final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed; 1614 if (earliestEndElapsed == Long.MAX_VALUE) { 1615 // Couldn't find a good time to clean up. Maybe this was called after we deleted all 1616 // timing sessions. 1617 if (DEBUG) { 1618 Slog.d(TAG, "Didn't find a time to schedule cleanup"); 1619 } 1620 return; 1621 } 1622 // Need to keep sessions for all apps up to the max period, regardless of their current 1623 // standby bucket. 1624 long nextCleanupElapsed = earliestEndElapsed + MAX_PERIOD_MS; 1625 if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) { 1626 // No need to clean up too often. Delay the alarm if the next cleanup would be too soon 1627 // after it. 1628 nextCleanupElapsed = mNextCleanupTimeElapsed + 10 * MINUTE_IN_MILLIS; 1629 } 1630 mNextCleanupTimeElapsed = nextCleanupElapsed; 1631 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP, 1632 mSessionCleanupAlarmListener, mHandler); 1633 if (DEBUG) { 1634 Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed); 1635 } 1636 } 1637 1638 private class TimerChargingUpdateFunctor implements Consumer<Timer> { 1639 private long mNowElapsed; 1640 private boolean mIsCharging; 1641 setStatus(long nowElapsed, boolean isCharging)1642 private void setStatus(long nowElapsed, boolean isCharging) { 1643 mNowElapsed = nowElapsed; 1644 mIsCharging = isCharging; 1645 } 1646 1647 @Override accept(Timer timer)1648 public void accept(Timer timer) { 1649 if (JobSchedulerService.standbyBucketForPackage(timer.mPkg.packageName, 1650 timer.mPkg.userId, mNowElapsed) != RESTRICTED_INDEX) { 1651 // Restricted jobs need additional constraints even when charging, so don't 1652 // immediately say that quota is free. 1653 timer.onStateChangedLocked(mNowElapsed, mIsCharging); 1654 } 1655 } 1656 } 1657 1658 private final TimerChargingUpdateFunctor 1659 mTimerChargingUpdateFunctor = new TimerChargingUpdateFunctor(); 1660 handleNewChargingStateLocked()1661 private void handleNewChargingStateLocked() { 1662 mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(), 1663 mService.isBatteryCharging()); 1664 if (DEBUG) { 1665 Slog.d(TAG, "handleNewChargingStateLocked: " + mService.isBatteryCharging()); 1666 } 1667 // Deal with Timers first. 1668 mEJPkgTimers.forEach(mTimerChargingUpdateFunctor); 1669 mPkgTimers.forEach(mTimerChargingUpdateFunctor); 1670 // Now update jobs out of band so broadcast processing can proceed. 1671 AppSchedulingModuleThread.getHandler().post(() -> { 1672 synchronized (mLock) { 1673 maybeUpdateAllConstraintsLocked(); 1674 } 1675 }); 1676 } 1677 maybeUpdateAllConstraintsLocked()1678 private void maybeUpdateAllConstraintsLocked() { 1679 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1680 final long nowElapsed = sElapsedRealtimeClock.millis(); 1681 for (int u = 0; u < mTrackedJobs.numMaps(); ++u) { 1682 final int userId = mTrackedJobs.keyAt(u); 1683 for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) { 1684 final String packageName = mTrackedJobs.keyAt(u, p); 1685 changedJobs.addAll( 1686 maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)); 1687 } 1688 } 1689 if (changedJobs.size() > 0) { 1690 mStateChangedListener.onControllerStateChanged(changedJobs); 1691 } 1692 } 1693 1694 /** 1695 * Update the CONSTRAINT_WITHIN_QUOTA bit for all of the Jobs for a given package. 1696 * 1697 * @return the set of jobs whose status changed 1698 */ 1699 @NonNull maybeUpdateConstraintForPkgLocked(final long nowElapsed, final int userId, @NonNull final String packageName)1700 private ArraySet<JobStatus> maybeUpdateConstraintForPkgLocked(final long nowElapsed, 1701 final int userId, @NonNull final String packageName) { 1702 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 1703 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1704 if (jobs == null || jobs.size() == 0) { 1705 return changedJobs; 1706 } 1707 1708 // Quota is the same for all jobs within a package. 1709 final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket(); 1710 final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket); 1711 boolean outOfEJQuota = false; 1712 for (int i = jobs.size() - 1; i >= 0; --i) { 1713 final JobStatus js = jobs.valueAt(i); 1714 final boolean isWithinEJQuota = 1715 js.isRequestedExpeditedJob() && isWithinEJQuotaLocked(js); 1716 if (isTopStartedJobLocked(js)) { 1717 // Job was started while the app was in the TOP state so we should allow it to 1718 // finish. 1719 if (js.setQuotaConstraintSatisfied(nowElapsed, true)) { 1720 changedJobs.add(js); 1721 } 1722 } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX 1723 && realStandbyBucket == js.getEffectiveStandbyBucket() 1724 && !(Flags.countQuotaFix() && mService.isCurrentlyRunningLocked(js))) { 1725 // An app in the ACTIVE bucket may be out of quota while the job could be in quota 1726 // for some reason. Therefore, avoid setting the real value here and check each job 1727 // individually. Running job need to determine its own quota status as well. 1728 if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) { 1729 changedJobs.add(js); 1730 } 1731 } else { 1732 // This job is somehow exempted. Need to determine its own quota status. 1733 if (setConstraintSatisfied(js, nowElapsed, 1734 isWithinQuotaLocked(js), isWithinEJQuota)) { 1735 changedJobs.add(js); 1736 } 1737 } 1738 1739 if (js.isRequestedExpeditedJob()) { 1740 if (setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota)) { 1741 changedJobs.add(js); 1742 } 1743 outOfEJQuota |= !isWithinEJQuota; 1744 } 1745 } 1746 if (!realInQuota || outOfEJQuota) { 1747 // Don't want to use the effective standby bucket here since that bump the bucket to 1748 // ACTIVE for one of the jobs, which doesn't help with other jobs that aren't 1749 // exempted. 1750 maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket); 1751 } else { 1752 mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); 1753 } 1754 return changedJobs; 1755 } 1756 1757 private class UidConstraintUpdater implements Consumer<JobStatus> { 1758 private final SparseArrayMap<String, Integer> mToScheduleStartAlarms = 1759 new SparseArrayMap<>(); 1760 public final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1761 long mUpdateTimeElapsed = 0; 1762 prepare()1763 void prepare() { 1764 mUpdateTimeElapsed = sElapsedRealtimeClock.millis(); 1765 changedJobs.clear(); 1766 } 1767 1768 @Override accept(JobStatus jobStatus)1769 public void accept(JobStatus jobStatus) { 1770 final boolean isWithinEJQuota; 1771 if (jobStatus.isRequestedExpeditedJob()) { 1772 isWithinEJQuota = isWithinEJQuotaLocked(jobStatus); 1773 } else { 1774 isWithinEJQuota = false; 1775 } 1776 if (setConstraintSatisfied(jobStatus, mUpdateTimeElapsed, 1777 isWithinQuotaLocked(jobStatus), isWithinEJQuota)) { 1778 changedJobs.add(jobStatus); 1779 } 1780 if (setExpeditedQuotaApproved(jobStatus, mUpdateTimeElapsed, isWithinEJQuota)) { 1781 changedJobs.add(jobStatus); 1782 } 1783 1784 final int userId = jobStatus.getSourceUserId(); 1785 final String packageName = jobStatus.getSourcePackageName(); 1786 final int realStandbyBucket = jobStatus.getStandbyBucket(); 1787 if (isWithinEJQuota 1788 && isWithinQuotaLocked(userId, packageName, realStandbyBucket)) { 1789 // TODO(141645789): we probably shouldn't cancel the alarm until we've verified 1790 // that all jobs for the userId-package are within quota. 1791 mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); 1792 } else { 1793 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket); 1794 } 1795 } 1796 postProcess()1797 void postProcess() { 1798 for (int u = 0; u < mToScheduleStartAlarms.numMaps(); ++u) { 1799 final int userId = mToScheduleStartAlarms.keyAt(u); 1800 for (int p = 0; p < mToScheduleStartAlarms.numElementsForKey(userId); ++p) { 1801 final String packageName = mToScheduleStartAlarms.keyAt(u, p); 1802 final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName); 1803 maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket); 1804 } 1805 } 1806 } 1807 reset()1808 void reset() { 1809 mToScheduleStartAlarms.clear(); 1810 } 1811 } 1812 1813 private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater(); 1814 1815 @GuardedBy("mLock") 1816 @NonNull maybeUpdateConstraintForUidLocked(final int uid)1817 private ArraySet<JobStatus> maybeUpdateConstraintForUidLocked(final int uid) { 1818 mUpdateUidConstraints.prepare(); 1819 mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints); 1820 1821 mUpdateUidConstraints.postProcess(); 1822 mUpdateUidConstraints.reset(); 1823 return mUpdateUidConstraints.changedJobs; 1824 } 1825 1826 /** 1827 * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run 1828 * again. This should only be called if the package is already out of quota. 1829 */ 1830 @VisibleForTesting maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName, final int standbyBucket)1831 void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName, 1832 final int standbyBucket) { 1833 if (standbyBucket == NEVER_INDEX) { 1834 return; 1835 } 1836 1837 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 1838 if (jobs == null || jobs.size() == 0) { 1839 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " 1840 + packageToString(userId, packageName) + " that has no jobs"); 1841 mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); 1842 return; 1843 } 1844 1845 ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 1846 final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats); 1847 final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats); 1848 final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName); 1849 1850 final boolean inRegularQuota = 1851 stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs[standbyBucket] 1852 && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs 1853 && isUnderJobCountQuota 1854 && isUnderTimingSessionCountQuota; 1855 if (inRegularQuota && remainingEJQuota > 0) { 1856 // Already in quota. Why was this method called? 1857 if (DEBUG) { 1858 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " 1859 + packageToString(userId, packageName) 1860 + " even though it already has " 1861 + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) 1862 + "ms in its quota."); 1863 } 1864 mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); 1865 mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); 1866 return; 1867 } 1868 1869 long inRegularQuotaTimeElapsed = Long.MAX_VALUE; 1870 long inEJQuotaTimeElapsed = Long.MAX_VALUE; 1871 if (!inRegularQuota) { 1872 // The time this app will have quota again. 1873 long inQuotaTimeElapsed = stats.inQuotaTimeElapsed; 1874 if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) { 1875 // App hit the rate limit. 1876 inQuotaTimeElapsed = 1877 Math.max(inQuotaTimeElapsed, stats.jobRateLimitExpirationTimeElapsed); 1878 } 1879 if (!isUnderTimingSessionCountQuota 1880 && stats.sessionCountInWindow < stats.sessionCountLimit) { 1881 // App hit the rate limit. 1882 inQuotaTimeElapsed = 1883 Math.max(inQuotaTimeElapsed, stats.sessionRateLimitExpirationTimeElapsed); 1884 } 1885 inRegularQuotaTimeElapsed = inQuotaTimeElapsed; 1886 } 1887 if (remainingEJQuota <= 0) { 1888 final long limitMs = 1889 getEJLimitMsLocked(userId, packageName, standbyBucket) - mQuotaBufferMs; 1890 long sumMs = 0; 1891 final Timer ejTimer = mEJPkgTimers.get(userId, packageName); 1892 if (ejTimer != null && ejTimer.isActive()) { 1893 final long nowElapsed = sElapsedRealtimeClock.millis(); 1894 sumMs += ejTimer.getCurrentDuration(nowElapsed); 1895 if (sumMs >= limitMs) { 1896 inEJQuotaTimeElapsed = (nowElapsed - limitMs) + mEJLimitWindowSizeMs; 1897 } 1898 } 1899 List<TimedEvent> timingSessions = mEJTimingSessions.get(userId, packageName); 1900 if (timingSessions != null) { 1901 for (int i = timingSessions.size() - 1; i >= 0; --i) { 1902 TimingSession ts = (TimingSession) timingSessions.get(i); 1903 final long durationMs = ts.endTimeElapsed - ts.startTimeElapsed; 1904 sumMs += durationMs; 1905 if (sumMs >= limitMs) { 1906 inEJQuotaTimeElapsed = 1907 ts.startTimeElapsed + (sumMs - limitMs) + mEJLimitWindowSizeMs; 1908 break; 1909 } 1910 } 1911 } else if ((ejTimer == null || !ejTimer.isActive()) && inRegularQuota) { 1912 // In some strange cases, an app may end be in the NEVER bucket but could have run 1913 // some regular jobs. This results in no EJ timing sessions and QC having a bad 1914 // time. 1915 Slog.wtf(TAG, packageToString(userId, packageName) 1916 + " has 0 EJ quota without running anything"); 1917 return; 1918 } 1919 } 1920 long inQuotaTimeElapsed = Math.min(inRegularQuotaTimeElapsed, inEJQuotaTimeElapsed); 1921 1922 if (inQuotaTimeElapsed <= sElapsedRealtimeClock.millis()) { 1923 final long nowElapsed = sElapsedRealtimeClock.millis(); 1924 Slog.wtf(TAG, 1925 "In quota time is " + (nowElapsed - inQuotaTimeElapsed) + "ms old. Now=" 1926 + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats); 1927 inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS; 1928 } 1929 mInQuotaAlarmQueue.addAlarm(UserPackage.of(userId, packageName), inQuotaTimeElapsed); 1930 } 1931 1932 private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed, 1933 boolean isWithinQuota, boolean isWithinEjQuota) { 1934 final boolean isSatisfied; 1935 if (jobStatus.startedAsExpeditedJob) { 1936 // If the job started as an EJ, then we should only consider EJ quota for the constraint 1937 // satisfaction. 1938 isSatisfied = isWithinEjQuota; 1939 } else if (mService.isCurrentlyRunningLocked(jobStatus)) { 1940 // Job is running but didn't start as an EJ, so only the regular quota should be 1941 // considered. 1942 isSatisfied = isWithinQuota; 1943 } else { 1944 isSatisfied = isWithinEjQuota || isWithinQuota; 1945 } 1946 if (!isSatisfied && jobStatus.getWhenStandbyDeferred() == 0) { 1947 // Mark that the job is being deferred due to buckets. 1948 jobStatus.setWhenStandbyDeferred(nowElapsed); 1949 } 1950 return jobStatus.setQuotaConstraintSatisfied(nowElapsed, isSatisfied); 1951 } 1952 1953 /** 1954 * If the satisfaction changes, this will tell connectivity & background jobs controller to 1955 * also re-evaluate their state. 1956 */ 1957 private boolean setExpeditedQuotaApproved(@NonNull JobStatus jobStatus, long nowElapsed, 1958 boolean isWithinQuota) { 1959 if (jobStatus.setExpeditedJobQuotaApproved(nowElapsed, isWithinQuota)) { 1960 mBackgroundJobsController.evaluateStateLocked(jobStatus); 1961 mConnectivityController.evaluateStateLocked(jobStatus); 1962 if (isWithinQuota && jobStatus.isReady()) { 1963 mStateChangedListener.onRunJobNow(jobStatus); 1964 } 1965 return true; 1966 } 1967 return false; 1968 } 1969 1970 @VisibleForTesting 1971 interface TimedEvent { 1972 long getEndTimeElapsed(); 1973 1974 void dump(IndentingPrintWriter pw); 1975 } 1976 1977 @VisibleForTesting 1978 static final class TimingSession implements TimedEvent { 1979 // Start timestamp in elapsed realtime timebase. 1980 public final long startTimeElapsed; 1981 // End timestamp in elapsed realtime timebase. 1982 public final long endTimeElapsed; 1983 // How many background jobs ran during this session. 1984 public final int bgJobCount; 1985 1986 private final int mHashCode; 1987 1988 TimingSession(long startElapsed, long endElapsed, int bgJobCount) { 1989 this.startTimeElapsed = startElapsed; 1990 this.endTimeElapsed = endElapsed; 1991 this.bgJobCount = bgJobCount; 1992 1993 int hashCode = 0; 1994 hashCode = 31 * hashCode + hashLong(startTimeElapsed); 1995 hashCode = 31 * hashCode + hashLong(endTimeElapsed); 1996 hashCode = 31 * hashCode + bgJobCount; 1997 mHashCode = hashCode; 1998 } 1999 2000 @Override 2001 public long getEndTimeElapsed() { 2002 return endTimeElapsed; 2003 } 2004 2005 @Override 2006 public String toString() { 2007 return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount 2008 + "}"; 2009 } 2010 2011 @Override 2012 public boolean equals(Object obj) { 2013 if (obj instanceof TimingSession) { 2014 TimingSession other = (TimingSession) obj; 2015 return startTimeElapsed == other.startTimeElapsed 2016 && endTimeElapsed == other.endTimeElapsed 2017 && bgJobCount == other.bgJobCount; 2018 } else { 2019 return false; 2020 } 2021 } 2022 2023 @Override 2024 public int hashCode() { 2025 return mHashCode; 2026 } 2027 2028 @Override 2029 public void dump(IndentingPrintWriter pw) { 2030 pw.print(startTimeElapsed); 2031 pw.print(" -> "); 2032 pw.print(endTimeElapsed); 2033 pw.print(" ("); 2034 pw.print(endTimeElapsed - startTimeElapsed); 2035 pw.print("), "); 2036 pw.print(bgJobCount); 2037 pw.print(" bg jobs."); 2038 pw.println(); 2039 } 2040 2041 public void dump(@NonNull ProtoOutputStream proto, long fieldId) { 2042 final long token = proto.start(fieldId); 2043 2044 proto.write(StateControllerProto.QuotaController.TimingSession.START_TIME_ELAPSED, 2045 startTimeElapsed); 2046 proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED, 2047 endTimeElapsed); 2048 proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT, 2049 bgJobCount); 2050 2051 proto.end(token); 2052 } 2053 } 2054 2055 @VisibleForTesting 2056 static final class QuotaBump implements TimedEvent { 2057 // Event timestamp in elapsed realtime timebase. 2058 public final long eventTimeElapsed; 2059 2060 QuotaBump(long eventElapsed) { 2061 this.eventTimeElapsed = eventElapsed; 2062 } 2063 2064 @Override 2065 public long getEndTimeElapsed() { 2066 return eventTimeElapsed; 2067 } 2068 2069 @Override 2070 public void dump(IndentingPrintWriter pw) { 2071 pw.print("Quota bump @ "); 2072 pw.print(eventTimeElapsed); 2073 pw.println(); 2074 } 2075 } 2076 2077 @VisibleForTesting 2078 static final class ShrinkableDebits { 2079 /** The amount of quota remaining. Can be negative if limit changes. */ 2080 private long mDebitTally; 2081 private int mStandbyBucket; 2082 2083 ShrinkableDebits(int standbyBucket) { 2084 mDebitTally = 0; 2085 mStandbyBucket = standbyBucket; 2086 } 2087 2088 long getTallyLocked() { 2089 return mDebitTally; 2090 } 2091 2092 /** 2093 * Negative if the tally should decrease (therefore increasing available quota); 2094 * or positive if the tally should increase (therefore decreasing available quota). 2095 */ 2096 long transactLocked(final long amount) { 2097 final long leftover = amount < 0 && Math.abs(amount) > mDebitTally 2098 ? mDebitTally + amount : 0; 2099 mDebitTally = Math.max(0, mDebitTally + amount); 2100 return leftover; 2101 } 2102 2103 void setStandbyBucketLocked(int standbyBucket) { 2104 mStandbyBucket = standbyBucket; 2105 } 2106 2107 int getStandbyBucketLocked() { 2108 return mStandbyBucket; 2109 } 2110 2111 @Override 2112 public String toString() { 2113 return "ShrinkableDebits { debit tally: " 2114 + mDebitTally + ", bucket: " + mStandbyBucket 2115 + " }"; 2116 } 2117 2118 void dumpLocked(IndentingPrintWriter pw) { 2119 pw.println(toString()); 2120 } 2121 } 2122 2123 private final class Timer { 2124 private final UserPackage mPkg; 2125 private final int mUid; 2126 private final boolean mRegularJobTimer; 2127 2128 // List of jobs currently running for this app that started when the app wasn't in the 2129 // foreground. 2130 private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); 2131 private long mStartTimeElapsed; 2132 private int mBgJobCount; 2133 private long mDebitAdjustment; 2134 2135 Timer(int uid, int userId, String packageName, boolean regularJobTimer) { 2136 mPkg = UserPackage.of(userId, packageName); 2137 mUid = uid; 2138 mRegularJobTimer = regularJobTimer; 2139 } 2140 2141 void startTrackingJobLocked(@NonNull JobStatus jobStatus) { 2142 if (jobStatus.shouldTreatAsUserInitiatedJob()) { 2143 if (DEBUG) { 2144 Slog.v(TAG, "Timer ignoring " + jobStatus.toShortString() 2145 + " because it's user-initiated"); 2146 } 2147 return; 2148 } 2149 if (isTopStartedJobLocked(jobStatus)) { 2150 // We intentionally don't pay attention to fg state changes after a TOP job has 2151 // started. 2152 if (DEBUG) { 2153 Slog.v(TAG, 2154 "Timer ignoring " + jobStatus.toShortString() + " because isTop"); 2155 } 2156 return; 2157 } 2158 if (DEBUG) { 2159 Slog.v(TAG, "Starting to track " + jobStatus.toShortString()); 2160 } 2161 // Always maintain list of running jobs, even when quota is free. 2162 if (mRunningBgJobs.add(jobStatus) && shouldTrackLocked()) { 2163 mBgJobCount++; 2164 if (mRegularJobTimer) { 2165 incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1); 2166 if (Flags.countQuotaFix()) { 2167 final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId, 2168 mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false); 2169 if (!isUnderJobCountQuotaLocked(stats)) { 2170 mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget(); 2171 } 2172 } 2173 } 2174 if (mRunningBgJobs.size() == 1) { 2175 // Started tracking the first job. 2176 mStartTimeElapsed = sElapsedRealtimeClock.millis(); 2177 mDebitAdjustment = 0; 2178 if (mRegularJobTimer) { 2179 // Starting the timer means that all cached execution stats are now 2180 // incorrect. 2181 invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); 2182 } 2183 scheduleCutoff(); 2184 } 2185 } else { 2186 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 2187 Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, 2188 JobSchedulerService.TRACE_TRACK_NAME, 2189 "QC/- " + mPkg); 2190 } 2191 } 2192 } 2193 2194 void stopTrackingJob(@NonNull JobStatus jobStatus) { 2195 if (DEBUG) { 2196 Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString()); 2197 } 2198 synchronized (mLock) { 2199 if (mRunningBgJobs.size() == 0) { 2200 // maybeStopTrackingJobLocked can be called when an app cancels a job, so a 2201 // timer may not be running when it's asked to stop tracking a job. 2202 if (DEBUG) { 2203 Slog.d(TAG, "Timer isn't tracking any jobs but still told to stop"); 2204 } 2205 return; 2206 } 2207 final long nowElapsed = sElapsedRealtimeClock.millis(); 2208 final int standbyBucket = JobSchedulerService.standbyBucketForPackage( 2209 mPkg.packageName, mPkg.userId, nowElapsed); 2210 if (mRunningBgJobs.remove(jobStatus) && mRunningBgJobs.size() == 0 2211 && !isQuotaFreeLocked(standbyBucket)) { 2212 emitSessionLocked(nowElapsed); 2213 cancelCutoff(); 2214 } 2215 } 2216 } 2217 2218 void updateDebitAdjustment(long nowElapsed, long debit) { 2219 // Make sure we don't have a credit larger than the expected session. 2220 mDebitAdjustment = Math.max(mDebitAdjustment + debit, mStartTimeElapsed - nowElapsed); 2221 } 2222 2223 /** 2224 * Stops tracking all jobs and cancels any pending alarms. This should only be called if 2225 * the Timer is not going to be used anymore. 2226 */ 2227 void dropEverythingLocked() { 2228 mRunningBgJobs.clear(); 2229 cancelCutoff(); 2230 } 2231 2232 @GuardedBy("mLock") 2233 private void emitSessionLocked(long nowElapsed) { 2234 if (mBgJobCount <= 0) { 2235 // Nothing to emit. 2236 return; 2237 } 2238 TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount); 2239 saveTimingSession(mPkg.userId, mPkg.packageName, ts, !mRegularJobTimer, 2240 mDebitAdjustment); 2241 mBgJobCount = 0; 2242 // Don't reset the tracked jobs list as we need to keep tracking the current number 2243 // of jobs. 2244 // However, cancel the currently scheduled cutoff since it's not currently useful. 2245 cancelCutoff(); 2246 if (mRegularJobTimer) { 2247 incrementTimingSessionCountLocked(mPkg.userId, mPkg.packageName); 2248 } 2249 } 2250 2251 /** 2252 * Returns true if the Timer is actively tracking, as opposed to passively ref counting 2253 * during charging. 2254 */ 2255 public boolean isActive() { 2256 synchronized (mLock) { 2257 return mBgJobCount > 0; 2258 } 2259 } 2260 2261 boolean isRunning(JobStatus jobStatus) { 2262 return mRunningBgJobs.contains(jobStatus); 2263 } 2264 2265 long getCurrentDuration(long nowElapsed) { 2266 synchronized (mLock) { 2267 return !isActive() ? 0 : nowElapsed - mStartTimeElapsed + mDebitAdjustment; 2268 } 2269 } 2270 2271 int getBgJobCount() { 2272 synchronized (mLock) { 2273 return mBgJobCount; 2274 } 2275 } 2276 2277 @GuardedBy("mLock") 2278 private boolean shouldTrackLocked() { 2279 final long nowElapsed = sElapsedRealtimeClock.millis(); 2280 final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName, 2281 mPkg.userId, nowElapsed); 2282 final boolean hasTempAllowlistExemption = !mRegularJobTimer 2283 && hasTempAllowlistExemptionLocked(mUid, standbyBucket, nowElapsed); 2284 final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid); 2285 final boolean hasTopAppExemption = !mRegularJobTimer 2286 && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed); 2287 if (DEBUG) { 2288 Slog.d(TAG, "quotaFree=" + isQuotaFreeLocked(standbyBucket) 2289 + " isFG=" + mForegroundUids.get(mUid) 2290 + " tempEx=" + hasTempAllowlistExemption 2291 + " topEx=" + hasTopAppExemption); 2292 } 2293 return !isQuotaFreeLocked(standbyBucket) 2294 && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption 2295 && !hasTopAppExemption; 2296 } 2297 2298 void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) { 2299 if (isQuotaFree) { 2300 emitSessionLocked(nowElapsed); 2301 } else if (!isActive() && shouldTrackLocked()) { 2302 // Start timing from unplug. 2303 if (mRunningBgJobs.size() > 0) { 2304 mStartTimeElapsed = nowElapsed; 2305 mDebitAdjustment = 0; 2306 // NOTE: this does have the unfortunate consequence that if the device is 2307 // repeatedly plugged in and unplugged, or an app changes foreground state 2308 // very frequently, the job count for a package may be artificially high. 2309 mBgJobCount = mRunningBgJobs.size(); 2310 if (mRegularJobTimer) { 2311 incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount); 2312 // Starting the timer means that all cached execution stats are now 2313 // incorrect. 2314 invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); 2315 } 2316 // Schedule cutoff since we're now actively tracking for quotas again. 2317 scheduleCutoff(); 2318 } 2319 } 2320 } 2321 2322 void rescheduleCutoff() { 2323 cancelCutoff(); 2324 scheduleCutoff(); 2325 } 2326 2327 private void scheduleCutoff() { 2328 // Each package can only be in one standby bucket, so we only need to have one 2329 // message per timer. We only need to reschedule when restarting timer or when 2330 // standby bucket changes. 2331 synchronized (mLock) { 2332 if (!isActive()) { 2333 return; 2334 } 2335 Message msg = mHandler.obtainMessage( 2336 mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, 2337 mPkg); 2338 final long timeRemainingMs = mRegularJobTimer 2339 ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName) 2340 : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName); 2341 if (DEBUG) { 2342 Slog.i(TAG, 2343 (mRegularJobTimer ? "Regular job" : "EJ") + " for " + mPkg + " has " 2344 + timeRemainingMs + "ms left."); 2345 } 2346 // If the job was running the entire time, then the system would be up, so it's 2347 // fine to use uptime millis for these messages. 2348 mHandler.sendMessageDelayed(msg, timeRemainingMs); 2349 } 2350 } 2351 2352 private void cancelCutoff() { 2353 mHandler.removeMessages( 2354 mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg); 2355 } 2356 2357 public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { 2358 pw.print("Timer<"); 2359 pw.print(mRegularJobTimer ? "REG" : "EJ"); 2360 pw.print(">{"); 2361 pw.print(mPkg); 2362 pw.print("} "); 2363 if (isActive()) { 2364 pw.print("started at "); 2365 pw.print(mStartTimeElapsed); 2366 pw.print(" ("); 2367 pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed); 2368 pw.print("ms ago)"); 2369 } else { 2370 pw.print("NOT active"); 2371 } 2372 pw.print(", "); 2373 pw.print(mBgJobCount); 2374 pw.print(" running bg jobs"); 2375 if (!mRegularJobTimer) { 2376 pw.print(" (debit adj="); 2377 pw.print(mDebitAdjustment); 2378 pw.print(")"); 2379 } 2380 pw.println(); 2381 pw.increaseIndent(); 2382 for (int i = 0; i < mRunningBgJobs.size(); i++) { 2383 JobStatus js = mRunningBgJobs.valueAt(i); 2384 if (predicate.test(js)) { 2385 pw.println(js.toShortString()); 2386 } 2387 } 2388 pw.decreaseIndent(); 2389 } 2390 2391 public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) { 2392 final long token = proto.start(fieldId); 2393 2394 proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive()); 2395 proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED, 2396 mStartTimeElapsed); 2397 proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount); 2398 for (int i = 0; i < mRunningBgJobs.size(); i++) { 2399 JobStatus js = mRunningBgJobs.valueAt(i); 2400 if (predicate.test(js)) { 2401 js.writeToShortProto(proto, 2402 StateControllerProto.QuotaController.Timer.RUNNING_JOBS); 2403 } 2404 } 2405 2406 proto.end(token); 2407 } 2408 } 2409 2410 private final class TopAppTimer { 2411 private final UserPackage mPkg; 2412 2413 // List of jobs currently running for this app that started when the app wasn't in the 2414 // foreground. 2415 private final SparseArray<UsageEvents.Event> mActivities = new SparseArray<>(); 2416 private long mStartTimeElapsed; 2417 2418 TopAppTimer(int userId, String packageName) { 2419 mPkg = UserPackage.of(userId, packageName); 2420 } 2421 2422 private int calculateTimeChunks(final long nowElapsed) { 2423 final long totalTopTimeMs = nowElapsed - mStartTimeElapsed; 2424 int numTimeChunks = (int) (totalTopTimeMs / mEJTopAppTimeChunkSizeMs); 2425 final long remainderMs = totalTopTimeMs % mEJTopAppTimeChunkSizeMs; 2426 if (remainderMs >= SECOND_IN_MILLIS) { 2427 // "Round up" 2428 numTimeChunks++; 2429 } 2430 return numTimeChunks; 2431 } 2432 2433 long getPendingReward(final long nowElapsed) { 2434 return mEJRewardTopAppMs * calculateTimeChunks(nowElapsed); 2435 } 2436 2437 void processEventLocked(@NonNull UsageEvents.Event event) { 2438 final long nowElapsed = sElapsedRealtimeClock.millis(); 2439 switch (event.getEventType()) { 2440 case UsageEvents.Event.ACTIVITY_RESUMED: 2441 if (mActivities.size() == 0) { 2442 mStartTimeElapsed = nowElapsed; 2443 } 2444 mActivities.put(event.mInstanceId, event); 2445 break; 2446 case UsageEvents.Event.ACTIVITY_PAUSED: 2447 case UsageEvents.Event.ACTIVITY_STOPPED: 2448 case UsageEvents.Event.ACTIVITY_DESTROYED: 2449 final UsageEvents.Event existingEvent = 2450 mActivities.removeReturnOld(event.mInstanceId); 2451 if (existingEvent != null && mActivities.size() == 0) { 2452 final long pendingReward = getPendingReward(nowElapsed); 2453 if (DEBUG) { 2454 Slog.d(TAG, "Crediting " + mPkg + " " + pendingReward + "ms" 2455 + " for " + calculateTimeChunks(nowElapsed) + " time chunks"); 2456 } 2457 final ShrinkableDebits debits = 2458 getEJDebitsLocked(mPkg.userId, mPkg.packageName); 2459 if (transactQuotaLocked(mPkg.userId, mPkg.packageName, 2460 nowElapsed, debits, pendingReward)) { 2461 mStateChangedListener.onControllerStateChanged( 2462 maybeUpdateConstraintForPkgLocked(nowElapsed, 2463 mPkg.userId, mPkg.packageName)); 2464 } 2465 } 2466 break; 2467 } 2468 } 2469 2470 boolean isActive() { 2471 synchronized (mLock) { 2472 return mActivities.size() > 0; 2473 } 2474 } 2475 2476 public void dump(IndentingPrintWriter pw) { 2477 pw.print("TopAppTimer{"); 2478 pw.print(mPkg); 2479 pw.print("} "); 2480 if (isActive()) { 2481 pw.print("started at "); 2482 pw.print(mStartTimeElapsed); 2483 pw.print(" ("); 2484 pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed); 2485 pw.print("ms ago)"); 2486 } else { 2487 pw.print("NOT active"); 2488 } 2489 pw.println(); 2490 pw.increaseIndent(); 2491 for (int i = 0; i < mActivities.size(); i++) { 2492 UsageEvents.Event event = mActivities.valueAt(i); 2493 pw.println(event.getClassName()); 2494 } 2495 pw.decreaseIndent(); 2496 } 2497 2498 public void dump(ProtoOutputStream proto, long fieldId) { 2499 final long token = proto.start(fieldId); 2500 2501 proto.write(StateControllerProto.QuotaController.TopAppTimer.IS_ACTIVE, isActive()); 2502 proto.write(StateControllerProto.QuotaController.TopAppTimer.START_TIME_ELAPSED, 2503 mStartTimeElapsed); 2504 proto.write(StateControllerProto.QuotaController.TopAppTimer.ACTIVITY_COUNT, 2505 mActivities.size()); 2506 // TODO: maybe dump activities/events 2507 2508 proto.end(token); 2509 } 2510 } 2511 2512 /** 2513 * Tracking of app assignments to standby buckets 2514 */ 2515 final class StandbyTracker extends AppIdleStateChangeListener { 2516 2517 @Override 2518 public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, 2519 boolean idle, int bucket, int reason) { 2520 // Update job bookkeeping out of band. 2521 AppSchedulingModuleThread.getHandler().post(() -> { 2522 final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket); 2523 updateStandbyBucket(userId, packageName, bucketIndex); 2524 }); 2525 } 2526 2527 @Override 2528 public void triggerTemporaryQuotaBump(String packageName, @UserIdInt int userId) { 2529 synchronized (mLock) { 2530 List<TimedEvent> events = mTimingEvents.get(userId, packageName); 2531 if (events == null || events.size() == 0) { 2532 // If the app hasn't run any jobs, there's no point giving it a quota bump. 2533 return; 2534 } 2535 events.add(new QuotaBump(sElapsedRealtimeClock.millis())); 2536 invalidateAllExecutionStatsLocked(userId, packageName); 2537 } 2538 // Update jobs out of band. 2539 mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); 2540 } 2541 } 2542 2543 @VisibleForTesting 2544 void updateStandbyBucket( 2545 final int userId, final @NonNull String packageName, final int bucketIndex) { 2546 if (DEBUG) { 2547 Slog.i(TAG, "Moving pkg " + packageToString(userId, packageName) 2548 + " to bucketIndex " + bucketIndex); 2549 } 2550 List<JobStatus> restrictedChanges = new ArrayList<>(); 2551 synchronized (mLock) { 2552 ShrinkableDebits debits = mEJStats.get(userId, packageName); 2553 if (debits != null) { 2554 debits.setStandbyBucketLocked(bucketIndex); 2555 } 2556 2557 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 2558 if (jobs == null || jobs.size() == 0) { 2559 // Nothing further to do. 2560 return; 2561 } 2562 for (int i = jobs.size() - 1; i >= 0; i--) { 2563 JobStatus js = jobs.valueAt(i); 2564 // Effective standby bucket can change after this in some situations so 2565 // use the real bucket so that the job is tracked by the controllers. 2566 if ((bucketIndex == RESTRICTED_INDEX || js.getStandbyBucket() == RESTRICTED_INDEX) 2567 && bucketIndex != js.getStandbyBucket()) { 2568 restrictedChanges.add(js); 2569 } 2570 js.setStandbyBucket(bucketIndex); 2571 } 2572 Timer timer = mPkgTimers.get(userId, packageName); 2573 if (timer != null && timer.isActive()) { 2574 timer.rescheduleCutoff(); 2575 } 2576 timer = mEJPkgTimers.get(userId, packageName); 2577 if (timer != null && timer.isActive()) { 2578 timer.rescheduleCutoff(); 2579 } 2580 mStateChangedListener.onControllerStateChanged( 2581 maybeUpdateConstraintForPkgLocked( 2582 sElapsedRealtimeClock.millis(), userId, packageName)); 2583 } 2584 if (restrictedChanges.size() > 0) { 2585 mStateChangedListener.onRestrictedBucketChanged(restrictedChanges); 2586 } 2587 } 2588 2589 final class UsageEventTracker implements UsageEventListener { 2590 /** 2591 * Callback to inform listeners of a new event. 2592 */ 2593 @Override 2594 public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) { 2595 // Skip posting a message to the handler for events we don't care about. 2596 switch (event.getEventType()) { 2597 case UsageEvents.Event.ACTIVITY_RESUMED: 2598 case UsageEvents.Event.ACTIVITY_PAUSED: 2599 case UsageEvents.Event.ACTIVITY_STOPPED: 2600 case UsageEvents.Event.ACTIVITY_DESTROYED: 2601 case UsageEvents.Event.USER_INTERACTION: 2602 case UsageEvents.Event.CHOOSER_ACTION: 2603 case UsageEvents.Event.NOTIFICATION_INTERRUPTION: 2604 case UsageEvents.Event.NOTIFICATION_SEEN: 2605 mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event) 2606 .sendToTarget(); 2607 break; 2608 default: 2609 if (DEBUG) { 2610 Slog.d(TAG, "Dropping usage event " + event.getEventType()); 2611 } 2612 break; 2613 } 2614 } 2615 } 2616 2617 final class TempAllowlistTracker implements PowerAllowlistInternal.TempAllowlistChangeListener { 2618 2619 @Override 2620 public void onAppAdded(int uid) { 2621 synchronized (mLock) { 2622 final long nowElapsed = sElapsedRealtimeClock.millis(); 2623 mTempAllowlistCache.put(uid, true); 2624 final ArraySet<String> packages = mService.getPackagesForUidLocked(uid); 2625 if (packages != null) { 2626 final int userId = UserHandle.getUserId(uid); 2627 for (int i = packages.size() - 1; i >= 0; --i) { 2628 Timer t = mEJPkgTimers.get(userId, packages.valueAt(i)); 2629 if (t != null) { 2630 t.onStateChangedLocked(nowElapsed, true); 2631 } 2632 } 2633 final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForUidLocked(uid); 2634 if (changedJobs.size() > 0) { 2635 mStateChangedListener.onControllerStateChanged(changedJobs); 2636 } 2637 } 2638 } 2639 } 2640 2641 @Override 2642 public void onAppRemoved(int uid) { 2643 synchronized (mLock) { 2644 final long nowElapsed = sElapsedRealtimeClock.millis(); 2645 final long endElapsed = nowElapsed + mEJGracePeriodTempAllowlistMs; 2646 mTempAllowlistCache.delete(uid); 2647 mTempAllowlistGraceCache.put(uid, endElapsed); 2648 Message msg = mHandler.obtainMessage(MSG_END_GRACE_PERIOD, uid, 0); 2649 mHandler.sendMessageDelayed(msg, mEJGracePeriodTempAllowlistMs); 2650 } 2651 } 2652 } 2653 2654 private static final class TimedEventTooOldPredicate implements Predicate<TimedEvent> { 2655 private long mNowElapsed; 2656 2657 private void updateNow() { 2658 mNowElapsed = sElapsedRealtimeClock.millis(); 2659 } 2660 2661 @Override 2662 public boolean test(TimedEvent ts) { 2663 return ts.getEndTimeElapsed() <= mNowElapsed - MAX_PERIOD_MS; 2664 } 2665 } 2666 2667 private final TimedEventTooOldPredicate mTimedEventTooOld = new TimedEventTooOldPredicate(); 2668 2669 private final Consumer<List<TimedEvent>> mDeleteOldEventsFunctor = events -> { 2670 if (events != null) { 2671 // Remove everything older than MAX_PERIOD_MS time ago. 2672 events.removeIf(mTimedEventTooOld); 2673 } 2674 }; 2675 2676 @VisibleForTesting deleteObsoleteSessionsLocked()2677 void deleteObsoleteSessionsLocked() { 2678 mTimedEventTooOld.updateNow(); 2679 2680 // Regular sessions 2681 mTimingEvents.forEach(mDeleteOldEventsFunctor); 2682 2683 // EJ sessions 2684 for (int uIdx = 0; uIdx < mEJTimingSessions.numMaps(); ++uIdx) { 2685 final int userId = mEJTimingSessions.keyAt(uIdx); 2686 for (int pIdx = 0; pIdx < mEJTimingSessions.numElementsForKey(userId); ++pIdx) { 2687 final String packageName = mEJTimingSessions.keyAt(uIdx, pIdx); 2688 final ShrinkableDebits debits = getEJDebitsLocked(userId, packageName); 2689 final List<TimedEvent> sessions = mEJTimingSessions.get(userId, packageName); 2690 if (sessions == null) { 2691 continue; 2692 } 2693 2694 while (sessions.size() > 0) { 2695 final TimingSession ts = (TimingSession) sessions.get(0); 2696 if (mTimedEventTooOld.test(ts)) { 2697 // Stale sessions may still be factored into tally. Remove them. 2698 final long duration = ts.endTimeElapsed - ts.startTimeElapsed; 2699 debits.transactLocked(-duration); 2700 sessions.remove(0); 2701 } else { 2702 break; 2703 } 2704 } 2705 } 2706 } 2707 } 2708 2709 private class QcHandler extends Handler { 2710 QcHandler(Looper looper)2711 QcHandler(Looper looper) { 2712 super(looper); 2713 } 2714 2715 @Override handleMessage(Message msg)2716 public void handleMessage(Message msg) { 2717 synchronized (mLock) { 2718 switch (msg.what) { 2719 case MSG_REACHED_TIME_QUOTA: { 2720 UserPackage pkg = (UserPackage) msg.obj; 2721 if (DEBUG) { 2722 Slog.d(TAG, "Checking if " + pkg + " has reached its quota."); 2723 } 2724 2725 long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId, 2726 pkg.packageName); 2727 if (timeRemainingMs <= 50) { 2728 // Less than 50 milliseconds left. Start process of shutting down jobs. 2729 if (DEBUG) Slog.d(TAG, pkg + " has reached its quota."); 2730 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 2731 Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, 2732 JobSchedulerService.TRACE_TRACK_NAME, 2733 pkg + "#" + MSG_REACHED_TIME_QUOTA); 2734 } 2735 mStateChangedListener.onControllerStateChanged( 2736 maybeUpdateConstraintForPkgLocked( 2737 sElapsedRealtimeClock.millis(), 2738 pkg.userId, pkg.packageName)); 2739 } else { 2740 // This could potentially happen if an old session phases out while a 2741 // job is currently running. 2742 // Reschedule message 2743 Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg); 2744 timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, 2745 pkg.packageName); 2746 if (DEBUG) { 2747 Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left."); 2748 } 2749 sendMessageDelayed(rescheduleMsg, timeRemainingMs); 2750 } 2751 break; 2752 } 2753 case MSG_REACHED_EJ_TIME_QUOTA: { 2754 UserPackage pkg = (UserPackage) msg.obj; 2755 if (DEBUG) { 2756 Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota."); 2757 } 2758 2759 long timeRemainingMs = getRemainingEJExecutionTimeLocked( 2760 pkg.userId, pkg.packageName); 2761 if (timeRemainingMs <= 0) { 2762 if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota."); 2763 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 2764 Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, 2765 JobSchedulerService.TRACE_TRACK_NAME, 2766 pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA); 2767 } 2768 mStateChangedListener.onControllerStateChanged( 2769 maybeUpdateConstraintForPkgLocked( 2770 sElapsedRealtimeClock.millis(), 2771 pkg.userId, pkg.packageName)); 2772 } else { 2773 // This could potentially happen if an old session phases out while a 2774 // job is currently running. 2775 // Reschedule message 2776 Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg); 2777 timeRemainingMs = getTimeUntilEJQuotaConsumedLocked( 2778 pkg.userId, pkg.packageName); 2779 if (DEBUG) { 2780 Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left for EJ"); 2781 } 2782 sendMessageDelayed(rescheduleMsg, timeRemainingMs); 2783 } 2784 break; 2785 } 2786 case MSG_REACHED_COUNT_QUOTA: { 2787 UserPackage pkg = (UserPackage) msg.obj; 2788 if (DEBUG) { 2789 Slog.d(TAG, pkg + " has reached its count quota."); 2790 } 2791 2792 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 2793 Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, 2794 JobSchedulerService.TRACE_TRACK_NAME, 2795 pkg + "#" + MSG_REACHED_COUNT_QUOTA); 2796 } 2797 2798 mStateChangedListener.onControllerStateChanged( 2799 maybeUpdateConstraintForPkgLocked( 2800 sElapsedRealtimeClock.millis(), 2801 pkg.userId, pkg.packageName)); 2802 break; 2803 } 2804 case MSG_CLEAN_UP_SESSIONS: 2805 if (DEBUG) { 2806 Slog.d(TAG, "Cleaning up timing sessions."); 2807 } 2808 deleteObsoleteSessionsLocked(); 2809 maybeScheduleCleanupAlarmLocked(); 2810 2811 break; 2812 case MSG_CHECK_PACKAGE: { 2813 String packageName = (String) msg.obj; 2814 int userId = msg.arg1; 2815 if (DEBUG) { 2816 Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName)); 2817 } 2818 mStateChangedListener.onControllerStateChanged( 2819 maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), 2820 userId, packageName)); 2821 break; 2822 } 2823 case MSG_UID_PROCESS_STATE_CHANGED: { 2824 final int uid = msg.arg1; 2825 final int procState = msg.arg2; 2826 final int userId = UserHandle.getUserId(uid); 2827 final long nowElapsed = sElapsedRealtimeClock.millis(); 2828 2829 synchronized (mLock) { 2830 boolean isQuotaFree; 2831 if (procState <= ActivityManager.PROCESS_STATE_TOP) { 2832 mTopAppCache.put(uid, true); 2833 mTopAppGraceCache.delete(uid); 2834 if (mForegroundUids.get(uid)) { 2835 // Went from FGS to TOP. We don't need to reprocess timers or 2836 // jobs. 2837 break; 2838 } 2839 mForegroundUids.put(uid, true); 2840 isQuotaFree = true; 2841 } else { 2842 final boolean reprocess; 2843 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 2844 reprocess = !mForegroundUids.get(uid); 2845 mForegroundUids.put(uid, true); 2846 isQuotaFree = true; 2847 } else { 2848 reprocess = true; 2849 mForegroundUids.delete(uid); 2850 isQuotaFree = false; 2851 } 2852 if (mTopAppCache.get(uid)) { 2853 final long endElapsed = nowElapsed + mEJGracePeriodTopAppMs; 2854 mTopAppCache.delete(uid); 2855 mTopAppGraceCache.put(uid, endElapsed); 2856 sendMessageDelayed(obtainMessage(MSG_END_GRACE_PERIOD, uid, 0), 2857 mEJGracePeriodTopAppMs); 2858 } 2859 if (!reprocess) { 2860 break; 2861 } 2862 } 2863 // Update Timers first. 2864 if (mPkgTimers.indexOfKey(userId) >= 0 2865 || mEJPkgTimers.indexOfKey(userId) >= 0) { 2866 final ArraySet<String> packages = 2867 mService.getPackagesForUidLocked(uid); 2868 if (packages != null) { 2869 for (int i = packages.size() - 1; i >= 0; --i) { 2870 Timer t = mEJPkgTimers.get(userId, packages.valueAt(i)); 2871 if (t != null) { 2872 t.onStateChangedLocked(nowElapsed, isQuotaFree); 2873 } 2874 t = mPkgTimers.get(userId, packages.valueAt(i)); 2875 if (t != null) { 2876 t.onStateChangedLocked(nowElapsed, isQuotaFree); 2877 } 2878 } 2879 } 2880 } 2881 final ArraySet<JobStatus> changedJobs = 2882 maybeUpdateConstraintForUidLocked(uid); 2883 if (changedJobs.size() > 0) { 2884 mStateChangedListener.onControllerStateChanged(changedJobs); 2885 } 2886 } 2887 break; 2888 } 2889 case MSG_PROCESS_USAGE_EVENT: { 2890 final int userId = msg.arg1; 2891 final UsageEvents.Event event = (UsageEvents.Event) msg.obj; 2892 final String pkgName = event.getPackageName(); 2893 if (DEBUG) { 2894 Slog.d(TAG, "Processing event " + event.getEventType() 2895 + " for " + packageToString(userId, pkgName)); 2896 } 2897 switch (event.getEventType()) { 2898 case UsageEvents.Event.ACTIVITY_RESUMED: 2899 case UsageEvents.Event.ACTIVITY_PAUSED: 2900 case UsageEvents.Event.ACTIVITY_STOPPED: 2901 case UsageEvents.Event.ACTIVITY_DESTROYED: 2902 synchronized (mLock) { 2903 TopAppTimer timer = mTopAppTrackers.get(userId, pkgName); 2904 if (timer == null) { 2905 timer = new TopAppTimer(userId, pkgName); 2906 mTopAppTrackers.add(userId, pkgName, timer); 2907 } 2908 timer.processEventLocked(event); 2909 } 2910 break; 2911 case UsageEvents.Event.USER_INTERACTION: 2912 case UsageEvents.Event.CHOOSER_ACTION: 2913 case UsageEvents.Event.NOTIFICATION_INTERRUPTION: 2914 // Don't need to include SHORTCUT_INVOCATION. The app will be 2915 // launched through it (if it's not already on top). 2916 grantRewardForInstantEvent( 2917 userId, pkgName, mEJRewardInteractionMs); 2918 break; 2919 case UsageEvents.Event.NOTIFICATION_SEEN: 2920 // Intentionally don't give too much for notification seen. 2921 // Interactions will award more. 2922 grantRewardForInstantEvent( 2923 userId, pkgName, mEJRewardNotificationSeenMs); 2924 break; 2925 } 2926 2927 break; 2928 } 2929 case MSG_END_GRACE_PERIOD: { 2930 final int uid = msg.arg1; 2931 synchronized (mLock) { 2932 if (mTempAllowlistCache.get(uid) || mTopAppCache.get(uid)) { 2933 // App added back to the temp allowlist or became top again 2934 // during the grace period. 2935 if (DEBUG) { 2936 Slog.d(TAG, uid + " is still allowed"); 2937 } 2938 break; 2939 } 2940 final long nowElapsed = sElapsedRealtimeClock.millis(); 2941 if (nowElapsed < mTempAllowlistGraceCache.get(uid) 2942 || nowElapsed < mTopAppGraceCache.get(uid)) { 2943 // One of the grace periods is still in effect. 2944 if (DEBUG) { 2945 Slog.d(TAG, uid + " is still in grace period"); 2946 } 2947 break; 2948 } 2949 if (DEBUG) { 2950 Slog.d(TAG, uid + " is now out of grace period"); 2951 } 2952 mTempAllowlistGraceCache.delete(uid); 2953 mTopAppGraceCache.delete(uid); 2954 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 2955 Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, 2956 JobSchedulerService.TRACE_TRACK_NAME, 2957 "<" + uid + ">#" + MSG_END_GRACE_PERIOD); 2958 } 2959 final ArraySet<String> packages = mService.getPackagesForUidLocked(uid); 2960 if (packages != null) { 2961 final int userId = UserHandle.getUserId(uid); 2962 for (int i = packages.size() - 1; i >= 0; --i) { 2963 Timer t = mEJPkgTimers.get(userId, packages.valueAt(i)); 2964 if (t != null) { 2965 t.onStateChangedLocked(nowElapsed, false); 2966 } 2967 } 2968 final ArraySet<JobStatus> changedJobs = 2969 maybeUpdateConstraintForUidLocked(uid); 2970 if (changedJobs.size() > 0) { 2971 mStateChangedListener.onControllerStateChanged(changedJobs); 2972 } 2973 } 2974 } 2975 2976 break; 2977 } 2978 } 2979 } 2980 } 2981 } 2982 2983 /** Track when UPTCs are expected to come back into quota. */ 2984 private class InQuotaAlarmQueue extends AlarmQueue<UserPackage> { InQuotaAlarmQueue(Context context, Looper looper)2985 private InQuotaAlarmQueue(Context context, Looper looper) { 2986 super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false, 2987 QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS); 2988 } 2989 2990 @Override isForUser(@onNull UserPackage key, int userId)2991 protected boolean isForUser(@NonNull UserPackage key, int userId) { 2992 return key.userId == userId; 2993 } 2994 2995 @Override processExpiredAlarms(@onNull ArraySet<UserPackage> expired)2996 protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) { 2997 for (int i = 0; i < expired.size(); ++i) { 2998 UserPackage p = expired.valueAt(i); 2999 mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName) 3000 .sendToTarget(); 3001 } 3002 } 3003 } 3004 3005 @Override prepareForUpdatedConstantsLocked()3006 public void prepareForUpdatedConstantsLocked() { 3007 mQcConstants.mShouldReevaluateConstraints = false; 3008 mQcConstants.mRateLimitingConstantsUpdated = false; 3009 mQcConstants.mExecutionPeriodConstantsUpdated = false; 3010 mQcConstants.mEJLimitConstantsUpdated = false; 3011 mQcConstants.mQuotaBumpConstantsUpdated = false; 3012 } 3013 3014 @Override processConstantLocked(DeviceConfig.Properties properties, String key)3015 public void processConstantLocked(DeviceConfig.Properties properties, String key) { 3016 mQcConstants.processConstantLocked(properties, key); 3017 } 3018 3019 @Override onConstantsUpdatedLocked()3020 public void onConstantsUpdatedLocked() { 3021 if (mQcConstants.mShouldReevaluateConstraints) { 3022 // Update job bookkeeping out of band. 3023 AppSchedulingModuleThread.getHandler().post(() -> { 3024 synchronized (mLock) { 3025 invalidateAllExecutionStatsLocked(); 3026 maybeUpdateAllConstraintsLocked(); 3027 } 3028 }); 3029 } 3030 } 3031 3032 @VisibleForTesting 3033 class QcConstants { 3034 private boolean mShouldReevaluateConstraints = false; 3035 private boolean mRateLimitingConstantsUpdated = false; 3036 private boolean mExecutionPeriodConstantsUpdated = false; 3037 private boolean mEJLimitConstantsUpdated = false; 3038 private boolean mQuotaBumpConstantsUpdated = false; 3039 3040 /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ 3041 private static final String QC_CONSTANT_PREFIX = "qc_"; 3042 3043 /** 3044 * Previously used keys: 3045 * * allowed_time_per_period_ms -- No longer used after splitting by bucket 3046 */ 3047 3048 @VisibleForTesting 3049 static final String KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 3050 QC_CONSTANT_PREFIX + "allowed_time_per_period_exempted_ms"; 3051 @VisibleForTesting 3052 static final String KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 3053 QC_CONSTANT_PREFIX + "allowed_time_per_period_active_ms"; 3054 @VisibleForTesting 3055 static final String KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS = 3056 QC_CONSTANT_PREFIX + "allowed_time_per_period_working_ms"; 3057 @VisibleForTesting 3058 static final String KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = 3059 QC_CONSTANT_PREFIX + "allowed_time_per_period_frequent_ms"; 3060 @VisibleForTesting 3061 static final String KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS = 3062 QC_CONSTANT_PREFIX + "allowed_time_per_period_rare_ms"; 3063 @VisibleForTesting 3064 static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 3065 QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms"; 3066 @VisibleForTesting 3067 static final String KEY_IN_QUOTA_BUFFER_MS = 3068 QC_CONSTANT_PREFIX + "in_quota_buffer_ms"; 3069 @VisibleForTesting 3070 static final String KEY_WINDOW_SIZE_EXEMPTED_MS = 3071 QC_CONSTANT_PREFIX + "window_size_exempted_ms"; 3072 @VisibleForTesting 3073 static final String KEY_WINDOW_SIZE_ACTIVE_MS = 3074 QC_CONSTANT_PREFIX + "window_size_active_ms"; 3075 @VisibleForTesting 3076 static final String KEY_WINDOW_SIZE_WORKING_MS = 3077 QC_CONSTANT_PREFIX + "window_size_working_ms"; 3078 @VisibleForTesting 3079 static final String KEY_WINDOW_SIZE_FREQUENT_MS = 3080 QC_CONSTANT_PREFIX + "window_size_frequent_ms"; 3081 @VisibleForTesting 3082 static final String KEY_WINDOW_SIZE_RARE_MS = 3083 QC_CONSTANT_PREFIX + "window_size_rare_ms"; 3084 @VisibleForTesting 3085 static final String KEY_WINDOW_SIZE_RESTRICTED_MS = 3086 QC_CONSTANT_PREFIX + "window_size_restricted_ms"; 3087 @VisibleForTesting 3088 static final String KEY_MAX_EXECUTION_TIME_MS = 3089 QC_CONSTANT_PREFIX + "max_execution_time_ms"; 3090 @VisibleForTesting 3091 static final String KEY_MAX_JOB_COUNT_EXEMPTED = 3092 QC_CONSTANT_PREFIX + "max_job_count_exempted"; 3093 @VisibleForTesting 3094 static final String KEY_MAX_JOB_COUNT_ACTIVE = 3095 QC_CONSTANT_PREFIX + "max_job_count_active"; 3096 @VisibleForTesting 3097 static final String KEY_MAX_JOB_COUNT_WORKING = 3098 QC_CONSTANT_PREFIX + "max_job_count_working"; 3099 @VisibleForTesting 3100 static final String KEY_MAX_JOB_COUNT_FREQUENT = 3101 QC_CONSTANT_PREFIX + "max_job_count_frequent"; 3102 @VisibleForTesting 3103 static final String KEY_MAX_JOB_COUNT_RARE = 3104 QC_CONSTANT_PREFIX + "max_job_count_rare"; 3105 @VisibleForTesting 3106 static final String KEY_MAX_JOB_COUNT_RESTRICTED = 3107 QC_CONSTANT_PREFIX + "max_job_count_restricted"; 3108 @VisibleForTesting 3109 static final String KEY_RATE_LIMITING_WINDOW_MS = 3110 QC_CONSTANT_PREFIX + "rate_limiting_window_ms"; 3111 @VisibleForTesting 3112 static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 3113 QC_CONSTANT_PREFIX + "max_job_count_per_rate_limiting_window"; 3114 @VisibleForTesting 3115 static final String KEY_MAX_SESSION_COUNT_EXEMPTED = 3116 QC_CONSTANT_PREFIX + "max_session_count_exempted"; 3117 @VisibleForTesting 3118 static final String KEY_MAX_SESSION_COUNT_ACTIVE = 3119 QC_CONSTANT_PREFIX + "max_session_count_active"; 3120 @VisibleForTesting 3121 static final String KEY_MAX_SESSION_COUNT_WORKING = 3122 QC_CONSTANT_PREFIX + "max_session_count_working"; 3123 @VisibleForTesting 3124 static final String KEY_MAX_SESSION_COUNT_FREQUENT = 3125 QC_CONSTANT_PREFIX + "max_session_count_frequent"; 3126 @VisibleForTesting 3127 static final String KEY_MAX_SESSION_COUNT_RARE = 3128 QC_CONSTANT_PREFIX + "max_session_count_rare"; 3129 @VisibleForTesting 3130 static final String KEY_MAX_SESSION_COUNT_RESTRICTED = 3131 QC_CONSTANT_PREFIX + "max_session_count_restricted"; 3132 @VisibleForTesting 3133 static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 3134 QC_CONSTANT_PREFIX + "max_session_count_per_rate_limiting_window"; 3135 @VisibleForTesting 3136 static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS = 3137 QC_CONSTANT_PREFIX + "timing_session_coalescing_duration_ms"; 3138 @VisibleForTesting 3139 static final String KEY_MIN_QUOTA_CHECK_DELAY_MS = 3140 QC_CONSTANT_PREFIX + "min_quota_check_delay_ms"; 3141 @VisibleForTesting 3142 static final String KEY_EJ_LIMIT_EXEMPTED_MS = 3143 QC_CONSTANT_PREFIX + "ej_limit_exempted_ms"; 3144 @VisibleForTesting 3145 static final String KEY_EJ_LIMIT_ACTIVE_MS = 3146 QC_CONSTANT_PREFIX + "ej_limit_active_ms"; 3147 @VisibleForTesting 3148 static final String KEY_EJ_LIMIT_WORKING_MS = 3149 QC_CONSTANT_PREFIX + "ej_limit_working_ms"; 3150 @VisibleForTesting 3151 static final String KEY_EJ_LIMIT_FREQUENT_MS = 3152 QC_CONSTANT_PREFIX + "ej_limit_frequent_ms"; 3153 @VisibleForTesting 3154 static final String KEY_EJ_LIMIT_RARE_MS = 3155 QC_CONSTANT_PREFIX + "ej_limit_rare_ms"; 3156 @VisibleForTesting 3157 static final String KEY_EJ_LIMIT_RESTRICTED_MS = 3158 QC_CONSTANT_PREFIX + "ej_limit_restricted_ms"; 3159 @VisibleForTesting 3160 static final String KEY_EJ_LIMIT_ADDITION_SPECIAL_MS = 3161 QC_CONSTANT_PREFIX + "ej_limit_addition_special_ms"; 3162 @VisibleForTesting 3163 static final String KEY_EJ_LIMIT_ADDITION_INSTALLER_MS = 3164 QC_CONSTANT_PREFIX + "ej_limit_addition_installer_ms"; 3165 @VisibleForTesting 3166 static final String KEY_EJ_WINDOW_SIZE_MS = 3167 QC_CONSTANT_PREFIX + "ej_window_size_ms"; 3168 @VisibleForTesting 3169 static final String KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 3170 QC_CONSTANT_PREFIX + "ej_top_app_time_chunk_size_ms"; 3171 @VisibleForTesting 3172 static final String KEY_EJ_REWARD_TOP_APP_MS = 3173 QC_CONSTANT_PREFIX + "ej_reward_top_app_ms"; 3174 @VisibleForTesting 3175 static final String KEY_EJ_REWARD_INTERACTION_MS = 3176 QC_CONSTANT_PREFIX + "ej_reward_interaction_ms"; 3177 @VisibleForTesting 3178 static final String KEY_EJ_REWARD_NOTIFICATION_SEEN_MS = 3179 QC_CONSTANT_PREFIX + "ej_reward_notification_seen_ms"; 3180 @VisibleForTesting 3181 static final String KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3182 QC_CONSTANT_PREFIX + "ej_grace_period_temp_allowlist_ms"; 3183 @VisibleForTesting 3184 static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = 3185 QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; 3186 @VisibleForTesting 3187 static final String KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS = 3188 QC_CONSTANT_PREFIX + "quota_bump_additional_duration_ms"; 3189 @VisibleForTesting 3190 static final String KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = 3191 QC_CONSTANT_PREFIX + "quota_bump_additional_job_count"; 3192 @VisibleForTesting 3193 static final String KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 3194 QC_CONSTANT_PREFIX + "quota_bump_additional_session_count"; 3195 @VisibleForTesting 3196 static final String KEY_QUOTA_BUMP_WINDOW_SIZE_MS = 3197 QC_CONSTANT_PREFIX + "quota_bump_window_size_ms"; 3198 @VisibleForTesting 3199 static final String KEY_QUOTA_BUMP_LIMIT = 3200 QC_CONSTANT_PREFIX + "quota_bump_limit"; 3201 3202 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 3203 10 * 60 * 1000L; // 10 minutes 3204 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 3205 10 * 60 * 1000L; // 10 minutes 3206 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS = 3207 10 * 60 * 1000L; // 10 minutes 3208 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = 3209 10 * 60 * 1000L; // 10 minutes 3210 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS = 3211 10 * 60 * 1000L; // 10 minutes 3212 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 3213 10 * 60 * 1000L; // 10 minutes 3214 private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 3215 30 * 1000L; // 30 seconds 3216 private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS = 3217 DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time 3218 private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS = 3219 DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time 3220 private static final long DEFAULT_WINDOW_SIZE_WORKING_MS = 3221 2 * 60 * 60 * 1000L; // 2 hours 3222 private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS = 3223 8 * 60 * 60 * 1000L; // 8 hours 3224 private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 3225 24 * 60 * 60 * 1000L; // 24 hours 3226 private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = 3227 24 * 60 * 60 * 1000L; // 24 hours 3228 private static final long DEFAULT_MAX_EXECUTION_TIME_MS = 3229 4 * HOUR_IN_MILLIS; 3230 private static final long DEFAULT_RATE_LIMITING_WINDOW_MS = 3231 MINUTE_IN_MILLIS; 3232 private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20; 3233 private static final int DEFAULT_MAX_JOB_COUNT_EXEMPTED = 3234 75; // 75/window = 450/hr = 1/session 3235 private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED; 3236 private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session 3237 (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS); 3238 private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session 3239 (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS); 3240 private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session 3241 (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS); 3242 private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10; 3243 private static final int DEFAULT_MAX_SESSION_COUNT_EXEMPTED = 3244 75; // 450/hr 3245 private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = 3246 DEFAULT_MAX_SESSION_COUNT_EXEMPTED; 3247 private static final int DEFAULT_MAX_SESSION_COUNT_WORKING = 3248 10; // 5/hr 3249 private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT = 3250 8; // 1/hr 3251 private static final int DEFAULT_MAX_SESSION_COUNT_RARE = 3252 3; // .125/hr 3253 private static final int DEFAULT_MAX_SESSION_COUNT_RESTRICTED = 1; // 1/day 3254 private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20; 3255 private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds 3256 private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS; 3257 // TODO(267949143): set a different limit for headless system apps 3258 private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 60 * MINUTE_IN_MILLIS; 3259 private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS; 3260 private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; 3261 private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS; 3262 private static final long DEFAULT_EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS; 3263 private static final long DEFAULT_EJ_LIMIT_RESTRICTED_MS = 5 * MINUTE_IN_MILLIS; 3264 private static final long DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS = 15 * MINUTE_IN_MILLIS; 3265 private static final long DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS = 30 * MINUTE_IN_MILLIS; 3266 private static final long DEFAULT_EJ_WINDOW_SIZE_MS = 24 * HOUR_IN_MILLIS; 3267 private static final long DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 30 * SECOND_IN_MILLIS; 3268 private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS; 3269 private static final long DEFAULT_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS; 3270 private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0; 3271 private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS; 3272 private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS; 3273 private static final long DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS = 1 * MINUTE_IN_MILLIS; 3274 private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = 2; 3275 private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 1; 3276 private static final long DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS = 8 * HOUR_IN_MILLIS; 3277 private static final int DEFAULT_QUOTA_BUMP_LIMIT = 8; 3278 3279 /** 3280 * How much time each app in the exempted bucket will have to run jobs within their standby 3281 * bucket window. 3282 */ 3283 public long ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 3284 DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; 3285 /** 3286 * How much time each app in the active bucket will have to run jobs within their standby 3287 * bucket window. 3288 */ 3289 public long ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; 3290 /** 3291 * How much time each app in the working set bucket will have to run jobs within their 3292 * standby bucket window. 3293 */ 3294 public long ALLOWED_TIME_PER_PERIOD_WORKING_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS; 3295 /** 3296 * How much time each app in the frequent bucket will have to run jobs within their standby 3297 * bucket window. 3298 */ 3299 public long ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = 3300 DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS; 3301 /** 3302 * How much time each app in the rare bucket will have to run jobs within their standby 3303 * bucket window. 3304 */ 3305 public long ALLOWED_TIME_PER_PERIOD_RARE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS; 3306 /** 3307 * How much time each app in the restricted bucket will have to run jobs within their 3308 * standby bucket window. 3309 */ 3310 public long ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 3311 DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS; 3312 3313 /** 3314 * How much time the package should have before transitioning from out-of-quota to in-quota. 3315 * This should not affect processing if the package is already in-quota. 3316 */ 3317 public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS; 3318 3319 /** 3320 * The quota window size of the particular standby bucket. Apps in this standby bucket are 3321 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past 3322 * WINDOW_SIZE_MS. 3323 */ 3324 public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS; 3325 3326 /** 3327 * The quota window size of the particular standby bucket. Apps in this standby bucket are 3328 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past 3329 * WINDOW_SIZE_MS. 3330 */ 3331 public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS; 3332 3333 /** 3334 * The quota window size of the particular standby bucket. Apps in this standby bucket are 3335 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past 3336 * WINDOW_SIZE_MS. 3337 */ 3338 public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS; 3339 3340 /** 3341 * The quota window size of the particular standby bucket. Apps in this standby bucket are 3342 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past 3343 * WINDOW_SIZE_MS. 3344 */ 3345 public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS; 3346 3347 /** 3348 * The quota window size of the particular standby bucket. Apps in this standby bucket are 3349 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RARE_MS} within the past 3350 * WINDOW_SIZE_MS. 3351 */ 3352 public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS; 3353 3354 /** 3355 * The quota window size of the particular standby bucket. Apps in this standby bucket are 3356 * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS} within the past 3357 * WINDOW_SIZE_MS. 3358 */ 3359 public long WINDOW_SIZE_RESTRICTED_MS = DEFAULT_WINDOW_SIZE_RESTRICTED_MS; 3360 3361 /** 3362 * The maximum amount of time an app can have its jobs running within a 24 hour window. 3363 */ 3364 public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS; 3365 3366 /** 3367 * The maximum number of jobs an app can run within this particular standby bucket's 3368 * window size. 3369 */ 3370 public int MAX_JOB_COUNT_EXEMPTED = DEFAULT_MAX_JOB_COUNT_EXEMPTED; 3371 3372 /** 3373 * The maximum number of jobs an app can run within this particular standby bucket's 3374 * window size. 3375 */ 3376 public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE; 3377 3378 /** 3379 * The maximum number of jobs an app can run within this particular standby bucket's 3380 * window size. 3381 */ 3382 public int MAX_JOB_COUNT_WORKING = DEFAULT_MAX_JOB_COUNT_WORKING; 3383 3384 /** 3385 * The maximum number of jobs an app can run within this particular standby bucket's 3386 * window size. 3387 */ 3388 public int MAX_JOB_COUNT_FREQUENT = DEFAULT_MAX_JOB_COUNT_FREQUENT; 3389 3390 /** 3391 * The maximum number of jobs an app can run within this particular standby bucket's 3392 * window size. 3393 */ 3394 public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE; 3395 3396 /** 3397 * The maximum number of jobs an app can run within this particular standby bucket's 3398 * window size. 3399 */ 3400 public int MAX_JOB_COUNT_RESTRICTED = DEFAULT_MAX_JOB_COUNT_RESTRICTED; 3401 3402 /** The period of time used to rate limit recently run jobs. */ 3403 public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS; 3404 3405 /** 3406 * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}. 3407 */ 3408 public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 3409 DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 3410 3411 /** 3412 * The maximum number of {@link TimingSession TimingSessions} an app can run within this 3413 * particular standby bucket's window size. 3414 */ 3415 public int MAX_SESSION_COUNT_EXEMPTED = DEFAULT_MAX_SESSION_COUNT_EXEMPTED; 3416 3417 /** 3418 * The maximum number of {@link TimingSession TimingSessions} an app can run within this 3419 * particular standby bucket's window size. 3420 */ 3421 public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE; 3422 3423 /** 3424 * The maximum number of {@link TimingSession TimingSessions} an app can run within this 3425 * particular standby bucket's window size. 3426 */ 3427 public int MAX_SESSION_COUNT_WORKING = DEFAULT_MAX_SESSION_COUNT_WORKING; 3428 3429 /** 3430 * The maximum number of {@link TimingSession TimingSessions} an app can run within this 3431 * particular standby bucket's window size. 3432 */ 3433 public int MAX_SESSION_COUNT_FREQUENT = DEFAULT_MAX_SESSION_COUNT_FREQUENT; 3434 3435 /** 3436 * The maximum number of {@link TimingSession TimingSessions} an app can run within this 3437 * particular standby bucket's window size. 3438 */ 3439 public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE; 3440 3441 /** 3442 * The maximum number of {@link TimingSession TimingSessions} an app can run within this 3443 * particular standby bucket's window size. 3444 */ 3445 public int MAX_SESSION_COUNT_RESTRICTED = DEFAULT_MAX_SESSION_COUNT_RESTRICTED; 3446 3447 /** 3448 * The maximum number of {@link TimingSession TimingSessions} that can run within the past 3449 * {@link #RATE_LIMITING_WINDOW_MS}. 3450 */ 3451 public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 3452 DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; 3453 3454 /** 3455 * Treat two distinct {@link TimingSession TimingSessions} as the same if they start and 3456 * end within this amount of time of each other. 3457 */ 3458 public long TIMING_SESSION_COALESCING_DURATION_MS = 3459 DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS; 3460 3461 /** The minimum amount of time between quota check alarms. */ 3462 public long MIN_QUOTA_CHECK_DELAY_MS = DEFAULT_MIN_QUOTA_CHECK_DELAY_MS; 3463 3464 // Safeguards 3465 3466 /** The minimum number of jobs that any bucket will be allowed to run within its window. */ 3467 private static final int MIN_BUCKET_JOB_COUNT = 10; 3468 3469 /** 3470 * The minimum number of {@link TimingSession TimingSessions} that any bucket will be 3471 * allowed to run within its window. 3472 */ 3473 private static final int MIN_BUCKET_SESSION_COUNT = 1; 3474 3475 /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */ 3476 private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS; 3477 3478 /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */ 3479 private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10; 3480 3481 /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */ 3482 private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10; 3483 3484 /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */ 3485 private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS; 3486 3487 /** 3488 * The total expedited job session limit of the particular standby bucket. Apps in this 3489 * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring 3490 * in any rewards or free EJs). 3491 */ 3492 public long EJ_LIMIT_EXEMPTED_MS = DEFAULT_EJ_LIMIT_EXEMPTED_MS; 3493 3494 /** 3495 * The total expedited job session limit of the particular standby bucket. Apps in this 3496 * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring 3497 * in any rewards or free EJs). 3498 */ 3499 public long EJ_LIMIT_ACTIVE_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; 3500 3501 /** 3502 * The total expedited job session limit of the particular standby bucket. Apps in this 3503 * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring 3504 * in any rewards or free EJs). 3505 */ 3506 public long EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_WORKING_MS; 3507 3508 /** 3509 * The total expedited job session limit of the particular standby bucket. Apps in this 3510 * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring 3511 * in any rewards or free EJs). 3512 */ 3513 public long EJ_LIMIT_FREQUENT_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS; 3514 3515 /** 3516 * The total expedited job session limit of the particular standby bucket. Apps in this 3517 * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring 3518 * in any rewards or free EJs). 3519 */ 3520 public long EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_RARE_MS; 3521 3522 /** 3523 * The total expedited job session limit of the particular standby bucket. Apps in this 3524 * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring 3525 * in any rewards or free EJs). 3526 */ 3527 public long EJ_LIMIT_RESTRICTED_MS = DEFAULT_EJ_LIMIT_RESTRICTED_MS; 3528 3529 /** 3530 * How much additional EJ quota special, critical apps should get. 3531 */ 3532 public long EJ_LIMIT_ADDITION_SPECIAL_MS = DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS; 3533 3534 /** 3535 * How much additional EJ quota system installers (with the INSTALL_PACKAGES permission) 3536 * should get. 3537 */ 3538 public long EJ_LIMIT_ADDITION_INSTALLER_MS = DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS; 3539 3540 /** 3541 * The period of time used to calculate expedited job sessions. Apps can only have expedited 3542 * job sessions totalling EJ_LIMIT_<bucket>_MS within this period of time (without factoring 3543 * in any rewards or free EJs). 3544 */ 3545 public long EJ_WINDOW_SIZE_MS = DEFAULT_EJ_WINDOW_SIZE_MS; 3546 3547 /** 3548 * Length of time used to split an app's top time into chunks. 3549 */ 3550 public long EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; 3551 3552 /** 3553 * How much EJ quota to give back to an app based on the number of top app time chunks it 3554 * had. 3555 */ 3556 public long EJ_REWARD_TOP_APP_MS = DEFAULT_EJ_REWARD_TOP_APP_MS; 3557 3558 /** 3559 * How much EJ quota to give back to an app based on each non-top user interaction. 3560 */ 3561 public long EJ_REWARD_INTERACTION_MS = DEFAULT_EJ_REWARD_INTERACTION_MS; 3562 3563 /** 3564 * How much EJ quota to give back to an app based on each notification seen event. 3565 */ 3566 public long EJ_REWARD_NOTIFICATION_SEEN_MS = DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS; 3567 3568 /** 3569 * How much additional grace period to add to the end of an app's temp allowlist 3570 * duration. 3571 */ 3572 public long EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS; 3573 3574 /** 3575 * How much additional grace period to give an app when it leaves the TOP state. 3576 */ 3577 public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; 3578 3579 /** 3580 * How much additional session duration to give an app for each accepted quota bump. 3581 */ 3582 public long QUOTA_BUMP_ADDITIONAL_DURATION_MS = DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS; 3583 3584 /** 3585 * How many additional regular jobs to give an app for each accepted quota bump. 3586 */ 3587 public int QUOTA_BUMP_ADDITIONAL_JOB_COUNT = DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT; 3588 3589 /** 3590 * How many additional sessions to give an app for each accepted quota bump. 3591 */ 3592 public int QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 3593 DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT; 3594 3595 /** 3596 * The rolling window size within which to accept and apply quota bump events. 3597 */ 3598 public long QUOTA_BUMP_WINDOW_SIZE_MS = DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS; 3599 3600 /** 3601 * The maximum number of quota bumps to accept and apply within the 3602 * {@link #QUOTA_BUMP_WINDOW_SIZE_MS window}. 3603 */ 3604 public int QUOTA_BUMP_LIMIT = DEFAULT_QUOTA_BUMP_LIMIT; 3605 processConstantLocked(@onNull DeviceConfig.Properties properties, @NonNull String key)3606 public void processConstantLocked(@NonNull DeviceConfig.Properties properties, 3607 @NonNull String key) { 3608 switch (key) { 3609 case KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS: 3610 case KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS: 3611 case KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS: 3612 case KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS: 3613 case KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS: 3614 case KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS: 3615 case KEY_IN_QUOTA_BUFFER_MS: 3616 case KEY_MAX_EXECUTION_TIME_MS: 3617 case KEY_WINDOW_SIZE_ACTIVE_MS: 3618 case KEY_WINDOW_SIZE_WORKING_MS: 3619 case KEY_WINDOW_SIZE_FREQUENT_MS: 3620 case KEY_WINDOW_SIZE_RARE_MS: 3621 case KEY_WINDOW_SIZE_RESTRICTED_MS: 3622 updateExecutionPeriodConstantsLocked(); 3623 break; 3624 3625 case KEY_RATE_LIMITING_WINDOW_MS: 3626 case KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW: 3627 case KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW: 3628 updateRateLimitingConstantsLocked(); 3629 break; 3630 3631 case KEY_EJ_LIMIT_ACTIVE_MS: 3632 case KEY_EJ_LIMIT_WORKING_MS: 3633 case KEY_EJ_LIMIT_FREQUENT_MS: 3634 case KEY_EJ_LIMIT_RARE_MS: 3635 case KEY_EJ_LIMIT_RESTRICTED_MS: 3636 case KEY_EJ_LIMIT_ADDITION_SPECIAL_MS: 3637 case KEY_EJ_LIMIT_ADDITION_INSTALLER_MS: 3638 case KEY_EJ_WINDOW_SIZE_MS: 3639 updateEJLimitConstantsLocked(); 3640 break; 3641 3642 case KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS: 3643 case KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT: 3644 case KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT: 3645 case KEY_QUOTA_BUMP_WINDOW_SIZE_MS: 3646 case KEY_QUOTA_BUMP_LIMIT: 3647 updateQuotaBumpConstantsLocked(); 3648 break; 3649 3650 case KEY_MAX_JOB_COUNT_EXEMPTED: 3651 MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED); 3652 int newExemptedMaxJobCount = 3653 Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_EXEMPTED); 3654 if (mMaxBucketJobCounts[EXEMPTED_INDEX] != newExemptedMaxJobCount) { 3655 mMaxBucketJobCounts[EXEMPTED_INDEX] = newExemptedMaxJobCount; 3656 mShouldReevaluateConstraints = true; 3657 } 3658 break; 3659 case KEY_MAX_JOB_COUNT_ACTIVE: 3660 MAX_JOB_COUNT_ACTIVE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_ACTIVE); 3661 int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE); 3662 if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) { 3663 mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount; 3664 mShouldReevaluateConstraints = true; 3665 } 3666 break; 3667 case KEY_MAX_JOB_COUNT_WORKING: 3668 MAX_JOB_COUNT_WORKING = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_WORKING); 3669 int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, 3670 MAX_JOB_COUNT_WORKING); 3671 if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) { 3672 mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount; 3673 mShouldReevaluateConstraints = true; 3674 } 3675 break; 3676 case KEY_MAX_JOB_COUNT_FREQUENT: 3677 MAX_JOB_COUNT_FREQUENT = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_FREQUENT); 3678 int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, 3679 MAX_JOB_COUNT_FREQUENT); 3680 if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) { 3681 mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount; 3682 mShouldReevaluateConstraints = true; 3683 } 3684 break; 3685 case KEY_MAX_JOB_COUNT_RARE: 3686 MAX_JOB_COUNT_RARE = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_RARE); 3687 int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE); 3688 if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) { 3689 mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount; 3690 mShouldReevaluateConstraints = true; 3691 } 3692 break; 3693 case KEY_MAX_JOB_COUNT_RESTRICTED: 3694 MAX_JOB_COUNT_RESTRICTED = 3695 properties.getInt(key, DEFAULT_MAX_JOB_COUNT_RESTRICTED); 3696 int newRestrictedMaxJobCount = 3697 Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RESTRICTED); 3698 if (mMaxBucketJobCounts[RESTRICTED_INDEX] != newRestrictedMaxJobCount) { 3699 mMaxBucketJobCounts[RESTRICTED_INDEX] = newRestrictedMaxJobCount; 3700 mShouldReevaluateConstraints = true; 3701 } 3702 break; 3703 case KEY_MAX_SESSION_COUNT_EXEMPTED: 3704 MAX_SESSION_COUNT_EXEMPTED = 3705 properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_EXEMPTED); 3706 int newExemptedMaxSessionCount = 3707 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_EXEMPTED); 3708 if (mMaxBucketSessionCounts[EXEMPTED_INDEX] != newExemptedMaxSessionCount) { 3709 mMaxBucketSessionCounts[EXEMPTED_INDEX] = newExemptedMaxSessionCount; 3710 mShouldReevaluateConstraints = true; 3711 } 3712 break; 3713 case KEY_MAX_SESSION_COUNT_ACTIVE: 3714 MAX_SESSION_COUNT_ACTIVE = 3715 properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_ACTIVE); 3716 int newActiveMaxSessionCount = 3717 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_ACTIVE); 3718 if (mMaxBucketSessionCounts[ACTIVE_INDEX] != newActiveMaxSessionCount) { 3719 mMaxBucketSessionCounts[ACTIVE_INDEX] = newActiveMaxSessionCount; 3720 mShouldReevaluateConstraints = true; 3721 } 3722 break; 3723 case KEY_MAX_SESSION_COUNT_WORKING: 3724 MAX_SESSION_COUNT_WORKING = 3725 properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_WORKING); 3726 int newWorkingMaxSessionCount = 3727 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_WORKING); 3728 if (mMaxBucketSessionCounts[WORKING_INDEX] != newWorkingMaxSessionCount) { 3729 mMaxBucketSessionCounts[WORKING_INDEX] = newWorkingMaxSessionCount; 3730 mShouldReevaluateConstraints = true; 3731 } 3732 break; 3733 case KEY_MAX_SESSION_COUNT_FREQUENT: 3734 MAX_SESSION_COUNT_FREQUENT = 3735 properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_FREQUENT); 3736 int newFrequentMaxSessionCount = 3737 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_FREQUENT); 3738 if (mMaxBucketSessionCounts[FREQUENT_INDEX] != newFrequentMaxSessionCount) { 3739 mMaxBucketSessionCounts[FREQUENT_INDEX] = newFrequentMaxSessionCount; 3740 mShouldReevaluateConstraints = true; 3741 } 3742 break; 3743 case KEY_MAX_SESSION_COUNT_RARE: 3744 MAX_SESSION_COUNT_RARE = properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_RARE); 3745 int newRareMaxSessionCount = 3746 Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_RARE); 3747 if (mMaxBucketSessionCounts[RARE_INDEX] != newRareMaxSessionCount) { 3748 mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount; 3749 mShouldReevaluateConstraints = true; 3750 } 3751 break; 3752 case KEY_MAX_SESSION_COUNT_RESTRICTED: 3753 MAX_SESSION_COUNT_RESTRICTED = 3754 properties.getInt(key, DEFAULT_MAX_SESSION_COUNT_RESTRICTED); 3755 int newRestrictedMaxSessionCount = Math.max(0, MAX_SESSION_COUNT_RESTRICTED); 3756 if (mMaxBucketSessionCounts[RESTRICTED_INDEX] != newRestrictedMaxSessionCount) { 3757 mMaxBucketSessionCounts[RESTRICTED_INDEX] = newRestrictedMaxSessionCount; 3758 mShouldReevaluateConstraints = true; 3759 } 3760 break; 3761 case KEY_TIMING_SESSION_COALESCING_DURATION_MS: 3762 TIMING_SESSION_COALESCING_DURATION_MS = 3763 properties.getLong(key, DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS); 3764 long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS, 3765 Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS)); 3766 if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) { 3767 mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs; 3768 mShouldReevaluateConstraints = true; 3769 } 3770 break; 3771 case KEY_MIN_QUOTA_CHECK_DELAY_MS: 3772 MIN_QUOTA_CHECK_DELAY_MS = 3773 properties.getLong(key, DEFAULT_MIN_QUOTA_CHECK_DELAY_MS); 3774 // We don't need to re-evaluate execution stats or constraint status for this. 3775 // Limit the delay to the range [0, 15] minutes. 3776 mInQuotaAlarmQueue.setMinTimeBetweenAlarmsMs( 3777 Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS))); 3778 break; 3779 case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS: 3780 // We don't need to re-evaluate execution stats or constraint status for this. 3781 EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 3782 properties.getLong(key, DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS); 3783 // Limit chunking to be in the range [1 millisecond, 15 minutes] per event. 3784 long newChunkSizeMs = Math.min(15 * MINUTE_IN_MILLIS, 3785 Math.max(1, EJ_TOP_APP_TIME_CHUNK_SIZE_MS)); 3786 if (mEJTopAppTimeChunkSizeMs != newChunkSizeMs) { 3787 mEJTopAppTimeChunkSizeMs = newChunkSizeMs; 3788 if (mEJTopAppTimeChunkSizeMs < mEJRewardTopAppMs) { 3789 // Not making chunk sizes and top rewards to be the upper/lower 3790 // limits of the other to allow trying different policies. Just log 3791 // the discrepancy. 3792 Slog.w(TAG, "EJ top app time chunk less than reward: " 3793 + mEJTopAppTimeChunkSizeMs + " vs " + mEJRewardTopAppMs); 3794 } 3795 } 3796 break; 3797 case KEY_EJ_REWARD_TOP_APP_MS: 3798 // We don't need to re-evaluate execution stats or constraint status for this. 3799 EJ_REWARD_TOP_APP_MS = 3800 properties.getLong(key, DEFAULT_EJ_REWARD_TOP_APP_MS); 3801 // Limit top reward to be in the range [10 seconds, 15 minutes] per event. 3802 long newTopReward = Math.min(15 * MINUTE_IN_MILLIS, 3803 Math.max(10 * SECOND_IN_MILLIS, EJ_REWARD_TOP_APP_MS)); 3804 if (mEJRewardTopAppMs != newTopReward) { 3805 mEJRewardTopAppMs = newTopReward; 3806 if (mEJTopAppTimeChunkSizeMs < mEJRewardTopAppMs) { 3807 // Not making chunk sizes and top rewards to be the upper/lower 3808 // limits of the other to allow trying different policies. Just log 3809 // the discrepancy. 3810 Slog.w(TAG, "EJ top app time chunk less than reward: " 3811 + mEJTopAppTimeChunkSizeMs + " vs " + mEJRewardTopAppMs); 3812 } 3813 } 3814 break; 3815 case KEY_EJ_REWARD_INTERACTION_MS: 3816 // We don't need to re-evaluate execution stats or constraint status for this. 3817 EJ_REWARD_INTERACTION_MS = 3818 properties.getLong(key, DEFAULT_EJ_REWARD_INTERACTION_MS); 3819 // Limit interaction reward to be in the range [5 seconds, 15 minutes] per 3820 // event. 3821 mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS, 3822 Math.max(5 * SECOND_IN_MILLIS, EJ_REWARD_INTERACTION_MS)); 3823 break; 3824 case KEY_EJ_REWARD_NOTIFICATION_SEEN_MS: 3825 // We don't need to re-evaluate execution stats or constraint status for this. 3826 EJ_REWARD_NOTIFICATION_SEEN_MS = 3827 properties.getLong(key, DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS); 3828 // Limit notification seen reward to be in the range [0, 5] minutes per event. 3829 mEJRewardNotificationSeenMs = Math.min(5 * MINUTE_IN_MILLIS, 3830 Math.max(0, EJ_REWARD_NOTIFICATION_SEEN_MS)); 3831 break; 3832 case KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS: 3833 // We don't need to re-evaluate execution stats or constraint status for this. 3834 EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3835 properties.getLong(key, DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS); 3836 // Limit grace period to be in the range [0 minutes, 1 hour]. 3837 mEJGracePeriodTempAllowlistMs = Math.min(HOUR_IN_MILLIS, 3838 Math.max(0, EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS)); 3839 break; 3840 case KEY_EJ_GRACE_PERIOD_TOP_APP_MS: 3841 // We don't need to re-evaluate execution stats or constraint status for this. 3842 EJ_GRACE_PERIOD_TOP_APP_MS = 3843 properties.getLong(key, DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS); 3844 // Limit grace period to be in the range [0 minutes, 1 hour]. 3845 mEJGracePeriodTopAppMs = Math.min(HOUR_IN_MILLIS, 3846 Math.max(0, EJ_GRACE_PERIOD_TOP_APP_MS)); 3847 break; 3848 } 3849 } 3850 updateExecutionPeriodConstantsLocked()3851 private void updateExecutionPeriodConstantsLocked() { 3852 if (mExecutionPeriodConstantsUpdated) { 3853 return; 3854 } 3855 mExecutionPeriodConstantsUpdated = true; 3856 3857 // Query the values as an atomic set. 3858 final DeviceConfig.Properties properties = DeviceConfig.getProperties( 3859 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 3860 KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 3861 KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 3862 KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 3863 KEY_IN_QUOTA_BUFFER_MS, 3864 KEY_MAX_EXECUTION_TIME_MS, 3865 KEY_WINDOW_SIZE_EXEMPTED_MS, KEY_WINDOW_SIZE_ACTIVE_MS, 3866 KEY_WINDOW_SIZE_WORKING_MS, 3867 KEY_WINDOW_SIZE_FREQUENT_MS, KEY_WINDOW_SIZE_RARE_MS, 3868 KEY_WINDOW_SIZE_RESTRICTED_MS); 3869 ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 3870 properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 3871 DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS); 3872 ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 3873 properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 3874 DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS); 3875 ALLOWED_TIME_PER_PERIOD_WORKING_MS = 3876 properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 3877 DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS); 3878 ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = 3879 properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 3880 DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS); 3881 ALLOWED_TIME_PER_PERIOD_RARE_MS = 3882 properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 3883 DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS); 3884 ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 3885 properties.getLong(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 3886 DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS); 3887 IN_QUOTA_BUFFER_MS = properties.getLong(KEY_IN_QUOTA_BUFFER_MS, 3888 DEFAULT_IN_QUOTA_BUFFER_MS); 3889 MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, 3890 DEFAULT_MAX_EXECUTION_TIME_MS); 3891 WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, 3892 DEFAULT_WINDOW_SIZE_EXEMPTED_MS); 3893 WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, 3894 DEFAULT_WINDOW_SIZE_ACTIVE_MS); 3895 WINDOW_SIZE_WORKING_MS = 3896 properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS); 3897 WINDOW_SIZE_FREQUENT_MS = 3898 properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS, 3899 DEFAULT_WINDOW_SIZE_FREQUENT_MS); 3900 WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS, 3901 DEFAULT_WINDOW_SIZE_RARE_MS); 3902 WINDOW_SIZE_RESTRICTED_MS = 3903 properties.getLong(KEY_WINDOW_SIZE_RESTRICTED_MS, 3904 DEFAULT_WINDOW_SIZE_RESTRICTED_MS); 3905 3906 long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS, 3907 Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS)); 3908 if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) { 3909 mMaxExecutionTimeMs = newMaxExecutionTimeMs; 3910 mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; 3911 mShouldReevaluateConstraints = true; 3912 } 3913 long minAllowedTimeMs = Long.MAX_VALUE; 3914 long newAllowedTimeExemptedMs = Math.min(mMaxExecutionTimeMs, 3915 Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS)); 3916 minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeExemptedMs); 3917 if (mAllowedTimePerPeriodMs[EXEMPTED_INDEX] != newAllowedTimeExemptedMs) { 3918 mAllowedTimePerPeriodMs[EXEMPTED_INDEX] = newAllowedTimeExemptedMs; 3919 mShouldReevaluateConstraints = true; 3920 } 3921 long newAllowedTimeActiveMs = Math.min(mMaxExecutionTimeMs, 3922 Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS)); 3923 minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeActiveMs); 3924 if (mAllowedTimePerPeriodMs[ACTIVE_INDEX] != newAllowedTimeActiveMs) { 3925 mAllowedTimePerPeriodMs[ACTIVE_INDEX] = newAllowedTimeActiveMs; 3926 mShouldReevaluateConstraints = true; 3927 } 3928 long newAllowedTimeWorkingMs = Math.min(mMaxExecutionTimeMs, 3929 Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_WORKING_MS)); 3930 minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeWorkingMs); 3931 if (mAllowedTimePerPeriodMs[WORKING_INDEX] != newAllowedTimeWorkingMs) { 3932 mAllowedTimePerPeriodMs[WORKING_INDEX] = newAllowedTimeWorkingMs; 3933 mShouldReevaluateConstraints = true; 3934 } 3935 long newAllowedTimeFrequentMs = Math.min(mMaxExecutionTimeMs, 3936 Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS)); 3937 minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeFrequentMs); 3938 if (mAllowedTimePerPeriodMs[FREQUENT_INDEX] != newAllowedTimeFrequentMs) { 3939 mAllowedTimePerPeriodMs[FREQUENT_INDEX] = newAllowedTimeFrequentMs; 3940 mShouldReevaluateConstraints = true; 3941 } 3942 long newAllowedTimeRareMs = Math.min(mMaxExecutionTimeMs, 3943 Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RARE_MS)); 3944 minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRareMs); 3945 if (mAllowedTimePerPeriodMs[RARE_INDEX] != newAllowedTimeRareMs) { 3946 mAllowedTimePerPeriodMs[RARE_INDEX] = newAllowedTimeRareMs; 3947 mShouldReevaluateConstraints = true; 3948 } 3949 long newAllowedTimeRestrictedMs = Math.min(mMaxExecutionTimeMs, 3950 Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS)); 3951 minAllowedTimeMs = Math.min(minAllowedTimeMs, newAllowedTimeRestrictedMs); 3952 if (mAllowedTimePerPeriodMs[RESTRICTED_INDEX] != newAllowedTimeRestrictedMs) { 3953 mAllowedTimePerPeriodMs[RESTRICTED_INDEX] = newAllowedTimeRestrictedMs; 3954 mShouldReevaluateConstraints = true; 3955 } 3956 // Make sure quota buffer is non-negative, not greater than allowed time per period, 3957 // and no more than 5 minutes. 3958 long newQuotaBufferMs = Math.max(0, Math.min(minAllowedTimeMs, 3959 Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS))); 3960 if (mQuotaBufferMs != newQuotaBufferMs) { 3961 mQuotaBufferMs = newQuotaBufferMs; 3962 mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs; 3963 mShouldReevaluateConstraints = true; 3964 } 3965 long newExemptedPeriodMs = Math.max(mAllowedTimePerPeriodMs[EXEMPTED_INDEX], 3966 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS)); 3967 if (mBucketPeriodsMs[EXEMPTED_INDEX] != newExemptedPeriodMs) { 3968 mBucketPeriodsMs[EXEMPTED_INDEX] = newExemptedPeriodMs; 3969 mShouldReevaluateConstraints = true; 3970 } 3971 long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs[ACTIVE_INDEX], 3972 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS)); 3973 if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) { 3974 mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs; 3975 mShouldReevaluateConstraints = true; 3976 } 3977 long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs[WORKING_INDEX], 3978 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS)); 3979 if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) { 3980 mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs; 3981 mShouldReevaluateConstraints = true; 3982 } 3983 long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs[FREQUENT_INDEX], 3984 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); 3985 if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) { 3986 mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs; 3987 mShouldReevaluateConstraints = true; 3988 } 3989 long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs[RARE_INDEX], 3990 Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS)); 3991 if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) { 3992 mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs; 3993 mShouldReevaluateConstraints = true; 3994 } 3995 // Fit in the range [allowed time (10 mins), 1 week]. 3996 long newRestrictedPeriodMs = Math.max(mAllowedTimePerPeriodMs[RESTRICTED_INDEX], 3997 Math.min(7 * 24 * 60 * MINUTE_IN_MILLIS, WINDOW_SIZE_RESTRICTED_MS)); 3998 if (mBucketPeriodsMs[RESTRICTED_INDEX] != newRestrictedPeriodMs) { 3999 mBucketPeriodsMs[RESTRICTED_INDEX] = newRestrictedPeriodMs; 4000 mShouldReevaluateConstraints = true; 4001 } 4002 } 4003 updateRateLimitingConstantsLocked()4004 private void updateRateLimitingConstantsLocked() { 4005 if (mRateLimitingConstantsUpdated) { 4006 return; 4007 } 4008 mRateLimitingConstantsUpdated = true; 4009 4010 // Query the values as an atomic set. 4011 final DeviceConfig.Properties properties = DeviceConfig.getProperties( 4012 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 4013 KEY_RATE_LIMITING_WINDOW_MS, KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 4014 KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); 4015 4016 RATE_LIMITING_WINDOW_MS = 4017 properties.getLong(KEY_RATE_LIMITING_WINDOW_MS, 4018 DEFAULT_RATE_LIMITING_WINDOW_MS); 4019 4020 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 4021 properties.getInt(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 4022 DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW); 4023 4024 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 4025 properties.getInt(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 4026 DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); 4027 4028 long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS, 4029 Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS)); 4030 if (mRateLimitingWindowMs != newRateLimitingWindowMs) { 4031 mRateLimitingWindowMs = newRateLimitingWindowMs; 4032 mShouldReevaluateConstraints = true; 4033 } 4034 int newMaxJobCountPerRateLimitingWindow = Math.max( 4035 MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 4036 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW); 4037 if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) { 4038 mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow; 4039 mShouldReevaluateConstraints = true; 4040 } 4041 int newMaxSessionCountPerRateLimitPeriod = Math.max( 4042 MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 4043 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); 4044 if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) { 4045 mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod; 4046 mShouldReevaluateConstraints = true; 4047 } 4048 } 4049 updateEJLimitConstantsLocked()4050 private void updateEJLimitConstantsLocked() { 4051 if (mEJLimitConstantsUpdated) { 4052 return; 4053 } 4054 mEJLimitConstantsUpdated = true; 4055 4056 // Query the values as an atomic set. 4057 final DeviceConfig.Properties properties = DeviceConfig.getProperties( 4058 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 4059 KEY_EJ_LIMIT_EXEMPTED_MS, 4060 KEY_EJ_LIMIT_ACTIVE_MS, KEY_EJ_LIMIT_WORKING_MS, 4061 KEY_EJ_LIMIT_FREQUENT_MS, KEY_EJ_LIMIT_RARE_MS, 4062 KEY_EJ_LIMIT_RESTRICTED_MS, KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, 4063 KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, 4064 KEY_EJ_WINDOW_SIZE_MS); 4065 EJ_LIMIT_EXEMPTED_MS = properties.getLong( 4066 KEY_EJ_LIMIT_EXEMPTED_MS, DEFAULT_EJ_LIMIT_EXEMPTED_MS); 4067 EJ_LIMIT_ACTIVE_MS = properties.getLong( 4068 KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS); 4069 EJ_LIMIT_WORKING_MS = properties.getLong( 4070 KEY_EJ_LIMIT_WORKING_MS, DEFAULT_EJ_LIMIT_WORKING_MS); 4071 EJ_LIMIT_FREQUENT_MS = properties.getLong( 4072 KEY_EJ_LIMIT_FREQUENT_MS, DEFAULT_EJ_LIMIT_FREQUENT_MS); 4073 EJ_LIMIT_RARE_MS = properties.getLong( 4074 KEY_EJ_LIMIT_RARE_MS, DEFAULT_EJ_LIMIT_RARE_MS); 4075 EJ_LIMIT_RESTRICTED_MS = properties.getLong( 4076 KEY_EJ_LIMIT_RESTRICTED_MS, DEFAULT_EJ_LIMIT_RESTRICTED_MS); 4077 EJ_LIMIT_ADDITION_INSTALLER_MS = properties.getLong( 4078 KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS); 4079 EJ_LIMIT_ADDITION_SPECIAL_MS = properties.getLong( 4080 KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS); 4081 EJ_WINDOW_SIZE_MS = properties.getLong( 4082 KEY_EJ_WINDOW_SIZE_MS, DEFAULT_EJ_WINDOW_SIZE_MS); 4083 4084 // The window must be in the range [1 hour, 24 hours]. 4085 long newWindowSizeMs = Math.max(HOUR_IN_MILLIS, 4086 Math.min(MAX_PERIOD_MS, EJ_WINDOW_SIZE_MS)); 4087 if (mEJLimitWindowSizeMs != newWindowSizeMs) { 4088 mEJLimitWindowSizeMs = newWindowSizeMs; 4089 mShouldReevaluateConstraints = true; 4090 } 4091 // The limit must be in the range [15 minutes, window size]. 4092 long newExemptLimitMs = Math.max(15 * MINUTE_IN_MILLIS, 4093 Math.min(newWindowSizeMs, EJ_LIMIT_EXEMPTED_MS)); 4094 if (mEJLimitsMs[EXEMPTED_INDEX] != newExemptLimitMs) { 4095 mEJLimitsMs[EXEMPTED_INDEX] = newExemptLimitMs; 4096 mShouldReevaluateConstraints = true; 4097 } 4098 // The limit must be in the range [15 minutes, exempted limit]. 4099 long newActiveLimitMs = Math.max(15 * MINUTE_IN_MILLIS, 4100 Math.min(newExemptLimitMs, EJ_LIMIT_ACTIVE_MS)); 4101 if (mEJLimitsMs[ACTIVE_INDEX] != newActiveLimitMs) { 4102 mEJLimitsMs[ACTIVE_INDEX] = newActiveLimitMs; 4103 mShouldReevaluateConstraints = true; 4104 } 4105 // The limit must be in the range [15 minutes, active limit]. 4106 long newWorkingLimitMs = Math.max(15 * MINUTE_IN_MILLIS, 4107 Math.min(newActiveLimitMs, EJ_LIMIT_WORKING_MS)); 4108 if (mEJLimitsMs[WORKING_INDEX] != newWorkingLimitMs) { 4109 mEJLimitsMs[WORKING_INDEX] = newWorkingLimitMs; 4110 mShouldReevaluateConstraints = true; 4111 } 4112 // The limit must be in the range [10 minutes, working limit]. 4113 long newFrequentLimitMs = Math.max(10 * MINUTE_IN_MILLIS, 4114 Math.min(newWorkingLimitMs, EJ_LIMIT_FREQUENT_MS)); 4115 if (mEJLimitsMs[FREQUENT_INDEX] != newFrequentLimitMs) { 4116 mEJLimitsMs[FREQUENT_INDEX] = newFrequentLimitMs; 4117 mShouldReevaluateConstraints = true; 4118 } 4119 // The limit must be in the range [10 minutes, frequent limit]. 4120 long newRareLimitMs = Math.max(10 * MINUTE_IN_MILLIS, 4121 Math.min(newFrequentLimitMs, EJ_LIMIT_RARE_MS)); 4122 if (mEJLimitsMs[RARE_INDEX] != newRareLimitMs) { 4123 mEJLimitsMs[RARE_INDEX] = newRareLimitMs; 4124 mShouldReevaluateConstraints = true; 4125 } 4126 // The limit must be in the range [5 minutes, rare limit]. 4127 long newRestrictedLimitMs = Math.max(5 * MINUTE_IN_MILLIS, 4128 Math.min(newRareLimitMs, EJ_LIMIT_RESTRICTED_MS)); 4129 if (mEJLimitsMs[RESTRICTED_INDEX] != newRestrictedLimitMs) { 4130 mEJLimitsMs[RESTRICTED_INDEX] = newRestrictedLimitMs; 4131 mShouldReevaluateConstraints = true; 4132 } 4133 // The additions must be in the range [0 minutes, window size - active limit]. 4134 long newAdditionInstallerMs = Math.max(0, 4135 Math.min(newWindowSizeMs - newActiveLimitMs, EJ_LIMIT_ADDITION_INSTALLER_MS)); 4136 if (mEjLimitAdditionInstallerMs != newAdditionInstallerMs) { 4137 mEjLimitAdditionInstallerMs = newAdditionInstallerMs; 4138 mShouldReevaluateConstraints = true; 4139 } 4140 long newAdditionSpecialMs = Math.max(0, 4141 Math.min(newWindowSizeMs - newActiveLimitMs, EJ_LIMIT_ADDITION_SPECIAL_MS)); 4142 if (mEjLimitAdditionSpecialMs != newAdditionSpecialMs) { 4143 mEjLimitAdditionSpecialMs = newAdditionSpecialMs; 4144 mShouldReevaluateConstraints = true; 4145 } 4146 } 4147 updateQuotaBumpConstantsLocked()4148 private void updateQuotaBumpConstantsLocked() { 4149 if (mQuotaBumpConstantsUpdated) { 4150 return; 4151 } 4152 mQuotaBumpConstantsUpdated = true; 4153 4154 // Query the values as an atomic set. 4155 final DeviceConfig.Properties properties = DeviceConfig.getProperties( 4156 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 4157 KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 4158 KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 4159 KEY_QUOTA_BUMP_WINDOW_SIZE_MS, KEY_QUOTA_BUMP_LIMIT); 4160 QUOTA_BUMP_ADDITIONAL_DURATION_MS = properties.getLong( 4161 KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 4162 DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS); 4163 QUOTA_BUMP_ADDITIONAL_JOB_COUNT = properties.getInt( 4164 KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT); 4165 QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = properties.getInt( 4166 KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 4167 DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT); 4168 QUOTA_BUMP_WINDOW_SIZE_MS = properties.getLong( 4169 KEY_QUOTA_BUMP_WINDOW_SIZE_MS, DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS); 4170 QUOTA_BUMP_LIMIT = properties.getInt( 4171 KEY_QUOTA_BUMP_LIMIT, DEFAULT_QUOTA_BUMP_LIMIT); 4172 4173 // The window must be in the range [1 hour, 24 hours]. 4174 long newWindowSizeMs = Math.max(HOUR_IN_MILLIS, 4175 Math.min(MAX_PERIOD_MS, QUOTA_BUMP_WINDOW_SIZE_MS)); 4176 if (mQuotaBumpWindowSizeMs != newWindowSizeMs) { 4177 mQuotaBumpWindowSizeMs = newWindowSizeMs; 4178 mShouldReevaluateConstraints = true; 4179 } 4180 // The limit must be nonnegative. 4181 int newLimit = Math.max(0, QUOTA_BUMP_LIMIT); 4182 if (mQuotaBumpLimit != newLimit) { 4183 mQuotaBumpLimit = newLimit; 4184 mShouldReevaluateConstraints = true; 4185 } 4186 // The job count must be nonnegative. 4187 int newJobAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_JOB_COUNT); 4188 if (mQuotaBumpAdditionalJobCount != newJobAddition) { 4189 mQuotaBumpAdditionalJobCount = newJobAddition; 4190 mShouldReevaluateConstraints = true; 4191 } 4192 // The session count must be nonnegative. 4193 int newSessionAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_SESSION_COUNT); 4194 if (mQuotaBumpAdditionalSessionCount != newSessionAddition) { 4195 mQuotaBumpAdditionalSessionCount = newSessionAddition; 4196 mShouldReevaluateConstraints = true; 4197 } 4198 // The additional duration must be in the range [0, 10 minutes]. 4199 long newAdditionalDuration = Math.max(0, 4200 Math.min(10 * MINUTE_IN_MILLIS, QUOTA_BUMP_ADDITIONAL_DURATION_MS)); 4201 if (mQuotaBumpAdditionalDurationMs != newAdditionalDuration) { 4202 mQuotaBumpAdditionalDurationMs = newAdditionalDuration; 4203 mShouldReevaluateConstraints = true; 4204 } 4205 } 4206 dump(IndentingPrintWriter pw)4207 private void dump(IndentingPrintWriter pw) { 4208 pw.println(); 4209 pw.println("QuotaController:"); 4210 pw.increaseIndent(); 4211 pw.print(KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS) 4212 .println(); 4213 pw.print(KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, ALLOWED_TIME_PER_PERIOD_ACTIVE_MS) 4214 .println(); 4215 pw.print(KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, ALLOWED_TIME_PER_PERIOD_WORKING_MS) 4216 .println(); 4217 pw.print(KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, ALLOWED_TIME_PER_PERIOD_FREQUENT_MS) 4218 .println(); 4219 pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, ALLOWED_TIME_PER_PERIOD_RARE_MS) 4220 .println(); 4221 pw.print(KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 4222 ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS).println(); 4223 pw.print(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println(); 4224 pw.print(KEY_WINDOW_SIZE_EXEMPTED_MS, WINDOW_SIZE_EXEMPTED_MS).println(); 4225 pw.print(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println(); 4226 pw.print(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println(); 4227 pw.print(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println(); 4228 pw.print(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println(); 4229 pw.print(KEY_WINDOW_SIZE_RESTRICTED_MS, WINDOW_SIZE_RESTRICTED_MS).println(); 4230 pw.print(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println(); 4231 pw.print(KEY_MAX_JOB_COUNT_EXEMPTED, MAX_JOB_COUNT_EXEMPTED).println(); 4232 pw.print(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println(); 4233 pw.print(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println(); 4234 pw.print(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println(); 4235 pw.print(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println(); 4236 pw.print(KEY_MAX_JOB_COUNT_RESTRICTED, MAX_JOB_COUNT_RESTRICTED).println(); 4237 pw.print(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println(); 4238 pw.print(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 4239 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println(); 4240 pw.print(KEY_MAX_SESSION_COUNT_EXEMPTED, MAX_SESSION_COUNT_EXEMPTED).println(); 4241 pw.print(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println(); 4242 pw.print(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println(); 4243 pw.print(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println(); 4244 pw.print(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println(); 4245 pw.print(KEY_MAX_SESSION_COUNT_RESTRICTED, MAX_SESSION_COUNT_RESTRICTED).println(); 4246 pw.print(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 4247 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println(); 4248 pw.print(KEY_TIMING_SESSION_COALESCING_DURATION_MS, 4249 TIMING_SESSION_COALESCING_DURATION_MS).println(); 4250 pw.print(KEY_MIN_QUOTA_CHECK_DELAY_MS, MIN_QUOTA_CHECK_DELAY_MS).println(); 4251 4252 pw.print(KEY_EJ_LIMIT_EXEMPTED_MS, EJ_LIMIT_EXEMPTED_MS).println(); 4253 pw.print(KEY_EJ_LIMIT_ACTIVE_MS, EJ_LIMIT_ACTIVE_MS).println(); 4254 pw.print(KEY_EJ_LIMIT_WORKING_MS, EJ_LIMIT_WORKING_MS).println(); 4255 pw.print(KEY_EJ_LIMIT_FREQUENT_MS, EJ_LIMIT_FREQUENT_MS).println(); 4256 pw.print(KEY_EJ_LIMIT_RARE_MS, EJ_LIMIT_RARE_MS).println(); 4257 pw.print(KEY_EJ_LIMIT_RESTRICTED_MS, EJ_LIMIT_RESTRICTED_MS).println(); 4258 pw.print(KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, EJ_LIMIT_ADDITION_INSTALLER_MS).println(); 4259 pw.print(KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, EJ_LIMIT_ADDITION_SPECIAL_MS).println(); 4260 pw.print(KEY_EJ_WINDOW_SIZE_MS, EJ_WINDOW_SIZE_MS).println(); 4261 pw.print(KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, EJ_TOP_APP_TIME_CHUNK_SIZE_MS).println(); 4262 pw.print(KEY_EJ_REWARD_TOP_APP_MS, EJ_REWARD_TOP_APP_MS).println(); 4263 pw.print(KEY_EJ_REWARD_INTERACTION_MS, EJ_REWARD_INTERACTION_MS).println(); 4264 pw.print(KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, EJ_REWARD_NOTIFICATION_SEEN_MS).println(); 4265 pw.print(KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 4266 EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS).println(); 4267 pw.print(KEY_EJ_GRACE_PERIOD_TOP_APP_MS, EJ_GRACE_PERIOD_TOP_APP_MS).println(); 4268 4269 pw.print(KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 4270 QUOTA_BUMP_ADDITIONAL_DURATION_MS).println(); 4271 pw.print(KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 4272 QUOTA_BUMP_ADDITIONAL_JOB_COUNT).println(); 4273 pw.print(KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 4274 QUOTA_BUMP_ADDITIONAL_SESSION_COUNT).println(); 4275 pw.print(KEY_QUOTA_BUMP_WINDOW_SIZE_MS, QUOTA_BUMP_WINDOW_SIZE_MS).println(); 4276 pw.print(KEY_QUOTA_BUMP_LIMIT, QUOTA_BUMP_LIMIT).println(); 4277 4278 pw.decreaseIndent(); 4279 } 4280 dump(ProtoOutputStream proto)4281 private void dump(ProtoOutputStream proto) { 4282 final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER); 4283 proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS); 4284 proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS, 4285 WINDOW_SIZE_ACTIVE_MS); 4286 proto.write(ConstantsProto.QuotaController.WORKING_WINDOW_SIZE_MS, 4287 WINDOW_SIZE_WORKING_MS); 4288 proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS, 4289 WINDOW_SIZE_FREQUENT_MS); 4290 proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS); 4291 proto.write(ConstantsProto.QuotaController.RESTRICTED_WINDOW_SIZE_MS, 4292 WINDOW_SIZE_RESTRICTED_MS); 4293 proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS, 4294 MAX_EXECUTION_TIME_MS); 4295 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE); 4296 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING, 4297 MAX_JOB_COUNT_WORKING); 4298 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT, 4299 MAX_JOB_COUNT_FREQUENT); 4300 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE); 4301 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RESTRICTED, 4302 MAX_JOB_COUNT_RESTRICTED); 4303 proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS, 4304 RATE_LIMITING_WINDOW_MS); 4305 proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 4306 MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW); 4307 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE, 4308 MAX_SESSION_COUNT_ACTIVE); 4309 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING, 4310 MAX_SESSION_COUNT_WORKING); 4311 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_FREQUENT, 4312 MAX_SESSION_COUNT_FREQUENT); 4313 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE, 4314 MAX_SESSION_COUNT_RARE); 4315 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RESTRICTED, 4316 MAX_SESSION_COUNT_RESTRICTED); 4317 proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 4318 MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW); 4319 proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS, 4320 TIMING_SESSION_COALESCING_DURATION_MS); 4321 proto.write(ConstantsProto.QuotaController.MIN_QUOTA_CHECK_DELAY_MS, 4322 MIN_QUOTA_CHECK_DELAY_MS); 4323 4324 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_LIMIT_ACTIVE_MS, 4325 EJ_LIMIT_ACTIVE_MS); 4326 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_LIMIT_WORKING_MS, 4327 EJ_LIMIT_WORKING_MS); 4328 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_LIMIT_FREQUENT_MS, 4329 EJ_LIMIT_FREQUENT_MS); 4330 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_LIMIT_RARE_MS, 4331 EJ_LIMIT_RARE_MS); 4332 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_LIMIT_RESTRICTED_MS, 4333 EJ_LIMIT_RESTRICTED_MS); 4334 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_WINDOW_SIZE_MS, 4335 EJ_WINDOW_SIZE_MS); 4336 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_TOP_APP_TIME_CHUNK_SIZE_MS, 4337 EJ_TOP_APP_TIME_CHUNK_SIZE_MS); 4338 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_REWARD_TOP_APP_MS, 4339 EJ_REWARD_TOP_APP_MS); 4340 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_REWARD_INTERACTION_MS, 4341 EJ_REWARD_INTERACTION_MS); 4342 proto.write(ConstantsProto.QuotaController.EXPEDITED_JOB_REWARD_NOTIFICATION_SEEN_MS, 4343 EJ_REWARD_NOTIFICATION_SEEN_MS); 4344 4345 proto.end(qcToken); 4346 } 4347 } 4348 4349 //////////////////////// TESTING HELPERS ///////////////////////////// 4350 4351 @VisibleForTesting getAllowedTimePerPeriodMs()4352 long[] getAllowedTimePerPeriodMs() { 4353 return mAllowedTimePerPeriodMs; 4354 } 4355 4356 @VisibleForTesting 4357 @NonNull getBucketMaxJobCounts()4358 int[] getBucketMaxJobCounts() { 4359 return mMaxBucketJobCounts; 4360 } 4361 4362 @VisibleForTesting 4363 @NonNull getBucketMaxSessionCounts()4364 int[] getBucketMaxSessionCounts() { 4365 return mMaxBucketSessionCounts; 4366 } 4367 4368 @VisibleForTesting 4369 @NonNull getBucketWindowSizes()4370 long[] getBucketWindowSizes() { 4371 return mBucketPeriodsMs; 4372 } 4373 4374 @VisibleForTesting 4375 @NonNull getForegroundUids()4376 SparseBooleanArray getForegroundUids() { 4377 return mForegroundUids; 4378 } 4379 4380 @VisibleForTesting 4381 @NonNull getHandler()4382 Handler getHandler() { 4383 return mHandler; 4384 } 4385 4386 @VisibleForTesting getEJGracePeriodTempAllowlistMs()4387 long getEJGracePeriodTempAllowlistMs() { 4388 return mEJGracePeriodTempAllowlistMs; 4389 } 4390 4391 @VisibleForTesting getEJGracePeriodTopAppMs()4392 long getEJGracePeriodTopAppMs() { 4393 return mEJGracePeriodTopAppMs; 4394 } 4395 4396 @VisibleForTesting 4397 @NonNull getEJLimitsMs()4398 long[] getEJLimitsMs() { 4399 return mEJLimitsMs; 4400 } 4401 4402 @VisibleForTesting getEjLimitAdditionInstallerMs()4403 long getEjLimitAdditionInstallerMs() { 4404 return mEjLimitAdditionInstallerMs; 4405 } 4406 4407 @VisibleForTesting getEjLimitAdditionSpecialMs()4408 long getEjLimitAdditionSpecialMs() { 4409 return mEjLimitAdditionSpecialMs; 4410 } 4411 4412 @VisibleForTesting 4413 @NonNull getEJLimitWindowSizeMs()4414 long getEJLimitWindowSizeMs() { 4415 return mEJLimitWindowSizeMs; 4416 } 4417 4418 @VisibleForTesting 4419 @NonNull getEJRewardInteractionMs()4420 long getEJRewardInteractionMs() { 4421 return mEJRewardInteractionMs; 4422 } 4423 4424 @VisibleForTesting 4425 @NonNull getEJRewardNotificationSeenMs()4426 long getEJRewardNotificationSeenMs() { 4427 return mEJRewardNotificationSeenMs; 4428 } 4429 4430 @VisibleForTesting 4431 @NonNull getEJRewardTopAppMs()4432 long getEJRewardTopAppMs() { 4433 return mEJRewardTopAppMs; 4434 } 4435 4436 @VisibleForTesting 4437 @Nullable getEJTimingSessions(int userId, String packageName)4438 List<TimedEvent> getEJTimingSessions(int userId, String packageName) { 4439 return mEJTimingSessions.get(userId, packageName); 4440 } 4441 4442 @VisibleForTesting 4443 @NonNull getEJTopAppTimeChunkSizeMs()4444 long getEJTopAppTimeChunkSizeMs() { 4445 return mEJTopAppTimeChunkSizeMs; 4446 } 4447 4448 @VisibleForTesting getInQuotaBufferMs()4449 long getInQuotaBufferMs() { 4450 return mQuotaBufferMs; 4451 } 4452 4453 @VisibleForTesting getMaxExecutionTimeMs()4454 long getMaxExecutionTimeMs() { 4455 return mMaxExecutionTimeMs; 4456 } 4457 4458 @VisibleForTesting getMaxJobCountPerRateLimitingWindow()4459 int getMaxJobCountPerRateLimitingWindow() { 4460 return mMaxJobCountPerRateLimitingWindow; 4461 } 4462 4463 @VisibleForTesting getMaxSessionCountPerRateLimitingWindow()4464 int getMaxSessionCountPerRateLimitingWindow() { 4465 return mMaxSessionCountPerRateLimitingWindow; 4466 } 4467 4468 @VisibleForTesting getMinQuotaCheckDelayMs()4469 long getMinQuotaCheckDelayMs() { 4470 return mInQuotaAlarmQueue.getMinTimeBetweenAlarmsMs(); 4471 } 4472 4473 @VisibleForTesting getRateLimitingWindowMs()4474 long getRateLimitingWindowMs() { 4475 return mRateLimitingWindowMs; 4476 } 4477 4478 @VisibleForTesting getTimingSessionCoalescingDurationMs()4479 long getTimingSessionCoalescingDurationMs() { 4480 return mTimingSessionCoalescingDurationMs; 4481 } 4482 4483 @VisibleForTesting 4484 @Nullable getTimingSessions(int userId, String packageName)4485 List<TimedEvent> getTimingSessions(int userId, String packageName) { 4486 return mTimingEvents.get(userId, packageName); 4487 } 4488 4489 @VisibleForTesting 4490 @NonNull getQcConstants()4491 QcConstants getQcConstants() { 4492 return mQcConstants; 4493 } 4494 4495 @VisibleForTesting getQuotaBumpAdditionDurationMs()4496 long getQuotaBumpAdditionDurationMs() { 4497 return mQuotaBumpAdditionalDurationMs; 4498 } 4499 4500 @VisibleForTesting getQuotaBumpAdditionJobCount()4501 int getQuotaBumpAdditionJobCount() { 4502 return mQuotaBumpAdditionalJobCount; 4503 } 4504 4505 @VisibleForTesting getQuotaBumpAdditionSessionCount()4506 int getQuotaBumpAdditionSessionCount() { 4507 return mQuotaBumpAdditionalSessionCount; 4508 } 4509 4510 @VisibleForTesting getQuotaBumpLimit()4511 int getQuotaBumpLimit() { 4512 return mQuotaBumpLimit; 4513 } 4514 4515 @VisibleForTesting getQuotaBumpWindowSizeMs()4516 long getQuotaBumpWindowSizeMs() { 4517 return mQuotaBumpWindowSizeMs; 4518 } 4519 4520 //////////////////////////// DATA DUMP ////////////////////////////// 4521 4522 @NeverCompile // Avoid size overhead of debugging code. 4523 @Override dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate)4524 public void dumpControllerStateLocked(final IndentingPrintWriter pw, 4525 final Predicate<JobStatus> predicate) { 4526 pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis()); 4527 pw.println(); 4528 4529 pw.print("Foreground UIDs: "); 4530 pw.println(mForegroundUids.toString()); 4531 pw.println(); 4532 4533 pw.print("Cached top apps: "); 4534 pw.println(mTopAppCache.toString()); 4535 pw.print("Cached top app grace period: "); 4536 pw.println(mTopAppGraceCache.toString()); 4537 4538 pw.print("Cached temp allowlist: "); 4539 pw.println(mTempAllowlistCache.toString()); 4540 pw.print("Cached temp allowlist grace period: "); 4541 pw.println(mTempAllowlistGraceCache.toString()); 4542 pw.println(); 4543 4544 pw.println("Special apps:"); 4545 pw.increaseIndent(); 4546 pw.print("System installers={"); 4547 for (int si = 0; si < mSystemInstallers.size(); ++si) { 4548 if (si > 0) { 4549 pw.print(", "); 4550 } 4551 pw.print(mSystemInstallers.keyAt(si)); 4552 pw.print("->"); 4553 pw.print(mSystemInstallers.get(si)); 4554 } 4555 pw.println("}"); 4556 pw.decreaseIndent(); 4557 4558 pw.println(); 4559 mTrackedJobs.forEach((jobs) -> { 4560 for (int j = 0; j < jobs.size(); j++) { 4561 final JobStatus js = jobs.valueAt(j); 4562 if (!predicate.test(js)) { 4563 continue; 4564 } 4565 pw.print("#"); 4566 js.printUniqueId(pw); 4567 pw.print(" from "); 4568 UserHandle.formatUid(pw, js.getSourceUid()); 4569 if (mTopStartedJobs.contains(js)) { 4570 pw.print(" (TOP)"); 4571 } 4572 pw.println(); 4573 4574 pw.increaseIndent(); 4575 pw.print(JobStatus.bucketName(js.getEffectiveStandbyBucket())); 4576 pw.print(", "); 4577 if (js.shouldTreatAsExpeditedJob()) { 4578 pw.print("within EJ quota"); 4579 } else if (js.startedAsExpeditedJob) { 4580 pw.print("out of EJ quota"); 4581 } else if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { 4582 pw.print("within regular quota"); 4583 } else { 4584 pw.print("not within quota"); 4585 } 4586 pw.print(", "); 4587 if (js.shouldTreatAsExpeditedJob()) { 4588 pw.print(getRemainingEJExecutionTimeLocked( 4589 js.getSourceUserId(), js.getSourcePackageName())); 4590 pw.print("ms remaining in EJ quota"); 4591 } else if (js.startedAsExpeditedJob) { 4592 pw.print("should be stopped after min execution time"); 4593 } else { 4594 pw.print(getRemainingExecutionTimeLocked(js)); 4595 pw.print("ms remaining in quota"); 4596 } 4597 pw.println(); 4598 pw.decreaseIndent(); 4599 } 4600 }); 4601 4602 pw.println(); 4603 for (int u = 0; u < mPkgTimers.numMaps(); ++u) { 4604 final int userId = mPkgTimers.keyAt(u); 4605 for (int p = 0; p < mPkgTimers.numElementsForKey(userId); ++p) { 4606 final String pkgName = mPkgTimers.keyAt(u, p); 4607 mPkgTimers.valueAt(u, p).dump(pw, predicate); 4608 pw.println(); 4609 List<TimedEvent> events = mTimingEvents.get(userId, pkgName); 4610 if (events != null) { 4611 pw.increaseIndent(); 4612 pw.println("Saved events:"); 4613 pw.increaseIndent(); 4614 for (int j = events.size() - 1; j >= 0; j--) { 4615 TimedEvent event = events.get(j); 4616 event.dump(pw); 4617 } 4618 pw.decreaseIndent(); 4619 pw.decreaseIndent(); 4620 pw.println(); 4621 } 4622 } 4623 } 4624 4625 pw.println(); 4626 for (int u = 0; u < mEJPkgTimers.numMaps(); ++u) { 4627 final int userId = mEJPkgTimers.keyAt(u); 4628 for (int p = 0; p < mEJPkgTimers.numElementsForKey(userId); ++p) { 4629 final String pkgName = mEJPkgTimers.keyAt(u, p); 4630 mEJPkgTimers.valueAt(u, p).dump(pw, predicate); 4631 pw.println(); 4632 List<TimedEvent> sessions = mEJTimingSessions.get(userId, pkgName); 4633 if (sessions != null) { 4634 pw.increaseIndent(); 4635 pw.println("Saved sessions:"); 4636 pw.increaseIndent(); 4637 for (int j = sessions.size() - 1; j >= 0; j--) { 4638 TimedEvent session = sessions.get(j); 4639 session.dump(pw); 4640 } 4641 pw.decreaseIndent(); 4642 pw.decreaseIndent(); 4643 pw.println(); 4644 } 4645 } 4646 } 4647 4648 pw.println(); 4649 mTopAppTrackers.forEach((timer) -> timer.dump(pw)); 4650 4651 pw.println(); 4652 pw.println("Cached execution stats:"); 4653 pw.increaseIndent(); 4654 for (int u = 0; u < mExecutionStatsCache.numMaps(); ++u) { 4655 final int userId = mExecutionStatsCache.keyAt(u); 4656 for (int p = 0; p < mExecutionStatsCache.numElementsForKey(userId); ++p) { 4657 final String pkgName = mExecutionStatsCache.keyAt(u, p); 4658 ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p); 4659 4660 pw.println(packageToString(userId, pkgName)); 4661 pw.increaseIndent(); 4662 for (int i = 0; i < stats.length; ++i) { 4663 ExecutionStats executionStats = stats[i]; 4664 if (executionStats != null) { 4665 pw.print(JobStatus.bucketName(i)); 4666 pw.print(": "); 4667 pw.println(executionStats); 4668 } 4669 } 4670 pw.decreaseIndent(); 4671 } 4672 } 4673 pw.decreaseIndent(); 4674 4675 pw.println(); 4676 pw.println("EJ debits:"); 4677 pw.increaseIndent(); 4678 for (int u = 0; u < mEJStats.numMaps(); ++u) { 4679 final int userId = mEJStats.keyAt(u); 4680 for (int p = 0; p < mEJStats.numElementsForKey(userId); ++p) { 4681 final String pkgName = mEJStats.keyAt(u, p); 4682 ShrinkableDebits debits = mEJStats.valueAt(u, p); 4683 4684 pw.print(packageToString(userId, pkgName)); 4685 pw.print(": "); 4686 debits.dumpLocked(pw); 4687 } 4688 } 4689 pw.decreaseIndent(); 4690 4691 pw.println(); 4692 mInQuotaAlarmQueue.dump(pw); 4693 pw.decreaseIndent(); 4694 } 4695 4696 @Override dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)4697 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, 4698 Predicate<JobStatus> predicate) { 4699 final long token = proto.start(fieldId); 4700 final long mToken = proto.start(StateControllerProto.QUOTA); 4701 4702 proto.write(StateControllerProto.QuotaController.IS_CHARGING, 4703 mService.isBatteryCharging()); 4704 proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME, 4705 sElapsedRealtimeClock.millis()); 4706 4707 for (int i = 0; i < mForegroundUids.size(); ++i) { 4708 proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS, 4709 mForegroundUids.keyAt(i)); 4710 } 4711 4712 mTrackedJobs.forEach((jobs) -> { 4713 for (int j = 0; j < jobs.size(); j++) { 4714 final JobStatus js = jobs.valueAt(j); 4715 if (!predicate.test(js)) { 4716 continue; 4717 } 4718 final long jsToken = proto.start(StateControllerProto.QuotaController.TRACKED_JOBS); 4719 js.writeToShortProto(proto, StateControllerProto.QuotaController.TrackedJob.INFO); 4720 proto.write(StateControllerProto.QuotaController.TrackedJob.SOURCE_UID, 4721 js.getSourceUid()); 4722 proto.write( 4723 StateControllerProto.QuotaController.TrackedJob.EFFECTIVE_STANDBY_BUCKET, 4724 js.getEffectiveStandbyBucket()); 4725 proto.write(StateControllerProto.QuotaController.TrackedJob.IS_TOP_STARTED_JOB, 4726 mTopStartedJobs.contains(js)); 4727 proto.write(StateControllerProto.QuotaController.TrackedJob.HAS_QUOTA, 4728 js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4729 proto.write(StateControllerProto.QuotaController.TrackedJob.REMAINING_QUOTA_MS, 4730 getRemainingExecutionTimeLocked(js)); 4731 proto.write( 4732 StateControllerProto.QuotaController.TrackedJob.IS_REQUESTED_FOREGROUND_JOB, 4733 js.isRequestedExpeditedJob()); 4734 proto.write( 4735 StateControllerProto.QuotaController.TrackedJob.IS_WITHIN_FG_JOB_QUOTA, 4736 js.isExpeditedQuotaApproved()); 4737 proto.end(jsToken); 4738 } 4739 }); 4740 4741 for (int u = 0; u < mPkgTimers.numMaps(); ++u) { 4742 final int userId = mPkgTimers.keyAt(u); 4743 for (int p = 0; p < mPkgTimers.numElementsForKey(userId); ++p) { 4744 final String pkgName = mPkgTimers.keyAt(u, p); 4745 final long psToken = proto.start( 4746 StateControllerProto.QuotaController.PACKAGE_STATS); 4747 4748 mPkgTimers.valueAt(u, p).dump(proto, 4749 StateControllerProto.QuotaController.PackageStats.TIMER, predicate); 4750 final Timer ejTimer = mEJPkgTimers.get(userId, pkgName); 4751 if (ejTimer != null) { 4752 ejTimer.dump(proto, 4753 StateControllerProto.QuotaController.PackageStats.FG_JOB_TIMER, 4754 predicate); 4755 } 4756 4757 List<TimedEvent> events = mTimingEvents.get(userId, pkgName); 4758 if (events != null) { 4759 for (int j = events.size() - 1; j >= 0; j--) { 4760 TimedEvent event = events.get(j); 4761 if (!(event instanceof TimingSession)) { 4762 continue; 4763 } 4764 TimingSession session = (TimingSession) event; 4765 session.dump(proto, 4766 StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS); 4767 } 4768 } 4769 4770 ExecutionStats[] stats = mExecutionStatsCache.get(userId, pkgName); 4771 if (stats != null) { 4772 for (int bucketIndex = 0; bucketIndex < stats.length; ++bucketIndex) { 4773 ExecutionStats es = stats[bucketIndex]; 4774 if (es == null) { 4775 continue; 4776 } 4777 final long esToken = proto.start( 4778 StateControllerProto.QuotaController.PackageStats.EXECUTION_STATS); 4779 proto.write( 4780 StateControllerProto.QuotaController.ExecutionStats.STANDBY_BUCKET, 4781 bucketIndex); 4782 proto.write( 4783 StateControllerProto.QuotaController.ExecutionStats.EXPIRATION_TIME_ELAPSED, 4784 es.expirationTimeElapsed); 4785 proto.write( 4786 StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS, 4787 es.windowSizeMs); 4788 proto.write( 4789 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT, 4790 es.jobCountLimit); 4791 proto.write( 4792 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT, 4793 es.sessionCountLimit); 4794 proto.write( 4795 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS, 4796 es.executionTimeInWindowMs); 4797 proto.write( 4798 StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_WINDOW, 4799 es.bgJobCountInWindow); 4800 proto.write( 4801 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_MAX_PERIOD_MS, 4802 es.executionTimeInMaxPeriodMs); 4803 proto.write( 4804 StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_MAX_PERIOD, 4805 es.bgJobCountInMaxPeriod); 4806 proto.write( 4807 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW, 4808 es.sessionCountInWindow); 4809 proto.write( 4810 StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED, 4811 es.inQuotaTimeElapsed); 4812 proto.write( 4813 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED, 4814 es.jobRateLimitExpirationTimeElapsed); 4815 proto.write( 4816 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW, 4817 es.jobCountInRateLimitingWindow); 4818 proto.write( 4819 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED, 4820 es.sessionRateLimitExpirationTimeElapsed); 4821 proto.write( 4822 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW, 4823 es.sessionCountInRateLimitingWindow); 4824 proto.end(esToken); 4825 } 4826 } 4827 4828 proto.end(psToken); 4829 } 4830 } 4831 4832 proto.end(mToken); 4833 proto.end(token); 4834 } 4835 4836 @Override dumpConstants(IndentingPrintWriter pw)4837 public void dumpConstants(IndentingPrintWriter pw) { 4838 mQcConstants.dump(pw); 4839 } 4840 4841 @Override dumpConstants(ProtoOutputStream proto)4842 public void dumpConstants(ProtoOutputStream proto) { 4843 mQcConstants.dump(proto); 4844 } 4845 } 4846