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