1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.job.controllers;
18 
19 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
20 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
21 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
22 
23 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
24 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
25 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
26 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
27 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
28 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.UserIdInt;
33 import android.app.ActivityManager;
34 import android.app.ActivityManagerInternal;
35 import android.app.AlarmManager;
36 import android.app.AppGlobals;
37 import android.app.IUidObserver;
38 import android.app.usage.UsageStatsManagerInternal;
39 import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
40 import android.content.BroadcastReceiver;
41 import android.content.ContentResolver;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.IntentFilter;
45 import android.database.ContentObserver;
46 import android.net.Uri;
47 import android.os.BatteryManager;
48 import android.os.BatteryManagerInternal;
49 import android.os.Handler;
50 import android.os.Looper;
51 import android.os.Message;
52 import android.os.RemoteException;
53 import android.os.UserHandle;
54 import android.provider.Settings;
55 import android.util.ArrayMap;
56 import android.util.ArraySet;
57 import android.util.KeyValueListParser;
58 import android.util.Log;
59 import android.util.Slog;
60 import android.util.SparseArray;
61 import android.util.SparseBooleanArray;
62 import android.util.SparseSetArray;
63 import android.util.proto.ProtoOutputStream;
64 
65 import com.android.internal.annotations.VisibleForTesting;
66 import com.android.internal.os.BackgroundThread;
67 import com.android.internal.util.IndentingPrintWriter;
68 import com.android.server.LocalServices;
69 import com.android.server.job.ConstantsProto;
70 import com.android.server.job.JobSchedulerService;
71 import com.android.server.job.StateControllerProto;
72 
73 import java.util.ArrayList;
74 import java.util.List;
75 import java.util.Objects;
76 import java.util.function.Consumer;
77 import java.util.function.Predicate;
78 
79 /**
80  * Controller that tracks whether an app has exceeded its standby bucket quota.
81  *
82  * With initial defaults, each app in each bucket is given 10 minutes to run within its respective
83  * time window. Active jobs can run indefinitely, working set jobs can run for 10 minutes within a
84  * 2 hour window, frequent jobs get to run 10 minutes in an 8 hour window, and rare jobs get to run
85  * 10 minutes in a 24 hour window. The windows are rolling, so as soon as a job would have some
86  * quota based on its bucket, it will be eligible to run. When a job's bucket changes, its new
87  * quota is immediately applied to it.
88  *
89  * Job and session count limits are included to prevent abuse/spam. Each bucket has its own limit on
90  * the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
91  * not be allowed to run more than 20 jobs within the past 10 minutes.
92  *
93  * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
94  * freely when an app enters the foreground state and are restricted when the app leaves the
95  * foreground state. However, jobs that are started while the app is in the TOP state do not count
96  * towards any quota and are not restricted regardless of the app's state change.
97  *
98  * Jobs will not be throttled when the device is charging. The device is considered to be charging
99  * once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
100  *
101  * Note: all limits are enforced per bucket window unless explicitly stated otherwise.
102  * All stated values are configurable and subject to change. See {@link QcConstants} for current
103  * defaults.
104  *
105  * Test: atest com.android.server.job.controllers.QuotaControllerTest
106  */
107 public final class QuotaController extends StateController {
108     private static final String TAG = "JobScheduler.Quota";
109     private static final boolean DEBUG = JobSchedulerService.DEBUG
110             || Log.isLoggable(TAG, Log.DEBUG);
111 
112     private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
113     private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
114 
115     /**
116      * A sparse array of ArrayMaps, which is suitable for holding (userId, packageName)->object
117      * associations.
118      */
119     private static class UserPackageMap<T> {
120         private final SparseArray<ArrayMap<String, T>> mData = new SparseArray<>();
121 
add(int userId, @NonNull String packageName, @Nullable T obj)122         public void add(int userId, @NonNull String packageName, @Nullable T obj) {
123             ArrayMap<String, T> data = mData.get(userId);
124             if (data == null) {
125                 data = new ArrayMap<String, T>();
126                 mData.put(userId, data);
127             }
128             data.put(packageName, obj);
129         }
130 
clear()131         public void clear() {
132             for (int i = 0; i < mData.size(); ++i) {
133                 mData.valueAt(i).clear();
134             }
135         }
136 
137         /** Removes all the data for the user, if there was any. */
delete(int userId)138         public void delete(int userId) {
139             mData.delete(userId);
140         }
141 
142         /** Removes the data for the user and package, if there was any. */
delete(int userId, @NonNull String packageName)143         public void delete(int userId, @NonNull String packageName) {
144             ArrayMap<String, T> data = mData.get(userId);
145             if (data != null) {
146                 data.remove(packageName);
147             }
148         }
149 
150         @Nullable
get(int userId, @NonNull String packageName)151         public T get(int userId, @NonNull String packageName) {
152             ArrayMap<String, T> data = mData.get(userId);
153             if (data != null) {
154                 return data.get(packageName);
155             }
156             return null;
157         }
158 
159         /** @see SparseArray#indexOfKey */
indexOfKey(int userId)160         public int indexOfKey(int userId) {
161             return mData.indexOfKey(userId);
162         }
163 
164         /** Returns the userId at the given index. */
keyAt(int index)165         public int keyAt(int index) {
166             return mData.keyAt(index);
167         }
168 
169         /** Returns the package name at the given index. */
170         @NonNull
keyAt(int userIndex, int packageIndex)171         public String keyAt(int userIndex, int packageIndex) {
172             return mData.valueAt(userIndex).keyAt(packageIndex);
173         }
174 
175         /** Returns the size of the outer (userId) array. */
numUsers()176         public int numUsers() {
177             return mData.size();
178         }
179 
numPackagesForUser(int userId)180         public int numPackagesForUser(int userId) {
181             ArrayMap<String, T> data = mData.get(userId);
182             return data == null ? 0 : data.size();
183         }
184 
185         /** Returns the value T at the given user and index. */
186         @Nullable
valueAt(int userIndex, int packageIndex)187         public T valueAt(int userIndex, int packageIndex) {
188             return mData.valueAt(userIndex).valueAt(packageIndex);
189         }
190 
forEach(Consumer<T> consumer)191         public void forEach(Consumer<T> consumer) {
192             for (int i = numUsers() - 1; i >= 0; --i) {
193                 ArrayMap<String, T> data = mData.valueAt(i);
194                 for (int j = data.size() - 1; j >= 0; --j) {
195                     consumer.accept(data.valueAt(j));
196                 }
197             }
198         }
199     }
200 
201     /**
202      * Standardize the output of userId-packageName combo.
203      */
string(int userId, String packageName)204     private static String string(int userId, String packageName) {
205         return "<" + userId + ">" + packageName;
206     }
207 
208     private static final class Package {
209         public final String packageName;
210         public final int userId;
211 
Package(int userId, String packageName)212         Package(int userId, String packageName) {
213             this.userId = userId;
214             this.packageName = packageName;
215         }
216 
217         @Override
toString()218         public String toString() {
219             return string(userId, packageName);
220         }
221 
writeToProto(ProtoOutputStream proto, long fieldId)222         public void writeToProto(ProtoOutputStream proto, long fieldId) {
223             final long token = proto.start(fieldId);
224 
225             proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId);
226             proto.write(StateControllerProto.QuotaController.Package.NAME, packageName);
227 
228             proto.end(token);
229         }
230 
231         @Override
equals(Object obj)232         public boolean equals(Object obj) {
233             if (obj instanceof Package) {
234                 Package other = (Package) obj;
235                 return userId == other.userId && Objects.equals(packageName, other.packageName);
236             } else {
237                 return false;
238             }
239         }
240 
241         @Override
hashCode()242         public int hashCode() {
243             return packageName.hashCode() + userId;
244         }
245     }
246 
hashLong(long val)247     private static int hashLong(long val) {
248         return (int) (val ^ (val >>> 32));
249     }
250 
251     @VisibleForTesting
252     static class ExecutionStats {
253         /**
254          * The time after which this record should be considered invalid (out of date), in the
255          * elapsed realtime timebase.
256          */
257         public long expirationTimeElapsed;
258 
259         public long windowSizeMs;
260         public int jobCountLimit;
261         public int sessionCountLimit;
262 
263         /** The total amount of time the app ran in its respective bucket window size. */
264         public long executionTimeInWindowMs;
265         public int bgJobCountInWindow;
266 
267         /** The total amount of time the app ran in the last {@link #MAX_PERIOD_MS}. */
268         public long executionTimeInMaxPeriodMs;
269         public int bgJobCountInMaxPeriod;
270 
271         /**
272          * The number of {@link TimingSession}s within the bucket window size. This will include
273          * sessions that started before the window as long as they end within the window.
274          */
275         public int sessionCountInWindow;
276 
277         /**
278          * The time after which the app will be under the bucket quota and can start running jobs
279          * again. This is only valid if
280          * {@link #executionTimeInWindowMs} >= {@link #mAllowedTimePerPeriodMs},
281          * {@link #executionTimeInMaxPeriodMs} >= {@link #mMaxExecutionTimeMs},
282          * {@link #bgJobCountInWindow} >= {@link #jobCountLimit}, or
283          * {@link #sessionCountInWindow} >= {@link #sessionCountLimit}.
284          */
285         public long inQuotaTimeElapsed;
286 
287         /**
288          * The time after which {@link #jobCountInRateLimitingWindow} should be considered invalid,
289          * in the elapsed realtime timebase.
290          */
291         public long jobRateLimitExpirationTimeElapsed;
292 
293         /**
294          * The number of jobs that ran in at least the last {@link #mRateLimitingWindowMs}.
295          * It may contain a few stale entries since cleanup won't happen exactly every
296          * {@link #mRateLimitingWindowMs}.
297          */
298         public int jobCountInRateLimitingWindow;
299 
300         /**
301          * The time after which {@link #sessionCountInRateLimitingWindow} should be considered
302          * invalid, in the elapsed realtime timebase.
303          */
304         public long sessionRateLimitExpirationTimeElapsed;
305 
306         /**
307          * The number of {@link TimingSession}s that ran in at least the last
308          * {@link #mRateLimitingWindowMs}. It may contain a few stale entries since cleanup won't
309          * happen exactly every {@link #mRateLimitingWindowMs}. This should only be considered
310          * valid before elapsed realtime has reached {@link #sessionRateLimitExpirationTimeElapsed}.
311          */
312         public int sessionCountInRateLimitingWindow;
313 
314         @Override
toString()315         public String toString() {
316             return "expirationTime=" + expirationTimeElapsed + ", "
317                     + "windowSizeMs=" + windowSizeMs + ", "
318                     + "jobCountLimit=" + jobCountLimit + ", "
319                     + "sessionCountLimit=" + sessionCountLimit + ", "
320                     + "executionTimeInWindow=" + executionTimeInWindowMs + ", "
321                     + "bgJobCountInWindow=" + bgJobCountInWindow + ", "
322                     + "executionTimeInMaxPeriod=" + executionTimeInMaxPeriodMs + ", "
323                     + "bgJobCountInMaxPeriod=" + bgJobCountInMaxPeriod + ", "
324                     + "sessionCountInWindow=" + sessionCountInWindow + ", "
325                     + "inQuotaTime=" + inQuotaTimeElapsed + ", "
326                     + "jobCountExpirationTime=" + jobRateLimitExpirationTimeElapsed + ", "
327                     + "jobCountInRateLimitingWindow=" + jobCountInRateLimitingWindow + ", "
328                     + "sessionCountExpirationTime=" + sessionRateLimitExpirationTimeElapsed + ", "
329                     + "sessionCountInRateLimitingWindow=" + sessionCountInRateLimitingWindow;
330         }
331 
332         @Override
equals(Object obj)333         public boolean equals(Object obj) {
334             if (obj instanceof ExecutionStats) {
335                 ExecutionStats other = (ExecutionStats) obj;
336                 return this.expirationTimeElapsed == other.expirationTimeElapsed
337                         && this.windowSizeMs == other.windowSizeMs
338                         && this.jobCountLimit == other.jobCountLimit
339                         && this.sessionCountLimit == other.sessionCountLimit
340                         && this.executionTimeInWindowMs == other.executionTimeInWindowMs
341                         && this.bgJobCountInWindow == other.bgJobCountInWindow
342                         && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
343                         && this.sessionCountInWindow == other.sessionCountInWindow
344                         && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
345                         && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed
346                         && this.jobRateLimitExpirationTimeElapsed
347                                 == other.jobRateLimitExpirationTimeElapsed
348                         && this.jobCountInRateLimitingWindow == other.jobCountInRateLimitingWindow
349                         && this.sessionRateLimitExpirationTimeElapsed
350                                 == other.sessionRateLimitExpirationTimeElapsed
351                         && this.sessionCountInRateLimitingWindow
352                                 == other.sessionCountInRateLimitingWindow;
353             } else {
354                 return false;
355             }
356         }
357 
358         @Override
hashCode()359         public int hashCode() {
360             int result = 0;
361             result = 31 * result + hashLong(expirationTimeElapsed);
362             result = 31 * result + hashLong(windowSizeMs);
363             result = 31 * result + hashLong(jobCountLimit);
364             result = 31 * result + hashLong(sessionCountLimit);
365             result = 31 * result + hashLong(executionTimeInWindowMs);
366             result = 31 * result + bgJobCountInWindow;
367             result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
368             result = 31 * result + bgJobCountInMaxPeriod;
369             result = 31 * result + sessionCountInWindow;
370             result = 31 * result + hashLong(inQuotaTimeElapsed);
371             result = 31 * result + hashLong(jobRateLimitExpirationTimeElapsed);
372             result = 31 * result + jobCountInRateLimitingWindow;
373             result = 31 * result + hashLong(sessionRateLimitExpirationTimeElapsed);
374             result = 31 * result + sessionCountInRateLimitingWindow;
375             return result;
376         }
377     }
378 
379     /** List of all tracked jobs keyed by source package-userId combo. */
380     private final UserPackageMap<ArraySet<JobStatus>> mTrackedJobs = new UserPackageMap<>();
381 
382     /** Timer for each package-userId combo. */
383     private final UserPackageMap<Timer> mPkgTimers = new UserPackageMap<>();
384 
385     /** List of all timing sessions for a package-userId combo, in chronological order. */
386     private final UserPackageMap<List<TimingSession>> mTimingSessions = new UserPackageMap<>();
387 
388     /**
389      * List of alarm listeners for each package that listen for when each package comes back within
390      * quota.
391      */
392     private final UserPackageMap<QcAlarmListener> mInQuotaAlarmListeners = new UserPackageMap<>();
393 
394     /** Cached calculation results for each app, with the standby buckets as the array indices. */
395     private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
396 
397     /** List of UIDs currently in the foreground. */
398     private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
399 
400     /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
401     private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>();
402 
403     /**
404      * List of jobs that started while the UID was in the TOP state. There will be no more than
405      * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
406      * fine.
407      */
408     private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
409 
410     private final ActivityManagerInternal mActivityManagerInternal;
411     private final AlarmManager mAlarmManager;
412     private final ChargingTracker mChargeTracker;
413     private final Handler mHandler;
414     private final QcConstants mQcConstants;
415 
416     private volatile boolean mInParole;
417 
418     /**
419      * If the QuotaController should throttle apps based on their standby bucket and job activity.
420      * If false, all jobs will have their CONSTRAINT_WITHIN_QUOTA bit set to true immediately and
421      * indefinitely.
422      */
423     private boolean mShouldThrottle;
424 
425     /** How much time each app will have to run jobs within their standby bucket window. */
426     private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
427 
428     /**
429      * The maximum amount of time an app can have its jobs running within a {@link #MAX_PERIOD_MS}
430      * window.
431      */
432     private long mMaxExecutionTimeMs = QcConstants.DEFAULT_MAX_EXECUTION_TIME_MS;
433 
434     /**
435      * How much time the app should have before transitioning from out-of-quota to in-quota.
436      * This should not affect processing if the app is already in-quota.
437      */
438     private long mQuotaBufferMs = QcConstants.DEFAULT_IN_QUOTA_BUFFER_MS;
439 
440     /**
441      * {@link #mAllowedTimePerPeriodMs} - {@link #mQuotaBufferMs}. This can be used to determine
442      * when an app will have enough quota to transition from out-of-quota to in-quota.
443      */
444     private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
445 
446     /**
447      * {@link #mMaxExecutionTimeMs} - {@link #mQuotaBufferMs}. This can be used to determine when an
448      * app will have enough quota to transition from out-of-quota to in-quota.
449      */
450     private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
451 
452     /** The period of time used to rate limit recently run jobs. */
453     private long mRateLimitingWindowMs = QcConstants.DEFAULT_RATE_LIMITING_WINDOW_MS;
454 
455     /** The maximum number of jobs that can run within the past {@link #mRateLimitingWindowMs}. */
456     private int mMaxJobCountPerRateLimitingWindow =
457             QcConstants.DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
458 
459     /**
460      * The maximum number of {@link TimingSession}s that can run within the past {@link
461      * #mRateLimitingWindowMs}.
462      */
463     private int mMaxSessionCountPerRateLimitingWindow =
464             QcConstants.DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
465 
466     private long mNextCleanupTimeElapsed = 0;
467     private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
468             new AlarmManager.OnAlarmListener() {
469                 @Override
470                 public void onAlarm() {
471                     mHandler.obtainMessage(MSG_CLEAN_UP_SESSIONS).sendToTarget();
472                 }
473             };
474 
475     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
476         @Override
477         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
478             mHandler.obtainMessage(MSG_UID_PROCESS_STATE_CHANGED, uid, procState).sendToTarget();
479         }
480 
481         @Override
482         public void onUidGone(int uid, boolean disabled) {
483         }
484 
485         @Override
486         public void onUidActive(int uid) {
487         }
488 
489         @Override
490         public void onUidIdle(int uid, boolean disabled) {
491         }
492 
493         @Override
494         public void onUidCachedChanged(int uid, boolean cached) {
495         }
496     };
497 
498     private final BroadcastReceiver mPackageAddedReceiver = new BroadcastReceiver() {
499         @Override
500         public void onReceive(Context context, Intent intent) {
501             if (intent == null) {
502                 return;
503             }
504             if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
505                 return;
506             }
507             final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
508             synchronized (mLock) {
509                 mUidToPackageCache.remove(uid);
510             }
511         }
512     };
513 
514     /**
515      * The rolling window size for each standby bucket. Within each window, an app will have 10
516      * minutes to run its jobs.
517      */
518     private final long[] mBucketPeriodsMs = new long[]{
519             QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS,
520             QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS,
521             QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS,
522             QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS
523     };
524 
525     /** The maximum period any bucket can have. */
526     private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
527 
528     /**
529      * The maximum number of jobs based on its standby bucket. For each max value count in the
530      * array, the app will not be allowed to run more than that many number of jobs within the
531      * latest time interval of its rolling window size.
532      *
533      * @see #mBucketPeriodsMs
534      */
535     private final int[] mMaxBucketJobCounts = new int[]{
536             QcConstants.DEFAULT_MAX_JOB_COUNT_ACTIVE,
537             QcConstants.DEFAULT_MAX_JOB_COUNT_WORKING,
538             QcConstants.DEFAULT_MAX_JOB_COUNT_FREQUENT,
539             QcConstants.DEFAULT_MAX_JOB_COUNT_RARE
540     };
541 
542     /**
543      * The maximum number of {@link TimingSession}s based on its standby bucket. For each max value
544      * count in the array, the app will not be allowed to have more than that many number of
545      * {@link TimingSession}s within the latest time interval of its rolling window size.
546      *
547      * @see #mBucketPeriodsMs
548      */
549     private final int[] mMaxBucketSessionCounts = new int[]{
550             QcConstants.DEFAULT_MAX_SESSION_COUNT_ACTIVE,
551             QcConstants.DEFAULT_MAX_SESSION_COUNT_WORKING,
552             QcConstants.DEFAULT_MAX_SESSION_COUNT_FREQUENT,
553             QcConstants.DEFAULT_MAX_SESSION_COUNT_RARE
554     };
555 
556     /**
557      * Treat two distinct {@link TimingSession}s as the same if they start and end within this
558      * amount of time of each other.
559      */
560     private long mTimingSessionCoalescingDurationMs =
561             QcConstants.DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
562 
563     /** An app has reached its quota. The message should contain a {@link Package} object. */
564     private static final int MSG_REACHED_QUOTA = 0;
565     /** Drop any old timing sessions. */
566     private static final int MSG_CLEAN_UP_SESSIONS = 1;
567     /** Check if a package is now within its quota. */
568     private static final int MSG_CHECK_PACKAGE = 2;
569     /** Process state for a UID has changed. */
570     private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
571 
QuotaController(JobSchedulerService service)572     public QuotaController(JobSchedulerService service) {
573         super(service);
574         mHandler = new QcHandler(mContext.getMainLooper());
575         mChargeTracker = new ChargingTracker();
576         mChargeTracker.startTracking();
577         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
578         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
579         mQcConstants = new QcConstants(mHandler);
580 
581         final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
582         mContext.registerReceiverAsUser(mPackageAddedReceiver, UserHandle.ALL, filter, null, null);
583 
584         // Set up the app standby bucketing tracker
585         UsageStatsManagerInternal usageStats = LocalServices.getService(
586                 UsageStatsManagerInternal.class);
587         usageStats.addAppIdleStateChangeListener(new StandbyTracker());
588 
589         try {
590             ActivityManager.getService().registerUidObserver(mUidObserver,
591                     ActivityManager.UID_OBSERVER_PROCSTATE,
592                     ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
593         } catch (RemoteException e) {
594             // ignored; both services live in system_server
595         }
596 
597         mShouldThrottle = !mConstants.USE_HEARTBEATS;
598     }
599 
600     @Override
onSystemServicesReady()601     public void onSystemServicesReady() {
602         mQcConstants.start(mContext.getContentResolver());
603     }
604 
605     @Override
maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)606     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
607         final int userId = jobStatus.getSourceUserId();
608         final String pkgName = jobStatus.getSourcePackageName();
609         // Still need to track jobs even if mShouldThrottle is false in case it's set to true at
610         // some point.
611         ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
612         if (jobs == null) {
613             jobs = new ArraySet<>();
614             mTrackedJobs.add(userId, pkgName, jobs);
615         }
616         jobs.add(jobStatus);
617         jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA);
618         if (mShouldThrottle) {
619             final boolean isWithinQuota = isWithinQuotaLocked(jobStatus);
620             setConstraintSatisfied(jobStatus, isWithinQuota);
621             if (!isWithinQuota) {
622                 maybeScheduleStartAlarmLocked(userId, pkgName,
623                         getEffectiveStandbyBucket(jobStatus));
624             }
625         } else {
626             // QuotaController isn't throttling, so always set to true.
627             jobStatus.setQuotaConstraintSatisfied(true);
628         }
629     }
630 
631     @Override
prepareForExecutionLocked(JobStatus jobStatus)632     public void prepareForExecutionLocked(JobStatus jobStatus) {
633         if (DEBUG) {
634             Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
635         }
636 
637         final int uid = jobStatus.getSourceUid();
638         if (mActivityManagerInternal.getUidProcessState(uid) <= ActivityManager.PROCESS_STATE_TOP) {
639             if (DEBUG) {
640                 Slog.d(TAG, jobStatus.toShortString() + " is top started job");
641             }
642             mTopStartedJobs.add(jobStatus);
643             // Top jobs won't count towards quota so there's no need to involve the Timer.
644             return;
645         }
646 
647         final int userId = jobStatus.getSourceUserId();
648         final String packageName = jobStatus.getSourcePackageName();
649         Timer timer = mPkgTimers.get(userId, packageName);
650         if (timer == null) {
651             timer = new Timer(uid, userId, packageName);
652             mPkgTimers.add(userId, packageName, timer);
653         }
654         timer.startTrackingJobLocked(jobStatus);
655     }
656 
657     @Override
maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate)658     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
659             boolean forUpdate) {
660         if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
661             Timer timer = mPkgTimers.get(jobStatus.getSourceUserId(),
662                     jobStatus.getSourcePackageName());
663             if (timer != null) {
664                 timer.stopTrackingJob(jobStatus);
665             }
666             ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
667                     jobStatus.getSourcePackageName());
668             if (jobs != null) {
669                 jobs.remove(jobStatus);
670             }
671             mTopStartedJobs.remove(jobStatus);
672         }
673     }
674 
675     @Override
onConstantsUpdatedLocked()676     public void onConstantsUpdatedLocked() {
677         if (mShouldThrottle == mConstants.USE_HEARTBEATS) {
678             mShouldThrottle = !mConstants.USE_HEARTBEATS;
679 
680             // Update job bookkeeping out of band.
681             BackgroundThread.getHandler().post(() -> {
682                 synchronized (mLock) {
683                     maybeUpdateAllConstraintsLocked();
684                 }
685             });
686         }
687     }
688 
689     @Override
onAppRemovedLocked(String packageName, int uid)690     public void onAppRemovedLocked(String packageName, int uid) {
691         if (packageName == null) {
692             Slog.wtf(TAG, "Told app removed but given null package name.");
693             return;
694         }
695         final int userId = UserHandle.getUserId(uid);
696         mTrackedJobs.delete(userId, packageName);
697         Timer timer = mPkgTimers.get(userId, packageName);
698         if (timer != null) {
699             if (timer.isActive()) {
700                 Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off.");
701                 timer.dropEverythingLocked();
702             }
703             mPkgTimers.delete(userId, packageName);
704         }
705         mTimingSessions.delete(userId, packageName);
706         QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
707         if (alarmListener != null) {
708             mAlarmManager.cancel(alarmListener);
709             mInQuotaAlarmListeners.delete(userId, packageName);
710         }
711         mExecutionStatsCache.delete(userId, packageName);
712         mForegroundUids.delete(uid);
713         mUidToPackageCache.remove(uid);
714     }
715 
716     @Override
onUserRemovedLocked(int userId)717     public void onUserRemovedLocked(int userId) {
718         mTrackedJobs.delete(userId);
719         mPkgTimers.delete(userId);
720         mTimingSessions.delete(userId);
721         mInQuotaAlarmListeners.delete(userId);
722         mExecutionStatsCache.delete(userId);
723         mUidToPackageCache.clear();
724     }
725 
isUidInForeground(int uid)726     private boolean isUidInForeground(int uid) {
727         if (UserHandle.isCore(uid)) {
728             return true;
729         }
730         synchronized (mLock) {
731             return mForegroundUids.get(uid);
732         }
733     }
734 
735     /** @return true if the job was started while the app was in the TOP state. */
isTopStartedJobLocked(@onNull final JobStatus jobStatus)736     private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
737         return mTopStartedJobs.contains(jobStatus);
738     }
739 
740     /**
741      * Returns an appropriate standby bucket for the job, taking into account any standby
742      * exemptions.
743      */
getEffectiveStandbyBucket(@onNull final JobStatus jobStatus)744     private int getEffectiveStandbyBucket(@NonNull final JobStatus jobStatus) {
745         if (jobStatus.uidActive || jobStatus.getJob().isExemptedFromAppStandby()) {
746             // Treat these cases as if they're in the ACTIVE bucket so that they get throttled
747             // like other ACTIVE apps.
748             return ACTIVE_INDEX;
749         }
750         return jobStatus.getStandbyBucket();
751     }
752 
753     @VisibleForTesting
isWithinQuotaLocked(@onNull final JobStatus jobStatus)754     boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
755         final int standbyBucket = getEffectiveStandbyBucket(jobStatus);
756         // A job is within quota if one of the following is true:
757         //   1. it was started while the app was in the TOP state
758         //   2. the app is currently in the foreground
759         //   3. the app overall is within its quota
760         return isTopStartedJobLocked(jobStatus)
761                 || isUidInForeground(jobStatus.getSourceUid())
762                 || isWithinQuotaLocked(
763                 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
764     }
765 
766     @VisibleForTesting
isWithinQuotaLocked(final int userId, @NonNull final String packageName, final int standbyBucket)767     boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
768             final int standbyBucket) {
769         if (standbyBucket == NEVER_INDEX) return false;
770         // This check is needed in case the flag is toggled after a job has been registered.
771         if (!mShouldThrottle) return true;
772 
773         // Quota constraint is not enforced while charging or when parole is on.
774         if (mChargeTracker.isCharging() || mInParole) {
775             return true;
776         }
777 
778         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
779         return getRemainingExecutionTimeLocked(stats) > 0
780                 && isUnderJobCountQuotaLocked(stats, standbyBucket)
781                 && isUnderSessionCountQuotaLocked(stats, standbyBucket);
782     }
783 
isUnderJobCountQuotaLocked(@onNull ExecutionStats stats, final int standbyBucket)784     private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
785             final int standbyBucket) {
786         final long now = sElapsedRealtimeClock.millis();
787         final boolean isUnderAllowedTimeQuota =
788                 (stats.jobRateLimitExpirationTimeElapsed <= now
789                         || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
790         return isUnderAllowedTimeQuota
791                 && (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
792     }
793 
isUnderSessionCountQuotaLocked(@onNull ExecutionStats stats, final int standbyBucket)794     private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
795             final int standbyBucket) {
796         final long now = sElapsedRealtimeClock.millis();
797         final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
798                 || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
799         return isUnderAllowedTimeQuota
800                 && stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket];
801     }
802 
803     @VisibleForTesting
getRemainingExecutionTimeLocked(@onNull final JobStatus jobStatus)804     long getRemainingExecutionTimeLocked(@NonNull final JobStatus jobStatus) {
805         return getRemainingExecutionTimeLocked(jobStatus.getSourceUserId(),
806                 jobStatus.getSourcePackageName(),
807                 getEffectiveStandbyBucket(jobStatus));
808     }
809 
810     @VisibleForTesting
getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName)811     long getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName) {
812         final int standbyBucket = JobSchedulerService.standbyBucketForPackage(packageName,
813                 userId, sElapsedRealtimeClock.millis());
814         return getRemainingExecutionTimeLocked(userId, packageName, standbyBucket);
815     }
816 
817     /**
818      * Returns the amount of time, in milliseconds, that this job has remaining to run based on its
819      * current standby bucket. Time remaining could be negative if the app was moved from a less
820      * restricted to a more restricted bucket.
821      */
getRemainingExecutionTimeLocked(final int userId, @NonNull final String packageName, final int standbyBucket)822     private long getRemainingExecutionTimeLocked(final int userId,
823             @NonNull final String packageName, final int standbyBucket) {
824         if (standbyBucket == NEVER_INDEX) {
825             return 0;
826         }
827         return getRemainingExecutionTimeLocked(
828                 getExecutionStatsLocked(userId, packageName, standbyBucket));
829     }
830 
getRemainingExecutionTimeLocked(@onNull ExecutionStats stats)831     private long getRemainingExecutionTimeLocked(@NonNull ExecutionStats stats) {
832         return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs,
833                 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs);
834     }
835 
836     /**
837      * Returns the amount of time, in milliseconds, until the package would have reached its
838      * duration quota, assuming it has a job counting towards its quota the entire time. This takes
839      * into account any {@link TimingSession}s that may roll out of the window as the job is
840      * running.
841      */
842     @VisibleForTesting
getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName)843     long getTimeUntilQuotaConsumedLocked(final int userId, @NonNull final String packageName) {
844         final long nowElapsed = sElapsedRealtimeClock.millis();
845         final int standbyBucket = JobSchedulerService.standbyBucketForPackage(
846                 packageName, userId, nowElapsed);
847         if (standbyBucket == NEVER_INDEX) {
848             return 0;
849         }
850         List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
851         if (sessions == null || sessions.size() == 0) {
852             return mAllowedTimePerPeriodMs;
853         }
854 
855         final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
856         final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
857         final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
858         final long allowedTimeRemainingMs = mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs;
859         final long maxExecutionTimeRemainingMs =
860                 mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs;
861 
862         // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
863         // essentially run until they reach the maximum limit.
864         if (stats.windowSizeMs == mAllowedTimePerPeriodMs) {
865             return calculateTimeUntilQuotaConsumedLocked(
866                     sessions, startMaxElapsed, maxExecutionTimeRemainingMs);
867         }
868 
869         // Need to check both max time and period time in case one is less than the other.
870         // For example, max time remaining could be less than bucket time remaining, but sessions
871         // contributing to the max time remaining could phase out enough that we'd want to use the
872         // bucket value.
873         return Math.min(
874                 calculateTimeUntilQuotaConsumedLocked(
875                         sessions, startMaxElapsed, maxExecutionTimeRemainingMs),
876                 calculateTimeUntilQuotaConsumedLocked(
877                         sessions, startWindowElapsed, allowedTimeRemainingMs));
878     }
879 
880     /**
881      * Calculates how much time it will take, in milliseconds, until the quota is fully consumed.
882      *
883      * @param windowStartElapsed The start of the window, in the elapsed realtime timebase.
884      * @param deadSpaceMs        How much time can be allowed to count towards the quota
885      */
calculateTimeUntilQuotaConsumedLocked(@onNull List<TimingSession> sessions, final long windowStartElapsed, long deadSpaceMs)886     private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions,
887             final long windowStartElapsed, long deadSpaceMs) {
888         long timeUntilQuotaConsumedMs = 0;
889         long start = windowStartElapsed;
890         for (int i = 0; i < sessions.size(); ++i) {
891             TimingSession session = sessions.get(i);
892 
893             if (session.endTimeElapsed < windowStartElapsed) {
894                 // Outside of window. Ignore.
895                 continue;
896             } else if (session.startTimeElapsed <= windowStartElapsed) {
897                 // Overlapping session. Can extend time by portion of session in window.
898                 timeUntilQuotaConsumedMs += session.endTimeElapsed - windowStartElapsed;
899                 start = session.endTimeElapsed;
900             } else {
901                 // Completely within the window. Can only consider if there's enough dead space
902                 // to get to the start of the session.
903                 long diff = session.startTimeElapsed - start;
904                 if (diff > deadSpaceMs) {
905                     break;
906                 }
907                 timeUntilQuotaConsumedMs += diff
908                         + (session.endTimeElapsed - session.startTimeElapsed);
909                 deadSpaceMs -= diff;
910                 start = session.endTimeElapsed;
911             }
912         }
913         // Will be non-zero if the loop didn't look at any sessions.
914         timeUntilQuotaConsumedMs += deadSpaceMs;
915         if (timeUntilQuotaConsumedMs > mMaxExecutionTimeMs) {
916             Slog.wtf(TAG, "Calculated quota consumed time too high: " + timeUntilQuotaConsumedMs);
917         }
918         return timeUntilQuotaConsumedMs;
919     }
920 
921     /** Returns the execution stats of the app in the most recent window. */
922     @VisibleForTesting
923     @NonNull
getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket)924     ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
925             final int standbyBucket) {
926         return getExecutionStatsLocked(userId, packageName, standbyBucket, true);
927     }
928 
929     @NonNull
getExecutionStatsLocked(final int userId, @NonNull final String packageName, final int standbyBucket, final boolean refreshStatsIfOld)930     private ExecutionStats getExecutionStatsLocked(final int userId,
931             @NonNull final String packageName, final int standbyBucket,
932             final boolean refreshStatsIfOld) {
933         if (standbyBucket == NEVER_INDEX) {
934             Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
935             return new ExecutionStats();
936         }
937         ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
938         if (appStats == null) {
939             appStats = new ExecutionStats[mBucketPeriodsMs.length];
940             mExecutionStatsCache.add(userId, packageName, appStats);
941         }
942         ExecutionStats stats = appStats[standbyBucket];
943         if (stats == null) {
944             stats = new ExecutionStats();
945             appStats[standbyBucket] = stats;
946         }
947         if (refreshStatsIfOld) {
948             final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
949             final int jobCountLimit = mMaxBucketJobCounts[standbyBucket];
950             final int sessionCountLimit = mMaxBucketSessionCounts[standbyBucket];
951             Timer timer = mPkgTimers.get(userId, packageName);
952             if ((timer != null && timer.isActive())
953                     || stats.expirationTimeElapsed <= sElapsedRealtimeClock.millis()
954                     || stats.windowSizeMs != bucketWindowSizeMs
955                     || stats.jobCountLimit != jobCountLimit
956                     || stats.sessionCountLimit != sessionCountLimit) {
957                 // The stats are no longer valid.
958                 stats.windowSizeMs = bucketWindowSizeMs;
959                 stats.jobCountLimit = jobCountLimit;
960                 stats.sessionCountLimit = sessionCountLimit;
961                 updateExecutionStatsLocked(userId, packageName, stats);
962             }
963         }
964 
965         return stats;
966     }
967 
968     @VisibleForTesting
updateExecutionStatsLocked(final int userId, @NonNull final String packageName, @NonNull ExecutionStats stats)969     void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
970             @NonNull ExecutionStats stats) {
971         stats.executionTimeInWindowMs = 0;
972         stats.bgJobCountInWindow = 0;
973         stats.executionTimeInMaxPeriodMs = 0;
974         stats.bgJobCountInMaxPeriod = 0;
975         stats.sessionCountInWindow = 0;
976         stats.inQuotaTimeElapsed = 0;
977 
978         Timer timer = mPkgTimers.get(userId, packageName);
979         final long nowElapsed = sElapsedRealtimeClock.millis();
980         stats.expirationTimeElapsed = nowElapsed + MAX_PERIOD_MS;
981         if (timer != null && timer.isActive()) {
982             stats.executionTimeInWindowMs =
983                     stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
984             stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
985             // If the timer is active, the value will be stale at the next method call, so
986             // invalidate now.
987             stats.expirationTimeElapsed = nowElapsed;
988             if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
989                 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
990                         nowElapsed - mAllowedTimeIntoQuotaMs + stats.windowSizeMs);
991             }
992             if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
993                 stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
994                         nowElapsed - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
995             }
996         }
997 
998         List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
999         if (sessions == null || sessions.size() == 0) {
1000             return;
1001         }
1002 
1003         final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
1004         final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
1005         int sessionCountInWindow = 0;
1006         // The minimum time between the start time and the beginning of the sessions that were
1007         // looked at --> how much time the stats will be valid for.
1008         long emptyTimeMs = Long.MAX_VALUE;
1009         // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
1010         // the most recent ones.
1011         final int loopStart = sessions.size() - 1;
1012         for (int i = loopStart; i >= 0; --i) {
1013             TimingSession session = sessions.get(i);
1014 
1015             // Window management.
1016             if (startWindowElapsed < session.endTimeElapsed) {
1017                 final long start;
1018                 if (startWindowElapsed < session.startTimeElapsed) {
1019                     start = session.startTimeElapsed;
1020                     emptyTimeMs =
1021                             Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
1022                 } else {
1023                     // The session started before the window but ended within the window. Only
1024                     // include the portion that was within the window.
1025                     start = startWindowElapsed;
1026                     emptyTimeMs = 0;
1027                 }
1028 
1029                 stats.executionTimeInWindowMs += session.endTimeElapsed - start;
1030                 stats.bgJobCountInWindow += session.bgJobCount;
1031                 if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
1032                     stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
1033                             start + stats.executionTimeInWindowMs - mAllowedTimeIntoQuotaMs
1034                                     + stats.windowSizeMs);
1035                 }
1036                 if (stats.bgJobCountInWindow >= stats.jobCountLimit) {
1037                     stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
1038                             session.endTimeElapsed + stats.windowSizeMs);
1039                 }
1040                 if (i == loopStart
1041                         || (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
1042                                 > mTimingSessionCoalescingDurationMs) {
1043                     // Coalesce sessions if they are very close to each other in time
1044                     sessionCountInWindow++;
1045 
1046                     if (sessionCountInWindow >= stats.sessionCountLimit) {
1047                         stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
1048                                 session.endTimeElapsed + stats.windowSizeMs);
1049                     }
1050                 }
1051             }
1052 
1053             // Max period check.
1054             if (startMaxElapsed < session.startTimeElapsed) {
1055                 stats.executionTimeInMaxPeriodMs +=
1056                         session.endTimeElapsed - session.startTimeElapsed;
1057                 stats.bgJobCountInMaxPeriod += session.bgJobCount;
1058                 emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
1059                 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
1060                     stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
1061                             session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
1062                                     - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
1063                 }
1064             } else if (startMaxElapsed < session.endTimeElapsed) {
1065                 // The session started before the window but ended within the window. Only include
1066                 // the portion that was within the window.
1067                 stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed;
1068                 stats.bgJobCountInMaxPeriod += session.bgJobCount;
1069                 emptyTimeMs = 0;
1070                 if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
1071                     stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
1072                             startMaxElapsed + stats.executionTimeInMaxPeriodMs
1073                                     - mMaxExecutionTimeIntoQuotaMs + MAX_PERIOD_MS);
1074                 }
1075             } else {
1076                 // This session ended before the window. No point in going any further.
1077                 break;
1078             }
1079         }
1080         stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
1081         stats.sessionCountInWindow = sessionCountInWindow;
1082     }
1083 
1084     /** Invalidate ExecutionStats for all apps. */
1085     @VisibleForTesting
invalidateAllExecutionStatsLocked()1086     void invalidateAllExecutionStatsLocked() {
1087         final long nowElapsed = sElapsedRealtimeClock.millis();
1088         mExecutionStatsCache.forEach((appStats) -> {
1089             if (appStats != null) {
1090                 for (int i = 0; i < appStats.length; ++i) {
1091                     ExecutionStats stats = appStats[i];
1092                     if (stats != null) {
1093                         stats.expirationTimeElapsed = nowElapsed;
1094                     }
1095                 }
1096             }
1097         });
1098     }
1099 
1100     @VisibleForTesting
invalidateAllExecutionStatsLocked(final int userId, @NonNull final String packageName)1101     void invalidateAllExecutionStatsLocked(final int userId,
1102             @NonNull final String packageName) {
1103         ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
1104         if (appStats != null) {
1105             final long nowElapsed = sElapsedRealtimeClock.millis();
1106             for (int i = 0; i < appStats.length; ++i) {
1107                 ExecutionStats stats = appStats[i];
1108                 if (stats != null) {
1109                     stats.expirationTimeElapsed = nowElapsed;
1110                 }
1111             }
1112         }
1113     }
1114 
1115     @VisibleForTesting
incrementJobCount(final int userId, @NonNull final String packageName, int count)1116     void incrementJobCount(final int userId, @NonNull final String packageName, int count) {
1117         final long now = sElapsedRealtimeClock.millis();
1118         ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
1119         if (appStats == null) {
1120             appStats = new ExecutionStats[mBucketPeriodsMs.length];
1121             mExecutionStatsCache.add(userId, packageName, appStats);
1122         }
1123         for (int i = 0; i < appStats.length; ++i) {
1124             ExecutionStats stats = appStats[i];
1125             if (stats == null) {
1126                 stats = new ExecutionStats();
1127                 appStats[i] = stats;
1128             }
1129             if (stats.jobRateLimitExpirationTimeElapsed <= now) {
1130                 stats.jobRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
1131                 stats.jobCountInRateLimitingWindow = 0;
1132             }
1133             stats.jobCountInRateLimitingWindow += count;
1134         }
1135     }
1136 
incrementTimingSessionCount(final int userId, @NonNull final String packageName)1137     private void incrementTimingSessionCount(final int userId, @NonNull final String packageName) {
1138         final long now = sElapsedRealtimeClock.millis();
1139         ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
1140         if (appStats == null) {
1141             appStats = new ExecutionStats[mBucketPeriodsMs.length];
1142             mExecutionStatsCache.add(userId, packageName, appStats);
1143         }
1144         for (int i = 0; i < appStats.length; ++i) {
1145             ExecutionStats stats = appStats[i];
1146             if (stats == null) {
1147                 stats = new ExecutionStats();
1148                 appStats[i] = stats;
1149             }
1150             if (stats.sessionRateLimitExpirationTimeElapsed <= now) {
1151                 stats.sessionRateLimitExpirationTimeElapsed = now + mRateLimitingWindowMs;
1152                 stats.sessionCountInRateLimitingWindow = 0;
1153             }
1154             stats.sessionCountInRateLimitingWindow++;
1155         }
1156     }
1157 
1158     @VisibleForTesting
saveTimingSession(final int userId, @NonNull final String packageName, @NonNull final TimingSession session)1159     void saveTimingSession(final int userId, @NonNull final String packageName,
1160             @NonNull final TimingSession session) {
1161         synchronized (mLock) {
1162             List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
1163             if (sessions == null) {
1164                 sessions = new ArrayList<>();
1165                 mTimingSessions.add(userId, packageName, sessions);
1166             }
1167             sessions.add(session);
1168             // Adding a new session means that the current stats are now incorrect.
1169             invalidateAllExecutionStatsLocked(userId, packageName);
1170 
1171             maybeScheduleCleanupAlarmLocked();
1172         }
1173     }
1174 
1175     private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> {
1176         public long earliestEndElapsed = Long.MAX_VALUE;
1177 
1178         @Override
accept(List<TimingSession> sessions)1179         public void accept(List<TimingSession> sessions) {
1180             if (sessions != null && sessions.size() > 0) {
1181                 earliestEndElapsed = Math.min(earliestEndElapsed, sessions.get(0).endTimeElapsed);
1182             }
1183         }
1184 
reset()1185         void reset() {
1186             earliestEndElapsed = Long.MAX_VALUE;
1187         }
1188     }
1189 
1190     private final EarliestEndTimeFunctor mEarliestEndTimeFunctor = new EarliestEndTimeFunctor();
1191 
1192     /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */
1193     @VisibleForTesting
maybeScheduleCleanupAlarmLocked()1194     void maybeScheduleCleanupAlarmLocked() {
1195         if (mNextCleanupTimeElapsed > sElapsedRealtimeClock.millis()) {
1196             // There's already an alarm scheduled. Just stick with that one. There's no way we'll
1197             // end up scheduling an earlier alarm.
1198             if (DEBUG) {
1199                 Slog.v(TAG, "Not scheduling cleanup since there's already one at "
1200                         + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed
1201                         - sElapsedRealtimeClock.millis()) + "ms)");
1202             }
1203             return;
1204         }
1205         mEarliestEndTimeFunctor.reset();
1206         mTimingSessions.forEach(mEarliestEndTimeFunctor);
1207         final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed;
1208         if (earliestEndElapsed == Long.MAX_VALUE) {
1209             // Couldn't find a good time to clean up. Maybe this was called after we deleted all
1210             // timing sessions.
1211             if (DEBUG) {
1212                 Slog.d(TAG, "Didn't find a time to schedule cleanup");
1213             }
1214             return;
1215         }
1216         // Need to keep sessions for all apps up to the max period, regardless of their current
1217         // standby bucket.
1218         long nextCleanupElapsed = earliestEndElapsed + MAX_PERIOD_MS;
1219         if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) {
1220             // No need to clean up too often. Delay the alarm if the next cleanup would be too soon
1221             // after it.
1222             nextCleanupElapsed += 10 * MINUTE_IN_MILLIS;
1223         }
1224         mNextCleanupTimeElapsed = nextCleanupElapsed;
1225         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
1226                 mSessionCleanupAlarmListener, mHandler);
1227         if (DEBUG) {
1228             Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
1229         }
1230     }
1231 
handleNewChargingStateLocked()1232     private void handleNewChargingStateLocked() {
1233         final long nowElapsed = sElapsedRealtimeClock.millis();
1234         final boolean isCharging = mChargeTracker.isCharging();
1235         if (DEBUG) {
1236             Slog.d(TAG, "handleNewChargingStateLocked: " + isCharging);
1237         }
1238         // Deal with Timers first.
1239         mPkgTimers.forEach((t) -> t.onStateChangedLocked(nowElapsed, isCharging));
1240         // Now update jobs.
1241         maybeUpdateAllConstraintsLocked();
1242     }
1243 
maybeUpdateAllConstraintsLocked()1244     private void maybeUpdateAllConstraintsLocked() {
1245         boolean changed = false;
1246         for (int u = 0; u < mTrackedJobs.numUsers(); ++u) {
1247             final int userId = mTrackedJobs.keyAt(u);
1248             for (int p = 0; p < mTrackedJobs.numPackagesForUser(userId); ++p) {
1249                 final String packageName = mTrackedJobs.keyAt(u, p);
1250                 changed |= maybeUpdateConstraintForPkgLocked(userId, packageName);
1251             }
1252         }
1253         if (changed) {
1254             mStateChangedListener.onControllerStateChanged();
1255         }
1256     }
1257 
1258     /**
1259      * Update the CONSTRAINT_WITHIN_QUOTA bit for all of the Jobs for a given package.
1260      *
1261      * @return true if at least one job had its bit changed
1262      */
maybeUpdateConstraintForPkgLocked(final int userId, @NonNull final String packageName)1263     private boolean maybeUpdateConstraintForPkgLocked(final int userId,
1264             @NonNull final String packageName) {
1265         ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
1266         if (jobs == null || jobs.size() == 0) {
1267             return false;
1268         }
1269 
1270         // Quota is the same for all jobs within a package.
1271         final int realStandbyBucket = jobs.valueAt(0).getStandbyBucket();
1272         final boolean realInQuota = isWithinQuotaLocked(userId, packageName, realStandbyBucket);
1273         boolean changed = false;
1274         for (int i = jobs.size() - 1; i >= 0; --i) {
1275             final JobStatus js = jobs.valueAt(i);
1276             if (isTopStartedJobLocked(js)) {
1277                 // Job was started while the app was in the TOP state so we should allow it to
1278                 // finish.
1279                 changed |= js.setQuotaConstraintSatisfied(true);
1280             } else if (realStandbyBucket != ACTIVE_INDEX
1281                     && realStandbyBucket == getEffectiveStandbyBucket(js)) {
1282                 // An app in the ACTIVE bucket may be out of quota while the job could be in quota
1283                 // for some reason. Therefore, avoid setting the real value here and check each job
1284                 // individually.
1285                 changed |= setConstraintSatisfied(js, realInQuota);
1286             } else {
1287                 // This job is somehow exempted. Need to determine its own quota status.
1288                 changed |= setConstraintSatisfied(js, isWithinQuotaLocked(js));
1289             }
1290         }
1291         if (!realInQuota) {
1292             // Don't want to use the effective standby bucket here since that bump the bucket to
1293             // ACTIVE for one of the jobs, which doesn't help with other jobs that aren't
1294             // exempted.
1295             maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
1296         } else {
1297             QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
1298             if (alarmListener != null && alarmListener.isWaiting()) {
1299                 mAlarmManager.cancel(alarmListener);
1300                 // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
1301                 alarmListener.setTriggerTime(0);
1302             }
1303         }
1304         return changed;
1305     }
1306 
1307     private class UidConstraintUpdater implements Consumer<JobStatus> {
1308         private final UserPackageMap<Integer> mToScheduleStartAlarms = new UserPackageMap<>();
1309         public boolean wasJobChanged;
1310 
1311         @Override
accept(JobStatus jobStatus)1312         public void accept(JobStatus jobStatus) {
1313             wasJobChanged |= setConstraintSatisfied(jobStatus, isWithinQuotaLocked(jobStatus));
1314             final int userId = jobStatus.getSourceUserId();
1315             final String packageName = jobStatus.getSourcePackageName();
1316             final int realStandbyBucket = jobStatus.getStandbyBucket();
1317             if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
1318                 QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
1319                 if (alarmListener != null && alarmListener.isWaiting()) {
1320                     mAlarmManager.cancel(alarmListener);
1321                     // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
1322                     alarmListener.setTriggerTime(0);
1323                 }
1324             } else {
1325                 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
1326             }
1327         }
1328 
postProcess()1329         void postProcess() {
1330             for (int u = 0; u < mToScheduleStartAlarms.numUsers(); ++u) {
1331                 final int userId = mToScheduleStartAlarms.keyAt(u);
1332                 for (int p = 0; p < mToScheduleStartAlarms.numPackagesForUser(userId); ++p) {
1333                     final String packageName = mToScheduleStartAlarms.keyAt(u, p);
1334                     final int standbyBucket = mToScheduleStartAlarms.get(userId, packageName);
1335                     maybeScheduleStartAlarmLocked(userId, packageName, standbyBucket);
1336                 }
1337             }
1338         }
1339 
reset()1340         void reset() {
1341             wasJobChanged = false;
1342             mToScheduleStartAlarms.clear();
1343         }
1344     }
1345 
1346     private final UidConstraintUpdater mUpdateUidConstraints = new UidConstraintUpdater();
1347 
maybeUpdateConstraintForUidLocked(final int uid)1348     private boolean maybeUpdateConstraintForUidLocked(final int uid) {
1349         mService.getJobStore().forEachJobForSourceUid(uid, mUpdateUidConstraints);
1350 
1351         mUpdateUidConstraints.postProcess();
1352         boolean changed = mUpdateUidConstraints.wasJobChanged;
1353         mUpdateUidConstraints.reset();
1354         return changed;
1355     }
1356 
1357     /**
1358      * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run
1359      * again. This should only be called if the package is already out of quota.
1360      */
1361     @VisibleForTesting
maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName, final int standbyBucket)1362     void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName,
1363             final int standbyBucket) {
1364         if (standbyBucket == NEVER_INDEX) {
1365             return;
1366         }
1367 
1368         final String pkgString = string(userId, packageName);
1369         ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
1370         final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
1371         final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
1372                 standbyBucket);
1373 
1374         QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
1375         if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
1376                 && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
1377                 && isUnderJobCountQuota
1378                 && isUnderTimingSessionCountQuota) {
1379             // Already in quota. Why was this method called?
1380             if (DEBUG) {
1381                 Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
1382                         + " even though it already has "
1383                         + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
1384                         + "ms in its quota.");
1385             }
1386             if (alarmListener != null) {
1387                 // Cancel any pending alarm.
1388                 mAlarmManager.cancel(alarmListener);
1389                 // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
1390                 alarmListener.setTriggerTime(0);
1391             }
1392             mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
1393             return;
1394         }
1395 
1396         if (alarmListener == null) {
1397             alarmListener = new QcAlarmListener(userId, packageName);
1398             mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
1399         }
1400 
1401         // The time this app will have quota again.
1402         long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
1403         if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
1404             // App hit the rate limit.
1405             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
1406                     stats.jobRateLimitExpirationTimeElapsed);
1407         }
1408         if (!isUnderTimingSessionCountQuota
1409                 && stats.sessionCountInWindow < stats.sessionCountLimit) {
1410             // App hit the rate limit.
1411             inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
1412                     stats.sessionRateLimitExpirationTimeElapsed);
1413         }
1414         // Only schedule the alarm if:
1415         // 1. There isn't one currently scheduled
1416         // 2. The new alarm is significantly earlier than the previous alarm (which could be the
1417         // case if the package moves into a higher standby bucket). If it's earlier but not
1418         // significantly so, then we essentially delay the job a few extra minutes.
1419         // 3. The alarm is after the current alarm by more than the quota buffer.
1420         // TODO: this might be overengineering. Simplify if proven safe.
1421         if (!alarmListener.isWaiting()
1422                 || inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
1423                 || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
1424             if (DEBUG) {
1425                 Slog.d(TAG, "Scheduling start alarm for " + pkgString);
1426             }
1427             // If the next time this app will have quota is at least 3 minutes before the
1428             // alarm is supposed to go off, reschedule the alarm.
1429             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
1430                     ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
1431             alarmListener.setTriggerTime(inQuotaTimeElapsed);
1432         } else if (DEBUG) {
1433             Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
1434         }
1435     }
1436 
setConstraintSatisfied(@onNull JobStatus jobStatus, boolean isWithinQuota)1437     private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) {
1438         if (!isWithinQuota && jobStatus.getWhenStandbyDeferred() == 0) {
1439             // Mark that the job is being deferred due to buckets.
1440             jobStatus.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
1441         }
1442         return jobStatus.setQuotaConstraintSatisfied(isWithinQuota);
1443     }
1444 
1445     private final class ChargingTracker extends BroadcastReceiver {
1446         /**
1447          * Track whether we're charging. This has a slightly different definition than that of
1448          * BatteryController.
1449          */
1450         private boolean mCharging;
1451 
ChargingTracker()1452         ChargingTracker() {
1453         }
1454 
startTracking()1455         public void startTracking() {
1456             IntentFilter filter = new IntentFilter();
1457 
1458             // Charging/not charging.
1459             filter.addAction(BatteryManager.ACTION_CHARGING);
1460             filter.addAction(BatteryManager.ACTION_DISCHARGING);
1461             mContext.registerReceiver(this, filter);
1462 
1463             // Initialise tracker state.
1464             BatteryManagerInternal batteryManagerInternal =
1465                     LocalServices.getService(BatteryManagerInternal.class);
1466             mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
1467         }
1468 
isCharging()1469         public boolean isCharging() {
1470             return mCharging;
1471         }
1472 
1473         @Override
onReceive(Context context, Intent intent)1474         public void onReceive(Context context, Intent intent) {
1475             synchronized (mLock) {
1476                 final String action = intent.getAction();
1477                 if (BatteryManager.ACTION_CHARGING.equals(action)) {
1478                     if (DEBUG) {
1479                         Slog.d(TAG, "Received charging intent, fired @ "
1480                                 + sElapsedRealtimeClock.millis());
1481                     }
1482                     mCharging = true;
1483                     handleNewChargingStateLocked();
1484                 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
1485                     if (DEBUG) {
1486                         Slog.d(TAG, "Disconnected from power.");
1487                     }
1488                     mCharging = false;
1489                     handleNewChargingStateLocked();
1490                 }
1491             }
1492         }
1493     }
1494 
1495     @VisibleForTesting
1496     static final class TimingSession {
1497         // Start timestamp in elapsed realtime timebase.
1498         public final long startTimeElapsed;
1499         // End timestamp in elapsed realtime timebase.
1500         public final long endTimeElapsed;
1501         // How many background jobs ran during this session.
1502         public final int bgJobCount;
1503 
1504         private final int mHashCode;
1505 
TimingSession(long startElapsed, long endElapsed, int bgJobCount)1506         TimingSession(long startElapsed, long endElapsed, int bgJobCount) {
1507             this.startTimeElapsed = startElapsed;
1508             this.endTimeElapsed = endElapsed;
1509             this.bgJobCount = bgJobCount;
1510 
1511             int hashCode = 0;
1512             hashCode = 31 * hashCode + hashLong(startTimeElapsed);
1513             hashCode = 31 * hashCode + hashLong(endTimeElapsed);
1514             hashCode = 31 * hashCode + bgJobCount;
1515             mHashCode = hashCode;
1516         }
1517 
1518         @Override
toString()1519         public String toString() {
1520             return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
1521                     + "}";
1522         }
1523 
1524         @Override
equals(Object obj)1525         public boolean equals(Object obj) {
1526             if (obj instanceof TimingSession) {
1527                 TimingSession other = (TimingSession) obj;
1528                 return startTimeElapsed == other.startTimeElapsed
1529                         && endTimeElapsed == other.endTimeElapsed
1530                         && bgJobCount == other.bgJobCount;
1531             } else {
1532                 return false;
1533             }
1534         }
1535 
1536         @Override
hashCode()1537         public int hashCode() {
1538             return mHashCode;
1539         }
1540 
dump(IndentingPrintWriter pw)1541         public void dump(IndentingPrintWriter pw) {
1542             pw.print(startTimeElapsed);
1543             pw.print(" -> ");
1544             pw.print(endTimeElapsed);
1545             pw.print(" (");
1546             pw.print(endTimeElapsed - startTimeElapsed);
1547             pw.print("), ");
1548             pw.print(bgJobCount);
1549             pw.print(" bg jobs.");
1550             pw.println();
1551         }
1552 
dump(@onNull ProtoOutputStream proto, long fieldId)1553         public void dump(@NonNull ProtoOutputStream proto, long fieldId) {
1554             final long token = proto.start(fieldId);
1555 
1556             proto.write(StateControllerProto.QuotaController.TimingSession.START_TIME_ELAPSED,
1557                     startTimeElapsed);
1558             proto.write(StateControllerProto.QuotaController.TimingSession.END_TIME_ELAPSED,
1559                     endTimeElapsed);
1560             proto.write(StateControllerProto.QuotaController.TimingSession.BG_JOB_COUNT,
1561                     bgJobCount);
1562 
1563             proto.end(token);
1564         }
1565     }
1566 
1567     private final class Timer {
1568         private final Package mPkg;
1569         private final int mUid;
1570 
1571         // List of jobs currently running for this app that started when the app wasn't in the
1572         // foreground.
1573         private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
1574         private long mStartTimeElapsed;
1575         private int mBgJobCount;
1576 
Timer(int uid, int userId, String packageName)1577         Timer(int uid, int userId, String packageName) {
1578             mPkg = new Package(userId, packageName);
1579             mUid = uid;
1580         }
1581 
startTrackingJobLocked(@onNull JobStatus jobStatus)1582         void startTrackingJobLocked(@NonNull JobStatus jobStatus) {
1583             if (isTopStartedJobLocked(jobStatus)) {
1584                 // We intentionally don't pay attention to fg state changes after a TOP job has
1585                 // started.
1586                 if (DEBUG) {
1587                     Slog.v(TAG,
1588                             "Timer ignoring " + jobStatus.toShortString() + " because isTop");
1589                 }
1590                 return;
1591             }
1592             if (DEBUG) {
1593                 Slog.v(TAG, "Starting to track " + jobStatus.toShortString());
1594             }
1595             // Always track jobs, even when charging.
1596             mRunningBgJobs.add(jobStatus);
1597             if (shouldTrackLocked()) {
1598                 mBgJobCount++;
1599                 incrementJobCount(mPkg.userId, mPkg.packageName, 1);
1600                 if (mRunningBgJobs.size() == 1) {
1601                     // Started tracking the first job.
1602                     mStartTimeElapsed = sElapsedRealtimeClock.millis();
1603                     // Starting the timer means that all cached execution stats are now incorrect.
1604                     invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
1605                     scheduleCutoff();
1606                 }
1607             }
1608         }
1609 
stopTrackingJob(@onNull JobStatus jobStatus)1610         void stopTrackingJob(@NonNull JobStatus jobStatus) {
1611             if (DEBUG) {
1612                 Slog.v(TAG, "Stopping tracking of " + jobStatus.toShortString());
1613             }
1614             synchronized (mLock) {
1615                 if (mRunningBgJobs.size() == 0) {
1616                     // maybeStopTrackingJobLocked can be called when an app cancels a job, so a
1617                     // timer may not be running when it's asked to stop tracking a job.
1618                     if (DEBUG) {
1619                         Slog.d(TAG, "Timer isn't tracking any jobs but still told to stop");
1620                     }
1621                     return;
1622                 }
1623                 if (mRunningBgJobs.remove(jobStatus)
1624                         && !mChargeTracker.isCharging() && mRunningBgJobs.size() == 0) {
1625                     emitSessionLocked(sElapsedRealtimeClock.millis());
1626                     cancelCutoff();
1627                 }
1628             }
1629         }
1630 
1631         /**
1632          * Stops tracking all jobs and cancels any pending alarms. This should only be called if
1633          * the Timer is not going to be used anymore.
1634          */
dropEverythingLocked()1635         void dropEverythingLocked() {
1636             mRunningBgJobs.clear();
1637             cancelCutoff();
1638         }
1639 
emitSessionLocked(long nowElapsed)1640         private void emitSessionLocked(long nowElapsed) {
1641             if (mBgJobCount <= 0) {
1642                 // Nothing to emit.
1643                 return;
1644             }
1645             TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount);
1646             saveTimingSession(mPkg.userId, mPkg.packageName, ts);
1647             mBgJobCount = 0;
1648             // Don't reset the tracked jobs list as we need to keep tracking the current number
1649             // of jobs.
1650             // However, cancel the currently scheduled cutoff since it's not currently useful.
1651             cancelCutoff();
1652             incrementTimingSessionCount(mPkg.userId, mPkg.packageName);
1653         }
1654 
1655         /**
1656          * Returns true if the Timer is actively tracking, as opposed to passively ref counting
1657          * during charging.
1658          */
isActive()1659         public boolean isActive() {
1660             synchronized (mLock) {
1661                 return mBgJobCount > 0;
1662             }
1663         }
1664 
isRunning(JobStatus jobStatus)1665         boolean isRunning(JobStatus jobStatus) {
1666             return mRunningBgJobs.contains(jobStatus);
1667         }
1668 
getCurrentDuration(long nowElapsed)1669         long getCurrentDuration(long nowElapsed) {
1670             synchronized (mLock) {
1671                 return !isActive() ? 0 : nowElapsed - mStartTimeElapsed;
1672             }
1673         }
1674 
getBgJobCount()1675         int getBgJobCount() {
1676             synchronized (mLock) {
1677                 return mBgJobCount;
1678             }
1679         }
1680 
shouldTrackLocked()1681         private boolean shouldTrackLocked() {
1682             return !mChargeTracker.isCharging() && !mForegroundUids.get(mUid);
1683         }
1684 
onStateChangedLocked(long nowElapsed, boolean isQuotaFree)1685         void onStateChangedLocked(long nowElapsed, boolean isQuotaFree) {
1686             if (isQuotaFree) {
1687                 emitSessionLocked(nowElapsed);
1688             } else if (!isActive() && shouldTrackLocked()) {
1689                 // Start timing from unplug.
1690                 if (mRunningBgJobs.size() > 0) {
1691                     mStartTimeElapsed = nowElapsed;
1692                     // NOTE: this does have the unfortunate consequence that if the device is
1693                     // repeatedly plugged in and unplugged, or an app changes foreground state
1694                     // very frequently, the job count for a package may be artificially high.
1695                     mBgJobCount = mRunningBgJobs.size();
1696                     incrementJobCount(mPkg.userId, mPkg.packageName, mBgJobCount);
1697                     // Starting the timer means that all cached execution stats are now
1698                     // incorrect.
1699                     invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
1700                     // Schedule cutoff since we're now actively tracking for quotas again.
1701                     scheduleCutoff();
1702                 }
1703             }
1704         }
1705 
rescheduleCutoff()1706         void rescheduleCutoff() {
1707             cancelCutoff();
1708             scheduleCutoff();
1709         }
1710 
scheduleCutoff()1711         private void scheduleCutoff() {
1712             // Each package can only be in one standby bucket, so we only need to have one
1713             // message per timer. We only need to reschedule when restarting timer or when
1714             // standby bucket changes.
1715             synchronized (mLock) {
1716                 if (!isActive()) {
1717                     return;
1718                 }
1719                 Message msg = mHandler.obtainMessage(MSG_REACHED_QUOTA, mPkg);
1720                 final long timeRemainingMs = getTimeUntilQuotaConsumedLocked(mPkg.userId,
1721                         mPkg.packageName);
1722                 if (DEBUG) {
1723                     Slog.i(TAG, "Job for " + mPkg + " has " + timeRemainingMs + "ms left.");
1724                 }
1725                 // If the job was running the entire time, then the system would be up, so it's
1726                 // fine to use uptime millis for these messages.
1727                 mHandler.sendMessageDelayed(msg, timeRemainingMs);
1728             }
1729         }
1730 
cancelCutoff()1731         private void cancelCutoff() {
1732             mHandler.removeMessages(MSG_REACHED_QUOTA, mPkg);
1733         }
1734 
dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate)1735         public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
1736             pw.print("Timer{");
1737             pw.print(mPkg);
1738             pw.print("} ");
1739             if (isActive()) {
1740                 pw.print("started at ");
1741                 pw.print(mStartTimeElapsed);
1742                 pw.print(" (");
1743                 pw.print(sElapsedRealtimeClock.millis() - mStartTimeElapsed);
1744                 pw.print("ms ago)");
1745             } else {
1746                 pw.print("NOT active");
1747             }
1748             pw.print(", ");
1749             pw.print(mBgJobCount);
1750             pw.print(" running bg jobs");
1751             pw.println();
1752             pw.increaseIndent();
1753             for (int i = 0; i < mRunningBgJobs.size(); i++) {
1754                 JobStatus js = mRunningBgJobs.valueAt(i);
1755                 if (predicate.test(js)) {
1756                     pw.println(js.toShortString());
1757                 }
1758             }
1759             pw.decreaseIndent();
1760         }
1761 
dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)1762         public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) {
1763             final long token = proto.start(fieldId);
1764 
1765             mPkg.writeToProto(proto, StateControllerProto.QuotaController.Timer.PKG);
1766             proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
1767             proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
1768                     mStartTimeElapsed);
1769             proto.write(StateControllerProto.QuotaController.Timer.BG_JOB_COUNT, mBgJobCount);
1770             for (int i = 0; i < mRunningBgJobs.size(); i++) {
1771                 JobStatus js = mRunningBgJobs.valueAt(i);
1772                 if (predicate.test(js)) {
1773                     js.writeToShortProto(proto,
1774                             StateControllerProto.QuotaController.Timer.RUNNING_JOBS);
1775                 }
1776             }
1777 
1778             proto.end(token);
1779         }
1780     }
1781 
1782     /**
1783      * Tracking of app assignments to standby buckets
1784      */
1785     final class StandbyTracker extends AppIdleStateChangeListener {
1786 
1787         @Override
onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, boolean idle, int bucket, int reason)1788         public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
1789                 boolean idle, int bucket, int reason) {
1790             // Update job bookkeeping out of band.
1791             BackgroundThread.getHandler().post(() -> {
1792                 final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
1793                 if (DEBUG) {
1794                     Slog.i(TAG, "Moving pkg " + string(userId, packageName) + " to bucketIndex "
1795                             + bucketIndex);
1796                 }
1797                 synchronized (mLock) {
1798                     ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, packageName);
1799                     if (jobs == null || jobs.size() == 0) {
1800                         return;
1801                     }
1802                     for (int i = jobs.size() - 1; i >= 0; i--) {
1803                         JobStatus js = jobs.valueAt(i);
1804                         js.setStandbyBucket(bucketIndex);
1805                     }
1806                     Timer timer = mPkgTimers.get(userId, packageName);
1807                     if (timer != null && timer.isActive()) {
1808                         timer.rescheduleCutoff();
1809                     }
1810                     if (!mShouldThrottle || maybeUpdateConstraintForPkgLocked(userId,
1811                             packageName)) {
1812                         mStateChangedListener.onControllerStateChanged();
1813                     }
1814                 }
1815             });
1816         }
1817 
1818         @Override
onParoleStateChanged(final boolean isParoleOn)1819         public void onParoleStateChanged(final boolean isParoleOn) {
1820             mInParole = isParoleOn;
1821             if (DEBUG) {
1822                 Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
1823             }
1824             // Update job bookkeeping out of band.
1825             BackgroundThread.getHandler().post(() -> {
1826                 synchronized (mLock) {
1827                     maybeUpdateAllConstraintsLocked();
1828                 }
1829             });
1830         }
1831     }
1832 
1833     private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
1834         private final Predicate<TimingSession> mTooOld = new Predicate<TimingSession>() {
1835             public boolean test(TimingSession ts) {
1836                 return ts.endTimeElapsed <= sElapsedRealtimeClock.millis() - MAX_PERIOD_MS;
1837             }
1838         };
1839 
1840         @Override
accept(List<TimingSession> sessions)1841         public void accept(List<TimingSession> sessions) {
1842             if (sessions != null) {
1843                 // Remove everything older than MAX_PERIOD_MS time ago.
1844                 sessions.removeIf(mTooOld);
1845             }
1846         }
1847     }
1848 
1849     private final DeleteTimingSessionsFunctor mDeleteOldSessionsFunctor =
1850             new DeleteTimingSessionsFunctor();
1851 
1852     @VisibleForTesting
deleteObsoleteSessionsLocked()1853     void deleteObsoleteSessionsLocked() {
1854         mTimingSessions.forEach(mDeleteOldSessionsFunctor);
1855     }
1856 
1857     private class QcHandler extends Handler {
QcHandler(Looper looper)1858         QcHandler(Looper looper) {
1859             super(looper);
1860         }
1861 
1862         @Override
handleMessage(Message msg)1863         public void handleMessage(Message msg) {
1864             synchronized (mLock) {
1865                 switch (msg.what) {
1866                     case MSG_REACHED_QUOTA: {
1867                         Package pkg = (Package) msg.obj;
1868                         if (DEBUG) {
1869                             Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
1870                         }
1871 
1872                         long timeRemainingMs = getRemainingExecutionTimeLocked(pkg.userId,
1873                                 pkg.packageName);
1874                         if (timeRemainingMs <= 50) {
1875                             // Less than 50 milliseconds left. Start process of shutting down jobs.
1876                             if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
1877                             if (maybeUpdateConstraintForPkgLocked(pkg.userId, pkg.packageName)) {
1878                                 mStateChangedListener.onControllerStateChanged();
1879                             }
1880                         } else {
1881                             // This could potentially happen if an old session phases out while a
1882                             // job is currently running.
1883                             // Reschedule message
1884                             Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
1885                             timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
1886                                     pkg.packageName);
1887                             if (DEBUG) {
1888                                 Slog.d(TAG, pkg + " has " + timeRemainingMs + "ms left.");
1889                             }
1890                             sendMessageDelayed(rescheduleMsg, timeRemainingMs);
1891                         }
1892                         break;
1893                     }
1894                     case MSG_CLEAN_UP_SESSIONS:
1895                         if (DEBUG) {
1896                             Slog.d(TAG, "Cleaning up timing sessions.");
1897                         }
1898                         deleteObsoleteSessionsLocked();
1899                         maybeScheduleCleanupAlarmLocked();
1900 
1901                         break;
1902                     case MSG_CHECK_PACKAGE: {
1903                         String packageName = (String) msg.obj;
1904                         int userId = msg.arg1;
1905                         if (DEBUG) {
1906                             Slog.d(TAG, "Checking pkg " + string(userId, packageName));
1907                         }
1908                         if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
1909                             mStateChangedListener.onControllerStateChanged();
1910                         }
1911                         break;
1912                     }
1913                     case MSG_UID_PROCESS_STATE_CHANGED: {
1914                         final int uid = msg.arg1;
1915                         final int procState = msg.arg2;
1916                         final int userId = UserHandle.getUserId(uid);
1917                         final long nowElapsed = sElapsedRealtimeClock.millis();
1918 
1919                         synchronized (mLock) {
1920                             boolean isQuotaFree;
1921                             if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
1922                                 mForegroundUids.put(uid, true);
1923                                 isQuotaFree = true;
1924                             } else {
1925                                 mForegroundUids.delete(uid);
1926                                 isQuotaFree = false;
1927                             }
1928                             // Update Timers first.
1929                             if (mPkgTimers.indexOfKey(userId) >= 0) {
1930                                 ArraySet<String> packages = mUidToPackageCache.get(uid);
1931                                 if (packages == null) {
1932                                     try {
1933                                         String[] pkgs = AppGlobals.getPackageManager()
1934                                                 .getPackagesForUid(uid);
1935                                         if (pkgs != null) {
1936                                             for (String pkg : pkgs) {
1937                                                 mUidToPackageCache.add(uid, pkg);
1938                                             }
1939                                             packages = mUidToPackageCache.get(uid);
1940                                         }
1941                                     } catch (RemoteException e) {
1942                                         Slog.wtf(TAG, "Failed to get package list", e);
1943                                     }
1944                                 }
1945                                 if (packages != null) {
1946                                     for (int i = packages.size() - 1; i >= 0; --i) {
1947                                         Timer t = mPkgTimers.get(userId, packages.valueAt(i));
1948                                         if (t != null) {
1949                                             t.onStateChangedLocked(nowElapsed, isQuotaFree);
1950                                         }
1951                                     }
1952                                 }
1953                             }
1954                             if (maybeUpdateConstraintForUidLocked(uid)) {
1955                                 mStateChangedListener.onControllerStateChanged();
1956                             }
1957                         }
1958                         break;
1959                     }
1960                 }
1961             }
1962         }
1963     }
1964 
1965     private class QcAlarmListener implements AlarmManager.OnAlarmListener {
1966         private final int mUserId;
1967         private final String mPackageName;
1968         private volatile long mTriggerTimeElapsed;
1969 
QcAlarmListener(int userId, String packageName)1970         QcAlarmListener(int userId, String packageName) {
1971             mUserId = userId;
1972             mPackageName = packageName;
1973         }
1974 
isWaiting()1975         boolean isWaiting() {
1976             return mTriggerTimeElapsed > 0;
1977         }
1978 
setTriggerTime(long timeElapsed)1979         void setTriggerTime(long timeElapsed) {
1980             mTriggerTimeElapsed = timeElapsed;
1981         }
1982 
getTriggerTimeElapsed()1983         long getTriggerTimeElapsed() {
1984             return mTriggerTimeElapsed;
1985         }
1986 
1987         @Override
onAlarm()1988         public void onAlarm() {
1989             mHandler.obtainMessage(MSG_CHECK_PACKAGE, mUserId, 0, mPackageName).sendToTarget();
1990             mTriggerTimeElapsed = 0;
1991         }
1992     }
1993 
1994     @VisibleForTesting
1995     class QcConstants extends ContentObserver {
1996         private ContentResolver mResolver;
1997         private final KeyValueListParser mParser = new KeyValueListParser(',');
1998 
1999         private static final String KEY_ALLOWED_TIME_PER_PERIOD_MS = "allowed_time_per_period_ms";
2000         private static final String KEY_IN_QUOTA_BUFFER_MS = "in_quota_buffer_ms";
2001         private static final String KEY_WINDOW_SIZE_ACTIVE_MS = "window_size_active_ms";
2002         private static final String KEY_WINDOW_SIZE_WORKING_MS = "window_size_working_ms";
2003         private static final String KEY_WINDOW_SIZE_FREQUENT_MS = "window_size_frequent_ms";
2004         private static final String KEY_WINDOW_SIZE_RARE_MS = "window_size_rare_ms";
2005         private static final String KEY_MAX_EXECUTION_TIME_MS = "max_execution_time_ms";
2006         private static final String KEY_MAX_JOB_COUNT_ACTIVE = "max_job_count_active";
2007         private static final String KEY_MAX_JOB_COUNT_WORKING = "max_job_count_working";
2008         private static final String KEY_MAX_JOB_COUNT_FREQUENT = "max_job_count_frequent";
2009         private static final String KEY_MAX_JOB_COUNT_RARE = "max_job_count_rare";
2010         private static final String KEY_RATE_LIMITING_WINDOW_MS = "rate_limiting_window_ms";
2011         private static final String KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
2012                 "max_job_count_per_rate_limiting_window";
2013         private static final String KEY_MAX_SESSION_COUNT_ACTIVE = "max_session_count_active";
2014         private static final String KEY_MAX_SESSION_COUNT_WORKING = "max_session_count_working";
2015         private static final String KEY_MAX_SESSION_COUNT_FREQUENT = "max_session_count_frequent";
2016         private static final String KEY_MAX_SESSION_COUNT_RARE = "max_session_count_rare";
2017         private static final String KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
2018                 "max_session_count_per_rate_limiting_window";
2019         private static final String KEY_TIMING_SESSION_COALESCING_DURATION_MS =
2020                 "timing_session_coalescing_duration_ms";
2021 
2022         private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_MS =
2023                 10 * 60 * 1000L; // 10 minutes
2024         private static final long DEFAULT_IN_QUOTA_BUFFER_MS =
2025                 30 * 1000L; // 30 seconds
2026         private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS =
2027                 DEFAULT_ALLOWED_TIME_PER_PERIOD_MS; // ACTIVE apps can run jobs at any time
2028         private static final long DEFAULT_WINDOW_SIZE_WORKING_MS =
2029                 2 * 60 * 60 * 1000L; // 2 hours
2030         private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS =
2031                 8 * 60 * 60 * 1000L; // 8 hours
2032         private static final long DEFAULT_WINDOW_SIZE_RARE_MS =
2033                 24 * 60 * 60 * 1000L; // 24 hours
2034         private static final long DEFAULT_MAX_EXECUTION_TIME_MS =
2035                 4 * HOUR_IN_MILLIS;
2036         private static final long DEFAULT_RATE_LIMITING_WINDOW_MS =
2037                 MINUTE_IN_MILLIS;
2038         private static final int DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 20;
2039         private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE =
2040                 75; // 75/window = 450/hr = 1/session
2041         private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session
2042                 (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS);
2043         private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session
2044                 (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS);
2045         private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session
2046                 (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS);
2047         private static final int DEFAULT_MAX_SESSION_COUNT_ACTIVE =
2048                 75; // 450/hr
2049         private static final int DEFAULT_MAX_SESSION_COUNT_WORKING =
2050                 10; // 5/hr
2051         private static final int DEFAULT_MAX_SESSION_COUNT_FREQUENT =
2052                 8; // 1/hr
2053         private static final int DEFAULT_MAX_SESSION_COUNT_RARE =
2054                 3; // .125/hr
2055         private static final int DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 20;
2056         private static final long DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS = 5000; // 5 seconds
2057 
2058         /** How much time each app will have to run jobs within their standby bucket window. */
2059         public long ALLOWED_TIME_PER_PERIOD_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
2060 
2061         /**
2062          * How much time the package should have before transitioning from out-of-quota to in-quota.
2063          * This should not affect processing if the package is already in-quota.
2064          */
2065         public long IN_QUOTA_BUFFER_MS = DEFAULT_IN_QUOTA_BUFFER_MS;
2066 
2067         /**
2068          * The quota window size of the particular standby bucket. Apps in this standby bucket are
2069          * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
2070          * WINDOW_SIZE_MS.
2071          */
2072         public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS;
2073 
2074         /**
2075          * The quota window size of the particular standby bucket. Apps in this standby bucket are
2076          * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
2077          * WINDOW_SIZE_MS.
2078          */
2079         public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS;
2080 
2081         /**
2082          * The quota window size of the particular standby bucket. Apps in this standby bucket are
2083          * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
2084          * WINDOW_SIZE_MS.
2085          */
2086         public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS;
2087 
2088         /**
2089          * The quota window size of the particular standby bucket. Apps in this standby bucket are
2090          * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_MS} within the past
2091          * WINDOW_SIZE_MS.
2092          */
2093         public long WINDOW_SIZE_RARE_MS = DEFAULT_WINDOW_SIZE_RARE_MS;
2094 
2095         /**
2096          * The maximum amount of time an app can have its jobs running within a 24 hour window.
2097          */
2098         public long MAX_EXECUTION_TIME_MS = DEFAULT_MAX_EXECUTION_TIME_MS;
2099 
2100         /**
2101          * The maximum number of jobs an app can run within this particular standby bucket's
2102          * window size.
2103          */
2104         public int MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_ACTIVE;
2105 
2106         /**
2107          * The maximum number of jobs an app can run within this particular standby bucket's
2108          * window size.
2109          */
2110         public int MAX_JOB_COUNT_WORKING = DEFAULT_MAX_JOB_COUNT_WORKING;
2111 
2112         /**
2113          * The maximum number of jobs an app can run within this particular standby bucket's
2114          * window size.
2115          */
2116         public int MAX_JOB_COUNT_FREQUENT = DEFAULT_MAX_JOB_COUNT_FREQUENT;
2117 
2118         /**
2119          * The maximum number of jobs an app can run within this particular standby bucket's
2120          * window size.
2121          */
2122         public int MAX_JOB_COUNT_RARE = DEFAULT_MAX_JOB_COUNT_RARE;
2123 
2124         /** The period of time used to rate limit recently run jobs. */
2125         public long RATE_LIMITING_WINDOW_MS = DEFAULT_RATE_LIMITING_WINDOW_MS;
2126 
2127         /**
2128          * The maximum number of jobs that can run within the past {@link #RATE_LIMITING_WINDOW_MS}.
2129          */
2130         public int MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW =
2131                 DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
2132 
2133         /**
2134          * The maximum number of {@link TimingSession}s an app can run within this particular
2135          * standby bucket's window size.
2136          */
2137         public int MAX_SESSION_COUNT_ACTIVE = DEFAULT_MAX_SESSION_COUNT_ACTIVE;
2138 
2139         /**
2140          * The maximum number of {@link TimingSession}s an app can run within this particular
2141          * standby bucket's window size.
2142          */
2143         public int MAX_SESSION_COUNT_WORKING = DEFAULT_MAX_SESSION_COUNT_WORKING;
2144 
2145         /**
2146          * The maximum number of {@link TimingSession}s an app can run within this particular
2147          * standby bucket's window size.
2148          */
2149         public int MAX_SESSION_COUNT_FREQUENT = DEFAULT_MAX_SESSION_COUNT_FREQUENT;
2150 
2151         /**
2152          * The maximum number of {@link TimingSession}s an app can run within this particular
2153          * standby bucket's window size.
2154          */
2155         public int MAX_SESSION_COUNT_RARE = DEFAULT_MAX_SESSION_COUNT_RARE;
2156 
2157         /**
2158          * The maximum number of {@link TimingSession}s that can run within the past
2159          * {@link #ALLOWED_TIME_PER_PERIOD_MS}.
2160          */
2161         public int MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW =
2162                 DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW;
2163 
2164         /**
2165          * Treat two distinct {@link TimingSession}s as the same if they start and end within this
2166          * amount of time of each other.
2167          */
2168         public long TIMING_SESSION_COALESCING_DURATION_MS =
2169                 DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS;
2170 
2171         // Safeguards
2172 
2173         /** The minimum number of jobs that any bucket will be allowed to run within its window. */
2174         private static final int MIN_BUCKET_JOB_COUNT = 10;
2175 
2176         /**
2177          * The minimum number of {@link TimingSession}s that any bucket will be allowed to run
2178          * within its window.
2179          */
2180         private static final int MIN_BUCKET_SESSION_COUNT = 1;
2181 
2182         /** The minimum value that {@link #MAX_EXECUTION_TIME_MS} can have. */
2183         private static final long MIN_MAX_EXECUTION_TIME_MS = 60 * MINUTE_IN_MILLIS;
2184 
2185         /** The minimum value that {@link #MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
2186         private static final int MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 10;
2187 
2188         /** The minimum value that {@link #MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW} can have. */
2189         private static final int MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 10;
2190 
2191         /** The minimum value that {@link #RATE_LIMITING_WINDOW_MS} can have. */
2192         private static final long MIN_RATE_LIMITING_WINDOW_MS = 30 * SECOND_IN_MILLIS;
2193 
QcConstants(Handler handler)2194         QcConstants(Handler handler) {
2195             super(handler);
2196         }
2197 
start(ContentResolver resolver)2198         private void start(ContentResolver resolver) {
2199             mResolver = resolver;
2200             mResolver.registerContentObserver(Settings.Global.getUriFor(
2201                     Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS), false, this);
2202             onChange(true, null);
2203         }
2204 
2205         @Override
onChange(boolean selfChange, Uri uri)2206         public void onChange(boolean selfChange, Uri uri) {
2207             final String constants = Settings.Global.getString(
2208                     mResolver, Settings.Global.JOB_SCHEDULER_QUOTA_CONTROLLER_CONSTANTS);
2209 
2210             try {
2211                 mParser.setString(constants);
2212             } catch (Exception e) {
2213                 // Failed to parse the settings string, log this and move on with defaults.
2214                 Slog.e(TAG, "Bad jobscheduler quota controller settings", e);
2215             }
2216 
2217             ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis(
2218                     KEY_ALLOWED_TIME_PER_PERIOD_MS, DEFAULT_ALLOWED_TIME_PER_PERIOD_MS);
2219             IN_QUOTA_BUFFER_MS = mParser.getDurationMillis(
2220                     KEY_IN_QUOTA_BUFFER_MS, DEFAULT_IN_QUOTA_BUFFER_MS);
2221             WINDOW_SIZE_ACTIVE_MS = mParser.getDurationMillis(
2222                     KEY_WINDOW_SIZE_ACTIVE_MS, DEFAULT_WINDOW_SIZE_ACTIVE_MS);
2223             WINDOW_SIZE_WORKING_MS = mParser.getDurationMillis(
2224                     KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS);
2225             WINDOW_SIZE_FREQUENT_MS = mParser.getDurationMillis(
2226                     KEY_WINDOW_SIZE_FREQUENT_MS, DEFAULT_WINDOW_SIZE_FREQUENT_MS);
2227             WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
2228                     KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS);
2229             MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
2230                     KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS);
2231             MAX_JOB_COUNT_ACTIVE = mParser.getInt(
2232                     KEY_MAX_JOB_COUNT_ACTIVE, DEFAULT_MAX_JOB_COUNT_ACTIVE);
2233             MAX_JOB_COUNT_WORKING = mParser.getInt(
2234                     KEY_MAX_JOB_COUNT_WORKING, DEFAULT_MAX_JOB_COUNT_WORKING);
2235             MAX_JOB_COUNT_FREQUENT = mParser.getInt(
2236                     KEY_MAX_JOB_COUNT_FREQUENT, DEFAULT_MAX_JOB_COUNT_FREQUENT);
2237             MAX_JOB_COUNT_RARE = mParser.getInt(
2238                     KEY_MAX_JOB_COUNT_RARE, DEFAULT_MAX_JOB_COUNT_RARE);
2239             RATE_LIMITING_WINDOW_MS = mParser.getLong(
2240                     KEY_RATE_LIMITING_WINDOW_MS, DEFAULT_RATE_LIMITING_WINDOW_MS);
2241             MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
2242                     KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
2243                     DEFAULT_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
2244             MAX_SESSION_COUNT_ACTIVE = mParser.getInt(
2245                     KEY_MAX_SESSION_COUNT_ACTIVE, DEFAULT_MAX_SESSION_COUNT_ACTIVE);
2246             MAX_SESSION_COUNT_WORKING = mParser.getInt(
2247                     KEY_MAX_SESSION_COUNT_WORKING, DEFAULT_MAX_SESSION_COUNT_WORKING);
2248             MAX_SESSION_COUNT_FREQUENT = mParser.getInt(
2249                     KEY_MAX_SESSION_COUNT_FREQUENT, DEFAULT_MAX_SESSION_COUNT_FREQUENT);
2250             MAX_SESSION_COUNT_RARE = mParser.getInt(
2251                     KEY_MAX_SESSION_COUNT_RARE, DEFAULT_MAX_SESSION_COUNT_RARE);
2252             MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = mParser.getInt(
2253                     KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
2254                     DEFAULT_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
2255             TIMING_SESSION_COALESCING_DURATION_MS = mParser.getLong(
2256                     KEY_TIMING_SESSION_COALESCING_DURATION_MS,
2257                     DEFAULT_TIMING_SESSION_COALESCING_DURATION_MS);
2258 
2259             updateConstants();
2260         }
2261 
2262         @VisibleForTesting
updateConstants()2263         void updateConstants() {
2264             synchronized (mLock) {
2265                 boolean changed = false;
2266 
2267                 long newMaxExecutionTimeMs = Math.max(MIN_MAX_EXECUTION_TIME_MS,
2268                         Math.min(MAX_PERIOD_MS, MAX_EXECUTION_TIME_MS));
2269                 if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
2270                     mMaxExecutionTimeMs = newMaxExecutionTimeMs;
2271                     mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
2272                     changed = true;
2273                 }
2274                 long newAllowedTimeMs = Math.min(mMaxExecutionTimeMs,
2275                         Math.max(MINUTE_IN_MILLIS, ALLOWED_TIME_PER_PERIOD_MS));
2276                 if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
2277                     mAllowedTimePerPeriodMs = newAllowedTimeMs;
2278                     mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
2279                     changed = true;
2280                 }
2281                 long newQuotaBufferMs = Math.max(0,
2282                         Math.min(5 * MINUTE_IN_MILLIS, IN_QUOTA_BUFFER_MS));
2283                 if (mQuotaBufferMs != newQuotaBufferMs) {
2284                     mQuotaBufferMs = newQuotaBufferMs;
2285                     mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
2286                     mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
2287                     changed = true;
2288                 }
2289                 long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
2290                         Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS));
2291                 if (mBucketPeriodsMs[ACTIVE_INDEX] != newActivePeriodMs) {
2292                     mBucketPeriodsMs[ACTIVE_INDEX] = newActivePeriodMs;
2293                     changed = true;
2294                 }
2295                 long newWorkingPeriodMs = Math.max(mAllowedTimePerPeriodMs,
2296                         Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS));
2297                 if (mBucketPeriodsMs[WORKING_INDEX] != newWorkingPeriodMs) {
2298                     mBucketPeriodsMs[WORKING_INDEX] = newWorkingPeriodMs;
2299                     changed = true;
2300                 }
2301                 long newFrequentPeriodMs = Math.max(mAllowedTimePerPeriodMs,
2302                         Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS));
2303                 if (mBucketPeriodsMs[FREQUENT_INDEX] != newFrequentPeriodMs) {
2304                     mBucketPeriodsMs[FREQUENT_INDEX] = newFrequentPeriodMs;
2305                     changed = true;
2306                 }
2307                 long newRarePeriodMs = Math.max(mAllowedTimePerPeriodMs,
2308                         Math.min(MAX_PERIOD_MS, WINDOW_SIZE_RARE_MS));
2309                 if (mBucketPeriodsMs[RARE_INDEX] != newRarePeriodMs) {
2310                     mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
2311                     changed = true;
2312                 }
2313                 long newRateLimitingWindowMs = Math.min(MAX_PERIOD_MS,
2314                         Math.max(MIN_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS));
2315                 if (mRateLimitingWindowMs != newRateLimitingWindowMs) {
2316                     mRateLimitingWindowMs = newRateLimitingWindowMs;
2317                     changed = true;
2318                 }
2319                 int newMaxJobCountPerRateLimitingWindow = Math.max(
2320                         MIN_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
2321                         MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
2322                 if (mMaxJobCountPerRateLimitingWindow != newMaxJobCountPerRateLimitingWindow) {
2323                     mMaxJobCountPerRateLimitingWindow = newMaxJobCountPerRateLimitingWindow;
2324                     changed = true;
2325                 }
2326                 int newActiveMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_ACTIVE);
2327                 if (mMaxBucketJobCounts[ACTIVE_INDEX] != newActiveMaxJobCount) {
2328                     mMaxBucketJobCounts[ACTIVE_INDEX] = newActiveMaxJobCount;
2329                     changed = true;
2330                 }
2331                 int newWorkingMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_WORKING);
2332                 if (mMaxBucketJobCounts[WORKING_INDEX] != newWorkingMaxJobCount) {
2333                     mMaxBucketJobCounts[WORKING_INDEX] = newWorkingMaxJobCount;
2334                     changed = true;
2335                 }
2336                 int newFrequentMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_FREQUENT);
2337                 if (mMaxBucketJobCounts[FREQUENT_INDEX] != newFrequentMaxJobCount) {
2338                     mMaxBucketJobCounts[FREQUENT_INDEX] = newFrequentMaxJobCount;
2339                     changed = true;
2340                 }
2341                 int newRareMaxJobCount = Math.max(MIN_BUCKET_JOB_COUNT, MAX_JOB_COUNT_RARE);
2342                 if (mMaxBucketJobCounts[RARE_INDEX] != newRareMaxJobCount) {
2343                     mMaxBucketJobCounts[RARE_INDEX] = newRareMaxJobCount;
2344                     changed = true;
2345                 }
2346                 int newMaxSessionCountPerRateLimitPeriod = Math.max(
2347                         MIN_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
2348                         MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
2349                 if (mMaxSessionCountPerRateLimitingWindow != newMaxSessionCountPerRateLimitPeriod) {
2350                     mMaxSessionCountPerRateLimitingWindow = newMaxSessionCountPerRateLimitPeriod;
2351                     changed = true;
2352                 }
2353                 int newActiveMaxSessionCount =
2354                         Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_ACTIVE);
2355                 if (mMaxBucketSessionCounts[ACTIVE_INDEX] != newActiveMaxSessionCount) {
2356                     mMaxBucketSessionCounts[ACTIVE_INDEX] = newActiveMaxSessionCount;
2357                     changed = true;
2358                 }
2359                 int newWorkingMaxSessionCount =
2360                         Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_WORKING);
2361                 if (mMaxBucketSessionCounts[WORKING_INDEX] != newWorkingMaxSessionCount) {
2362                     mMaxBucketSessionCounts[WORKING_INDEX] = newWorkingMaxSessionCount;
2363                     changed = true;
2364                 }
2365                 int newFrequentMaxSessionCount =
2366                         Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_FREQUENT);
2367                 if (mMaxBucketSessionCounts[FREQUENT_INDEX] != newFrequentMaxSessionCount) {
2368                     mMaxBucketSessionCounts[FREQUENT_INDEX] = newFrequentMaxSessionCount;
2369                     changed = true;
2370                 }
2371                 int newRareMaxSessionCount =
2372                         Math.max(MIN_BUCKET_SESSION_COUNT, MAX_SESSION_COUNT_RARE);
2373                 if (mMaxBucketSessionCounts[RARE_INDEX] != newRareMaxSessionCount) {
2374                     mMaxBucketSessionCounts[RARE_INDEX] = newRareMaxSessionCount;
2375                     changed = true;
2376                 }
2377                 long newSessionCoalescingDurationMs = Math.min(15 * MINUTE_IN_MILLIS,
2378                         Math.max(0, TIMING_SESSION_COALESCING_DURATION_MS));
2379                 if (mTimingSessionCoalescingDurationMs != newSessionCoalescingDurationMs) {
2380                     mTimingSessionCoalescingDurationMs = newSessionCoalescingDurationMs;
2381                     changed = true;
2382                 }
2383 
2384                 if (changed && mShouldThrottle) {
2385                     // Update job bookkeeping out of band.
2386                     BackgroundThread.getHandler().post(() -> {
2387                         synchronized (mLock) {
2388                             invalidateAllExecutionStatsLocked();
2389                             maybeUpdateAllConstraintsLocked();
2390                         }
2391                     });
2392                 }
2393             }
2394         }
2395 
dump(IndentingPrintWriter pw)2396         private void dump(IndentingPrintWriter pw) {
2397             pw.println();
2398             pw.println("QuotaController:");
2399             pw.increaseIndent();
2400             pw.printPair(KEY_ALLOWED_TIME_PER_PERIOD_MS, ALLOWED_TIME_PER_PERIOD_MS).println();
2401             pw.printPair(KEY_IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS).println();
2402             pw.printPair(KEY_WINDOW_SIZE_ACTIVE_MS, WINDOW_SIZE_ACTIVE_MS).println();
2403             pw.printPair(KEY_WINDOW_SIZE_WORKING_MS, WINDOW_SIZE_WORKING_MS).println();
2404             pw.printPair(KEY_WINDOW_SIZE_FREQUENT_MS, WINDOW_SIZE_FREQUENT_MS).println();
2405             pw.printPair(KEY_WINDOW_SIZE_RARE_MS, WINDOW_SIZE_RARE_MS).println();
2406             pw.printPair(KEY_MAX_EXECUTION_TIME_MS, MAX_EXECUTION_TIME_MS).println();
2407             pw.printPair(KEY_MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE).println();
2408             pw.printPair(KEY_MAX_JOB_COUNT_WORKING, MAX_JOB_COUNT_WORKING).println();
2409             pw.printPair(KEY_MAX_JOB_COUNT_FREQUENT, MAX_JOB_COUNT_FREQUENT).println();
2410             pw.printPair(KEY_MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE).println();
2411             pw.printPair(KEY_RATE_LIMITING_WINDOW_MS, RATE_LIMITING_WINDOW_MS).println();
2412             pw.printPair(KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
2413                     MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW).println();
2414             pw.printPair(KEY_MAX_SESSION_COUNT_ACTIVE, MAX_SESSION_COUNT_ACTIVE).println();
2415             pw.printPair(KEY_MAX_SESSION_COUNT_WORKING, MAX_SESSION_COUNT_WORKING).println();
2416             pw.printPair(KEY_MAX_SESSION_COUNT_FREQUENT, MAX_SESSION_COUNT_FREQUENT).println();
2417             pw.printPair(KEY_MAX_SESSION_COUNT_RARE, MAX_SESSION_COUNT_RARE).println();
2418             pw.printPair(KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
2419                     MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW).println();
2420             pw.printPair(KEY_TIMING_SESSION_COALESCING_DURATION_MS,
2421                     TIMING_SESSION_COALESCING_DURATION_MS).println();
2422             pw.decreaseIndent();
2423         }
2424 
dump(ProtoOutputStream proto)2425         private void dump(ProtoOutputStream proto) {
2426             final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER);
2427             proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS,
2428                     ALLOWED_TIME_PER_PERIOD_MS);
2429             proto.write(ConstantsProto.QuotaController.IN_QUOTA_BUFFER_MS, IN_QUOTA_BUFFER_MS);
2430             proto.write(ConstantsProto.QuotaController.ACTIVE_WINDOW_SIZE_MS,
2431                     WINDOW_SIZE_ACTIVE_MS);
2432             proto.write(ConstantsProto.QuotaController.WORKING_WINDOW_SIZE_MS,
2433                     WINDOW_SIZE_WORKING_MS);
2434             proto.write(ConstantsProto.QuotaController.FREQUENT_WINDOW_SIZE_MS,
2435                     WINDOW_SIZE_FREQUENT_MS);
2436             proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS, WINDOW_SIZE_RARE_MS);
2437             proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
2438                     MAX_EXECUTION_TIME_MS);
2439             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_ACTIVE, MAX_JOB_COUNT_ACTIVE);
2440             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_WORKING,
2441                     MAX_JOB_COUNT_WORKING);
2442             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_FREQUENT,
2443                     MAX_JOB_COUNT_FREQUENT);
2444             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_RARE, MAX_JOB_COUNT_RARE);
2445             proto.write(ConstantsProto.QuotaController.RATE_LIMITING_WINDOW_MS,
2446                     RATE_LIMITING_WINDOW_MS);
2447             proto.write(ConstantsProto.QuotaController.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
2448                     MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW);
2449             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_ACTIVE,
2450                     MAX_SESSION_COUNT_ACTIVE);
2451             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_WORKING,
2452                     MAX_SESSION_COUNT_WORKING);
2453             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_FREQUENT,
2454                     MAX_SESSION_COUNT_FREQUENT);
2455             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_RARE,
2456                     MAX_SESSION_COUNT_RARE);
2457             proto.write(ConstantsProto.QuotaController.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
2458                     MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW);
2459             proto.write(ConstantsProto.QuotaController.TIMING_SESSION_COALESCING_DURATION_MS,
2460                     TIMING_SESSION_COALESCING_DURATION_MS);
2461             proto.end(qcToken);
2462         }
2463     }
2464 
2465     //////////////////////// TESTING HELPERS /////////////////////////////
2466 
2467     @VisibleForTesting
getAllowedTimePerPeriodMs()2468     long getAllowedTimePerPeriodMs() {
2469         return mAllowedTimePerPeriodMs;
2470     }
2471 
2472     @VisibleForTesting
2473     @NonNull
getBucketMaxJobCounts()2474     int[] getBucketMaxJobCounts() {
2475         return mMaxBucketJobCounts;
2476     }
2477 
2478     @VisibleForTesting
2479     @NonNull
getBucketMaxSessionCounts()2480     int[] getBucketMaxSessionCounts() {
2481         return mMaxBucketSessionCounts;
2482     }
2483 
2484     @VisibleForTesting
2485     @NonNull
getBucketWindowSizes()2486     long[] getBucketWindowSizes() {
2487         return mBucketPeriodsMs;
2488     }
2489 
2490     @VisibleForTesting
2491     @NonNull
getForegroundUids()2492     SparseBooleanArray getForegroundUids() {
2493         return mForegroundUids;
2494     }
2495 
2496     @VisibleForTesting
2497     @NonNull
getHandler()2498     Handler getHandler() {
2499         return mHandler;
2500     }
2501 
2502     @VisibleForTesting
getInQuotaBufferMs()2503     long getInQuotaBufferMs() {
2504         return mQuotaBufferMs;
2505     }
2506 
2507     @VisibleForTesting
getMaxExecutionTimeMs()2508     long getMaxExecutionTimeMs() {
2509         return mMaxExecutionTimeMs;
2510     }
2511 
2512     @VisibleForTesting
getMaxJobCountPerRateLimitingWindow()2513     int getMaxJobCountPerRateLimitingWindow() {
2514         return mMaxJobCountPerRateLimitingWindow;
2515     }
2516 
2517     @VisibleForTesting
getMaxSessionCountPerRateLimitingWindow()2518     int getMaxSessionCountPerRateLimitingWindow() {
2519         return mMaxSessionCountPerRateLimitingWindow;
2520     }
2521 
2522     @VisibleForTesting
getRateLimitingWindowMs()2523     long getRateLimitingWindowMs() {
2524         return mRateLimitingWindowMs;
2525     }
2526 
2527     @VisibleForTesting
getTimingSessionCoalescingDurationMs()2528     long getTimingSessionCoalescingDurationMs() {
2529         return mTimingSessionCoalescingDurationMs;
2530     }
2531 
2532     @VisibleForTesting
2533     @Nullable
getTimingSessions(int userId, String packageName)2534     List<TimingSession> getTimingSessions(int userId, String packageName) {
2535         return mTimingSessions.get(userId, packageName);
2536     }
2537 
2538     @VisibleForTesting
2539     @NonNull
getQcConstants()2540     QcConstants getQcConstants() {
2541         return mQcConstants;
2542     }
2543 
2544     //////////////////////////// DATA DUMP //////////////////////////////
2545 
2546     @Override
dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate)2547     public void dumpControllerStateLocked(final IndentingPrintWriter pw,
2548             final Predicate<JobStatus> predicate) {
2549         pw.println("Is throttling: " + mShouldThrottle);
2550         pw.println("Is charging: " + mChargeTracker.isCharging());
2551         pw.println("In parole: " + mInParole);
2552         pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
2553         pw.println();
2554 
2555         pw.print("Foreground UIDs: ");
2556         pw.println(mForegroundUids.toString());
2557         pw.println();
2558 
2559         pw.println("Cached UID->package map:");
2560         pw.increaseIndent();
2561         for (int i = 0; i < mUidToPackageCache.size(); ++i) {
2562             final int uid = mUidToPackageCache.keyAt(i);
2563             pw.print(uid);
2564             pw.print(": ");
2565             pw.println(mUidToPackageCache.get(uid));
2566         }
2567         pw.decreaseIndent();
2568         pw.println();
2569 
2570         mTrackedJobs.forEach((jobs) -> {
2571             for (int j = 0; j < jobs.size(); j++) {
2572                 final JobStatus js = jobs.valueAt(j);
2573                 if (!predicate.test(js)) {
2574                     continue;
2575                 }
2576                 pw.print("#");
2577                 js.printUniqueId(pw);
2578                 pw.print(" from ");
2579                 UserHandle.formatUid(pw, js.getSourceUid());
2580                 if (mTopStartedJobs.contains(js)) {
2581                     pw.print(" (TOP)");
2582                 }
2583                 pw.println();
2584 
2585                 pw.increaseIndent();
2586                 pw.print(JobStatus.bucketName(getEffectiveStandbyBucket(js)));
2587                 pw.print(", ");
2588                 if (js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
2589                     pw.print("within quota");
2590                 } else {
2591                     pw.print("not within quota");
2592                 }
2593                 pw.print(", ");
2594                 pw.print(getRemainingExecutionTimeLocked(js));
2595                 pw.print("ms remaining in quota");
2596                 pw.decreaseIndent();
2597                 pw.println();
2598             }
2599         });
2600 
2601         pw.println();
2602         for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
2603             final int userId = mPkgTimers.keyAt(u);
2604             for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
2605                 final String pkgName = mPkgTimers.keyAt(u, p);
2606                 mPkgTimers.valueAt(u, p).dump(pw, predicate);
2607                 pw.println();
2608                 List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
2609                 if (sessions != null) {
2610                     pw.increaseIndent();
2611                     pw.println("Saved sessions:");
2612                     pw.increaseIndent();
2613                     for (int j = sessions.size() - 1; j >= 0; j--) {
2614                         TimingSession session = sessions.get(j);
2615                         session.dump(pw);
2616                     }
2617                     pw.decreaseIndent();
2618                     pw.decreaseIndent();
2619                     pw.println();
2620                 }
2621             }
2622         }
2623 
2624         pw.println("Cached execution stats:");
2625         pw.increaseIndent();
2626         for (int u = 0; u < mExecutionStatsCache.numUsers(); ++u) {
2627             final int userId = mExecutionStatsCache.keyAt(u);
2628             for (int p = 0; p < mExecutionStatsCache.numPackagesForUser(userId); ++p) {
2629                 final String pkgName = mExecutionStatsCache.keyAt(u, p);
2630                 ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);
2631 
2632                 pw.println(string(userId, pkgName));
2633                 pw.increaseIndent();
2634                 for (int i = 0; i < stats.length; ++i) {
2635                     ExecutionStats executionStats = stats[i];
2636                     if (executionStats != null) {
2637                         pw.print(JobStatus.bucketName(i));
2638                         pw.print(": ");
2639                         pw.println(executionStats);
2640                     }
2641                 }
2642                 pw.decreaseIndent();
2643             }
2644         }
2645         pw.decreaseIndent();
2646 
2647         pw.println();
2648         pw.println("In quota alarms:");
2649         pw.increaseIndent();
2650         for (int u = 0; u < mInQuotaAlarmListeners.numUsers(); ++u) {
2651             final int userId = mInQuotaAlarmListeners.keyAt(u);
2652             for (int p = 0; p < mInQuotaAlarmListeners.numPackagesForUser(userId); ++p) {
2653                 final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
2654                 QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);
2655 
2656                 pw.print(string(userId, pkgName));
2657                 pw.print(": ");
2658                 if (alarmListener.isWaiting()) {
2659                     pw.println(alarmListener.getTriggerTimeElapsed());
2660                 } else {
2661                     pw.println("NOT WAITING");
2662                 }
2663             }
2664         }
2665         pw.decreaseIndent();
2666     }
2667 
2668     @Override
dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)2669     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
2670             Predicate<JobStatus> predicate) {
2671         final long token = proto.start(fieldId);
2672         final long mToken = proto.start(StateControllerProto.QUOTA);
2673 
2674         proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
2675         proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
2676         proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
2677                 sElapsedRealtimeClock.millis());
2678 
2679         for (int i = 0; i < mForegroundUids.size(); ++i) {
2680             proto.write(StateControllerProto.QuotaController.FOREGROUND_UIDS,
2681                     mForegroundUids.keyAt(i));
2682         }
2683 
2684         mTrackedJobs.forEach((jobs) -> {
2685             for (int j = 0; j < jobs.size(); j++) {
2686                 final JobStatus js = jobs.valueAt(j);
2687                 if (!predicate.test(js)) {
2688                     continue;
2689                 }
2690                 final long jsToken = proto.start(
2691                         StateControllerProto.QuotaController.TRACKED_JOBS);
2692                 js.writeToShortProto(proto,
2693                         StateControllerProto.QuotaController.TrackedJob.INFO);
2694                 proto.write(StateControllerProto.QuotaController.TrackedJob.SOURCE_UID,
2695                         js.getSourceUid());
2696                 proto.write(
2697                         StateControllerProto.QuotaController.TrackedJob.EFFECTIVE_STANDBY_BUCKET,
2698                         getEffectiveStandbyBucket(js));
2699                 proto.write(StateControllerProto.QuotaController.TrackedJob.IS_TOP_STARTED_JOB,
2700                         mTopStartedJobs.contains(js));
2701                 proto.write(StateControllerProto.QuotaController.TrackedJob.HAS_QUOTA,
2702                         js.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2703                 proto.write(StateControllerProto.QuotaController.TrackedJob.REMAINING_QUOTA_MS,
2704                         getRemainingExecutionTimeLocked(js));
2705                 proto.end(jsToken);
2706             }
2707         });
2708 
2709         for (int u = 0; u < mPkgTimers.numUsers(); ++u) {
2710             final int userId = mPkgTimers.keyAt(u);
2711             for (int p = 0; p < mPkgTimers.numPackagesForUser(userId); ++p) {
2712                 final String pkgName = mPkgTimers.keyAt(u, p);
2713                 final long psToken = proto.start(
2714                         StateControllerProto.QuotaController.PACKAGE_STATS);
2715                 mPkgTimers.valueAt(u, p).dump(proto,
2716                         StateControllerProto.QuotaController.PackageStats.TIMER, predicate);
2717 
2718                 List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
2719                 if (sessions != null) {
2720                     for (int j = sessions.size() - 1; j >= 0; j--) {
2721                         TimingSession session = sessions.get(j);
2722                         session.dump(proto,
2723                                 StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS);
2724                     }
2725                 }
2726 
2727                 ExecutionStats[] stats = mExecutionStatsCache.get(userId, pkgName);
2728                 if (stats != null) {
2729                     for (int bucketIndex = 0; bucketIndex < stats.length; ++bucketIndex) {
2730                         ExecutionStats es = stats[bucketIndex];
2731                         if (es == null) {
2732                             continue;
2733                         }
2734                         final long esToken = proto.start(
2735                                 StateControllerProto.QuotaController.PackageStats.EXECUTION_STATS);
2736                         proto.write(
2737                                 StateControllerProto.QuotaController.ExecutionStats.STANDBY_BUCKET,
2738                                 bucketIndex);
2739                         proto.write(
2740                                 StateControllerProto.QuotaController.ExecutionStats.EXPIRATION_TIME_ELAPSED,
2741                                 es.expirationTimeElapsed);
2742                         proto.write(
2743                                 StateControllerProto.QuotaController.ExecutionStats.WINDOW_SIZE_MS,
2744                                 es.windowSizeMs);
2745                         proto.write(
2746                                 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_LIMIT,
2747                                 es.jobCountLimit);
2748                         proto.write(
2749                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_LIMIT,
2750                                 es.sessionCountLimit);
2751                         proto.write(
2752                                 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_WINDOW_MS,
2753                                 es.executionTimeInWindowMs);
2754                         proto.write(
2755                                 StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_WINDOW,
2756                                 es.bgJobCountInWindow);
2757                         proto.write(
2758                                 StateControllerProto.QuotaController.ExecutionStats.EXECUTION_TIME_IN_MAX_PERIOD_MS,
2759                                 es.executionTimeInMaxPeriodMs);
2760                         proto.write(
2761                                 StateControllerProto.QuotaController.ExecutionStats.BG_JOB_COUNT_IN_MAX_PERIOD,
2762                                 es.bgJobCountInMaxPeriod);
2763                         proto.write(
2764                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_WINDOW,
2765                                 es.sessionCountInWindow);
2766                         proto.write(
2767                                 StateControllerProto.QuotaController.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
2768                                 es.inQuotaTimeElapsed);
2769                         proto.write(
2770                                 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_EXPIRATION_TIME_ELAPSED,
2771                                 es.jobRateLimitExpirationTimeElapsed);
2772                         proto.write(
2773                                 StateControllerProto.QuotaController.ExecutionStats.JOB_COUNT_IN_RATE_LIMITING_WINDOW,
2774                                 es.jobCountInRateLimitingWindow);
2775                         proto.write(
2776                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_EXPIRATION_TIME_ELAPSED,
2777                                 es.sessionRateLimitExpirationTimeElapsed);
2778                         proto.write(
2779                                 StateControllerProto.QuotaController.ExecutionStats.SESSION_COUNT_IN_RATE_LIMITING_WINDOW,
2780                                 es.sessionCountInRateLimitingWindow);
2781                         proto.end(esToken);
2782                     }
2783                 }
2784 
2785                 QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, pkgName);
2786                 if (alarmListener != null) {
2787                     final long alToken = proto.start(
2788                             StateControllerProto.QuotaController.PackageStats.IN_QUOTA_ALARM_LISTENER);
2789                     proto.write(StateControllerProto.QuotaController.AlarmListener.IS_WAITING,
2790                             alarmListener.isWaiting());
2791                     proto.write(
2792                             StateControllerProto.QuotaController.AlarmListener.TRIGGER_TIME_ELAPSED,
2793                             alarmListener.getTriggerTimeElapsed());
2794                     proto.end(alToken);
2795                 }
2796 
2797                 proto.end(psToken);
2798             }
2799         }
2800 
2801         proto.end(mToken);
2802         proto.end(token);
2803     }
2804 
2805     @Override
dumpConstants(IndentingPrintWriter pw)2806     public void dumpConstants(IndentingPrintWriter pw) {
2807         mQcConstants.dump(pw);
2808     }
2809 
2810     @Override
dumpConstants(ProtoOutputStream proto)2811     public void dumpConstants(ProtoOutputStream proto) {
2812         mQcConstants.dump(proto);
2813     }
2814 }
2815