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