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