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