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