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 import static com.android.server.job.controllers.Package.packageToString; 32 33 import android.Manifest; 34 import android.annotation.NonNull; 35 import android.annotation.Nullable; 36 import android.annotation.UserIdInt; 37 import android.app.ActivityManager; 38 import android.app.AlarmManager; 39 import android.app.IUidObserver; 40 import android.app.usage.UsageEvents; 41 import android.app.usage.UsageStatsManagerInternal; 42 import android.app.usage.UsageStatsManagerInternal.UsageEventListener; 43 import android.content.Context; 44 import android.content.pm.ApplicationInfo; 45 import android.content.pm.PackageInfo; 46 import android.content.pm.PackageManager; 47 import android.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.JobSchedulerBackgroundThread; 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 IUidObserver.Stub { 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 @Override onUidGone(int uid, boolean disabled)392 public void onUidGone(int uid, boolean disabled) { 393 } 394 395 @Override onUidActive(int uid)396 public void onUidActive(int uid) { 397 } 398 399 @Override onUidIdle(int uid, boolean disabled)400 public void onUidIdle(int uid, boolean disabled) { 401 } 402 403 @Override onUidCachedChanged(int uid, boolean cached)404 public void onUidCachedChanged(int uid, boolean cached) { 405 } 406 407 @Override onUidProcAdjChanged(int uid)408 public void onUidProcAdjChanged(int uid) { 409 } 410 } 411 412 /** 413 * The rolling window size for each standby bucket. Within each window, an app will have 10 414 * minutes to run its jobs. 415 */ 416 private final long[] mBucketPeriodsMs = new long[]{ 417 QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS, 418 QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS, 419 QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS, 420 QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS, 421 0, // NEVER 422 QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS, 423 QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS 424 }; 425 426 /** The maximum period any bucket can have. */ 427 private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS; 428 429 /** 430 * The maximum number of jobs based on its standby bucket. For each max value count in the 431 * array, the app will not be allowed to run more than that many number of jobs within the 432 * latest time interval of its rolling window size. 433 * 434 * @see #mBucketPeriodsMs 435 */ 436 private final int[] mMaxBucketJobCounts = new int[]{ 437 QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE, 438 QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING, 439 QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT, 440 QcConstants.DEFAULT_MAX_JOB_COUNT_RARE, 441 0, // NEVER 442 QcConstants.DEFAULT_MAX_JOB_COUNT_RESTRICTED, 443 QcConstants.DEFAULT_MAX_JOB_COUNT_EXEMPTED 444 }; 445 446 /** 447 * The maximum number of {@link TimingSession TimingSessions} based on its standby bucket. 448 * For each max value count in the array, the app will not be allowed to have more than that 449 * many number of {@link TimingSession TimingSessions} within the latest time interval of its 450 * rolling window size. 451 * 452 * @see #mBucketPeriodsMs 453 */ 454 private final int[] mMaxBucketSessionCounts = new int[]{ 455 QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE, 456 QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING, 457 QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT, 458 QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE, 459 0, // NEVER 460 QcConstants.DEFAULT_MAX_SESSION_COUNT_RESTRICTED, 461 QcConstants.DEFAULT_MAX_SESSION_COUNT_EXEMPTED, 462 }; 463 464 /** 465 * Treat two distinct {@link TimingSession TimingSessions} as the same if they start and end 466 * within this amount of time of each other. 467 */ 468 private long mTimingSessionCoalescingDurationMs = 469 QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS; 470 471 /** 472 * The rolling window size for each standby bucket. Within each window, an app will have 10 473 * minutes to run its jobs. 474 */ 475 private final long[] mEJLimitsMs = new long[]{ 476 QcConstants.DEFAULT_EJ_LIMIT_ACTIVE_MS, 477 QcConstants.DEFAULT_EJ_LIMIT_WORKING_MS, 478 QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS, 479 QcConstants.DEFAULT_EJ_LIMIT_RARE_MS, 480 0, // NEVER 481 QcConstants.DEFAULT_EJ_LIMIT_RESTRICTED_MS, 482 QcConstants.DEFAULT_EJ_LIMIT_EXEMPTED_MS 483 }; 484 485 private long mEjLimitAdditionInstallerMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS; 486 487 private long mEjLimitAdditionSpecialMs = QcConstants.DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS; 488 489 /** 490 * The period of time used to calculate expedited job sessions. Apps can only have expedited job 491 * sessions totalling {@link #mEJLimitsMs}[bucket within this period of time (without factoring 492 * in any rewards or free EJs). 493 */ 494 private long mEJLimitWindowSizeMs = QcConstants.DEFAULT_EJ_WINDOW_SIZE_MS; 495 496 /** 497 * Length of time used to split an app's top time into chunks. 498 */ 499 private long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; 500 501 /** 502 * How much EJ quota to give back to an app based on the number of top app time chunks it had. 503 */ 504 private long mEJRewardTopAppMs = QcConstants.DEFAULT_EJ_REWARD_TOP_APP_MS; 505 506 /** 507 * How much EJ quota to give back to an app based on each non-top user interaction. 508 */ 509 private long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS; 510 511 /** 512 * How much EJ quota to give back to an app based on each notification seen event. 513 */ 514 private long mEJRewardNotificationSeenMs = QcConstants.DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS; 515 516 private long mEJGracePeriodTempAllowlistMs = 517 QcConstants.DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS; 518 519 private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; 520 521 private long mQuotaBumpAdditionalDurationMs = 522 QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS; 523 private int mQuotaBumpAdditionalJobCount = QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT; 524 private int mQuotaBumpAdditionalSessionCount = 525 QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT; 526 private long mQuotaBumpWindowSizeMs = QcConstants.DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS; 527 private int mQuotaBumpLimit = QcConstants.DEFAULT_QUOTA_BUMP_LIMIT; 528 529 /** 530 * List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission 531 * granted for each user. 532 */ 533 private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>(); 534 535 /** An app has reached its quota. The message should contain a {@link Package} object. */ 536 @VisibleForTesting 537 static final int MSG_REACHED_QUOTA = 0; 538 /** Drop any old timing sessions. */ 539 private static final int MSG_CLEAN_UP_SESSIONS = 1; 540 /** Check if a package is now within its quota. */ 541 private static final int MSG_CHECK_PACKAGE = 2; 542 /** Process state for a UID has changed. */ 543 private static final int MSG_UID_PROCESS_STATE_CHANGED = 3; 544 /** 545 * An app has reached its expedited job quota. The message should contain a {@link Package} 546 * object. 547 */ 548 @VisibleForTesting 549 static final int MSG_REACHED_EJ_QUOTA = 4; 550 /** 551 * Process a new {@link UsageEvents.Event}. The event will be the message's object and the 552 * userId will the first arg. 553 */ 554 private static final int MSG_PROCESS_USAGE_EVENT = 5; 555 /** A UID's free quota grace period has ended. */ 556 @VisibleForTesting 557 static final int MSG_END_GRACE_PERIOD = 6; 558 QuotaController(@onNull JobSchedulerService service, @NonNull BackgroundJobsController backgroundJobsController, @NonNull ConnectivityController connectivityController)559 public QuotaController(@NonNull JobSchedulerService service, 560 @NonNull BackgroundJobsController backgroundJobsController, 561 @NonNull ConnectivityController connectivityController) { 562 super(service); 563 mHandler = new QcHandler(mContext.getMainLooper()); 564 mAlarmManager = mContext.getSystemService(AlarmManager.class); 565 mQcConstants = new QcConstants(); 566 mBackgroundJobsController = backgroundJobsController; 567 mConnectivityController = connectivityController; 568 mIsEnabled = !mConstants.USE_TARE_POLICY; 569 mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, mContext.getMainLooper()); 570 571 // Set up the app standby bucketing tracker 572 AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); 573 appStandby.addListener(new StandbyTracker()); 574 575 UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class); 576 usmi.registerListener(new UsageEventTracker()); 577 578 PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class); 579 pai.registerTempAllowlistChangeListener(new TempAllowlistTracker()); 580 581 try { 582 ActivityManager.getService().registerUidObserver(new QcUidObserver(), 583 ActivityManager.UID_OBSERVER_PROCSTATE, 584 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null); 585 ActivityManager.getService().registerUidObserver(new QcUidObserver(), 586 ActivityManager.UID_OBSERVER_PROCSTATE, 587 ActivityManager.PROCESS_STATE_TOP, null); 588 } catch (RemoteException e) { 589 // ignored; both services live in system_server 590 } 591 } 592 593 @Override onSystemServicesReady()594 public void onSystemServicesReady() { 595 synchronized (mLock) { 596 cacheInstallerPackagesLocked(UserHandle.USER_SYSTEM); 597 } 598 } 599 600 @Override 601 @GuardedBy("mLock") maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)602 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 603 final long nowElapsed = sElapsedRealtimeClock.millis(); 604 final int userId = jobStatus.getSourceUserId(); 605 final String pkgName = jobStatus.getSourcePackageName(); 606 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); 607 if (jobs == null) { 608 jobs = new ArraySet<>(); 609 mTrackedJobs.add(userId, pkgName, jobs); 610 } 611 jobs.add(jobStatus); 612 jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA); 613 final boolean isWithinQuota = isWithinQuotaLocked(jobStatus); 614 final boolean isWithinEJQuota = 615 jobStatus.isRequestedExpeditedJob() && isWithinEJQuotaLocked(jobStatus); 616 setConstraintSatisfied(jobStatus, nowElapsed, isWithinQuota, isWithinEJQuota); 617 final boolean outOfEJQuota; 618 if (jobStatus.isRequestedExpeditedJob()) { 619 setExpeditedQuotaApproved(jobStatus, nowElapsed, isWithinEJQuota); 620 outOfEJQuota = !isWithinEJQuota; 621 } else { 622 outOfEJQuota = false; 623 } 624 if (!isWithinQuota || outOfEJQuota) { 625 maybeScheduleStartAlarmLocked(userId, pkgName, jobStatus.getEffectiveStandbyBucket()); 626 } 627 } 628 629 @Override 630 @GuardedBy("mLock") prepareForExecutionLocked(JobStatus jobStatus)631 public void prepareForExecutionLocked(JobStatus jobStatus) { 632 if (DEBUG) { 633 Slog.d(TAG, "Prepping for " + jobStatus.toShortString()); 634 } 635 636 final int uid = jobStatus.getSourceUid(); 637 if (mTopAppCache.get(uid)) { 638 if (DEBUG) { 639 Slog.d(TAG, jobStatus.toShortString() + " is top started job"); 640 } 641 mTopStartedJobs.add(jobStatus); 642 // Top jobs won't count towards quota so there's no need to involve the Timer. 643 return; 644 } 645 646 final int userId = jobStatus.getSourceUserId(); 647 final String packageName = jobStatus.getSourcePackageName(); 648 final SparseArrayMap<String, Timer> timerMap = 649 jobStatus.shouldTreatAsExpeditedJob() ? mEJPkgTimers : mPkgTimers; 650 Timer timer = timerMap.get(userId, packageName); 651 if (timer == null) { 652 timer = new Timer(uid, userId, packageName, !jobStatus.shouldTreatAsExpeditedJob()); 653 timerMap.add(userId, packageName, timer); 654 } 655 timer.startTrackingJobLocked(jobStatus); 656 } 657 658 @Override 659 @GuardedBy("mLock") unprepareFromExecutionLocked(JobStatus jobStatus)660 public void unprepareFromExecutionLocked(JobStatus jobStatus) { 661 Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 662 if (timer != null) { 663 timer.stopTrackingJob(jobStatus); 664 } 665 if (jobStatus.isRequestedExpeditedJob()) { 666 timer = mEJPkgTimers.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 667 if (timer != null) { 668 timer.stopTrackingJob(jobStatus); 669 } 670 } 671 mTopStartedJobs.remove(jobStatus); 672 } 673 674 @Override 675 @GuardedBy("mLock") maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate)676 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, 677 boolean forUpdate) { 678 if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) { 679 unprepareFromExecutionLocked(jobStatus); 680 final int userId = jobStatus.getSourceUserId(); 681 final String pkgName = jobStatus.getSourcePackageName(); 682 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); 683 if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) { 684 mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); 685 } 686 } 687 } 688 689 @Override onAppRemovedLocked(String packageName, int uid)690 public void onAppRemovedLocked(String packageName, int uid) { 691 if (packageName == null) { 692 Slog.wtf(TAG, "Told app removed but given null package name."); 693 return; 694 } 695 clearAppStatsLocked(UserHandle.getUserId(uid), packageName); 696 if (mService.getPackagesForUidLocked(uid) == null) { 697 // All packages in the UID have been removed. It's safe to remove things based on 698 // UID alone. 699 mForegroundUids.delete(uid); 700 mTempAllowlistCache.delete(uid); 701 mTempAllowlistGraceCache.delete(uid); 702 mTopAppCache.delete(uid); 703 mTopAppGraceCache.delete(uid); 704 } 705 } 706 707 @Override onUserAddedLocked(int userId)708 public void onUserAddedLocked(int userId) { 709 cacheInstallerPackagesLocked(userId); 710 } 711 712 @Override onUserRemovedLocked(int userId)713 public void onUserRemovedLocked(int userId) { 714 mTrackedJobs.delete(userId); 715 mPkgTimers.delete(userId); 716 mEJPkgTimers.delete(userId); 717 mTimingEvents.delete(userId); 718 mEJTimingSessions.delete(userId); 719 mInQuotaAlarmQueue.removeAlarmsForUserId(userId); 720 mExecutionStatsCache.delete(userId); 721 mEJStats.delete(userId); 722 mSystemInstallers.remove(userId); 723 mTopAppTrackers.delete(userId); 724 } 725 726 @Override onBatteryStateChangedLocked()727 public void onBatteryStateChangedLocked() { 728 handleNewChargingStateLocked(); 729 } 730 731 /** Drop all historical stats and stop tracking any active sessions for the specified app. */ clearAppStatsLocked(int userId, @NonNull String packageName)732 public void clearAppStatsLocked(int userId, @NonNull String packageName) { 733 mTrackedJobs.delete(userId, packageName); 734 Timer timer = mPkgTimers.delete(userId, packageName); 735 if (timer != null) { 736 if (timer.isActive()) { 737 Slog.e(TAG, "clearAppStats called before Timer turned off."); 738 timer.dropEverythingLocked(); 739 } 740 } 741 timer = mEJPkgTimers.delete(userId, packageName); 742 if (timer != null) { 743 if (timer.isActive()) { 744 Slog.e(TAG, "clearAppStats called before EJ Timer turned off."); 745 timer.dropEverythingLocked(); 746 } 747 } 748 mTimingEvents.delete(userId, packageName); 749 mEJTimingSessions.delete(userId, packageName); 750 mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); 751 mExecutionStatsCache.delete(userId, packageName); 752 mEJStats.delete(userId, packageName); 753 mTopAppTrackers.delete(userId, packageName); 754 } 755 cacheInstallerPackagesLocked(int userId)756 private void cacheInstallerPackagesLocked(int userId) { 757 final List<PackageInfo> packages = mContext.getPackageManager() 758 .getInstalledPackagesAsUser(SYSTEM_APP_CHECK_FLAGS, userId); 759 for (int i = packages.size() - 1; i >= 0; --i) { 760 final PackageInfo pi = packages.get(i); 761 final ApplicationInfo ai = pi.applicationInfo; 762 final int idx = ArrayUtils.indexOf( 763 pi.requestedPermissions, Manifest.permission.INSTALL_PACKAGES); 764 765 if (idx >= 0 && ai != null && PackageManager.PERMISSION_GRANTED 766 == mContext.checkPermission(Manifest.permission.INSTALL_PACKAGES, -1, ai.uid)) { 767 mSystemInstallers.add(UserHandle.getUserId(ai.uid), pi.packageName); 768 } 769 } 770 } 771 isUidInForeground(int uid)772 private boolean isUidInForeground(int uid) { 773 if (UserHandle.isCore(uid)) { 774 return true; 775 } 776 synchronized (mLock) { 777 return mForegroundUids.get(uid); 778 } 779 } 780 781 /** @return true if the job was started while the app was in the TOP state. */ isTopStartedJobLocked(@onNull final JobStatus jobStatus)782 private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { 783 return mTopStartedJobs.contains(jobStatus); 784 } 785 786 /** Returns the maximum amount of time this job could run for. */ 787 @GuardedBy("mLock") getMaxJobExecutionTimeMsLocked(@onNull final JobStatus jobStatus)788 public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) { 789 if (!jobStatus.shouldTreatAsExpeditedJob()) { 790 // If quota is currently "free", then the job can run for the full amount of time, 791 // regardless of bucket (hence using charging instead of isQuotaFreeLocked()). 792 if (mService.isBatteryCharging() 793 || mTopAppCache.get(jobStatus.getSourceUid()) 794 || isTopStartedJobLocked(jobStatus) 795 || isUidInForeground(jobStatus.getSourceUid())) { 796 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; 797 } 798 return getTimeUntilQuotaConsumedLocked( 799 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 800 } 801 802 // Expedited job. 803 if (mService.isBatteryCharging()) { 804 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; 805 } 806 if (jobStatus.getEffectiveStandbyBucket() == EXEMPTED_INDEX) { 807 return Math.max(mEJLimitsMs[EXEMPTED_INDEX] / 2, 808 getTimeUntilEJQuotaConsumedLocked( 809 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); 810 } 811 if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) { 812 return Math.max(mEJLimitsMs[ACTIVE_INDEX] / 2, 813 getTimeUntilEJQuotaConsumedLocked( 814 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); 815 } 816 if (isUidInForeground(jobStatus.getSourceUid())) { 817 return Math.max(mEJLimitsMs[WORKING_INDEX] / 2, 818 getTimeUntilEJQuotaConsumedLocked( 819 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName())); 820 } 821 return getTimeUntilEJQuotaConsumedLocked( 822 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 823 } 824 hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket, long nowElapsed)825 private boolean hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket, 826 long nowElapsed) { 827 if (standbyBucket == RESTRICTED_INDEX || standbyBucket == NEVER_INDEX) { 828 // Don't let RESTRICTED apps get free quota from the temp allowlist. 829 // TODO: consider granting the exemption to RESTRICTED apps if the temp allowlist allows 830 // them to start FGS 831 return false; 832 } 833 final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(sourceUid); 834 return mTempAllowlistCache.get(sourceUid) 835 || nowElapsed < tempAllowlistGracePeriodEndElapsed; 836 } 837 838 /** @return true if the job is within expedited job quota. */ 839 @GuardedBy("mLock") isWithinEJQuotaLocked(@onNull final JobStatus jobStatus)840 public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) { 841 if (!mIsEnabled) { 842 return true; 843 } 844 if (isQuotaFreeLocked(jobStatus.getEffectiveStandbyBucket())) { 845 return true; 846 } 847 // A job is within quota if one of the following is true: 848 // 1. the app is currently in the foreground 849 // 2. the app overall is within its quota 850 // 3. It's on the temp allowlist (or within the grace period) 851 if (isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { 852 return true; 853 } 854 855 final long nowElapsed = sElapsedRealtimeClock.millis(); 856 if (hasTempAllowlistExemptionLocked(jobStatus.getSourceUid(), 857 jobStatus.getEffectiveStandbyBucket(), nowElapsed)) { 858 return true; 859 } 860 861 final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(jobStatus.getSourceUid()); 862 final boolean hasTopAppExemption = mTopAppCache.get(jobStatus.getSourceUid()) 863 || nowElapsed < topAppGracePeriodEndElapsed; 864 if (hasTopAppExemption) { 865 return true; 866 } 867 868 return 0 < getRemainingEJExecutionTimeLocked( 869 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); 870 } 871 872 @NonNull 873 @VisibleForTesting 874 ShrinkableDebits getEJDebitsLocked(final int userId, @NonNull final String packageName) { 875 ShrinkableDebits debits = mEJStats.get(userId, packageName); 876 if (debits == null) { 877 debits = new ShrinkableDebits( 878 JobSchedulerService.standbyBucketForPackage( 879 packageName, userId, sElapsedRealtimeClock.millis()) 880 ); 881 mEJStats.add(userId, packageName, debits); 882 } 883 return debits; 884 } 885 886 @VisibleForTesting 887 boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { 888 if (!mIsEnabled) { 889 return true; 890 } 891 final int standbyBucket = jobStatus.getEffectiveStandbyBucket(); 892 // A job is within quota if one of the following is true: 893 // 1. it was started while the app was in the TOP state 894 // 2. the app is currently in the foreground 895 // 3. the app overall is within its quota 896 return isTopStartedJobLocked(jobStatus) 897 || isUidInForeground(jobStatus.getSourceUid()) 898 || isWithinQuotaLocked( 899 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); 900 } 901 902 @GuardedBy("mLock") 903 private boolean isQuotaFreeLocked(final int standbyBucket) { 904 // Quota constraint is not enforced while charging. 905 if (mService.isBatteryCharging()) { 906 // Restricted jobs require additional constraints when charging, so don't immediately 907 // mark quota as free when charging. 908 return standbyBucket != RESTRICTED_INDEX; 909 } 910 return false; 911 } 912 913 @VisibleForTesting 914 @GuardedBy("mLock") 915 boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, 916 final int standbyBucket) { 917 if (!mIsEnabled) { 918 return true; 919 } 920 if (standbyBucket == NEVER_INDEX) return false; 921 922 if (isQuotaFreeLocked(standbyBucket)) return true; 923 924 ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 925 // TODO: use a higher minimum remaining time for jobs with MINIMUM priority 926 return getRemainingExecutionTimeLocked(stats) > 0 927 && isUnderJobCountQuotaLocked(stats, standbyBucket) 928 && isUnderSessionCountQuotaLocked(stats, standbyBucket); 929 } 930 931 private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats, 932 final int standbyBucket) { 933 final long now = sElapsedRealtimeClock.millis(); 934 final boolean isUnderAllowedTimeQuota = 935 (stats.jobRateLimitExpirationTimeElapsed <= now 936 || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow); 937 return isUnderAllowedTimeQuota 938 && stats.bgJobCountInWindow < stats.jobCountLimit; 939 } 940 941 private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats, 942 final int standbyBucket) { 943 final long now = sElapsedRealtimeClock.millis(); 944 final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now 945 || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow); 946 return isUnderAllowedTimeQuota 947 && stats.sessionCountInWindow < stats.sessionCountLimit; 948 } 949 950 @VisibleForTesting 951 long getRemainingExecutionTimeLocked(@NonNull final JobStatus jobStatus) { 952 return getRemainingExecutionTimeLocked(jobStatus.getSourceUserId(), 953 jobStatus.getSourcePackageName(), 954 jobStatus.getEffectiveStandbyBucket()); 955 } 956 957 @VisibleForTesting 958 long getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName) { 959 final int standbyBucket = JobSchedulerService.standbyBucketForPackage(packageName, 960 userId, sElapsedRealtimeClock.millis()); 961 return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket); 962 } 963 964 /** 965 * Returns the amount of time, in milliseconds, that this job has remaining to run based on its 966 * current standby bucket. Time remaining could be negative if the app was moved from a less 967 * restricted to a more restricted bucket. 968 */ 969 private long getRemainingExecutionTimeLocked(final int userId, 970 @NonNull final String packageName, final int standbyBucket) { 971 if (standbyBucket == NEVER_INDEX) { 972 return 0; 973 } 974 return getRemainingExecutionTimeLocked( 975 getExecutionStatsLocked(userId, packageName, standbyBucket)); 976 } 977 978 private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) { 979 return Math.min(stats.allowedTimePerPeriodMs - stats.executionTimeInWindowMs, 980 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs); 981 } 982 983 @VisibleForTesting 984 long getRemainingEJExecutionTimeLocked(final int userId, @NonNull final String packageName) { 985 ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 986 if (quota.getStandbyBucketLocked() == NEVER_INDEX) { 987 return 0; 988 } 989 final long limitMs = 990 getEJLimitMsLocked(userId, packageName, quota.getStandbyBucketLocked()); 991 long remainingMs = limitMs - quota.getTallyLocked(); 992 993 // Stale sessions may still be factored into tally. Make sure they're removed. 994 List<TimedEvent> timingSessions = mEJTimingSessions.get(userId, packageName); 995 final long nowElapsed = sElapsedRealtimeClock.millis(); 996 final long windowStartTimeElapsed = nowElapsed - mEJLimitWindowSizeMs; 997 if (timingSessions != null) { 998 while (timingSessions.size() > 0) { 999 TimingSession ts = (TimingSession) timingSessions.get(0); 1000 if (ts.endTimeElapsed < windowStartTimeElapsed) { 1001 final long duration = ts.endTimeElapsed - ts.startTimeElapsed; 1002 remainingMs += duration; 1003 quota.transactLocked(-duration); 1004 timingSessions.remove(0); 1005 } else if (ts.startTimeElapsed < windowStartTimeElapsed) { 1006 remainingMs += windowStartTimeElapsed - ts.startTimeElapsed; 1007 break; 1008 } else { 1009 // Fully within the window. 1010 break; 1011 } 1012 } 1013 } 1014 1015 TopAppTimer topAppTimer = mTopAppTrackers.get(userId, packageName); 1016 if (topAppTimer != null && topAppTimer.isActive()) { 1017 remainingMs += topAppTimer.getPendingReward(nowElapsed); 1018 } 1019 1020 Timer timer = mEJPkgTimers.get(userId, packageName); 1021 if (timer == null) { 1022 return remainingMs; 1023 } 1024 1025 return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis()); 1026 } 1027 1028 private long getEJLimitMsLocked(final int userId, @NonNull final String packageName, 1029 final int standbyBucket) { 1030 final long baseLimitMs = mEJLimitsMs[standbyBucket]; 1031 if (mSystemInstallers.contains(userId, packageName)) { 1032 return baseLimitMs + mEjLimitAdditionInstallerMs; 1033 } 1034 return baseLimitMs; 1035 } 1036 1037 /** 1038 * Returns the amount of time, in milliseconds, until the package would have reached its 1039 * duration quota, assuming it has a job counting towards its quota the entire time. This takes 1040 * into account any {@link TimingSession TimingSessions} that may roll out of the window as the 1041 * job is running. 1042 */ 1043 @VisibleForTesting 1044 long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) { 1045 final long nowElapsed = sElapsedRealtimeClock.millis(); 1046 final int standbyBucket = JobSchedulerService.standbyBucketForPackage( 1047 packageName, userId, nowElapsed); 1048 if (standbyBucket == NEVER_INDEX) { 1049 return 0; 1050 } 1051 1052 List<TimedEvent> events = mTimingEvents.get(userId, packageName); 1053 final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 1054 if (events == null || events.size() == 0) { 1055 // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can 1056 // essentially run until they reach the maximum limit. 1057 if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { 1058 return mMaxExecutionTimeMs; 1059 } 1060 return mAllowedTimePerPeriodMs[standbyBucket]; 1061 } 1062 1063 final long startWindowElapsed = nowElapsed - stats.windowSizeMs; 1064 final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; 1065 final long allowedTimePerPeriodMs = mAllowedTimePerPeriodMs[standbyBucket]; 1066 final long allowedTimeRemainingMs = allowedTimePerPeriodMs - stats.executionTimeInWindowMs; 1067 final long maxExecutionTimeRemainingMs = 1068 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs; 1069 1070 // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can 1071 // essentially run until they reach the maximum limit. 1072 if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { 1073 return calculateTimeUntilQuotaConsumedLocked( 1074 events, startMaxElapsed, maxExecutionTimeRemainingMs, false); 1075 } 1076 1077 // Need to check both max time and period time in case one is less than the other. 1078 // For example, max time remaining could be less than bucket time remaining, but sessions 1079 // contributing to the max time remaining could phase out enough that we'd want to use the 1080 // bucket value. 1081 return Math.min( 1082 calculateTimeUntilQuotaConsumedLocked( 1083 events, startMaxElapsed, maxExecutionTimeRemainingMs, false), 1084 calculateTimeUntilQuotaConsumedLocked( 1085 events, startWindowElapsed, allowedTimeRemainingMs, true)); 1086 } 1087 1088 /** 1089 * Calculates how much time it will take, in milliseconds, until the quota is fully consumed. 1090 * 1091 * @param windowStartElapsed The start of the window, in the elapsed realtime timebase. 1092 * @param deadSpaceMs How much time can be allowed to count towards the quota 1093 */ 1094 private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimedEvent> sessions, 1095 final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps) { 1096 long timeUntilQuotaConsumedMs = 0; 1097 long start = windowStartElapsed; 1098 int numQuotaBumps = 0; 1099 final long quotaBumpWindowStartElapsed = 1100 sElapsedRealtimeClock.millis() - mQuotaBumpWindowSizeMs; 1101 final int numSessions = sessions.size(); 1102 if (allowQuotaBumps) { 1103 for (int i = numSessions - 1; i >= 0; --i) { 1104 TimedEvent event = sessions.get(i); 1105 1106 if (event instanceof QuotaBump) { 1107 if (event.getEndTimeElapsed() >= quotaBumpWindowStartElapsed 1108 && numQuotaBumps++ < mQuotaBumpLimit) { 1109 deadSpaceMs += mQuotaBumpAdditionalDurationMs; 1110 } else { 1111 break; 1112 } 1113 } 1114 } 1115 } 1116 for (int i = 0; i < numSessions; ++i) { 1117 TimedEvent event = sessions.get(i); 1118 1119 if (event instanceof QuotaBump) { 1120 continue; 1121 } 1122 1123 TimingSession session = (TimingSession) event; 1124 1125 if (session.endTimeElapsed < windowStartElapsed) { 1126 // Outside of window. Ignore. 1127 continue; 1128 } else if (session.startTimeElapsed <= windowStartElapsed) { 1129 // Overlapping session. Can extend time by portion of session in window. 1130 timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed; 1131 start = session.endTimeElapsed; 1132 } else { 1133 // Completely within the window. Can only consider if there's enough dead space 1134 // to get to the start of the session. 1135 long diff = session.startTimeElapsed - start; 1136 if (diff > deadSpaceMs) { 1137 break; 1138 } 1139 timeUntilQuotaConsumedMs += diff 1140 + (session.endTimeElapsed - session.startTimeElapsed); 1141 deadSpaceMs -= diff; 1142 start = session.endTimeElapsed; 1143 } 1144 } 1145 // Will be non-zero if the loop didn't look at any sessions. 1146 timeUntilQuotaConsumedMs += deadSpaceMs; 1147 if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) { 1148 Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs); 1149 } 1150 return timeUntilQuotaConsumedMs; 1151 } 1152 1153 /** 1154 * Returns the amount of time, in milliseconds, until the package would have reached its 1155 * expedited job quota, assuming it has a job counting towards the quota the entire time and 1156 * the quota isn't replenished at all in that time. 1157 */ 1158 @VisibleForTesting 1159 long getTimeUntilEJQuotaConsumedLocked(final int userId, @NonNull final String packageName) { 1160 final long remainingExecutionTimeMs = 1161 getRemainingEJExecutionTimeLocked(userId, packageName); 1162 1163 List<TimedEvent> sessions = mEJTimingSessions.get(userId, packageName); 1164 if (sessions == null || sessions.size() == 0) { 1165 return remainingExecutionTimeMs; 1166 } 1167 1168 final long nowElapsed = sElapsedRealtimeClock.millis(); 1169 ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 1170 final long limitMs = 1171 getEJLimitMsLocked(userId, packageName, quota.getStandbyBucketLocked()); 1172 final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs); 1173 long remainingDeadSpaceMs = remainingExecutionTimeMs; 1174 // Total time looked at where a session wouldn't be phasing out. 1175 long deadSpaceMs = 0; 1176 // Time regained from sessions phasing out 1177 long phasedOutSessionTimeMs = 0; 1178 1179 for (int i = 0; i < sessions.size(); ++i) { 1180 TimingSession session = (TimingSession) sessions.get(i); 1181 if (session.endTimeElapsed < startWindowElapsed) { 1182 // Edge case where a session became stale in the time between the call to 1183 // getRemainingEJExecutionTimeLocked and this line. 1184 remainingDeadSpaceMs += session.endTimeElapsed - session.startTimeElapsed; 1185 sessions.remove(i); 1186 i--; 1187 } else if (session.startTimeElapsed < startWindowElapsed) { 1188 // Session straddles start of window 1189 phasedOutSessionTimeMs = session.endTimeElapsed - startWindowElapsed; 1190 } else { 1191 // Session fully inside window 1192 final long timeBetweenSessions = session.startTimeElapsed 1193 - (i == 0 ? startWindowElapsed : sessions.get(i - 1).getEndTimeElapsed()); 1194 final long usedDeadSpaceMs = Math.min(remainingDeadSpaceMs, timeBetweenSessions); 1195 deadSpaceMs += usedDeadSpaceMs; 1196 if (usedDeadSpaceMs == timeBetweenSessions) { 1197 phasedOutSessionTimeMs += session.endTimeElapsed - session.startTimeElapsed; 1198 } 1199 remainingDeadSpaceMs -= usedDeadSpaceMs; 1200 if (remainingDeadSpaceMs <= 0) { 1201 break; 1202 } 1203 } 1204 } 1205 1206 return Math.min(limitMs, deadSpaceMs + phasedOutSessionTimeMs + remainingDeadSpaceMs); 1207 } 1208 1209 /** Returns the execution stats of the app in the most recent window. */ 1210 @VisibleForTesting 1211 @NonNull 1212 ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, 1213 final int standbyBucket) { 1214 return getExecutionStatsLocked(userId, packageName, standbyBucket, true); 1215 } 1216 1217 @NonNull 1218 private ExecutionStats getExecutionStatsLocked(final int userId, 1219 @NonNull final String packageName, final int standbyBucket, 1220 final boolean refreshStatsIfOld) { 1221 if (standbyBucket == NEVER_INDEX) { 1222 Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app."); 1223 return new ExecutionStats(); 1224 } 1225 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1226 if (appStats == null) { 1227 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1228 mExecutionStatsCache.add(userId, packageName, appStats); 1229 } 1230 ExecutionStats stats = appStats[standbyBucket]; 1231 if (stats == null) { 1232 stats = new ExecutionStats(); 1233 appStats[standbyBucket] = stats; 1234 } 1235 if (refreshStatsIfOld) { 1236 final long bucketAllowedTimeMs = mAllowedTimePerPeriodMs[standbyBucket]; 1237 final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket]; 1238 final int jobCountLimit = mMaxBucketJobCounts[standbyBucket]; 1239 final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket]; 1240 Timer timer = mPkgTimers.get(userId, packageName); 1241 if ((timer != null && timer.isActive()) 1242 || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis() 1243 || stats.allowedTimePerPeriodMs != bucketAllowedTimeMs 1244 || stats.windowSizeMs != bucketWindowSizeMs 1245 || stats.jobCountLimit != jobCountLimit 1246 || stats.sessionCountLimit != sessionCountLimit) { 1247 // The stats are no longer valid. 1248 stats.allowedTimePerPeriodMs = bucketAllowedTimeMs; 1249 stats.windowSizeMs = bucketWindowSizeMs; 1250 stats.jobCountLimit = jobCountLimit; 1251 stats.sessionCountLimit = sessionCountLimit; 1252 updateExecutionStatsLocked(userId, packageName, stats); 1253 } 1254 } 1255 1256 return stats; 1257 } 1258 1259 @VisibleForTesting 1260 void updateExecutionStatsLocked(final int userId, @NonNull final String packageName, 1261 @NonNull ExecutionStats stats) { 1262 stats.executionTimeInWindowMs = 0; 1263 stats.bgJobCountInWindow = 0; 1264 stats.executionTimeInMaxPeriodMs = 0; 1265 stats.bgJobCountInMaxPeriod = 0; 1266 stats.sessionCountInWindow = 0; 1267 if (stats.jobCountLimit == 0 || stats.sessionCountLimit == 0) { 1268 // App won't be in quota until configuration changes. 1269 stats.inQuotaTimeElapsed = Long.MAX_VALUE; 1270 } else { 1271 stats.inQuotaTimeElapsed = 0; 1272 } 1273 final long allowedTimeIntoQuotaMs = stats.allowedTimePerPeriodMs - mQuotaBufferMs; 1274 1275 Timer timer = mPkgTimers.get(userId, packageName); 1276 final long nowElapsed = sElapsedRealtimeClock.millis(); 1277 stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS; 1278 if (timer != null && timer.isActive()) { 1279 // Exclude active sessions from the session count so that new jobs aren't prevented 1280 // from starting due to an app hitting the session limit. 1281 stats.executionTimeInWindowMs = 1282 stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed); 1283 stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount(); 1284 // If the timer is active, the value will be stale at the next method call, so 1285 // invalidate now. 1286 stats.expirationTimeElapsed = nowElapsed; 1287 if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { 1288 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1289 nowElapsed - allowedTimeIntoQuotaMs + stats.windowSizeMs); 1290 } 1291 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 1292 final long inQuotaTime = nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS; 1293 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1294 } 1295 if (stats.bgJobCountInWindow >= stats.jobCountLimit) { 1296 final long inQuotaTime = nowElapsed + stats.windowSizeMs; 1297 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1298 } 1299 } 1300 1301 List<TimedEvent> events = mTimingEvents.get(userId, packageName); 1302 if (events == null || events.size() == 0) { 1303 return; 1304 } 1305 1306 final long startWindowElapsed = nowElapsed - stats.windowSizeMs; 1307 final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; 1308 int sessionCountInWindow = 0; 1309 int numQuotaBumps = 0; 1310 final long quotaBumpWindowStartElapsed = nowElapsed - mQuotaBumpWindowSizeMs; 1311 // The minimum time between the start time and the beginning of the events that were 1312 // looked at --> how much time the stats will be valid for. 1313 long emptyTimeMs = Long.MAX_VALUE; 1314 // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get 1315 // the most recent ones. 1316 final int loopStart = events.size() - 1; 1317 // Process QuotaBumps first to ensure the limits are properly adjusted. 1318 for (int i = loopStart; i >= 0; --i) { 1319 TimedEvent event = events.get(i); 1320 1321 if (event.getEndTimeElapsed() < quotaBumpWindowStartElapsed 1322 || numQuotaBumps >= mQuotaBumpLimit) { 1323 break; 1324 } 1325 1326 if (event instanceof QuotaBump) { 1327 stats.allowedTimePerPeriodMs += mQuotaBumpAdditionalDurationMs; 1328 stats.jobCountLimit += mQuotaBumpAdditionalJobCount; 1329 stats.sessionCountLimit += mQuotaBumpAdditionalSessionCount; 1330 emptyTimeMs = Math.min(emptyTimeMs, 1331 event.getEndTimeElapsed() - quotaBumpWindowStartElapsed); 1332 numQuotaBumps++; 1333 } 1334 } 1335 TimingSession lastSeenTimingSession = null; 1336 for (int i = loopStart; i >= 0; --i) { 1337 TimedEvent event = events.get(i); 1338 1339 if (event instanceof QuotaBump) { 1340 continue; 1341 } 1342 1343 TimingSession session = (TimingSession) event; 1344 1345 // Window management. 1346 if (startWindowElapsed < session.endTimeElapsed) { 1347 final long start; 1348 if (startWindowElapsed < session.startTimeElapsed) { 1349 start = session.startTimeElapsed; 1350 emptyTimeMs = 1351 Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed); 1352 } else { 1353 // The session started before the window but ended within the window. Only 1354 // include the portion that was within the window. 1355 start = startWindowElapsed; 1356 emptyTimeMs = 0; 1357 } 1358 1359 stats.executionTimeInWindowMs += session.endTimeElapsed - start; 1360 stats.bgJobCountInWindow += session.bgJobCount; 1361 if (stats.executionTimeInWindowMs >= allowedTimeIntoQuotaMs) { 1362 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1363 start + stats.executionTimeInWindowMs - allowedTimeIntoQuotaMs 1364 + stats.windowSizeMs); 1365 } 1366 if (stats.bgJobCountInWindow >= stats.jobCountLimit) { 1367 final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs; 1368 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1369 } 1370 // Coalesce sessions if they are very close to each other in time 1371 boolean shouldCoalesce = lastSeenTimingSession != null 1372 && lastSeenTimingSession.startTimeElapsed - session.endTimeElapsed 1373 <= mTimingSessionCoalescingDurationMs; 1374 if (!shouldCoalesce) { 1375 sessionCountInWindow++; 1376 1377 if (sessionCountInWindow >= stats.sessionCountLimit) { 1378 final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs; 1379 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 1380 } 1381 } 1382 } 1383 1384 // Max period check. 1385 if (startMaxElapsed < session.startTimeElapsed) { 1386 stats.executionTimeInMaxPeriodMs += 1387 session.endTimeElapsed - session.startTimeElapsed; 1388 stats.bgJobCountInMaxPeriod += session.bgJobCount; 1389 emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed); 1390 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 1391 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1392 session.startTimeElapsed + stats.executionTimeInMaxPeriodMs 1393 - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); 1394 } 1395 } else if (startMaxElapsed < session.endTimeElapsed) { 1396 // The session started before the window but ended within the window. Only include 1397 // the portion that was within the window. 1398 stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed; 1399 stats.bgJobCountInMaxPeriod += session.bgJobCount; 1400 emptyTimeMs = 0; 1401 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) { 1402 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, 1403 startMaxElapsed + stats.executionTimeInMaxPeriodMs 1404 - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS); 1405 } 1406 } else { 1407 // This session ended before the window. No point in going any further. 1408 break; 1409 } 1410 1411 lastSeenTimingSession = session; 1412 } 1413 stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; 1414 stats.sessionCountInWindow = sessionCountInWindow; 1415 } 1416 1417 /** Invalidate ExecutionStats for all apps. */ 1418 @VisibleForTesting 1419 void invalidateAllExecutionStatsLocked() { 1420 final long nowElapsed = sElapsedRealtimeClock.millis(); 1421 mExecutionStatsCache.forEach((appStats) -> { 1422 if (appStats != null) { 1423 for (int i = 0; i < appStats.length; ++i) { 1424 ExecutionStats stats = appStats[i]; 1425 if (stats != null) { 1426 stats.expirationTimeElapsed = nowElapsed; 1427 } 1428 } 1429 } 1430 }); 1431 } 1432 1433 @VisibleForTesting 1434 void invalidateAllExecutionStatsLocked(final int userId, 1435 @NonNull final String packageName) { 1436 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1437 if (appStats != null) { 1438 final long nowElapsed = sElapsedRealtimeClock.millis(); 1439 for (int i = 0; i < appStats.length; ++i) { 1440 ExecutionStats stats = appStats[i]; 1441 if (stats != null) { 1442 stats.expirationTimeElapsed = nowElapsed; 1443 } 1444 } 1445 } 1446 } 1447 1448 @VisibleForTesting 1449 void incrementJobCountLocked(final int userId, @NonNull final String packageName, int count) { 1450 final long now = sElapsedRealtimeClock.millis(); 1451 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1452 if (appStats == null) { 1453 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1454 mExecutionStatsCache.add(userId, packageName, appStats); 1455 } 1456 for (int i = 0; i < appStats.length; ++i) { 1457 ExecutionStats stats = appStats[i]; 1458 if (stats == null) { 1459 stats = new ExecutionStats(); 1460 appStats[i] = stats; 1461 } 1462 if (stats.jobRateLimitExpirationTimeElapsed <= now) { 1463 stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs; 1464 stats.jobCountInRateLimitingWindow = 0; 1465 } 1466 stats.jobCountInRateLimitingWindow += count; 1467 } 1468 } 1469 1470 private void incrementTimingSessionCountLocked(final int userId, 1471 @NonNull final String packageName) { 1472 final long now = sElapsedRealtimeClock.millis(); 1473 ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName); 1474 if (appStats == null) { 1475 appStats = new ExecutionStats[mBucketPeriodsMs.length]; 1476 mExecutionStatsCache.add(userId, packageName, appStats); 1477 } 1478 for (int i = 0; i < appStats.length; ++i) { 1479 ExecutionStats stats = appStats[i]; 1480 if (stats == null) { 1481 stats = new ExecutionStats(); 1482 appStats[i] = stats; 1483 } 1484 if (stats.sessionRateLimitExpirationTimeElapsed <= now) { 1485 stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs; 1486 stats.sessionCountInRateLimitingWindow = 0; 1487 } 1488 stats.sessionCountInRateLimitingWindow++; 1489 } 1490 } 1491 1492 @VisibleForTesting 1493 void saveTimingSession(final int userId, @NonNull final String packageName, 1494 @NonNull final TimingSession session, boolean isExpedited) { 1495 saveTimingSession(userId, packageName, session, isExpedited, 0); 1496 } 1497 1498 private void saveTimingSession(final int userId, @NonNull final String packageName, 1499 @NonNull final TimingSession session, boolean isExpedited, long debitAdjustment) { 1500 synchronized (mLock) { 1501 final SparseArrayMap<String, List<TimedEvent>> sessionMap = 1502 isExpedited ? mEJTimingSessions : mTimingEvents; 1503 List<TimedEvent> sessions = sessionMap.get(userId, packageName); 1504 if (sessions == null) { 1505 sessions = new ArrayList<>(); 1506 sessionMap.add(userId, packageName, sessions); 1507 } 1508 sessions.add(session); 1509 if (isExpedited) { 1510 final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 1511 quota.transactLocked(session.endTimeElapsed - session.startTimeElapsed 1512 + debitAdjustment); 1513 } else { 1514 // Adding a new session means that the current stats are now incorrect. 1515 invalidateAllExecutionStatsLocked(userId, packageName); 1516 1517 maybeScheduleCleanupAlarmLocked(); 1518 } 1519 } 1520 } 1521 1522 private void grantRewardForInstantEvent( 1523 final int userId, @NonNull final String packageName, final long credit) { 1524 if (credit == 0) { 1525 return; 1526 } 1527 synchronized (mLock) { 1528 final long nowElapsed = sElapsedRealtimeClock.millis(); 1529 final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); 1530 if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)) { 1531 mStateChangedListener.onControllerStateChanged( 1532 maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)); 1533 } 1534 } 1535 } 1536 1537 private boolean transactQuotaLocked(final int userId, @NonNull final String packageName, 1538 final long nowElapsed, @NonNull ShrinkableDebits debits, final long credit) { 1539 final long oldTally = debits.getTallyLocked(); 1540 final long leftover = debits.transactLocked(-credit); 1541 if (DEBUG) { 1542 Slog.d(TAG, "debits overflowed by " + leftover); 1543 } 1544 boolean changed = oldTally != debits.getTallyLocked(); 1545 if (leftover != 0) { 1546 // Only adjust timer if its active. 1547 final Timer ejTimer = mEJPkgTimers.get(userId, packageName); 1548 if (ejTimer != null && ejTimer.isActive()) { 1549 ejTimer.updateDebitAdjustment(nowElapsed, leftover); 1550 changed = true; 1551 } 1552 } 1553 return changed; 1554 } 1555 1556 private final class EarliestEndTimeFunctor implements Consumer<List<TimedEvent>> { 1557 public long earliestEndElapsed = Long.MAX_VALUE; 1558 1559 @Override 1560 public void accept(List<TimedEvent> events) { 1561 if (events != null && events.size() > 0) { 1562 earliestEndElapsed = 1563 Math.min(earliestEndElapsed, events.get(0).getEndTimeElapsed()); 1564 } 1565 } 1566 1567 void reset() { 1568 earliestEndElapsed = Long.MAX_VALUE; 1569 } 1570 } 1571 1572 private final EarliestEndTimeFunctor mEarliestEndTimeFunctor = new EarliestEndTimeFunctor(); 1573 1574 /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */ 1575 @VisibleForTesting 1576 void maybeScheduleCleanupAlarmLocked() { 1577 final long nowElapsed = sElapsedRealtimeClock.millis(); 1578 if (mNextCleanupTimeElapsed > nowElapsed) { 1579 // There's already an alarm scheduled. Just stick with that one. There's no way we'll 1580 // end up scheduling an earlier alarm. 1581 if (DEBUG) { 1582 Slog.v(TAG, "Not scheduling cleanup since there's already one at " 1583 + mNextCleanupTimeElapsed 1584 + " (in " + (mNextCleanupTimeElapsed - nowElapsed) + "ms)"); 1585 } 1586 return; 1587 } 1588 mEarliestEndTimeFunctor.reset(); 1589 mTimingEvents.forEach(mEarliestEndTimeFunctor); 1590 mEJTimingSessions.forEach(mEarliestEndTimeFunctor); 1591 final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed; 1592 if (earliestEndElapsed == Long.MAX_VALUE) { 1593 // Couldn't find a good time to clean up. Maybe this was called after we deleted all 1594 // timing sessions. 1595 if (DEBUG) { 1596 Slog.d(TAG, "Didn't find a time to schedule cleanup"); 1597 } 1598 return; 1599 } 1600 // Need to keep sessions for all apps up to the max period, regardless of their current 1601 // standby bucket. 1602 long nextCleanupElapsed = earliestEndElapsed + MAX_PERIOD_MS; 1603 if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) { 1604 // No need to clean up too often. Delay the alarm if the next cleanup would be too soon 1605 // after it. 1606 nextCleanupElapsed = mNextCleanupTimeElapsed + 10 * MINUTE_IN_MILLIS; 1607 } 1608 mNextCleanupTimeElapsed = nextCleanupElapsed; 1609 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP, 1610 mSessionCleanupAlarmListener, mHandler); 1611 if (DEBUG) { 1612 Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed); 1613 } 1614 } 1615 1616 private class TimerChargingUpdateFunctor implements Consumer<Timer> { 1617 private long mNowElapsed; 1618 private boolean mIsCharging; 1619 1620 private void setStatus(long nowElapsed, boolean isCharging) { 1621 mNowElapsed = nowElapsed; 1622 mIsCharging = isCharging; 1623 } 1624 1625 @Override 1626 public void accept(Timer timer) { 1627 if (JobSchedulerService.standbyBucketForPackage(timer.mPkg.packageName, 1628 timer.mPkg.userId, mNowElapsed) != RESTRICTED_INDEX) { 1629 // Restricted jobs need additional constraints even when charging, so don't 1630 // immediately say that quota is free. 1631 timer.onStateChangedLocked(mNowElapsed, mIsCharging); 1632 } 1633 } 1634 } 1635 1636 private final TimerChargingUpdateFunctor 1637 mTimerChargingUpdateFunctor = new TimerChargingUpdateFunctor(); 1638 1639 private void handleNewChargingStateLocked() { 1640 mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(), 1641 mService.isBatteryCharging()); 1642 if (DEBUG) { 1643 Slog.d(TAG, "handleNewChargingStateLocked: " + mService.isBatteryCharging()); 1644 } 1645 // Deal with Timers first. 1646 mEJPkgTimers.forEach(mTimerChargingUpdateFunctor); 1647 mPkgTimers.forEach(mTimerChargingUpdateFunctor); 1648 // Now update jobs out of band so broadcast processing can proceed. 1649 JobSchedulerBackgroundThread.getHandler().post(() -> { 1650 synchronized (mLock) { 1651 maybeUpdateAllConstraintsLocked(); 1652 } 1653 }); 1654 } 1655 1656 private void maybeUpdateAllConstraintsLocked() { 1657 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1658 final long nowElapsed = sElapsedRealtimeClock.millis(); 1659 for (int u = 0; u < mTrackedJobs.numMaps(); ++u) { 1660 final int userId = mTrackedJobs.keyAt(u); 1661 for (int p = 0; p < mTrackedJobs.numElementsForKey(userId); ++p) { 1662 final String packageName = mTrackedJobs.keyAt(u, p); 1663 changedJobs.addAll( 1664 maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)); 1665 } 1666 } 1667 if (changedJobs.size() > 0) { 1668 mStateChangedListener.onControllerStateChanged(changedJobs); 1669 } 1670 } 1671 1672 /** 1673 * Update the CONSTRAINT_WITHIN_QUOTA bit for all of the Jobs for a given package. 1674 * 1675 * @return the set of jobs whose status changed 1676 */ 1677 @NonNull 1678 private ArraySet<JobStatus> maybeUpdateConstraintForPkgLocked(final long nowElapsed, 1679 final int userId, @NonNull final String packageName) { 1680 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 1681 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1682 if (jobs == null || jobs.size() == 0) { 1683 return changedJobs; 1684 } 1685 1686 // Quota is the same for all jobs within a package. 1687 final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket(); 1688 final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket); 1689 boolean outOfEJQuota = false; 1690 for (int i = jobs.size() - 1; i >= 0; --i) { 1691 final JobStatus js = jobs.valueAt(i); 1692 final boolean isWithinEJQuota = 1693 js.isRequestedExpeditedJob() && isWithinEJQuotaLocked(js); 1694 if (isTopStartedJobLocked(js)) { 1695 // Job was started while the app was in the TOP state so we should allow it to 1696 // finish. 1697 if (js.setQuotaConstraintSatisfied(nowElapsed, true)) { 1698 changedJobs.add(js); 1699 } 1700 } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX 1701 && realStandbyBucket == js.getEffectiveStandbyBucket()) { 1702 // An app in the ACTIVE bucket may be out of quota while the job could be in quota 1703 // for some reason. Therefore, avoid setting the real value here and check each job 1704 // individually. 1705 if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) { 1706 changedJobs.add(js); 1707 } 1708 } else { 1709 // This job is somehow exempted. Need to determine its own quota status. 1710 if (setConstraintSatisfied(js, nowElapsed, 1711 isWithinQuotaLocked(js), isWithinEJQuota)) { 1712 changedJobs.add(js); 1713 } 1714 } 1715 1716 if (js.isRequestedExpeditedJob()) { 1717 if (setExpeditedQuotaApproved(js, nowElapsed, isWithinEJQuota)) { 1718 changedJobs.add(js); 1719 } 1720 outOfEJQuota |= !isWithinEJQuota; 1721 } 1722 } 1723 if (!realInQuota || outOfEJQuota) { 1724 // Don't want to use the effective standby bucket here since that bump the bucket to 1725 // ACTIVE for one of the jobs, which doesn't help with other jobs that aren't 1726 // exempted. 1727 maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket); 1728 } else { 1729 mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); 1730 } 1731 return changedJobs; 1732 } 1733 1734 private class UidConstraintUpdater implements Consumer<JobStatus> { 1735 private final SparseArrayMap<String, Integer> mToScheduleStartAlarms = 1736 new SparseArrayMap<>(); 1737 public final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 1738 long mUpdateTimeElapsed = 0; 1739 1740 void prepare() { 1741 mUpdateTimeElapsed = sElapsedRealtimeClock.millis(); 1742 changedJobs.clear(); 1743 } 1744 1745 @Override 1746 public void accept(JobStatus jobStatus) { 1747 final boolean isWithinEJQuota; 1748 if (jobStatus.isRequestedExpeditedJob()) { 1749 isWithinEJQuota = isWithinEJQuotaLocked(jobStatus); 1750 } else { 1751 isWithinEJQuota = false; 1752 } 1753 if (setConstraintSatisfied(jobStatus, mUpdateTimeElapsed, 1754 isWithinQuotaLocked(jobStatus), isWithinEJQuota)) { 1755 changedJobs.add(jobStatus); 1756 } 1757 if (setExpeditedQuotaApproved(jobStatus, mUpdateTimeElapsed, isWithinEJQuota)) { 1758 changedJobs.add(jobStatus); 1759 } 1760 1761 final int userId = jobStatus.getSourceUserId(); 1762 final String packageName = jobStatus.getSourcePackageName(); 1763 final int realStandbyBucket = jobStatus.getStandbyBucket(); 1764 if (isWithinEJQuota 1765 && isWithinQuotaLocked(userId, packageName, realStandbyBucket)) { 1766 // TODO(141645789): we probably shouldn't cancel the alarm until we've verified 1767 // that all jobs for the userId-package are within quota. 1768 mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); 1769 } else { 1770 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket); 1771 } 1772 } 1773 1774 void postProcess() { 1775 for (int u = 0; u < mToScheduleStartAlarms.numMaps(); ++u) { 1776 final int userId = mToScheduleStartAlarms.keyAt(u); 1777 for (int p = 0; p < mToScheduleStartAlarms.numElementsForKey(userId); ++p) { 1778 final String packageName = mToScheduleStartAlarms.keyAt(u, p); 1779 final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName); 1780 maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket); 1781 } 1782 } 1783 } 1784 1785 void reset() { 1786 mToScheduleStartAlarms.clear(); 1787 } 1788 } 1789 1790 private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater(); 1791 1792 @GuardedBy("mLock") 1793 @NonNull 1794 private ArraySet<JobStatus> maybeUpdateConstraintForUidLocked(final int uid) { 1795 mUpdateUidConstraints.prepare(); 1796 mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints); 1797 1798 mUpdateUidConstraints.postProcess(); 1799 mUpdateUidConstraints.reset(); 1800 return mUpdateUidConstraints.changedJobs; 1801 } 1802 1803 /** 1804 * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run 1805 * again. This should only be called if the package is already out of quota. 1806 */ 1807 @VisibleForTesting 1808 void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName, 1809 final int standbyBucket) { 1810 if (standbyBucket == NEVER_INDEX) { 1811 return; 1812 } 1813 1814 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 1815 if (jobs == null || jobs.size() == 0) { 1816 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " 1817 + packageToString(userId, packageName) + " that has no jobs"); 1818 mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); 1819 return; 1820 } 1821 1822 ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 1823 final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); 1824 final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats, 1825 standbyBucket); 1826 final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName); 1827 1828 final boolean inRegularQuota = 1829 stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs[standbyBucket] 1830 && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs 1831 && isUnderJobCountQuota 1832 && isUnderTimingSessionCountQuota; 1833 if (inRegularQuota && remainingEJQuota > 0) { 1834 // Already in quota. Why was this method called? 1835 if (DEBUG) { 1836 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " 1837 + packageToString(userId, packageName) 1838 + " even though it already has " 1839 + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) 1840 + "ms in its quota."); 1841 } 1842 mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); 1843 mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); 1844 return; 1845 } 1846 1847 long inRegularQuotaTimeElapsed = Long.MAX_VALUE; 1848 long inEJQuotaTimeElapsed = Long.MAX_VALUE; 1849 if (!inRegularQuota) { 1850 // The time this app will have quota again. 1851 long inQuotaTimeElapsed = stats.inQuotaTimeElapsed; 1852 if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) { 1853 // App hit the rate limit. 1854 inQuotaTimeElapsed = 1855 Math.max(inQuotaTimeElapsed, stats.jobRateLimitExpirationTimeElapsed); 1856 } 1857 if (!isUnderTimingSessionCountQuota 1858 && stats.sessionCountInWindow < stats.sessionCountLimit) { 1859 // App hit the rate limit. 1860 inQuotaTimeElapsed = 1861 Math.max(inQuotaTimeElapsed, stats.sessionRateLimitExpirationTimeElapsed); 1862 } 1863 inRegularQuotaTimeElapsed = inQuotaTimeElapsed; 1864 } 1865 if (remainingEJQuota <= 0) { 1866 final long limitMs = 1867 getEJLimitMsLocked(userId, packageName, standbyBucket) - mQuotaBufferMs; 1868 long sumMs = 0; 1869 final Timer ejTimer = mEJPkgTimers.get(userId, packageName); 1870 if (ejTimer != null && ejTimer.isActive()) { 1871 final long nowElapsed = sElapsedRealtimeClock.millis(); 1872 sumMs += ejTimer.getCurrentDuration(nowElapsed); 1873 if (sumMs >= limitMs) { 1874 inEJQuotaTimeElapsed = (nowElapsed - limitMs) + mEJLimitWindowSizeMs; 1875 } 1876 } 1877 List<TimedEvent> timingSessions = mEJTimingSessions.get(userId, packageName); 1878 if (timingSessions != null) { 1879 for (int i = timingSessions.size() - 1; i >= 0; --i) { 1880 TimingSession ts = (TimingSession) timingSessions.get(i); 1881 final long durationMs = ts.endTimeElapsed - ts.startTimeElapsed; 1882 sumMs += durationMs; 1883 if (sumMs >= limitMs) { 1884 inEJQuotaTimeElapsed = 1885 ts.startTimeElapsed + (sumMs - limitMs) + mEJLimitWindowSizeMs; 1886 break; 1887 } 1888 } 1889 } else if ((ejTimer == null || !ejTimer.isActive()) && inRegularQuota) { 1890 // In some strange cases, an app may end be in the NEVER bucket but could have run 1891 // some regular jobs. This results in no EJ timing sessions and QC having a bad 1892 // time. 1893 Slog.wtf(TAG, packageToString(userId, packageName) 1894 + " has 0 EJ quota without running anything"); 1895 return; 1896 } 1897 } 1898 long inQuotaTimeElapsed = Math.min(inRegularQuotaTimeElapsed, inEJQuotaTimeElapsed); 1899 1900 if (inQuotaTimeElapsed <= sElapsedRealtimeClock.millis()) { 1901 final long nowElapsed = sElapsedRealtimeClock.millis(); 1902 Slog.wtf(TAG, 1903 "In quota time is " + (nowElapsed - inQuotaTimeElapsed) + "ms old. Now=" 1904 + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats); 1905 inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS; 1906 } 1907 mInQuotaAlarmQueue.addAlarm(new Package(userId, packageName), inQuotaTimeElapsed); 1908 } 1909 1910 private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed, 1911 boolean isWithinQuota, boolean isWithinEjQuota) { 1912 final boolean isSatisfied; 1913 if (jobStatus.startedAsExpeditedJob) { 1914 // If the job started as an EJ, then we should only consider EJ quota for the constraint 1915 // satisfaction. 1916 isSatisfied = isWithinEjQuota; 1917 } else if (mService.isCurrentlyRunningLocked(jobStatus)) { 1918 // Job is running but didn't start as an EJ, so only the regular quota should be 1919 // considered. 1920 isSatisfied = isWithinQuota; 1921 } else { 1922 isSatisfied = isWithinEjQuota || isWithinQuota; 1923 } 1924 if (!isSatisfied && jobStatus.getWhenStandbyDeferred() == 0) { 1925 // Mark that the job is being deferred due to buckets. 1926 jobStatus.setWhenStandbyDeferred(nowElapsed); 1927 } 1928 return jobStatus.setQuotaConstraintSatisfied(nowElapsed, isSatisfied); 1929 } 1930 1931 /** 1932 * If the satisfaction changes, this will tell connectivity & background jobs controller to 1933 * also re-evaluate their state. 1934 */ 1935 private boolean setExpeditedQuotaApproved(@NonNull JobStatus jobStatus, long nowElapsed, 1936 boolean isWithinQuota) { 1937 if (jobStatus.setExpeditedJobQuotaApproved(nowElapsed, isWithinQuota)) { 1938 mBackgroundJobsController.evaluateStateLocked(jobStatus); 1939 mConnectivityController.evaluateStateLocked(jobStatus); 1940 if (isWithinQuota && jobStatus.isReady()) { 1941 mStateChangedListener.onRunJobNow(jobStatus); 1942 } 1943 return true; 1944 } 1945 return false; 1946 } 1947 1948 @VisibleForTesting 1949 interface TimedEvent { 1950 long getEndTimeElapsed(); 1951 1952 void dump(IndentingPrintWriter pw); 1953 } 1954 1955 @VisibleForTesting 1956 static final class TimingSession implements TimedEvent { 1957 // Start timestamp in elapsed realtime timebase. 1958 public final long startTimeElapsed; 1959 // End timestamp in elapsed realtime timebase. 1960 public final long endTimeElapsed; 1961 // How many background jobs ran during this session. 1962 public final int bgJobCount; 1963 1964 private final int mHashCode; 1965 1966 TimingSession(long startElapsed, long endElapsed, int bgJobCount) { 1967 this.startTimeElapsed = startElapsed; 1968 this.endTimeElapsed = endElapsed; 1969 this.bgJobCount = bgJobCount; 1970 1971 int hashCode = 0; 1972 hashCode = 31 * hashCode + hashLong(startTimeElapsed); 1973 hashCode = 31 * hashCode + hashLong(endTimeElapsed); 1974 hashCode = 31 * hashCode + bgJobCount; 1975 mHashCode = hashCode; 1976 } 1977 1978 @Override 1979 public long getEndTimeElapsed() { 1980 return endTimeElapsed; 1981 } 1982 1983 @Override 1984 public String toString() { 1985 return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount 1986 + "}"; 1987 } 1988 1989 @Override 1990 public boolean equals(Object obj) { 1991 if (obj instanceof TimingSession) { 1992 TimingSession other = (TimingSession) obj; 1993 return startTimeElapsed == other.startTimeElapsed 1994 && endTimeElapsed == other.endTimeElapsed 1995 && bgJobCount == other.bgJobCount; 1996 } else { 1997 return false; 1998 } 1999 } 2000 2001 @Override 2002 public int hashCode() { 2003 return mHashCode; 2004 } 2005 2006 @Override 2007 public void dump(IndentingPrintWriter pw) { 2008 pw.print(startTimeElapsed); 2009 pw.print(" -> "); 2010 pw.print(endTimeElapsed); 2011 pw.print(" ("); 2012 pw.print(endTimeElapsed - startTimeElapsed); 2013 pw.print("), "); 2014 pw.print(bgJobCount); 2015 pw.print(" bg jobs."); 2016 pw.println(); 2017 } 2018 2019 public void dump(@NonNull ProtoOutputStream proto, long fieldId) { 2020 final long token = proto.start(fieldId); 2021 2022 proto.write(StateControllerProto.QuotaController.TimingSession.START_TIME_ELAPSED, 2023 startTimeElapsed); 2024 proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED, 2025 endTimeElapsed); 2026 proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT, 2027 bgJobCount); 2028 2029 proto.end(token); 2030 } 2031 } 2032 2033 @VisibleForTesting 2034 static final class QuotaBump implements TimedEvent { 2035 // Event timestamp in elapsed realtime timebase. 2036 public final long eventTimeElapsed; 2037 2038 QuotaBump(long eventElapsed) { 2039 this.eventTimeElapsed = eventElapsed; 2040 } 2041 2042 @Override 2043 public long getEndTimeElapsed() { 2044 return eventTimeElapsed; 2045 } 2046 2047 @Override 2048 public void dump(IndentingPrintWriter pw) { 2049 pw.print("Quota bump @ "); 2050 pw.print(eventTimeElapsed); 2051 pw.println(); 2052 } 2053 } 2054 2055 @VisibleForTesting 2056 static final class ShrinkableDebits { 2057 /** The amount of quota remaining. Can be negative if limit changes. */ 2058 private long mDebitTally; 2059 private int mStandbyBucket; 2060 2061 ShrinkableDebits(int standbyBucket) { 2062 mDebitTally = 0; 2063 mStandbyBucket = standbyBucket; 2064 } 2065 2066 long getTallyLocked() { 2067 return mDebitTally; 2068 } 2069 2070 /** 2071 * Negative if the tally should decrease (therefore increasing available quota); 2072 * or positive if the tally should increase (therefore decreasing available quota). 2073 */ 2074 long transactLocked(final long amount) { 2075 final long leftover = amount < 0 && Math.abs(amount) > mDebitTally 2076 ? mDebitTally + amount : 0; 2077 mDebitTally = Math.max(0, mDebitTally + amount); 2078 return leftover; 2079 } 2080 2081 void setStandbyBucketLocked(int standbyBucket) { 2082 mStandbyBucket = standbyBucket; 2083 } 2084 2085 int getStandbyBucketLocked() { 2086 return mStandbyBucket; 2087 } 2088 2089 @Override 2090 public String toString() { 2091 return "ShrinkableDebits { debit tally: " 2092 + mDebitTally + ", bucket: " + mStandbyBucket 2093 + " }"; 2094 } 2095 2096 void dumpLocked(IndentingPrintWriter pw) { 2097 pw.println(toString()); 2098 } 2099 } 2100 2101 private final class Timer { 2102 private final Package mPkg; 2103 private final int mUid; 2104 private final boolean mRegularJobTimer; 2105 2106 // List of jobs currently running for this app that started when the app wasn't in the 2107 // foreground. 2108 private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>(); 2109 private long mStartTimeElapsed; 2110 private int mBgJobCount; 2111 private long mDebitAdjustment; 2112 2113 Timer(int uid, int userId, String packageName, boolean regularJobTimer) { 2114 mPkg = new Package(userId, packageName); 2115 mUid = uid; 2116 mRegularJobTimer = regularJobTimer; 2117 } 2118 2119 void startTrackingJobLocked(@NonNull JobStatus jobStatus) { 2120 if (isTopStartedJobLocked(jobStatus)) { 2121 // We intentionally don't pay attention to fg state changes after a TOP job has 2122 // started. 2123 if (DEBUG) { 2124 Slog.v(TAG, 2125 "Timer ignoring " + jobStatus.toShortString() + " because isTop"); 2126 } 2127 return; 2128 } 2129 if (DEBUG) { 2130 Slog.v(TAG, "Starting to track " + jobStatus.toShortString()); 2131 } 2132 // Always maintain list of running jobs, even when quota is free. 2133 if (mRunningBgJobs.add(jobStatus) && shouldTrackLocked()) { 2134 mBgJobCount++; 2135 if (mRegularJobTimer) { 2136 incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1); 2137 } 2138 if (mRunningBgJobs.size() == 1) { 2139 // Started tracking the first job. 2140 mStartTimeElapsed = sElapsedRealtimeClock.millis(); 2141 mDebitAdjustment = 0; 2142 if (mRegularJobTimer) { 2143 // Starting the timer means that all cached execution stats are now 2144 // incorrect. 2145 invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); 2146 } 2147 scheduleCutoff(); 2148 } 2149 } 2150 } 2151 2152 void stopTrackingJob(@NonNull JobStatus jobStatus) { 2153 if (DEBUG) { 2154 Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString()); 2155 } 2156 synchronized (mLock) { 2157 if (mRunningBgJobs.size() == 0) { 2158 // maybeStopTrackingJobLocked can be called when an app cancels a job, so a 2159 // timer may not be running when it's asked to stop tracking a job. 2160 if (DEBUG) { 2161 Slog.d(TAG, "Timer isn't tracking any jobs but still told to stop"); 2162 } 2163 return; 2164 } 2165 final long nowElapsed = sElapsedRealtimeClock.millis(); 2166 final int standbyBucket = JobSchedulerService.standbyBucketForPackage( 2167 mPkg.packageName, mPkg.userId, nowElapsed); 2168 if (mRunningBgJobs.remove(jobStatus) && mRunningBgJobs.size() == 0 2169 && !isQuotaFreeLocked(standbyBucket)) { 2170 emitSessionLocked(nowElapsed); 2171 cancelCutoff(); 2172 } 2173 } 2174 } 2175 2176 void updateDebitAdjustment(long nowElapsed, long debit) { 2177 // Make sure we don't have a credit larger than the expected session. 2178 mDebitAdjustment = Math.max(mDebitAdjustment + debit, mStartTimeElapsed - nowElapsed); 2179 } 2180 2181 /** 2182 * Stops tracking all jobs and cancels any pending alarms. This should only be called if 2183 * the Timer is not going to be used anymore. 2184 */ 2185 void dropEverythingLocked() { 2186 mRunningBgJobs.clear(); 2187 cancelCutoff(); 2188 } 2189 2190 @GuardedBy("mLock") 2191 private void emitSessionLocked(long nowElapsed) { 2192 if (mBgJobCount <= 0) { 2193 // Nothing to emit. 2194 return; 2195 } 2196 TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount); 2197 saveTimingSession(mPkg.userId, mPkg.packageName, ts, !mRegularJobTimer, 2198 mDebitAdjustment); 2199 mBgJobCount = 0; 2200 // Don't reset the tracked jobs list as we need to keep tracking the current number 2201 // of jobs. 2202 // However, cancel the currently scheduled cutoff since it's not currently useful. 2203 cancelCutoff(); 2204 if (mRegularJobTimer) { 2205 incrementTimingSessionCountLocked(mPkg.userId, mPkg.packageName); 2206 } 2207 } 2208 2209 /** 2210 * Returns true if the Timer is actively tracking, as opposed to passively ref counting 2211 * during charging. 2212 */ 2213 public boolean isActive() { 2214 synchronized (mLock) { 2215 return mBgJobCount > 0; 2216 } 2217 } 2218 2219 boolean isRunning(JobStatus jobStatus) { 2220 return mRunningBgJobs.contains(jobStatus); 2221 } 2222 2223 long getCurrentDuration(long nowElapsed) { 2224 synchronized (mLock) { 2225 return !isActive() ? 0 : nowElapsed - mStartTimeElapsed + mDebitAdjustment; 2226 } 2227 } 2228 2229 int getBgJobCount() { 2230 synchronized (mLock) { 2231 return mBgJobCount; 2232 } 2233 } 2234 2235 @GuardedBy("mLock") 2236 private boolean shouldTrackLocked() { 2237 final long nowElapsed = sElapsedRealtimeClock.millis(); 2238 final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName, 2239 mPkg.userId, nowElapsed); 2240 final boolean hasTempAllowlistExemption = !mRegularJobTimer 2241 && hasTempAllowlistExemptionLocked(mUid, standbyBucket, nowElapsed); 2242 final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid); 2243 final boolean hasTopAppExemption = !mRegularJobTimer 2244 && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed); 2245 if (DEBUG) { 2246 Slog.d(TAG, "quotaFree=" + isQuotaFreeLocked(standbyBucket) 2247 + " isFG=" + mForegroundUids.get(mUid) 2248 + " tempEx=" + hasTempAllowlistExemption 2249 + " topEx=" + hasTopAppExemption); 2250 } 2251 return !isQuotaFreeLocked(standbyBucket) 2252 && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption 2253 && !hasTopAppExemption; 2254 } 2255 2256 void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) { 2257 if (isQuotaFree) { 2258 emitSessionLocked(nowElapsed); 2259 } else if (!isActive() && shouldTrackLocked()) { 2260 // Start timing from unplug. 2261 if (mRunningBgJobs.size() > 0) { 2262 mStartTimeElapsed = nowElapsed; 2263 mDebitAdjustment = 0; 2264 // NOTE: this does have the unfortunate consequence that if the device is 2265 // repeatedly plugged in and unplugged, or an app changes foreground state 2266 // very frequently, the job count for a package may be artificially high. 2267 mBgJobCount = mRunningBgJobs.size(); 2268 2269 if (mRegularJobTimer) { 2270 incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount); 2271 // Starting the timer means that all cached execution stats are now 2272 // incorrect. 2273 invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName); 2274 } 2275 // Schedule cutoff since we're now actively tracking for quotas again. 2276 scheduleCutoff(); 2277 } 2278 } 2279 } 2280 2281 void rescheduleCutoff() { 2282 cancelCutoff(); 2283 scheduleCutoff(); 2284 } 2285 2286 private void scheduleCutoff() { 2287 // Each package can only be in one standby bucket, so we only need to have one 2288 // message per timer. We only need to reschedule when restarting timer or when 2289 // standby bucket changes. 2290 synchronized (mLock) { 2291 if (!isActive()) { 2292 return; 2293 } 2294 Message msg = mHandler.obtainMessage( 2295 mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg); 2296 final long timeRemainingMs = mRegularJobTimer 2297 ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName) 2298 : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName); 2299 if (DEBUG) { 2300 Slog.i(TAG, 2301 (mRegularJobTimer ? "Regular job" : "EJ") + " for " + mPkg + " has " 2302 + timeRemainingMs + "ms left."); 2303 } 2304 // If the job was running the entire time, then the system would be up, so it's 2305 // fine to use uptime millis for these messages. 2306 mHandler.sendMessageDelayed(msg, timeRemainingMs); 2307 } 2308 } 2309 2310 private void cancelCutoff() { 2311 mHandler.removeMessages( 2312 mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg); 2313 } 2314 2315 public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { 2316 pw.print("Timer<"); 2317 pw.print(mRegularJobTimer ? "REG" : "EJ"); 2318 pw.print(">{"); 2319 pw.print(mPkg); 2320 pw.print("} "); 2321 if (isActive()) { 2322 pw.print("started at "); 2323 pw.print(mStartTimeElapsed); 2324 pw.print(" ("); 2325 pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed); 2326 pw.print("ms ago)"); 2327 } else { 2328 pw.print("NOT active"); 2329 } 2330 pw.print(", "); 2331 pw.print(mBgJobCount); 2332 pw.print(" running bg jobs"); 2333 if (!mRegularJobTimer) { 2334 pw.print(" (debit adj="); 2335 pw.print(mDebitAdjustment); 2336 pw.print(")"); 2337 } 2338 pw.println(); 2339 pw.increaseIndent(); 2340 for (int i = 0; i < mRunningBgJobs.size(); i++) { 2341 JobStatus js = mRunningBgJobs.valueAt(i); 2342 if (predicate.test(js)) { 2343 pw.println(js.toShortString()); 2344 } 2345 } 2346 pw.decreaseIndent(); 2347 } 2348 2349 public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) { 2350 final long token = proto.start(fieldId); 2351 2352 proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive()); 2353 proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED, 2354 mStartTimeElapsed); 2355 proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount); 2356 for (int i = 0; i < mRunningBgJobs.size(); i++) { 2357 JobStatus js = mRunningBgJobs.valueAt(i); 2358 if (predicate.test(js)) { 2359 js.writeToShortProto(proto, 2360 StateControllerProto.QuotaController.Timer.RUNNING_JOBS); 2361 } 2362 } 2363 2364 proto.end(token); 2365 } 2366 } 2367 2368 private final class TopAppTimer { 2369 private final Package mPkg; 2370 2371 // List of jobs currently running for this app that started when the app wasn't in the 2372 // foreground. 2373 private final SparseArray<UsageEvents.Event> mActivities = new SparseArray<>(); 2374 private long mStartTimeElapsed; 2375 2376 TopAppTimer(int userId, String packageName) { 2377 mPkg = new Package(userId, packageName); 2378 } 2379 2380 private int calculateTimeChunks(final long nowElapsed) { 2381 final long totalTopTimeMs = nowElapsed - mStartTimeElapsed; 2382 int numTimeChunks = (int) (totalTopTimeMs / mEJTopAppTimeChunkSizeMs); 2383 final long remainderMs = totalTopTimeMs % mEJTopAppTimeChunkSizeMs; 2384 if (remainderMs >= SECOND_IN_MILLIS) { 2385 // "Round up" 2386 numTimeChunks++; 2387 } 2388 return numTimeChunks; 2389 } 2390 2391 long getPendingReward(final long nowElapsed) { 2392 return mEJRewardTopAppMs * calculateTimeChunks(nowElapsed); 2393 } 2394 2395 void processEventLocked(@NonNull UsageEvents.Event event) { 2396 final long nowElapsed = sElapsedRealtimeClock.millis(); 2397 switch (event.getEventType()) { 2398 case UsageEvents.Event.ACTIVITY_RESUMED: 2399 if (mActivities.size() == 0) { 2400 mStartTimeElapsed = nowElapsed; 2401 } 2402 mActivities.put(event.mInstanceId, event); 2403 break; 2404 case UsageEvents.Event.ACTIVITY_PAUSED: 2405 case UsageEvents.Event.ACTIVITY_STOPPED: 2406 case UsageEvents.Event.ACTIVITY_DESTROYED: 2407 final UsageEvents.Event existingEvent = 2408 mActivities.removeReturnOld(event.mInstanceId); 2409 if (existingEvent != null && mActivities.size() == 0) { 2410 final long pendingReward = getPendingReward(nowElapsed); 2411 if (DEBUG) { 2412 Slog.d(TAG, "Crediting " + mPkg + " " + pendingReward + "ms" 2413 + " for " + calculateTimeChunks(nowElapsed) + " time chunks"); 2414 } 2415 final ShrinkableDebits debits = 2416 getEJDebitsLocked(mPkg.userId, mPkg.packageName); 2417 if (transactQuotaLocked(mPkg.userId, mPkg.packageName, 2418 nowElapsed, debits, pendingReward)) { 2419 mStateChangedListener.onControllerStateChanged( 2420 maybeUpdateConstraintForPkgLocked(nowElapsed, 2421 mPkg.userId, mPkg.packageName)); 2422 } 2423 } 2424 break; 2425 } 2426 } 2427 2428 boolean isActive() { 2429 synchronized (mLock) { 2430 return mActivities.size() > 0; 2431 } 2432 } 2433 2434 public void dump(IndentingPrintWriter pw) { 2435 pw.print("TopAppTimer{"); 2436 pw.print(mPkg); 2437 pw.print("} "); 2438 if (isActive()) { 2439 pw.print("started at "); 2440 pw.print(mStartTimeElapsed); 2441 pw.print(" ("); 2442 pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed); 2443 pw.print("ms ago)"); 2444 } else { 2445 pw.print("NOT active"); 2446 } 2447 pw.println(); 2448 pw.increaseIndent(); 2449 for (int i = 0; i < mActivities.size(); i++) { 2450 UsageEvents.Event event = mActivities.valueAt(i); 2451 pw.println(event.getClassName()); 2452 } 2453 pw.decreaseIndent(); 2454 } 2455 2456 public void dump(ProtoOutputStream proto, long fieldId) { 2457 final long token = proto.start(fieldId); 2458 2459 proto.write(StateControllerProto.QuotaController.TopAppTimer.IS_ACTIVE, isActive()); 2460 proto.write(StateControllerProto.QuotaController.TopAppTimer.START_TIME_ELAPSED, 2461 mStartTimeElapsed); 2462 proto.write(StateControllerProto.QuotaController.TopAppTimer.ACTIVITY_COUNT, 2463 mActivities.size()); 2464 // TODO: maybe dump activities/events 2465 2466 proto.end(token); 2467 } 2468 } 2469 2470 /** 2471 * Tracking of app assignments to standby buckets 2472 */ 2473 final class StandbyTracker extends AppIdleStateChangeListener { 2474 2475 @Override 2476 public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, 2477 boolean idle, int bucket, int reason) { 2478 // Update job bookkeeping out of band. 2479 JobSchedulerBackgroundThread.getHandler().post(() -> { 2480 final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket); 2481 updateStandbyBucket(userId, packageName, bucketIndex); 2482 }); 2483 } 2484 2485 @Override 2486 public void triggerTemporaryQuotaBump(String packageName, @UserIdInt int userId) { 2487 synchronized (mLock) { 2488 List<TimedEvent> events = mTimingEvents.get(userId, packageName); 2489 if (events == null || events.size() == 0) { 2490 // If the app hasn't run any jobs, there's no point giving it a quota bump. 2491 return; 2492 } 2493 events.add(new QuotaBump(sElapsedRealtimeClock.millis())); 2494 invalidateAllExecutionStatsLocked(userId, packageName); 2495 } 2496 // Update jobs out of band. 2497 mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); 2498 } 2499 } 2500 2501 @VisibleForTesting 2502 void updateStandbyBucket( 2503 final int userId, final @NonNull String packageName, final int bucketIndex) { 2504 if (DEBUG) { 2505 Slog.i(TAG, "Moving pkg " + packageToString(userId, packageName) 2506 + " to bucketIndex " + bucketIndex); 2507 } 2508 List<JobStatus> restrictedChanges = new ArrayList<>(); 2509 synchronized (mLock) { 2510 ShrinkableDebits debits = mEJStats.get(userId, packageName); 2511 if (debits != null) { 2512 debits.setStandbyBucketLocked(bucketIndex); 2513 } 2514 2515 ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName); 2516 if (jobs == null || jobs.size() == 0) { 2517 // Nothing further to do. 2518 return; 2519 } 2520 for (int i = jobs.size() - 1; i >= 0; i--) { 2521 JobStatus js = jobs.valueAt(i); 2522 // Effective standby bucket can change after this in some situations so 2523 // use the real bucket so that the job is tracked by the controllers. 2524 if ((bucketIndex == RESTRICTED_INDEX || js.getStandbyBucket() == RESTRICTED_INDEX) 2525 && bucketIndex != js.getStandbyBucket()) { 2526 restrictedChanges.add(js); 2527 } 2528 js.setStandbyBucket(bucketIndex); 2529 } 2530 Timer timer = mPkgTimers.get(userId, packageName); 2531 if (timer != null && timer.isActive()) { 2532 timer.rescheduleCutoff(); 2533 } 2534 timer = mEJPkgTimers.get(userId, packageName); 2535 if (timer != null && timer.isActive()) { 2536 timer.rescheduleCutoff(); 2537 } 2538 mStateChangedListener.onControllerStateChanged( 2539 maybeUpdateConstraintForPkgLocked( 2540 sElapsedRealtimeClock.millis(), userId, packageName)); 2541 } 2542 if (restrictedChanges.size() > 0) { 2543 mStateChangedListener.onRestrictedBucketChanged(restrictedChanges); 2544 } 2545 } 2546 2547 final class UsageEventTracker implements UsageEventListener { 2548 /** 2549 * Callback to inform listeners of a new event. 2550 */ 2551 @Override 2552 public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) { 2553 mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget(); 2554 } 2555 } 2556 2557 final class TempAllowlistTracker implements PowerAllowlistInternal.TempAllowlistChangeListener { 2558 2559 @Override 2560 public void onAppAdded(int uid) { 2561 synchronized (mLock) { 2562 final long nowElapsed = sElapsedRealtimeClock.millis(); 2563 mTempAllowlistCache.put(uid, true); 2564 final ArraySet<String> packages = mService.getPackagesForUidLocked(uid); 2565 if (packages != null) { 2566 final int userId = UserHandle.getUserId(uid); 2567 for (int i = packages.size() - 1; i >= 0; --i) { 2568 Timer t = mEJPkgTimers.get(userId, packages.valueAt(i)); 2569 if (t != null) { 2570 t.onStateChangedLocked(nowElapsed, true); 2571 } 2572 } 2573 final ArraySet<JobStatus> changedJobs = maybeUpdateConstraintForUidLocked(uid); 2574 if (changedJobs.size() > 0) { 2575 mStateChangedListener.onControllerStateChanged(changedJobs); 2576 } 2577 } 2578 } 2579 } 2580 2581 @Override 2582 public void onAppRemoved(int uid) { 2583 synchronized (mLock) { 2584 final long nowElapsed = sElapsedRealtimeClock.millis(); 2585 final long endElapsed = nowElapsed + mEJGracePeriodTempAllowlistMs; 2586 mTempAllowlistCache.delete(uid); 2587 mTempAllowlistGraceCache.put(uid, endElapsed); 2588 Message msg = mHandler.obtainMessage(MSG_END_GRACE_PERIOD, uid, 0); 2589 mHandler.sendMessageDelayed(msg, mEJGracePeriodTempAllowlistMs); 2590 } 2591 } 2592 } 2593 2594 private static final class TimedEventTooOldPredicate implements Predicate<TimedEvent> { 2595 private long mNowElapsed; 2596 2597 private void updateNow() { 2598 mNowElapsed = sElapsedRealtimeClock.millis(); 2599 } 2600 2601 @Override 2602 public boolean test(TimedEvent ts) { 2603 return ts.getEndTimeElapsed() <= mNowElapsed - MAX_PERIOD_MS; 2604 } 2605 } 2606 2607 private final TimedEventTooOldPredicate mTimedEventTooOld = new TimedEventTooOldPredicate(); 2608 2609 private final Consumer<List<TimedEvent>> mDeleteOldEventsFunctor = events -> { 2610 if (events != null) { 2611 // Remove everything older than MAX_PERIOD_MS time ago. 2612 events.removeIf(mTimedEventTooOld); 2613 } 2614 }; 2615 2616 @VisibleForTesting 2617 void deleteObsoleteSessionsLocked() { 2618 mTimedEventTooOld.updateNow(); 2619 2620 // Regular sessions 2621 mTimingEvents.forEach(mDeleteOldEventsFunctor); 2622 2623 // EJ sessions 2624 for (int uIdx = 0; uIdx < mEJTimingSessions.numMaps(); ++uIdx) { 2625 final int userId = mEJTimingSessions.keyAt(uIdx); 2626 for (int pIdx = 0; pIdx < mEJTimingSessions.numElementsForKey(userId); ++pIdx) { 2627 final String packageName = mEJTimingSessions.keyAt(uIdx, pIdx); 2628 final ShrinkableDebits debits = getEJDebitsLocked(userId, packageName); 2629 final List<TimedEvent> sessions = mEJTimingSessions.get(userId, packageName); 2630 if (sessions == null) { 2631 continue; 2632 } 2633 2634 while (sessions.size() > 0) { 2635 final TimingSession ts = (TimingSession) sessions.get(0); 2636 if (mTimedEventTooOld.test(ts)) { 2637 // Stale sessions may still be factored into tally. Remove them. 2638 final long duration = ts.endTimeElapsed - ts.startTimeElapsed; 2639 debits.transactLocked(-duration); 2640 sessions.remove(0); 2641 } else { 2642 break; 2643 } 2644 } 2645 } 2646 } 2647 } 2648 2649 private class QcHandler extends Handler { 2650 2651 QcHandler(Looper looper) { 2652 super(looper); 2653 } 2654 2655 @Override 2656 public void handleMessage(Message msg) { 2657 synchronized (mLock) { 2658 switch (msg.what) { 2659 case MSG_REACHED_QUOTA: { 2660 Package pkg = (Package) msg.obj; 2661 if (DEBUG) { 2662 Slog.d(TAG, "Checking if " + pkg + " has reached its quota."); 2663 } 2664 2665 long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId, 2666 pkg.packageName); 2667 if (timeRemainingMs <= 50) { 2668 // Less than 50 milliseconds left. Start process of shutting down jobs. 2669 if (DEBUG) Slog.d(TAG, pkg + " has reached its quota."); 2670 mStateChangedListener.onControllerStateChanged( 2671 maybeUpdateConstraintForPkgLocked( 2672 sElapsedRealtimeClock.millis(), 2673 pkg.userId, pkg.packageName)); 2674 } else { 2675 // This could potentially happen if an old session phases out while a 2676 // job is currently running. 2677 // Reschedule message 2678 Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); 2679 timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, 2680 pkg.packageName); 2681 if (DEBUG) { 2682 Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left."); 2683 } 2684 sendMessageDelayed(rescheduleMsg, timeRemainingMs); 2685 } 2686 break; 2687 } 2688 case MSG_REACHED_EJ_QUOTA: { 2689 Package pkg = (Package) msg.obj; 2690 if (DEBUG) { 2691 Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota."); 2692 } 2693 2694 long timeRemainingMs = getRemainingEJExecutionTimeLocked( 2695 pkg.userId, pkg.packageName); 2696 if (timeRemainingMs <= 0) { 2697 if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota."); 2698 mStateChangedListener.onControllerStateChanged( 2699 maybeUpdateConstraintForPkgLocked( 2700 sElapsedRealtimeClock.millis(), 2701 pkg.userId, pkg.packageName)); 2702 } else { 2703 // This could potentially happen if an old session phases out while a 2704 // job is currently running. 2705 // Reschedule message 2706 Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg); 2707 timeRemainingMs = getTimeUntilEJQuotaConsumedLocked( 2708 pkg.userId, pkg.packageName); 2709 if (DEBUG) { 2710 Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left for EJ"); 2711 } 2712 sendMessageDelayed(rescheduleMsg, timeRemainingMs); 2713 } 2714 break; 2715 } 2716 case MSG_CLEAN_UP_SESSIONS: 2717 if (DEBUG) { 2718 Slog.d(TAG, "Cleaning up timing sessions."); 2719 } 2720 deleteObsoleteSessionsLocked(); 2721 maybeScheduleCleanupAlarmLocked(); 2722 2723 break; 2724 case MSG_CHECK_PACKAGE: { 2725 String packageName = (String) msg.obj; 2726 int userId = msg.arg1; 2727 if (DEBUG) { 2728 Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName)); 2729 } 2730 mStateChangedListener.onControllerStateChanged( 2731 maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(), 2732 userId, packageName)); 2733 break; 2734 } 2735 case MSG_UID_PROCESS_STATE_CHANGED: { 2736 final int uid = msg.arg1; 2737 final int procState = msg.arg2; 2738 final int userId = UserHandle.getUserId(uid); 2739 final long nowElapsed = sElapsedRealtimeClock.millis(); 2740 2741 synchronized (mLock) { 2742 boolean isQuotaFree; 2743 if (procState <= ActivityManager.PROCESS_STATE_TOP) { 2744 mTopAppCache.put(uid, true); 2745 mTopAppGraceCache.delete(uid); 2746 if (mForegroundUids.get(uid)) { 2747 // Went from FGS to TOP. We don't need to reprocess timers or 2748 // jobs. 2749 break; 2750 } 2751 mForegroundUids.put(uid, true); 2752 isQuotaFree = true; 2753 } else { 2754 final boolean reprocess; 2755 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 2756 reprocess = !mForegroundUids.get(uid); 2757 mForegroundUids.put(uid, true); 2758 isQuotaFree = true; 2759 } else { 2760 reprocess = true; 2761 mForegroundUids.delete(uid); 2762 isQuotaFree = false; 2763 } 2764 if (mTopAppCache.get(uid)) { 2765 final long endElapsed = nowElapsed + mEJGracePeriodTopAppMs; 2766 mTopAppCache.delete(uid); 2767 mTopAppGraceCache.put(uid, endElapsed); 2768 sendMessageDelayed(obtainMessage(MSG_END_GRACE_PERIOD, uid, 0), 2769 mEJGracePeriodTopAppMs); 2770 } 2771 if (!reprocess) { 2772 break; 2773 } 2774 } 2775 // Update Timers first. 2776 if (mPkgTimers.indexOfKey(userId) >= 0 2777 || mEJPkgTimers.indexOfKey(userId) >= 0) { 2778 final ArraySet<String> packages = 2779 mService.getPackagesForUidLocked(uid); 2780 if (packages != null) { 2781 for (int i = packages.size() - 1; i >= 0; --i) { 2782 Timer t = mEJPkgTimers.get(userId, packages.valueAt(i)); 2783 if (t != null) { 2784 t.onStateChangedLocked(nowElapsed, isQuotaFree); 2785 } 2786 t = mPkgTimers.get(userId, packages.valueAt(i)); 2787 if (t != null) { 2788 t.onStateChangedLocked(nowElapsed, isQuotaFree); 2789 } 2790 } 2791 } 2792 } 2793 final ArraySet<JobStatus> changedJobs = 2794 maybeUpdateConstraintForUidLocked(uid); 2795 if (changedJobs.size() > 0) { 2796 mStateChangedListener.onControllerStateChanged(changedJobs); 2797 } 2798 } 2799 break; 2800 } 2801 case MSG_PROCESS_USAGE_EVENT: { 2802 final int userId = msg.arg1; 2803 final UsageEvents.Event event = (UsageEvents.Event) msg.obj; 2804 final String pkgName = event.getPackageName(); 2805 if (DEBUG) { 2806 Slog.d(TAG, "Processing event " + event.getEventType() 2807 + " for " + packageToString(userId, pkgName)); 2808 } 2809 switch (event.getEventType()) { 2810 case UsageEvents.Event.ACTIVITY_RESUMED: 2811 case UsageEvents.Event.ACTIVITY_PAUSED: 2812 case UsageEvents.Event.ACTIVITY_STOPPED: 2813 case UsageEvents.Event.ACTIVITY_DESTROYED: 2814 synchronized (mLock) { 2815 TopAppTimer timer = mTopAppTrackers.get(userId, pkgName); 2816 if (timer == null) { 2817 timer = new TopAppTimer(userId, pkgName); 2818 mTopAppTrackers.add(userId, pkgName, timer); 2819 } 2820 timer.processEventLocked(event); 2821 } 2822 break; 2823 case UsageEvents.Event.USER_INTERACTION: 2824 case UsageEvents.Event.CHOOSER_ACTION: 2825 case UsageEvents.Event.NOTIFICATION_INTERRUPTION: 2826 // Don't need to include SHORTCUT_INVOCATION. The app will be 2827 // launched through it (if it's not already on top). 2828 grantRewardForInstantEvent( 2829 userId, pkgName, mEJRewardInteractionMs); 2830 break; 2831 case UsageEvents.Event.NOTIFICATION_SEEN: 2832 // Intentionally don't give too much for notification seen. 2833 // Interactions will award more. 2834 grantRewardForInstantEvent( 2835 userId, pkgName, mEJRewardNotificationSeenMs); 2836 break; 2837 } 2838 2839 break; 2840 } 2841 case MSG_END_GRACE_PERIOD: { 2842 final int uid = msg.arg1; 2843 synchronized (mLock) { 2844 if (mTempAllowlistCache.get(uid) || mTopAppCache.get(uid)) { 2845 // App added back to the temp allowlist or became top again 2846 // during the grace period. 2847 if (DEBUG) { 2848 Slog.d(TAG, uid + " is still allowed"); 2849 } 2850 break; 2851 } 2852 final long nowElapsed = sElapsedRealtimeClock.millis(); 2853 if (nowElapsed < mTempAllowlistGraceCache.get(uid) 2854 || nowElapsed < mTopAppGraceCache.get(uid)) { 2855 // One of the grace periods is still in effect. 2856 if (DEBUG) { 2857 Slog.d(TAG, uid + " is still in grace period"); 2858 } 2859 break; 2860 } 2861 if (DEBUG) { 2862 Slog.d(TAG, uid + " is now out of grace period"); 2863 } 2864 mTempAllowlistGraceCache.delete(uid); 2865 mTopAppGraceCache.delete(uid); 2866 final ArraySet<String> packages = mService.getPackagesForUidLocked(uid); 2867 if (packages != null) { 2868 final int userId = UserHandle.getUserId(uid); 2869 for (int i = packages.size() - 1; i >= 0; --i) { 2870 Timer t = mEJPkgTimers.get(userId, packages.valueAt(i)); 2871 if (t != null) { 2872 t.onStateChangedLocked(nowElapsed, false); 2873 } 2874 } 2875 final ArraySet<JobStatus> changedJobs = 2876 maybeUpdateConstraintForUidLocked(uid); 2877 if (changedJobs.size() > 0) { 2878 mStateChangedListener.onControllerStateChanged(changedJobs); 2879 } 2880 } 2881 } 2882 2883 break; 2884 } 2885 } 2886 } 2887 } 2888 } 2889 2890 /** Track when UPTCs are expected to come back into quota. */ 2891 private class InQuotaAlarmQueue extends AlarmQueue<Package> { 2892 private InQuotaAlarmQueue(Context context, Looper looper) { 2893 super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false, 2894 QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS); 2895 } 2896 2897 @Override 2898 protected boolean isForUser(@NonNull Package key, int userId) { 2899 return key.userId == userId; 2900 } 2901 2902 @Override 2903 protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) { 2904 for (int i = 0; i < expired.size(); ++i) { 2905 Package p = expired.valueAt(i); 2906 mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName) 2907 .sendToTarget(); 2908 } 2909 } 2910 } 2911 2912 @Override 2913 public void prepareForUpdatedConstantsLocked() { 2914 mQcConstants.mShouldReevaluateConstraints = false; 2915 mQcConstants.mRateLimitingConstantsUpdated = false; 2916 mQcConstants.mExecutionPeriodConstantsUpdated = false; 2917 mQcConstants.mEJLimitConstantsUpdated = false; 2918 mQcConstants.mQuotaBumpConstantsUpdated = false; 2919 } 2920 2921 @Override 2922 public void processConstantLocked(DeviceConfig.Properties properties, String key) { 2923 mQcConstants.processConstantLocked(properties, key); 2924 } 2925 2926 @Override 2927 public void onConstantsUpdatedLocked() { 2928 if (mQcConstants.mShouldReevaluateConstraints || mIsEnabled == mConstants.USE_TARE_POLICY) { 2929 mIsEnabled = !mConstants.USE_TARE_POLICY; 2930 // Update job bookkeeping out of band. 2931 JobSchedulerBackgroundThread.getHandler().post(() -> { 2932 synchronized (mLock) { 2933 invalidateAllExecutionStatsLocked(); 2934 maybeUpdateAllConstraintsLocked(); 2935 } 2936 }); 2937 } 2938 } 2939 2940 @VisibleForTesting 2941 class QcConstants { 2942 private boolean mShouldReevaluateConstraints = false; 2943 private boolean mRateLimitingConstantsUpdated = false; 2944 private boolean mExecutionPeriodConstantsUpdated = false; 2945 private boolean mEJLimitConstantsUpdated = false; 2946 private boolean mQuotaBumpConstantsUpdated = false; 2947 2948 /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ 2949 private static final String QC_CONSTANT_PREFIX = "qc_"; 2950 2951 /** 2952 * Previously used keys: 2953 * * allowed_time_per_period_ms -- No longer used after splitting by bucket 2954 */ 2955 2956 @VisibleForTesting 2957 static final String KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 2958 QC_CONSTANT_PREFIX + "allowed_time_per_period_exempted_ms"; 2959 @VisibleForTesting 2960 static final String KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 2961 QC_CONSTANT_PREFIX + "allowed_time_per_period_active_ms"; 2962 @VisibleForTesting 2963 static final String KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS = 2964 QC_CONSTANT_PREFIX + "allowed_time_per_period_working_ms"; 2965 @VisibleForTesting 2966 static final String KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = 2967 QC_CONSTANT_PREFIX + "allowed_time_per_period_frequent_ms"; 2968 @VisibleForTesting 2969 static final String KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS = 2970 QC_CONSTANT_PREFIX + "allowed_time_per_period_rare_ms"; 2971 @VisibleForTesting 2972 static final String KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 2973 QC_CONSTANT_PREFIX + "allowed_time_per_period_restricted_ms"; 2974 @VisibleForTesting 2975 static final String KEY_IN_QUOTA_BUFFER_MS = 2976 QC_CONSTANT_PREFIX + "in_quota_buffer_ms"; 2977 @VisibleForTesting 2978 static final String KEY_WINDOW_SIZE_EXEMPTED_MS = 2979 QC_CONSTANT_PREFIX + "window_size_exempted_ms"; 2980 @VisibleForTesting 2981 static final String KEY_WINDOW_SIZE_ACTIVE_MS = 2982 QC_CONSTANT_PREFIX + "window_size_active_ms"; 2983 @VisibleForTesting 2984 static final String KEY_WINDOW_SIZE_WORKING_MS = 2985 QC_CONSTANT_PREFIX + "window_size_working_ms"; 2986 @VisibleForTesting 2987 static final String KEY_WINDOW_SIZE_FREQUENT_MS = 2988 QC_CONSTANT_PREFIX + "window_size_frequent_ms"; 2989 @VisibleForTesting 2990 static final String KEY_WINDOW_SIZE_RARE_MS = 2991 QC_CONSTANT_PREFIX + "window_size_rare_ms"; 2992 @VisibleForTesting 2993 static final String KEY_WINDOW_SIZE_RESTRICTED_MS = 2994 QC_CONSTANT_PREFIX + "window_size_restricted_ms"; 2995 @VisibleForTesting 2996 static final String KEY_MAX_EXECUTION_TIME_MS = 2997 QC_CONSTANT_PREFIX + "max_execution_time_ms"; 2998 @VisibleForTesting 2999 static final String KEY_MAX_JOB_COUNT_EXEMPTED = 3000 QC_CONSTANT_PREFIX + "max_job_count_exempted"; 3001 @VisibleForTesting 3002 static final String KEY_MAX_JOB_COUNT_ACTIVE = 3003 QC_CONSTANT_PREFIX + "max_job_count_active"; 3004 @VisibleForTesting 3005 static final String KEY_MAX_JOB_COUNT_WORKING = 3006 QC_CONSTANT_PREFIX + "max_job_count_working"; 3007 @VisibleForTesting 3008 static final String KEY_MAX_JOB_COUNT_FREQUENT = 3009 QC_CONSTANT_PREFIX + "max_job_count_frequent"; 3010 @VisibleForTesting 3011 static final String KEY_MAX_JOB_COUNT_RARE = 3012 QC_CONSTANT_PREFIX + "max_job_count_rare"; 3013 @VisibleForTesting 3014 static final String KEY_MAX_JOB_COUNT_RESTRICTED = 3015 QC_CONSTANT_PREFIX + "max_job_count_restricted"; 3016 @VisibleForTesting 3017 static final String KEY_RATE_LIMITING_WINDOW_MS = 3018 QC_CONSTANT_PREFIX + "rate_limiting_window_ms"; 3019 @VisibleForTesting 3020 static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 3021 QC_CONSTANT_PREFIX + "max_job_count_per_rate_limiting_window"; 3022 @VisibleForTesting 3023 static final String KEY_MAX_SESSION_COUNT_EXEMPTED = 3024 QC_CONSTANT_PREFIX + "max_session_count_exempted"; 3025 @VisibleForTesting 3026 static final String KEY_MAX_SESSION_COUNT_ACTIVE = 3027 QC_CONSTANT_PREFIX + "max_session_count_active"; 3028 @VisibleForTesting 3029 static final String KEY_MAX_SESSION_COUNT_WORKING = 3030 QC_CONSTANT_PREFIX + "max_session_count_working"; 3031 @VisibleForTesting 3032 static final String KEY_MAX_SESSION_COUNT_FREQUENT = 3033 QC_CONSTANT_PREFIX + "max_session_count_frequent"; 3034 @VisibleForTesting 3035 static final String KEY_MAX_SESSION_COUNT_RARE = 3036 QC_CONSTANT_PREFIX + "max_session_count_rare"; 3037 @VisibleForTesting 3038 static final String KEY_MAX_SESSION_COUNT_RESTRICTED = 3039 QC_CONSTANT_PREFIX + "max_session_count_restricted"; 3040 @VisibleForTesting 3041 static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 3042 QC_CONSTANT_PREFIX + "max_session_count_per_rate_limiting_window"; 3043 @VisibleForTesting 3044 static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS = 3045 QC_CONSTANT_PREFIX + "timing_session_coalescing_duration_ms"; 3046 @VisibleForTesting 3047 static final String KEY_MIN_QUOTA_CHECK_DELAY_MS = 3048 QC_CONSTANT_PREFIX + "min_quota_check_delay_ms"; 3049 @VisibleForTesting 3050 static final String KEY_EJ_LIMIT_EXEMPTED_MS = 3051 QC_CONSTANT_PREFIX + "ej_limit_exempted_ms"; 3052 @VisibleForTesting 3053 static final String KEY_EJ_LIMIT_ACTIVE_MS = 3054 QC_CONSTANT_PREFIX + "ej_limit_active_ms"; 3055 @VisibleForTesting 3056 static final String KEY_EJ_LIMIT_WORKING_MS = 3057 QC_CONSTANT_PREFIX + "ej_limit_working_ms"; 3058 @VisibleForTesting 3059 static final String KEY_EJ_LIMIT_FREQUENT_MS = 3060 QC_CONSTANT_PREFIX + "ej_limit_frequent_ms"; 3061 @VisibleForTesting 3062 static final String KEY_EJ_LIMIT_RARE_MS = 3063 QC_CONSTANT_PREFIX + "ej_limit_rare_ms"; 3064 @VisibleForTesting 3065 static final String KEY_EJ_LIMIT_RESTRICTED_MS = 3066 QC_CONSTANT_PREFIX + "ej_limit_restricted_ms"; 3067 @VisibleForTesting 3068 static final String KEY_EJ_LIMIT_ADDITION_SPECIAL_MS = 3069 QC_CONSTANT_PREFIX + "ej_limit_addition_special_ms"; 3070 @VisibleForTesting 3071 static final String KEY_EJ_LIMIT_ADDITION_INSTALLER_MS = 3072 QC_CONSTANT_PREFIX + "ej_limit_addition_installer_ms"; 3073 @VisibleForTesting 3074 static final String KEY_EJ_WINDOW_SIZE_MS = 3075 QC_CONSTANT_PREFIX + "ej_window_size_ms"; 3076 @VisibleForTesting 3077 static final String KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 3078 QC_CONSTANT_PREFIX + "ej_top_app_time_chunk_size_ms"; 3079 @VisibleForTesting 3080 static final String KEY_EJ_REWARD_TOP_APP_MS = 3081 QC_CONSTANT_PREFIX + "ej_reward_top_app_ms"; 3082 @VisibleForTesting 3083 static final String KEY_EJ_REWARD_INTERACTION_MS = 3084 QC_CONSTANT_PREFIX + "ej_reward_interaction_ms"; 3085 @VisibleForTesting 3086 static final String KEY_EJ_REWARD_NOTIFICATION_SEEN_MS = 3087 QC_CONSTANT_PREFIX + "ej_reward_notification_seen_ms"; 3088 @VisibleForTesting 3089 static final String KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3090 QC_CONSTANT_PREFIX + "ej_grace_period_temp_allowlist_ms"; 3091 @VisibleForTesting 3092 static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = 3093 QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; 3094 @VisibleForTesting 3095 static final String KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS = 3096 QC_CONSTANT_PREFIX + "quota_bump_additional_duration_ms"; 3097 @VisibleForTesting 3098 static final String KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = 3099 QC_CONSTANT_PREFIX + "quota_bump_additional_job_count"; 3100 @VisibleForTesting 3101 static final String KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 3102 QC_CONSTANT_PREFIX + "quota_bump_additional_session_count"; 3103 @VisibleForTesting 3104 static final String KEY_QUOTA_BUMP_WINDOW_SIZE_MS = 3105 QC_CONSTANT_PREFIX + "quota_bump_window_size_ms"; 3106 @VisibleForTesting 3107 static final String KEY_QUOTA_BUMP_LIMIT = 3108 QC_CONSTANT_PREFIX + "quota_bump_limit"; 3109 3110 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 3111 10 * 60 * 1000L; // 10 minutes 3112 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS = 3113 10 * 60 * 1000L; // 10 minutes 3114 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_WORKING_MS = 3115 10 * 60 * 1000L; // 10 minutes 3116 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS = 3117 10 * 60 * 1000L; // 10 minutes 3118 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RARE_MS = 3119 10 * 60 * 1000L; // 10 minutes 3120 private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS = 3121 10 * 60 * 1000L; // 10 minutes 3122 private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 3123 30 * 1000L; // 30 seconds 3124 private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS = 3125 DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time 3126 private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS = 3127 DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time 3128 private static final long DEFAULT_WINDOW_SIZE_WORKING_MS = 3129 2 * 60 * 60 * 1000L; // 2 hours 3130 private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS = 3131 8 * 60 * 60 * 1000L; // 8 hours 3132 private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 3133 24 * 60 * 60 * 1000L; // 24 hours 3134 private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = 3135 24 * 60 * 60 * 1000L; // 24 hours 3136 private static final long DEFAULT_MAX_EXECUTION_TIME_MS = 3137 4 * HOUR_IN_MILLIS; 3138 private static final long DEFAULT_RATE_LIMITING_WINDOW_MS = 3139 MINUTE_IN_MILLIS; 3140 private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20; 3141 private static final int DEFAULT_MAX_JOB_COUNT_EXEMPTED = 3142 75; // 75/window = 450/hr = 1/session 3143 private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED; 3144 private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session 3145 (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS); 3146 private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session 3147 (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS); 3148 private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session 3149 (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS); 3150 private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10; 3151 private static final int DEFAULT_MAX_SESSION_COUNT_EXEMPTED = 3152 75; // 450/hr 3153 private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE = 3154 DEFAULT_MAX_SESSION_COUNT_EXEMPTED; 3155 private static final int DEFAULT_MAX_SESSION_COUNT_WORKING = 3156 10; // 5/hr 3157 private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT = 3158 8; // 1/hr 3159 private static final int DEFAULT_MAX_SESSION_COUNT_RARE = 3160 3; // .125/hr 3161 private static final int DEFAULT_MAX_SESSION_COUNT_RESTRICTED = 1; // 1/day 3162 private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20; 3163 private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds 3164 private static final long DEFAULT_MIN_QUOTA_CHECK_DELAY_MS = MINUTE_IN_MILLIS; 3165 private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 45 * 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