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