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