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