1 /*
2  * Copyright (C) 2022 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.app.job.JobInfo.PRIORITY_DEFAULT;
20 import static android.app.job.JobInfo.PRIORITY_HIGH;
21 import static android.app.job.JobInfo.PRIORITY_LOW;
22 import static android.app.job.JobInfo.PRIORITY_MAX;
23 import static android.app.job.JobInfo.PRIORITY_MIN;
24 import static android.text.format.DateUtils.DAY_IN_MILLIS;
25 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
26 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
27 
28 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
29 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
30 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
31 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
32 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
33 
34 import android.annotation.ElapsedRealtimeLong;
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 import android.app.job.JobInfo;
38 import android.content.BroadcastReceiver;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.pm.PackageManager;
43 import android.os.Handler;
44 import android.os.Looper;
45 import android.os.Message;
46 import android.os.PowerManager;
47 import android.os.UserHandle;
48 import android.provider.DeviceConfig;
49 import android.telephony.TelephonyManager;
50 import android.telephony.UiccSlotMapping;
51 import android.util.ArraySet;
52 import android.util.IndentingPrintWriter;
53 import android.util.IntArray;
54 import android.util.KeyValueListParser;
55 import android.util.Log;
56 import android.util.Slog;
57 import android.util.SparseArray;
58 import android.util.SparseArrayMap;
59 import android.util.SparseIntArray;
60 import android.util.SparseLongArray;
61 import android.util.SparseSetArray;
62 import android.util.TimeUtils;
63 
64 import com.android.internal.annotations.GuardedBy;
65 import com.android.internal.annotations.VisibleForTesting;
66 import com.android.server.AppSchedulingModuleThread;
67 import com.android.server.DeviceIdleInternal;
68 import com.android.server.LocalServices;
69 import com.android.server.job.JobSchedulerService;
70 import com.android.server.utils.AlarmQueue;
71 
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collection;
75 import java.util.Set;
76 import java.util.function.Predicate;
77 
78 /**
79  * Controller that tracks the number of flexible constraints being actively satisfied.
80  * Drops constraint for TOP apps and lowers number of required constraints with time.
81  */
82 public final class FlexibilityController extends StateController {
83     private static final String TAG = "JobScheduler.Flex";
84     private static final boolean DEBUG = JobSchedulerService.DEBUG
85             || Log.isLoggable(TAG, Log.DEBUG);
86 
87     /** List of all system-wide flexible constraints whose satisfaction is independent of job. */
88     static final int SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = CONSTRAINT_BATTERY_NOT_LOW
89             | CONSTRAINT_CHARGING
90             | CONSTRAINT_IDLE;
91 
92     /** List of all job flexible constraints whose satisfaction is job specific. */
93     private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY;
94 
95     /** List of all flexible constraints. */
96     @VisibleForTesting
97     static final int FLEXIBLE_CONSTRAINTS =
98             JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
99 
100     private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
101 
102     /**
103      * The default deadline that all flexible constraints should be dropped by if a job lacks
104      * a deadline.
105      */
106     private long mFallbackFlexibilityDeadlineMs =
107             FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
108     /**
109      * The default deadline that all flexible constraints should be dropped by if a job lacks
110      * a deadline, keyed by job priority.
111      */
112     private SparseLongArray mFallbackFlexibilityDeadlines =
113             FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES;
114     /**
115      * The scores to use for each job, keyed by job priority.
116      */
117     private SparseIntArray mFallbackFlexibilityDeadlineScores =
118             FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
119     /**
120      * The amount of time to add (scaled by job run score) to the fallback flexibility deadline,
121      * keyed by job priority.
122      */
123     private SparseLongArray mFallbackFlexibilityAdditionalScoreTimeFactors =
124             FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
125 
126     private long mRescheduledJobDeadline = FcConfig.DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
127     private long mMaxRescheduledDeadline = FcConfig.DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
128 
129     private long mUnseenConstraintGracePeriodMs =
130             FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
131 
132     /** Set of constraints supported on this device for flex scheduling. */
133     private final int mSupportedFlexConstraints;
134 
135     @GuardedBy("mLock")
136     private boolean mFlexibilityEnabled;
137 
138     /** Set of constraints that will be used in the flex policy. */
139     @GuardedBy("mLock")
140     private int mAppliedConstraints = FcConfig.DEFAULT_APPLIED_CONSTRAINTS;
141 
142     private long mMinTimeBetweenFlexibilityAlarmsMs =
143             FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
144 
145     /** Hard cutoff to remove flexible constraints. */
146     private long mDeadlineProximityLimitMs =
147             FcConfig.DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
148 
149     /**
150      * The percent of a job's lifecycle to drop number of required constraints.
151      * mPercentsToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
152      * the controller should have i+1 constraints dropped. Keyed by job priority.
153      */
154     private SparseArray<int[]> mPercentsToDropConstraints;
155 
156     /**
157      * Keeps track of what flexible constraints are satisfied at the moment.
158      * Is updated by the other controllers.
159      */
160     @VisibleForTesting
161     @GuardedBy("mLock")
162     int mSatisfiedFlexibleConstraints;
163 
164     @GuardedBy("mLock")
165     private final SparseLongArray mLastSeenConstraintTimesElapsed = new SparseLongArray();
166 
167     @VisibleForTesting
168     @GuardedBy("mLock")
169     final FlexibilityTracker mFlexibilityTracker;
170     @VisibleForTesting
171     @GuardedBy("mLock")
172     final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
173     @VisibleForTesting
174     final FcConfig mFcConfig;
175     private final FcHandler mHandler;
176     @VisibleForTesting
177     final PrefetchController mPrefetchController;
178     private final SpecialAppTracker mSpecialAppTracker;
179 
180     /**
181      * Stores the beginning of prefetch jobs lifecycle per app as a maximum of
182      * the last time the app was used and the last time the launch time was updated.
183      */
184     @VisibleForTesting
185     @GuardedBy("mLock")
186     final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>();
187 
188     @VisibleForTesting
189     final PrefetchController.PrefetchChangedListener mPrefetchChangedListener =
190             new PrefetchController.PrefetchChangedListener() {
191                 @Override
192                 public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId,
193                         String pkgName, long prevEstimatedLaunchTime,
194                         long newEstimatedLaunchTime, long nowElapsed) {
195                     synchronized (mLock) {
196                         final long prefetchThreshold =
197                                 mPrefetchController.getLaunchTimeThresholdMs();
198                         boolean jobWasInPrefetchWindow  = prevEstimatedLaunchTime
199                                 - prefetchThreshold < nowElapsed;
200                         boolean jobIsInPrefetchWindow  = newEstimatedLaunchTime
201                                 - prefetchThreshold < nowElapsed;
202                         if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) {
203                             // If the job was in the window previously then changing the start
204                             // of the lifecycle to the current moment without a large change in the
205                             // end would squeeze the window too tight fail to drop constraints.
206                             mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed,
207                                     mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L)));
208                         }
209                         for (int i = 0; i < jobs.size(); i++) {
210                             JobStatus js = jobs.valueAt(i);
211                             if (!js.hasFlexibilityConstraint()) {
212                                 continue;
213                             }
214                             mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
215                             mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
216                         }
217                     }
218                 }
219             };
220 
221     /** Helper object to track job run score for each app. */
222     private static class JobScoreTracker {
223         private static class JobScoreBucket {
224             @ElapsedRealtimeLong
225             public long startTimeElapsed;
226             public int score;
227 
reset()228             private void reset() {
229                 startTimeElapsed = 0;
230                 score = 0;
231             }
232         }
233 
234         private static final int NUM_SCORE_BUCKETS = 24;
235         private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS;
236         private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS];
237         private int mScoreBucketIndex = 0;
238         private long mCachedScoreExpirationTimeElapsed;
239         private int mCachedScore;
240 
addScore(int add, long nowElapsed)241         public void addScore(int add, long nowElapsed) {
242             JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex];
243             if (bucket == null) {
244                 bucket = new JobScoreBucket();
245                 bucket.startTimeElapsed = nowElapsed;
246                 mScoreBuckets[mScoreBucketIndex] = bucket;
247                 // Brand new bucket, there's nothing to remove from the score,
248                 // so just update the expiration time if needed.
249                 mCachedScoreExpirationTimeElapsed = Math.min(mCachedScoreExpirationTimeElapsed,
250                         nowElapsed + MAX_TIME_WINDOW_MS);
251             } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) {
252                 // The bucket is too old.
253                 bucket.reset();
254                 bucket.startTimeElapsed = nowElapsed;
255                 // Force a recalculation of the cached score instead of just updating the cached
256                 // value and time in case there are multiple stale buckets.
257                 mCachedScoreExpirationTimeElapsed = nowElapsed;
258             } else if (bucket.startTimeElapsed
259                     < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) {
260                 // The current bucket's duration has completed. Move on to the next bucket.
261                 mScoreBucketIndex = (mScoreBucketIndex + 1) % NUM_SCORE_BUCKETS;
262                 addScore(add, nowElapsed);
263                 return;
264             }
265 
266             bucket.score += add;
267             mCachedScore += add;
268         }
269 
getScore(long nowElapsed)270         public int getScore(long nowElapsed) {
271             if (nowElapsed < mCachedScoreExpirationTimeElapsed) {
272                 return mCachedScore;
273             }
274             int score = 0;
275             final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS;
276             long earliestValidBucketTimeElapsed = Long.MAX_VALUE;
277             for (JobScoreBucket bucket : mScoreBuckets) {
278                 if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) {
279                     score += bucket.score;
280                     if (earliestValidBucketTimeElapsed > bucket.startTimeElapsed) {
281                         earliestValidBucketTimeElapsed = bucket.startTimeElapsed;
282                     }
283                 }
284             }
285             mCachedScore = score;
286             mCachedScoreExpirationTimeElapsed = earliestValidBucketTimeElapsed + MAX_TIME_WINDOW_MS;
287             return score;
288         }
289 
dump(@onNull IndentingPrintWriter pw, long nowElapsed)290         public void dump(@NonNull IndentingPrintWriter pw, long nowElapsed) {
291             pw.print("{");
292 
293             boolean printed = false;
294             for (int x = 0; x < mScoreBuckets.length; ++x) {
295                 final int idx = (mScoreBucketIndex + 1 + x) % mScoreBuckets.length;
296                 final JobScoreBucket jsb = mScoreBuckets[idx];
297                 if (jsb == null || jsb.startTimeElapsed == 0) {
298                     continue;
299                 }
300                 if (printed) {
301                     pw.print(", ");
302                 }
303                 TimeUtils.formatDuration(jsb.startTimeElapsed, nowElapsed, pw);
304                 pw.print("=");
305                 pw.print(jsb.score);
306                 printed = true;
307             }
308 
309             pw.print("}");
310         }
311     }
312 
313     /**
314      * Set of {@link JobScoreTracker JobScoreTrackers} for each app.
315      * Keyed by source UID -> source package.
316      **/
317     private final SparseArrayMap<String, JobScoreTracker> mJobScoreTrackers =
318             new SparseArrayMap<>();
319 
320     private static final int MSG_CHECK_ALL_JOBS = 0;
321     /** Check the jobs in {@link #mJobsToCheck} */
322     private static final int MSG_CHECK_JOBS = 1;
323     /** Check the jobs of packages in {@link #mPackagesToCheck} */
324     private static final int MSG_CHECK_PACKAGES = 2;
325 
326     @GuardedBy("mLock")
327     private final ArraySet<JobStatus> mJobsToCheck = new ArraySet<>();
328     @GuardedBy("mLock")
329     private final ArraySet<String> mPackagesToCheck = new ArraySet<>();
330 
331     @GuardedBy("mLock")
332     private boolean mLocalOverride;
333 
FlexibilityController( JobSchedulerService service, PrefetchController prefetchController)334     public FlexibilityController(
335             JobSchedulerService service, PrefetchController prefetchController) {
336         super(service);
337         mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper());
338         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
339                 || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
340             // Embedded devices have no user-installable apps. Assume all jobs are critical
341             // and can't be flexed.
342             mSupportedFlexConstraints = 0;
343         } else {
344             // TODO(236261941): handle devices without a battery
345             mSupportedFlexConstraints = FLEXIBLE_CONSTRAINTS;
346         }
347         mFlexibilityEnabled = (mAppliedConstraints & mSupportedFlexConstraints) != 0;
348         mFlexibilityTracker = new FlexibilityTracker(Integer.bitCount(mSupportedFlexConstraints));
349         mFcConfig = new FcConfig();
350         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
351                 mContext, AppSchedulingModuleThread.get().getLooper());
352         mPercentsToDropConstraints =
353                 FcConfig.DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
354         mPrefetchController = prefetchController;
355         mSpecialAppTracker = new SpecialAppTracker();
356 
357         if (mFlexibilityEnabled) {
358             mSpecialAppTracker.startTracking();
359         }
360     }
361 
362     @Override
onSystemServicesReady()363     public void onSystemServicesReady() {
364         mSpecialAppTracker.onSystemServicesReady();
365     }
366 
367     @Override
startTrackingLocked()368     public void startTrackingLocked() {
369         if (mFlexibilityEnabled) {
370             mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener);
371         }
372     }
373 
374     /**
375      * StateController interface.
376      */
377     @Override
378     @GuardedBy("mLock")
maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob)379     public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) {
380         if (js.hasFlexibilityConstraint()) {
381             final long nowElapsed = sElapsedRealtimeClock.millis();
382             if (mSupportedFlexConstraints == 0) {
383                 js.setFlexibilityConstraintSatisfied(nowElapsed, true);
384                 return;
385             }
386             js.setNumAppliedFlexibleConstraints(
387                     Integer.bitCount(getRelevantAppliedConstraintsLocked(js)));
388             js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
389             mFlexibilityTracker.add(js);
390             js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
391             mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
392         }
393     }
394 
395     @Override
prepareForExecutionLocked(JobStatus jobStatus)396     public void prepareForExecutionLocked(JobStatus jobStatus) {
397         if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
398             // Don't include jobs for the TOP app in the score calculation.
399             return;
400         }
401         // Use the job's requested priority to determine its score since that is what the developer
402         // selected and it will be stable across job runs.
403         final int priority = jobStatus.getJob().getPriority();
404         final int score = mFallbackFlexibilityDeadlineScores.get(priority,
405                 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
406                         .get(priority, priority / 100));
407         JobScoreTracker jobScoreTracker =
408                 mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
409         if (jobScoreTracker == null) {
410             jobScoreTracker = new JobScoreTracker();
411             mJobScoreTrackers.add(jobStatus.getSourceUid(), jobStatus.getSourcePackageName(),
412                     jobScoreTracker);
413         }
414         jobScoreTracker.addScore(score, sElapsedRealtimeClock.millis());
415     }
416 
417     @Override
unprepareFromExecutionLocked(JobStatus jobStatus)418     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
419         if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
420             // Jobs for the TOP app are excluded from the score calculation.
421             return;
422         }
423         // The job didn't actually start. Undo the score increase.
424         JobScoreTracker jobScoreTracker =
425                 mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
426         if (jobScoreTracker == null) {
427             Slog.e(TAG, "Unprepared a job that didn't result in a score change");
428             return;
429         }
430         final int priority = jobStatus.getJob().getPriority();
431         final int score = mFallbackFlexibilityDeadlineScores.get(priority,
432                 FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
433                         .get(priority, priority / 100));
434         jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis());
435     }
436 
437     @Override
438     @GuardedBy("mLock")
maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob)439     public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) {
440         if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
441             mFlexibilityAlarmQueue.removeAlarmForKey(js);
442             mFlexibilityTracker.remove(js);
443         }
444         mJobsToCheck.remove(js);
445     }
446 
447     @Override
448     @GuardedBy("mLock")
onAppRemovedLocked(String packageName, int uid)449     public void onAppRemovedLocked(String packageName, int uid) {
450         final int userId = UserHandle.getUserId(uid);
451         mPrefetchLifeCycleStart.delete(userId, packageName);
452         mJobScoreTrackers.delete(uid, packageName);
453         mSpecialAppTracker.onAppRemoved(userId, packageName);
454         for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
455             final JobStatus js = mJobsToCheck.valueAt(i);
456             if ((js.getSourceUid() == uid && js.getSourcePackageName().equals(packageName))
457                     || (js.getUid() == uid && js.getCallingPackageName().equals(packageName))) {
458                 mJobsToCheck.removeAt(i);
459             }
460         }
461     }
462 
463     @Override
464     @GuardedBy("mLock")
onUserRemovedLocked(int userId)465     public void onUserRemovedLocked(int userId) {
466         mPrefetchLifeCycleStart.delete(userId);
467         mSpecialAppTracker.onUserRemoved(userId);
468         for (int u = mJobScoreTrackers.numMaps() - 1; u >= 0; --u) {
469             final int uid = mJobScoreTrackers.keyAt(u);
470             if (UserHandle.getUserId(uid) == userId) {
471                 mJobScoreTrackers.deleteAt(u);
472             }
473         }
474         for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
475             final JobStatus js = mJobsToCheck.valueAt(i);
476             if (UserHandle.getUserId(js.getSourceUid()) == userId
477                     || UserHandle.getUserId(js.getUid()) == userId) {
478                 mJobsToCheck.removeAt(i);
479             }
480         }
481     }
482 
isEnabled()483     boolean isEnabled() {
484         synchronized (mLock) {
485             return mFlexibilityEnabled;
486         }
487     }
488 
489     /** Checks if the flexibility constraint is actively satisfied for a given job. */
490     @GuardedBy("mLock")
isFlexibilitySatisfiedLocked(JobStatus js)491     boolean isFlexibilitySatisfiedLocked(JobStatus js) {
492         return !mFlexibilityEnabled
493                 // Exclude all jobs of the TOP app
494                 || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP
495                 // Only exclude DEFAULT+ priority jobs for BFGS+ apps
496                 || (mService.getUidBias(js.getSourceUid()) >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
497                         && js.getEffectivePriority() >= PRIORITY_DEFAULT)
498                 // For special/privileged apps, automatically exclude DEFAULT+ priority jobs.
499                 || (js.getEffectivePriority() >= PRIORITY_DEFAULT
500                         && mSpecialAppTracker.isSpecialApp(
501                                 js.getSourceUserId(), js.getSourcePackageName()))
502                 || hasEnoughSatisfiedConstraintsLocked(js)
503                 || mService.isCurrentlyRunningLocked(js);
504     }
505 
506     @VisibleForTesting
507     @GuardedBy("mLock")
getRelevantAppliedConstraintsLocked(@onNull JobStatus js)508     int getRelevantAppliedConstraintsLocked(@NonNull JobStatus js) {
509         final int relevantConstraints = SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
510                 | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0);
511         return mAppliedConstraints & relevantConstraints;
512     }
513 
514     /**
515      * Returns whether there are enough constraints satisfied to allow running the job from flex's
516      * perspective. This takes into account unseen constraint combinations and expectations around
517      * whether additional constraints can ever be satisfied.
518      */
519     @VisibleForTesting
520     @GuardedBy("mLock")
hasEnoughSatisfiedConstraintsLocked(@onNull JobStatus js)521     boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) {
522         final int satisfiedConstraints = mSatisfiedFlexibleConstraints & mAppliedConstraints
523                 & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
524                         | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0));
525         final int numSatisfied = Integer.bitCount(satisfiedConstraints);
526         if (numSatisfied >= js.getNumRequiredFlexibleConstraints()) {
527             return true;
528         }
529         // We don't yet have the full number of required flex constraints. See if we should expect
530         // to be able to reach it. If not, then there's no point waiting anymore.
531         final long nowElapsed = sElapsedRealtimeClock.millis();
532         if (nowElapsed < mUnseenConstraintGracePeriodMs) {
533             // Too soon after boot. Not enough time to start predicting. Wait longer.
534             return false;
535         }
536 
537         // The intention is to not force jobs to wait for constraint combinations that have never
538         // been seen together in a while. The job may still be allowed to wait for other constraint
539         // combinations. Thus, the logic is:
540         // If all the constraint combinations that have a count higher than the current satisfied
541         // count have not been seen recently enough, then assume they won't be seen anytime soon,
542         // so don't force the job to wait longer. If any combinations with a higher count have been
543         // seen recently, then the job can potentially wait for those combinations.
544         final int irrelevantConstraints = ~getRelevantAppliedConstraintsLocked(js);
545         for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) {
546             final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i);
547             if ((constraints & irrelevantConstraints) != 0) {
548                 // Ignore combinations that couldn't satisfy this job's needs.
549                 continue;
550             }
551             final long lastSeenElapsed = mLastSeenConstraintTimesElapsed.valueAt(i);
552             final boolean seenRecently =
553                     nowElapsed - lastSeenElapsed <= mUnseenConstraintGracePeriodMs;
554             if (Integer.bitCount(constraints) > numSatisfied && seenRecently) {
555                 // We've seen a set of constraints with a higher count than what is currently
556                 // satisfied recently enough, which means we can expect to see it again at some
557                 // point. Keep waiting for now.
558                 return false;
559             }
560         }
561 
562         // We haven't seen any constraint set with more satisfied than the current satisfied count.
563         // There's no reason to expect additional constraints to be satisfied. Let the job run.
564         return true;
565     }
566 
567     /**
568      * Sets the controller's constraint to a given state.
569      * Changes flexibility constraint satisfaction for affected jobs.
570      */
setConstraintSatisfied(int constraint, boolean state, long nowElapsed)571     void setConstraintSatisfied(int constraint, boolean state, long nowElapsed) {
572         synchronized (mLock) {
573             final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
574             if (old == state) {
575                 return;
576             }
577 
578             if (DEBUG) {
579                 Slog.d(TAG, "setConstraintSatisfied: "
580                         + " constraint: " + constraint + " state: " + state);
581             }
582 
583             // Mark now as the last time we saw this set of constraints.
584             mLastSeenConstraintTimesElapsed.put(mSatisfiedFlexibleConstraints, nowElapsed);
585             if (!state) {
586                 // Mark now as the last time we saw this particular constraint.
587                 // (Good for logging/dump purposes).
588                 mLastSeenConstraintTimesElapsed.put(constraint, nowElapsed);
589             }
590 
591             mSatisfiedFlexibleConstraints =
592                     (mSatisfiedFlexibleConstraints & ~constraint) | (state ? constraint : 0);
593 
594             if ((JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS & constraint) != 0) {
595                 // Job-specific constraint --> don't need to proceed with logic below that
596                 // works with system-wide constraints.
597                 return;
598             }
599 
600             if (mFlexibilityEnabled) {
601                 // Only attempt to update jobs if the flex logic is enabled. Otherwise, the status
602                 // of the jobs won't change, so all the work will be a waste.
603 
604                 // Push the job update to the handler to avoid blocking other controllers and
605                 // potentially batch back-to-back controller state updates together.
606                 mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
607             }
608         }
609     }
610 
611     /** Checks if the given constraint is satisfied in the flexibility controller. */
612     @VisibleForTesting
isConstraintSatisfied(int constraint)613     boolean isConstraintSatisfied(int constraint) {
614         return (mSatisfiedFlexibleConstraints & constraint) != 0;
615     }
616 
617     @VisibleForTesting
618     @GuardedBy("mLock")
getLifeCycleBeginningElapsedLocked(JobStatus js)619     long getLifeCycleBeginningElapsedLocked(JobStatus js) {
620         long earliestRuntime = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
621                 ? js.enqueueTime : js.getEarliestRunTime();
622         if (js.getJob().isPeriodic() && js.getNumPreviousAttempts() == 0) {
623             // Rescheduling periodic jobs (after a successful execution) may result in the job's
624             // start time being a little after the "true" periodic start time (to avoid jobs
625             // running back to back). See JobSchedulerService#getRescheduleJobForPeriodic for more
626             // details. Since rescheduled periodic jobs may already be delayed slightly by this
627             // policy, don't penalize them further by then enforcing the full set of applied
628             // flex constraints at the beginning of the newly determined start time. Let the flex
629             // constraint requirement start closer to the true periodic start time.
630             final long truePeriodicStartTimeElapsed =
631                     js.getLatestRunTimeElapsed() - js.getJob().getFlexMillis();
632             // For now, treat the lifecycle beginning as the midpoint between the true periodic
633             // start time and the adjusted start time.
634             earliestRuntime = (earliestRuntime + truePeriodicStartTimeElapsed) / 2;
635         }
636         if (js.getJob().isPrefetch()) {
637             final long estimatedLaunchTime =
638                     mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
639             long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault(
640                     js.getSourceUserId(), js.getSourcePackageName(), 0L);
641             if (estimatedLaunchTime != Long.MAX_VALUE) {
642                 prefetchWindowStart = Math.max(prefetchWindowStart,
643                         estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs());
644             }
645             return Math.max(prefetchWindowStart, earliestRuntime);
646         }
647         return earliestRuntime;
648     }
649 
650     @VisibleForTesting
651     @GuardedBy("mLock")
getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed)652     int getScoreLocked(int uid, @NonNull String pkgName, long nowElapsed) {
653         final JobScoreTracker scoreTracker = mJobScoreTrackers.get(uid, pkgName);
654         return scoreTracker == null ? 0 : scoreTracker.getScore(nowElapsed);
655     }
656 
657     @VisibleForTesting
658     @GuardedBy("mLock")
getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest)659     long getLifeCycleEndElapsedLocked(JobStatus js, long nowElapsed, long earliest) {
660         if (js.getJob().isPrefetch()) {
661             final long estimatedLaunchTime =
662                     mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
663             // Prefetch jobs aren't supposed to have deadlines after T.
664             // But some legacy apps might still schedule them with deadlines.
665             if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) {
666                 // If there is a deadline, the earliest time is the end of the lifecycle.
667                 return Math.min(
668                         estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
669                         js.getLatestRunTimeElapsed());
670             }
671             if (estimatedLaunchTime != Long.MAX_VALUE) {
672                 return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
673             }
674             // There is no deadline and no estimated launch time.
675             return NO_LIFECYCLE_END;
676         }
677         // Increase the flex deadline for jobs rescheduled more than once.
678         if (js.getNumPreviousAttempts() > 1) {
679             return earliest + Math.min(
680                     (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
681                     mMaxRescheduledDeadline);
682         }
683 
684         // Intentionally use the effective priority here. If a job's priority was effectively
685         // lowered, it will be less likely to run quickly given other policies in JobScheduler.
686         // Thus, there's no need to further delay the job based on flex policy.
687         final int jobPriority = js.getEffectivePriority();
688         final int jobScore =
689                 getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
690         // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
691         final long fallbackDurationMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
692                 mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
693                         + mFallbackFlexibilityAdditionalScoreTimeFactors
694                                 .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
695         final long fallbackDeadlineMs = earliest + fallbackDurationMs;
696 
697         if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {
698             return fallbackDeadlineMs;
699         }
700         return Math.max(fallbackDeadlineMs, js.getLatestRunTimeElapsed());
701     }
702 
703     @VisibleForTesting
704     @GuardedBy("mLock")
getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed)705     int getCurPercentOfLifecycleLocked(JobStatus js, long nowElapsed) {
706         final long earliest = getLifeCycleBeginningElapsedLocked(js);
707         final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
708         if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
709             return 0;
710         }
711         if (nowElapsed > latest || latest == earliest) {
712             return 100;
713         }
714         final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest));
715         return percentInTime;
716     }
717 
718     @VisibleForTesting
719     @ElapsedRealtimeLong
720     @GuardedBy("mLock")
getNextConstraintDropTimeElapsedLocked(JobStatus js)721     long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
722         final long earliest = getLifeCycleBeginningElapsedLocked(js);
723         final long latest =
724                 getLifeCycleEndElapsedLocked(js, sElapsedRealtimeClock.millis(), earliest);
725         return getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
726     }
727 
728     /** The elapsed time that marks when the next constraint should be dropped. */
729     @ElapsedRealtimeLong
730     @GuardedBy("mLock")
getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest)731     long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
732         final int[] percentsToDropConstraints =
733                 getPercentsToDropConstraints(js.getEffectivePriority());
734         if (latest == NO_LIFECYCLE_END
735                 || js.getNumDroppedFlexibleConstraints() == percentsToDropConstraints.length) {
736             return NO_LIFECYCLE_END;
737         }
738         final int percent = percentsToDropConstraints[js.getNumDroppedFlexibleConstraints()];
739         final long percentInTime = ((latest - earliest) * percent) / 100;
740         return earliest + percentInTime;
741     }
742 
743     @NonNull
getPercentsToDropConstraints(int priority)744     private int[] getPercentsToDropConstraints(int priority) {
745         int[] percentsToDropConstraints = mPercentsToDropConstraints.get(priority);
746         if (percentsToDropConstraints == null) {
747             Slog.wtf(TAG, "No %-to-drop for priority " + JobInfo.getPriorityString(priority));
748             return new int[]{50, 60, 70, 80};
749         }
750         return percentsToDropConstraints;
751     }
752 
753     @Override
754     @GuardedBy("mLock")
onUidBiasChangedLocked(int uid, int prevBias, int newBias)755     public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
756         if (prevBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE
757                 && newBias < JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) {
758             // All changes are below BFGS. There's no significant change to care about.
759             return;
760         }
761         final long nowElapsed = sElapsedRealtimeClock.millis();
762         ArraySet<JobStatus> jobsByUid = mService.getJobStore().getJobsBySourceUid(uid);
763         boolean hasPrefetch = false;
764         for (int i = 0; i < jobsByUid.size(); i++) {
765             JobStatus js = jobsByUid.valueAt(i);
766             if (js.hasFlexibilityConstraint()) {
767                 js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
768                 hasPrefetch |= js.getJob().isPrefetch();
769             }
770         }
771 
772         // Prefetch jobs can't run when the app is TOP, so it should not be included in their
773         // lifecycle, and marks the beginning of a new lifecycle.
774         if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) {
775             final int userId = UserHandle.getUserId(uid);
776             final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid);
777             if (pkgs == null) {
778                 return;
779             }
780             for (int i = 0; i < pkgs.size(); i++) {
781                 String pkg = pkgs.valueAt(i);
782                 mPrefetchLifeCycleStart.add(userId, pkg,
783                         Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L),
784                                 nowElapsed));
785             }
786         }
787     }
788 
789     @Override
790     @GuardedBy("mLock")
onConstantsUpdatedLocked()791     public void onConstantsUpdatedLocked() {
792         if (mFcConfig.mShouldReevaluateConstraints) {
793             AppSchedulingModuleThread.getHandler().post(() -> {
794                 final ArraySet<JobStatus> changedJobs = new ArraySet<>();
795                 synchronized (mLock) {
796                     final long nowElapsed = sElapsedRealtimeClock.millis();
797                     for (int j = 0; j < mFlexibilityTracker.size(); j++) {
798                         final ArraySet<JobStatus> jobs = mFlexibilityTracker
799                                 .getJobsByNumRequiredConstraints(j);
800                         for (int i = jobs.size() - 1; i >= 0; --i) {
801                             JobStatus js = jobs.valueAt(i);
802                             mFlexibilityTracker.updateFlexibleConstraints(js, nowElapsed);
803                             mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed);
804                             if (js.setFlexibilityConstraintSatisfied(
805                                     nowElapsed, isFlexibilitySatisfiedLocked(js))) {
806                                 changedJobs.add(js);
807                             }
808                         }
809                     }
810                 }
811                 if (changedJobs.size() > 0) {
812                     mStateChangedListener.onControllerStateChanged(changedJobs);
813                 }
814             });
815         }
816     }
817 
818     @Override
819     @GuardedBy("mLock")
prepareForUpdatedConstantsLocked()820     public void prepareForUpdatedConstantsLocked() {
821         mFcConfig.mShouldReevaluateConstraints = false;
822     }
823 
824     @Override
825     @GuardedBy("mLock")
processConstantLocked(DeviceConfig.Properties properties, String key)826     public void processConstantLocked(DeviceConfig.Properties properties, String key) {
827         mFcConfig.processConstantLocked(properties, key);
828     }
829 
830     @VisibleForTesting
831     class FlexibilityTracker {
832         final ArrayList<ArraySet<JobStatus>> mTrackedJobs;
833 
FlexibilityTracker(int numFlexibleConstraints)834         FlexibilityTracker(int numFlexibleConstraints) {
835             mTrackedJobs = new ArrayList<>();
836             for (int i = 0; i <= numFlexibleConstraints; i++) {
837                 mTrackedJobs.add(new ArraySet<JobStatus>());
838             }
839         }
840 
841         /** Gets every tracked job with a given number of required constraints. */
842         @Nullable
getJobsByNumRequiredConstraints(int numRequired)843         public ArraySet<JobStatus> getJobsByNumRequiredConstraints(int numRequired) {
844             if (numRequired > mTrackedJobs.size()) {
845                 Slog.wtfStack(TAG, "Asked for a larger number of constraints than exists.");
846                 return null;
847             }
848             return mTrackedJobs.get(numRequired);
849         }
850 
851         /** adds a JobStatus object based on number of required flexible constraints. */
add(JobStatus js)852         public void add(JobStatus js) {
853             if (js.getNumRequiredFlexibleConstraints() < 0) {
854                 return;
855             }
856             mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).add(js);
857         }
858 
859         /** Removes a JobStatus object. */
remove(JobStatus js)860         public void remove(JobStatus js) {
861             mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(js);
862         }
863 
864         /**
865          * Updates applied and dropped constraints for the job.
866          */
updateFlexibleConstraints(JobStatus js, long nowElapsed)867         public void updateFlexibleConstraints(JobStatus js, long nowElapsed) {
868             final int prevNumRequired = js.getNumRequiredFlexibleConstraints();
869 
870             final int numAppliedConstraints =
871                     Integer.bitCount(getRelevantAppliedConstraintsLocked(js));
872             js.setNumAppliedFlexibleConstraints(numAppliedConstraints);
873 
874             final int[] percentsToDropConstraints =
875                     getPercentsToDropConstraints(js.getEffectivePriority());
876             final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
877             int toDrop = 0;
878             for (int i = 0; i < numAppliedConstraints; i++) {
879                 if (curPercent >= percentsToDropConstraints[i]) {
880                     toDrop++;
881                 }
882             }
883             js.setNumDroppedFlexibleConstraints(toDrop);
884 
885             if (prevNumRequired == js.getNumRequiredFlexibleConstraints()) {
886                 return;
887             }
888             mTrackedJobs.get(prevNumRequired).remove(js);
889             add(js);
890         }
891 
892         /**
893          * Calculates the number of constraints that should be dropped for the job, based on how
894          * far along the job is into its lifecycle.
895          */
calculateNumDroppedConstraints(JobStatus js, long nowElapsed)896         public void calculateNumDroppedConstraints(JobStatus js, long nowElapsed) {
897             final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed);
898             int toDrop = 0;
899             final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints();
900             final int[] percentsToDropConstraints =
901                     getPercentsToDropConstraints(js.getEffectivePriority());
902             for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
903                 if (curPercent >= percentsToDropConstraints[i]) {
904                     toDrop++;
905                 }
906             }
907             setNumDroppedFlexibleConstraints(js, toDrop);
908         }
909 
910         /** Returns all tracked jobs. */
getArrayList()911         public ArrayList<ArraySet<JobStatus>> getArrayList() {
912             return mTrackedJobs;
913         }
914 
915         /**
916          * Updates the number of dropped flexible constraints and sorts it into the tracker.
917          */
setNumDroppedFlexibleConstraints(JobStatus js, int numDropped)918         public void setNumDroppedFlexibleConstraints(JobStatus js, int numDropped) {
919             if (numDropped != js.getNumDroppedFlexibleConstraints()) {
920                 remove(js);
921                 js.setNumDroppedFlexibleConstraints(numDropped);
922                 add(js);
923             }
924         }
925 
size()926         public int size() {
927             return mTrackedJobs.size();
928         }
929 
dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed)930         public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed) {
931             for (int i = 0; i < mTrackedJobs.size(); i++) {
932                 ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
933                 for (int j = 0; j < jobs.size(); j++) {
934                     final JobStatus js = jobs.valueAt(j);
935                     if (!predicate.test(js)) {
936                         continue;
937                     }
938                     js.printUniqueId(pw);
939                     pw.print(" from ");
940                     UserHandle.formatUid(pw, js.getSourceUid());
941                     pw.print("-> Num Required Constraints: ");
942                     pw.print(js.getNumRequiredFlexibleConstraints());
943 
944                     pw.print(", lifecycle=[");
945                     final long earliest = getLifeCycleBeginningElapsedLocked(js);
946                     pw.print(earliest);
947                     pw.print(", (");
948                     pw.print(getCurPercentOfLifecycleLocked(js, nowElapsed));
949                     pw.print("%), ");
950                     pw.print(getLifeCycleEndElapsedLocked(js, nowElapsed, earliest));
951                     pw.print("]");
952 
953                     pw.println();
954                 }
955             }
956         }
957     }
958 
959     @VisibleForTesting
960     class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
FlexibilityAlarmQueue(Context context, Looper looper)961         private FlexibilityAlarmQueue(Context context, Looper looper) {
962             super(context, looper, "*job.flexibility_check*",
963                     "Flexible Constraint Check", true,
964                     mMinTimeBetweenFlexibilityAlarmsMs);
965         }
966 
967         @Override
isForUser(@onNull JobStatus js, int userId)968         protected boolean isForUser(@NonNull JobStatus js, int userId) {
969             return js.getSourceUserId() == userId;
970         }
971 
scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed)972         public void scheduleDropNumConstraintsAlarm(JobStatus js, long nowElapsed) {
973             synchronized (mLock) {
974                 final long earliest = getLifeCycleBeginningElapsedLocked(js);
975                 final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
976                 if (latest <= earliest) {
977                     // Something has gone horribly wrong. This has only occurred on incorrectly
978                     // configured tests, but add a check here for safety.
979                     Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
980                             + " prefetch=" + js.getJob().isPrefetch()
981                             + " periodic=" + js.getJob().isPeriodic());
982                     // Since things have gone wrong, the safest and most reliable thing to do is
983                     // stop applying flex policy to the job.
984                     mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
985                             js.getNumAppliedFlexibleConstraints());
986                     mJobsToCheck.add(js);
987                     mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
988                     return;
989                 }
990 
991                 final long nextTimeElapsed =
992                         getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
993 
994                 if (DEBUG) {
995                     Slog.d(TAG, "scheduleDropNumConstraintsAlarm: "
996                             + js.toShortString()
997                             + " numApplied: " + js.getNumAppliedFlexibleConstraints()
998                             + " numRequired: " + js.getNumRequiredFlexibleConstraints()
999                             + " numSatisfied: " + Integer.bitCount(
1000                             mSatisfiedFlexibleConstraints & getRelevantAppliedConstraintsLocked(js))
1001                             + " curTime: " + nowElapsed
1002                             + " earliest: " + earliest
1003                             + " latest: " + latest
1004                             + " nextTime: " + nextTimeElapsed);
1005                 }
1006                 if (latest - nowElapsed < mDeadlineProximityLimitMs) {
1007                     if (DEBUG) {
1008                         Slog.d(TAG, "deadline proximity met: " + js);
1009                     }
1010                     mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
1011                             js.getNumAppliedFlexibleConstraints());
1012                     mJobsToCheck.add(js);
1013                     mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
1014                     return;
1015                 }
1016                 if (nextTimeElapsed == NO_LIFECYCLE_END) {
1017                     // There is no known or estimated next time to drop a constraint.
1018                     removeAlarmForKey(js);
1019                     return;
1020                 }
1021                 if (latest - nextTimeElapsed <= mDeadlineProximityLimitMs) {
1022                     if (DEBUG) {
1023                         Slog.d(TAG, "last alarm set: " + js);
1024                     }
1025                     addAlarm(js, latest - mDeadlineProximityLimitMs);
1026                     return;
1027                 }
1028                 addAlarm(js, nextTimeElapsed);
1029             }
1030         }
1031 
1032         @Override
processExpiredAlarms(@onNull ArraySet<JobStatus> expired)1033         protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
1034             synchronized (mLock) {
1035                 ArraySet<JobStatus> changedJobs = new ArraySet<>();
1036                 final long nowElapsed = sElapsedRealtimeClock.millis();
1037                 for (int i = 0; i < expired.size(); i++) {
1038                     JobStatus js = expired.valueAt(i);
1039                     if (DEBUG) {
1040                         Slog.d(TAG, "Alarm fired for " + js.toShortString());
1041                     }
1042                     mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed);
1043                     if (js.getNumRequiredFlexibleConstraints() > 0) {
1044                         scheduleDropNumConstraintsAlarm(js, nowElapsed);
1045                     }
1046                     if (js.setFlexibilityConstraintSatisfied(nowElapsed,
1047                             isFlexibilitySatisfiedLocked(js))) {
1048                         changedJobs.add(js);
1049                     }
1050                 }
1051                 mStateChangedListener.onControllerStateChanged(changedJobs);
1052             }
1053         }
1054     }
1055 
1056     private class FcHandler extends Handler {
FcHandler(Looper looper)1057         FcHandler(Looper looper) {
1058             super(looper);
1059         }
1060 
1061         @Override
handleMessage(Message msg)1062         public void handleMessage(Message msg) {
1063             switch (msg.what) {
1064                 case MSG_CHECK_ALL_JOBS:
1065                     removeMessages(MSG_CHECK_ALL_JOBS);
1066 
1067                     synchronized (mLock) {
1068                         mJobsToCheck.clear();
1069                         mPackagesToCheck.clear();
1070                         final long nowElapsed = sElapsedRealtimeClock.millis();
1071                         final ArraySet<JobStatus> changedJobs = new ArraySet<>();
1072 
1073                         final int numAppliedSystemWideConstraints = Integer.bitCount(
1074                                 mAppliedConstraints & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
1075                         for (int o = 0; o <= numAppliedSystemWideConstraints; ++o) {
1076                             final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
1077                                     .getJobsByNumRequiredConstraints(o);
1078 
1079                             if (jobsByNumConstraints != null) {
1080                                 for (int i = 0; i < jobsByNumConstraints.size(); i++) {
1081                                     final JobStatus js = jobsByNumConstraints.valueAt(i);
1082                                     if (js.setFlexibilityConstraintSatisfied(
1083                                             nowElapsed, isFlexibilitySatisfiedLocked(js))) {
1084                                         changedJobs.add(js);
1085                                     }
1086                                 }
1087                             }
1088                         }
1089                         if (changedJobs.size() > 0) {
1090                             mStateChangedListener.onControllerStateChanged(changedJobs);
1091                         }
1092                     }
1093                     break;
1094 
1095                 case MSG_CHECK_JOBS:
1096                     synchronized (mLock) {
1097                         final long nowElapsed = sElapsedRealtimeClock.millis();
1098                         ArraySet<JobStatus> changedJobs = new ArraySet<>();
1099 
1100                         for (int i = mJobsToCheck.size() - 1; i >= 0; --i) {
1101                             final JobStatus js = mJobsToCheck.valueAt(i);
1102                             if (DEBUG) {
1103                                 Slog.d(TAG, "Checking on " + js.toShortString());
1104                             }
1105                             if (js.setFlexibilityConstraintSatisfied(
1106                                     nowElapsed, isFlexibilitySatisfiedLocked(js))) {
1107                                 changedJobs.add(js);
1108                             }
1109                         }
1110 
1111                         mJobsToCheck.clear();
1112                         if (changedJobs.size() > 0) {
1113                             mStateChangedListener.onControllerStateChanged(changedJobs);
1114                         }
1115                     }
1116                     break;
1117 
1118                 case MSG_CHECK_PACKAGES:
1119                     synchronized (mLock) {
1120                         final long nowElapsed = sElapsedRealtimeClock.millis();
1121                         final ArraySet<JobStatus> changedJobs = new ArraySet<>();
1122 
1123                         mService.getJobStore().forEachJob(
1124                                 (js) -> mPackagesToCheck.contains(js.getSourcePackageName())
1125                                         || mPackagesToCheck.contains(js.getCallingPackageName()),
1126                                 (js) -> {
1127                                     if (DEBUG) {
1128                                         Slog.d(TAG, "Checking on " + js.toShortString());
1129                                     }
1130                                     if (js.setFlexibilityConstraintSatisfied(
1131                                             nowElapsed, isFlexibilitySatisfiedLocked(js))) {
1132                                         changedJobs.add(js);
1133                                     }
1134                                 });
1135 
1136                         mPackagesToCheck.clear();
1137                         if (changedJobs.size() > 0) {
1138                             mStateChangedListener.onControllerStateChanged(changedJobs);
1139                         }
1140                     }
1141                     break;
1142             }
1143         }
1144     }
1145 
1146     class FcConfig {
1147         private boolean mShouldReevaluateConstraints = false;
1148 
1149         /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
1150         private static final String FC_CONFIG_PREFIX = "fc_";
1151 
1152         @VisibleForTesting
1153         static final String KEY_APPLIED_CONSTRAINTS = FC_CONFIG_PREFIX + "applied_constraints";
1154         static final String KEY_DEADLINE_PROXIMITY_LIMIT =
1155                 FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
1156         static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
1157                 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
1158         static final String KEY_FALLBACK_FLEXIBILITY_DEADLINES =
1159                 FC_CONFIG_PREFIX + "fallback_flexibility_deadlines";
1160         static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
1161                 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_scores";
1162         static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
1163                 FC_CONFIG_PREFIX + "fallback_flexibility_deadline_additional_score_time_factors";
1164         static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
1165                 FC_CONFIG_PREFIX + "min_time_between_flexibility_alarms_ms";
1166         static final String KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
1167                 FC_CONFIG_PREFIX + "percents_to_drop_flexible_constraints";
1168         static final String KEY_MAX_RESCHEDULED_DEADLINE_MS =
1169                 FC_CONFIG_PREFIX + "max_rescheduled_deadline_ms";
1170         static final String KEY_RESCHEDULED_JOB_DEADLINE_MS =
1171                 FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms";
1172         static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS =
1173                 FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms";
1174 
1175         static final int DEFAULT_APPLIED_CONSTRAINTS = 0;
1176         @VisibleForTesting
1177         static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
1178         @VisibleForTesting
1179         static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
1180         static final SparseLongArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
1181         static final SparseIntArray DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES =
1182                 new SparseIntArray();
1183         static final SparseLongArray
1184                 DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
1185                 new SparseLongArray();
1186         @VisibleForTesting
1187         static final SparseArray<int[]> DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS =
1188                 new SparseArray<>();
1189 
1190         static {
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS)1191             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MAX, HOUR_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS)1192             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_HIGH, 6 * HOUR_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS)1193             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_DEFAULT, 12 * HOUR_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS)1194             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_LOW, 24 * HOUR_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS)1195             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.put(PRIORITY_MIN, 48 * HOUR_IN_MILLIS);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5)1196             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MAX, 5);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4)1197             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_HIGH, 4);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3)1198             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_DEFAULT, 3);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2)1199             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_LOW, 2);
DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1)1200             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(PRIORITY_MIN, 1);
1201             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
put(PRIORITY_MAX, 0)1202                     .put(PRIORITY_MAX, 0);
1203             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
put(PRIORITY_HIGH, 3 * MINUTE_IN_MILLIS)1204                     .put(PRIORITY_HIGH, 3 * MINUTE_IN_MILLIS);
1205             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
put(PRIORITY_DEFAULT, 2 * MINUTE_IN_MILLIS)1206                     .put(PRIORITY_DEFAULT, 2 * MINUTE_IN_MILLIS);
1207             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
put(PRIORITY_LOW, 1 * MINUTE_IN_MILLIS)1208                     .put(PRIORITY_LOW, 1 * MINUTE_IN_MILLIS);
1209             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS)1210                     .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS);
1211             DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
put(PRIORITY_MAX, new int[]{1, 2, 3, 4})1212                     .put(PRIORITY_MAX, new int[]{1, 2, 3, 4});
1213             DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
put(PRIORITY_HIGH, new int[]{33, 50, 60, 75})1214                     .put(PRIORITY_HIGH, new int[]{33, 50, 60, 75});
1215             DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80})1216                     .put(PRIORITY_DEFAULT, new int[]{50, 60, 70, 80});
1217             DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
put(PRIORITY_LOW, new int[]{50, 60, 70, 80})1218                     .put(PRIORITY_LOW, new int[]{50, 60, 70, 80});
1219             DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
put(PRIORITY_MIN, new int[]{55, 65, 75, 85})1220                     .put(PRIORITY_MIN, new int[]{55, 65, 75, 85});
1221         }
1222 
1223         private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
1224         private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
1225         private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = DAY_IN_MILLIS;
1226         @VisibleForTesting
1227         static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS;
1228 
1229         /** Which constraints to apply/consider in flex policy. */
1230         public int APPLIED_CONSTRAINTS = DEFAULT_APPLIED_CONSTRAINTS;
1231         /** How close to a jobs' deadline all flexible constraints will be dropped. */
1232         public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
1233         /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */
1234         public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
1235         public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
1236                 DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
1237         /**
1238          * The percentages of a jobs' lifecycle to drop the number of required constraints.
1239          * Keyed by job priority.
1240          */
1241         public SparseArray<int[]> PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS = new SparseArray<>();
1242         /** Initial fallback flexible deadline for rescheduled jobs. */
1243         public long RESCHEDULED_JOB_DEADLINE_MS = DEFAULT_RESCHEDULED_JOB_DEADLINE_MS;
1244         /** The max deadline for rescheduled jobs. */
1245         public long MAX_RESCHEDULED_DEADLINE_MS = DEFAULT_MAX_RESCHEDULED_DEADLINE_MS;
1246         /**
1247          * How long to wait after last seeing a constraint combination before no longer waiting for
1248          * it in order to run jobs.
1249          */
1250         public long UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
1251         /**
1252          * The base fallback deadlines to use if a job doesn't have its own deadline. Values are in
1253          * milliseconds and keyed by job priority.
1254          */
1255         public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINES = new SparseLongArray();
1256         /**
1257          * The score to ascribe to each job, keyed by job priority.
1258          */
1259         public final SparseIntArray FALLBACK_FLEXIBILITY_DEADLINE_SCORES = new SparseIntArray();
1260         /**
1261          * How much additional time to increase the fallback deadline by based on the app's current
1262          * job run score. Values are in
1263          * milliseconds and keyed by job priority.
1264          */
1265         public final SparseLongArray FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS =
1266                 new SparseLongArray();
1267 
FcConfig()1268         FcConfig() {
1269             // Copy the values from the DEFAULT_* data structures to avoid accidentally modifying
1270             // the DEFAULT_* data structures in other parts of the code.
1271             for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.size(); ++i) {
1272                 FALLBACK_FLEXIBILITY_DEADLINES.put(
1273                         DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.keyAt(i),
1274                         DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES.valueAt(i));
1275             }
1276             for (int i = 0; i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.size(); ++i) {
1277                 FALLBACK_FLEXIBILITY_DEADLINE_SCORES.put(
1278                         DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.keyAt(i),
1279                         DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES.valueAt(i));
1280             }
1281             for (int i = 0;
1282                     i < DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.size();
1283                     ++i) {
1284                 FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS.put(
1285                         DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
1286                                 .keyAt(i),
1287                         DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
1288                                 .valueAt(i));
1289             }
1290             for (int i = 0; i < DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.size(); ++i) {
1291                 PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.put(
1292                         DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.keyAt(i),
1293                         DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS.valueAt(i));
1294             }
1295         }
1296 
1297         @GuardedBy("mLock")
processConstantLocked(@onNull DeviceConfig.Properties properties, @NonNull String key)1298         public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
1299                 @NonNull String key) {
1300             // TODO(257322915): add appropriate minimums and maximums to constants when parsing
1301             switch (key) {
1302                 case KEY_APPLIED_CONSTRAINTS:
1303                     APPLIED_CONSTRAINTS =
1304                             properties.getInt(key, DEFAULT_APPLIED_CONSTRAINTS)
1305                                     & mSupportedFlexConstraints;
1306                     if (mAppliedConstraints != APPLIED_CONSTRAINTS) {
1307                         mAppliedConstraints = APPLIED_CONSTRAINTS;
1308                         mShouldReevaluateConstraints = true;
1309                         if (mAppliedConstraints != 0) {
1310                             mFlexibilityEnabled = true;
1311                             mPrefetchController
1312                                     .registerPrefetchChangedListener(mPrefetchChangedListener);
1313                             mSpecialAppTracker.startTracking();
1314                         } else {
1315                             mFlexibilityEnabled = false;
1316                             mPrefetchController
1317                                     .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
1318                             mSpecialAppTracker.stopTracking();
1319                         }
1320                     }
1321                     break;
1322                 case KEY_RESCHEDULED_JOB_DEADLINE_MS:
1323                     RESCHEDULED_JOB_DEADLINE_MS =
1324                             properties.getLong(key, DEFAULT_RESCHEDULED_JOB_DEADLINE_MS);
1325                     if (mRescheduledJobDeadline != RESCHEDULED_JOB_DEADLINE_MS) {
1326                         mRescheduledJobDeadline = RESCHEDULED_JOB_DEADLINE_MS;
1327                         mShouldReevaluateConstraints = true;
1328                     }
1329                     break;
1330                 case KEY_MAX_RESCHEDULED_DEADLINE_MS:
1331                     MAX_RESCHEDULED_DEADLINE_MS =
1332                             properties.getLong(key, DEFAULT_MAX_RESCHEDULED_DEADLINE_MS);
1333                     if (mMaxRescheduledDeadline != MAX_RESCHEDULED_DEADLINE_MS) {
1334                         mMaxRescheduledDeadline = MAX_RESCHEDULED_DEADLINE_MS;
1335                         mShouldReevaluateConstraints = true;
1336                     }
1337                     break;
1338                 case KEY_DEADLINE_PROXIMITY_LIMIT:
1339                     DEADLINE_PROXIMITY_LIMIT_MS =
1340                             properties.getLong(key, DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS);
1341                     if (mDeadlineProximityLimitMs != DEADLINE_PROXIMITY_LIMIT_MS) {
1342                         mDeadlineProximityLimitMs = DEADLINE_PROXIMITY_LIMIT_MS;
1343                         mShouldReevaluateConstraints = true;
1344                     }
1345                     break;
1346                 case KEY_FALLBACK_FLEXIBILITY_DEADLINE:
1347                     FALLBACK_FLEXIBILITY_DEADLINE_MS =
1348                             properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS);
1349                     if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) {
1350                         mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS;
1351                     }
1352                     break;
1353                 case KEY_FALLBACK_FLEXIBILITY_DEADLINES:
1354                     if (parsePriorityToLongKeyValueString(
1355                             properties.getString(key, null),
1356                             FALLBACK_FLEXIBILITY_DEADLINES,
1357                             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINES)) {
1358                         mFallbackFlexibilityDeadlines = FALLBACK_FLEXIBILITY_DEADLINES;
1359                         mShouldReevaluateConstraints = true;
1360                     }
1361                     break;
1362                 case KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES:
1363                     if (parsePriorityToIntKeyValueString(
1364                             properties.getString(key, null),
1365                             FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
1366                             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES)) {
1367                         mFallbackFlexibilityDeadlineScores = FALLBACK_FLEXIBILITY_DEADLINE_SCORES;
1368                         mShouldReevaluateConstraints = true;
1369                     }
1370                     break;
1371                 case KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS:
1372                     if (parsePriorityToLongKeyValueString(
1373                             properties.getString(key, null),
1374                             FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
1375                             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS)) {
1376                         mFallbackFlexibilityAdditionalScoreTimeFactors =
1377                                 FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS;
1378                         mShouldReevaluateConstraints = true;
1379                     }
1380                     break;
1381                 case KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS:
1382                     MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
1383                             properties.getLong(key, DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS);
1384                     if (mMinTimeBetweenFlexibilityAlarmsMs
1385                             != MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS) {
1386                         mMinTimeBetweenFlexibilityAlarmsMs = MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
1387                         mFlexibilityAlarmQueue
1388                                 .setMinTimeBetweenAlarmsMs(MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS);
1389                         mShouldReevaluateConstraints = true;
1390                     }
1391                     break;
1392                 case KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS:
1393                     UNSEEN_CONSTRAINT_GRACE_PERIOD_MS =
1394                             properties.getLong(key, DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS);
1395                     if (mUnseenConstraintGracePeriodMs != UNSEEN_CONSTRAINT_GRACE_PERIOD_MS) {
1396                         mUnseenConstraintGracePeriodMs = UNSEEN_CONSTRAINT_GRACE_PERIOD_MS;
1397                         mShouldReevaluateConstraints = true;
1398                     }
1399                     break;
1400                 case KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS:
1401                     if (parsePercentToDropKeyValueString(
1402                             properties.getString(key, null),
1403                             PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
1404                             DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS)) {
1405                         mPercentsToDropConstraints = PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS;
1406                         mShouldReevaluateConstraints = true;
1407                     }
1408                     break;
1409             }
1410         }
1411 
parsePercentToDropKeyValueString(@ullable String s, SparseArray<int[]> into, SparseArray<int[]> defaults)1412         private boolean parsePercentToDropKeyValueString(@Nullable String s,
1413                 SparseArray<int[]> into, SparseArray<int[]> defaults) {
1414             final KeyValueListParser priorityParser = new KeyValueListParser(',');
1415             try {
1416                 priorityParser.setString(s);
1417             } catch (IllegalArgumentException e) {
1418                 Slog.wtf(TAG, "Bad percent to drop key value string given", e);
1419                 // Clear the string and continue with the defaults.
1420                 priorityParser.setString(null);
1421             }
1422 
1423             final int[] oldMax = into.get(PRIORITY_MAX);
1424             final int[] oldHigh = into.get(PRIORITY_HIGH);
1425             final int[] oldDefault = into.get(PRIORITY_DEFAULT);
1426             final int[] oldLow = into.get(PRIORITY_LOW);
1427             final int[] oldMin = into.get(PRIORITY_MIN);
1428 
1429             final int[] newMax = parsePercentToDropString(priorityParser.getString(
1430                     String.valueOf(PRIORITY_MAX), null));
1431             final int[] newHigh = parsePercentToDropString(priorityParser.getString(
1432                     String.valueOf(PRIORITY_HIGH), null));
1433             final int[] newDefault = parsePercentToDropString(priorityParser.getString(
1434                     String.valueOf(PRIORITY_DEFAULT), null));
1435             final int[] newLow = parsePercentToDropString(priorityParser.getString(
1436                     String.valueOf(PRIORITY_LOW), null));
1437             final int[] newMin = parsePercentToDropString(priorityParser.getString(
1438                     String.valueOf(PRIORITY_MIN), null));
1439 
1440             into.put(PRIORITY_MAX, newMax == null ? defaults.get(PRIORITY_MAX) : newMax);
1441             into.put(PRIORITY_HIGH, newHigh == null ? defaults.get(PRIORITY_HIGH) : newHigh);
1442             into.put(PRIORITY_DEFAULT,
1443                     newDefault == null ? defaults.get(PRIORITY_DEFAULT) : newDefault);
1444             into.put(PRIORITY_LOW, newLow == null ? defaults.get(PRIORITY_LOW) : newLow);
1445             into.put(PRIORITY_MIN, newMin == null ? defaults.get(PRIORITY_MIN) : newMin);
1446 
1447             return !Arrays.equals(oldMax, into.get(PRIORITY_MAX))
1448                     || !Arrays.equals(oldHigh, into.get(PRIORITY_HIGH))
1449                     || !Arrays.equals(oldDefault, into.get(PRIORITY_DEFAULT))
1450                     || !Arrays.equals(oldLow, into.get(PRIORITY_LOW))
1451                     || !Arrays.equals(oldMin, into.get(PRIORITY_MIN));
1452         }
1453 
1454         @Nullable
parsePercentToDropString(@ullable String s)1455         private int[] parsePercentToDropString(@Nullable String s) {
1456             if (s == null || s.isEmpty()) {
1457                 return null;
1458             }
1459             final String[] dropPercentString = s.split("\\|");
1460             int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)];
1461             if (dropPercentInt.length != dropPercentString.length) {
1462                 return null;
1463             }
1464             int prevPercent = 0;
1465             for (int i = 0; i < dropPercentString.length; i++) {
1466                 try {
1467                     dropPercentInt[i] =
1468                             Integer.parseInt(dropPercentString[i]);
1469                 } catch (NumberFormatException ex) {
1470                     Slog.e(TAG, "Provided string was improperly formatted.", ex);
1471                     return null;
1472                 }
1473                 if (dropPercentInt[i] < prevPercent) {
1474                     Slog.wtf(TAG, "Percents to drop constraints were not in increasing order.");
1475                     return null;
1476                 }
1477                 if (dropPercentInt[i] > 100) {
1478                     Slog.e(TAG, "Found % over 100");
1479                     return null;
1480                 }
1481                 prevPercent = dropPercentInt[i];
1482             }
1483 
1484             return dropPercentInt;
1485         }
1486 
1487         /**
1488          * Parses the input string, expecting it to a key-value string where the keys are job
1489          * priorities, and replaces everything in {@code into} with the values from the string,
1490          * or the default values if the string contains none.
1491          *
1492          * Returns true if any values changed.
1493          */
parsePriorityToIntKeyValueString(@ullable String s, SparseIntArray into, SparseIntArray defaults)1494         private boolean parsePriorityToIntKeyValueString(@Nullable String s,
1495                 SparseIntArray into, SparseIntArray defaults) {
1496             final KeyValueListParser parser = new KeyValueListParser(',');
1497             try {
1498                 parser.setString(s);
1499             } catch (IllegalArgumentException e) {
1500                 Slog.wtf(TAG, "Bad string given", e);
1501                 // Clear the string and continue with the defaults.
1502                 parser.setString(null);
1503             }
1504 
1505             final int oldMax = into.get(PRIORITY_MAX);
1506             final int oldHigh = into.get(PRIORITY_HIGH);
1507             final int oldDefault = into.get(PRIORITY_DEFAULT);
1508             final int oldLow = into.get(PRIORITY_LOW);
1509             final int oldMin = into.get(PRIORITY_MIN);
1510 
1511             final int newMax = parser.getInt(String.valueOf(PRIORITY_MAX),
1512                     defaults.get(PRIORITY_MAX));
1513             final int newHigh = parser.getInt(String.valueOf(PRIORITY_HIGH),
1514                     defaults.get(PRIORITY_HIGH));
1515             final int newDefault = parser.getInt(String.valueOf(PRIORITY_DEFAULT),
1516                     defaults.get(PRIORITY_DEFAULT));
1517             final int newLow = parser.getInt(String.valueOf(PRIORITY_LOW),
1518                     defaults.get(PRIORITY_LOW));
1519             final int newMin = parser.getInt(String.valueOf(PRIORITY_MIN),
1520                     defaults.get(PRIORITY_MIN));
1521 
1522             into.put(PRIORITY_MAX, newMax);
1523             into.put(PRIORITY_HIGH, newHigh);
1524             into.put(PRIORITY_DEFAULT, newDefault);
1525             into.put(PRIORITY_LOW, newLow);
1526             into.put(PRIORITY_MIN, newMin);
1527 
1528             return oldMax != newMax
1529                     || oldHigh != newHigh
1530                     || oldDefault != newDefault
1531                     || oldLow != newLow
1532                     || oldMin != newMin;
1533         }
1534 
1535         /**
1536          * Parses the input string, expecting it to a key-value string where the keys are job
1537          * priorities, and replaces everything in {@code into} with the values from the string,
1538          * or the default values if the string contains none.
1539          *
1540          * Returns true if any values changed.
1541          */
parsePriorityToLongKeyValueString(@ullable String s, SparseLongArray into, SparseLongArray defaults)1542         private boolean parsePriorityToLongKeyValueString(@Nullable String s,
1543                 SparseLongArray into, SparseLongArray defaults) {
1544             final KeyValueListParser parser = new KeyValueListParser(',');
1545             try {
1546                 parser.setString(s);
1547             } catch (IllegalArgumentException e) {
1548                 Slog.wtf(TAG, "Bad string given", e);
1549                 // Clear the string and continue with the defaults.
1550                 parser.setString(null);
1551             }
1552 
1553             final long oldMax = into.get(PRIORITY_MAX);
1554             final long oldHigh = into.get(PRIORITY_HIGH);
1555             final long oldDefault = into.get(PRIORITY_DEFAULT);
1556             final long oldLow = into.get(PRIORITY_LOW);
1557             final long oldMin = into.get(PRIORITY_MIN);
1558 
1559             final long newMax = parser.getLong(String.valueOf(PRIORITY_MAX),
1560                     defaults.get(PRIORITY_MAX));
1561             final long newHigh = parser.getLong(String.valueOf(PRIORITY_HIGH),
1562                     defaults.get(PRIORITY_HIGH));
1563             final long newDefault = parser.getLong(String.valueOf(PRIORITY_DEFAULT),
1564                     defaults.get(PRIORITY_DEFAULT));
1565             final long newLow = parser.getLong(String.valueOf(PRIORITY_LOW),
1566                     defaults.get(PRIORITY_LOW));
1567             final long newMin = parser.getLong(String.valueOf(PRIORITY_MIN),
1568                     defaults.get(PRIORITY_MIN));
1569 
1570             into.put(PRIORITY_MAX, newMax);
1571             into.put(PRIORITY_HIGH, newHigh);
1572             into.put(PRIORITY_DEFAULT, newDefault);
1573             into.put(PRIORITY_LOW, newLow);
1574             into.put(PRIORITY_MIN, newMin);
1575 
1576             return oldMax != newMax
1577                     || oldHigh != newHigh
1578                     || oldDefault != newDefault
1579                     || oldLow != newLow
1580                     || oldMin != newMin;
1581         }
1582 
dump(IndentingPrintWriter pw)1583         private void dump(IndentingPrintWriter pw) {
1584             pw.println();
1585             pw.print(FlexibilityController.class.getSimpleName());
1586             pw.println(":");
1587             pw.increaseIndent();
1588 
1589             pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS);
1590             pw.print("(");
1591             if (APPLIED_CONSTRAINTS != 0) {
1592                 JobStatus.dumpConstraints(pw, APPLIED_CONSTRAINTS);
1593             } else {
1594                 pw.print("nothing");
1595             }
1596             pw.println(")");
1597             pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println();
1598             pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println();
1599             pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINES, FALLBACK_FLEXIBILITY_DEADLINES).println();
1600             pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_SCORES,
1601                     FALLBACK_FLEXIBILITY_DEADLINE_SCORES).println();
1602             pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS,
1603                     FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS).println();
1604             pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS,
1605                     MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS).println();
1606             pw.print(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
1607                     PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS).println();
1608             pw.print(KEY_RESCHEDULED_JOB_DEADLINE_MS, RESCHEDULED_JOB_DEADLINE_MS).println();
1609             pw.print(KEY_MAX_RESCHEDULED_DEADLINE_MS, MAX_RESCHEDULED_DEADLINE_MS).println();
1610             pw.print(KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS, UNSEEN_CONSTRAINT_GRACE_PERIOD_MS)
1611                     .println();
1612 
1613             pw.decreaseIndent();
1614         }
1615     }
1616 
1617     @VisibleForTesting
1618     @NonNull
getFcConfig()1619     FcConfig getFcConfig() {
1620         return mFcConfig;
1621     }
1622 
1623     private class SpecialAppTracker {
1624         /**
1625          * Lock for objects inside this class. This should never be held when attempting to acquire
1626          * {@link #mLock}. It is fine to acquire this if already holding {@link #mLock}.
1627          */
1628         private final Object mSatLock = new Object();
1629 
1630         private DeviceIdleInternal mDeviceIdleInternal;
1631         private TelephonyManager mTelephonyManager;
1632 
1633         private final boolean mHasFeatureTelephonySubscription;
1634 
1635         /** Set of all apps that have been deemed special, keyed by user ID. */
1636         private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>();
1637         /**
1638          * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged
1639          * for.
1640          */
1641         @GuardedBy("mSatLock")
1642         private final SparseSetArray<String> mCarrierPrivilegedApps = new SparseSetArray<>();
1643         @GuardedBy("mSatLock")
1644         private final SparseArray<LogicalIndexCarrierPrivilegesCallback>
1645                 mCarrierPrivilegedCallbacks = new SparseArray<>();
1646         @GuardedBy("mSatLock")
1647         private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
1648 
1649         private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1650             @Override
1651             public void onReceive(Context context, Intent intent) {
1652                 switch (intent.getAction()) {
1653                     case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
1654                         updateCarrierPrivilegedCallbackRegistration();
1655                         break;
1656 
1657                     case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
1658                         mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
1659                         break;
1660                 }
1661             }
1662         };
1663 
SpecialAppTracker()1664         SpecialAppTracker() {
1665             mHasFeatureTelephonySubscription = mContext.getPackageManager()
1666                     .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
1667         }
1668 
isSpecialApp(final int userId, @NonNull String packageName)1669         public boolean isSpecialApp(final int userId, @NonNull String packageName) {
1670             synchronized (mSatLock) {
1671                 if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) {
1672                     return true;
1673                 }
1674                 if (mSpecialApps.contains(userId, packageName)) {
1675                     return true;
1676                 }
1677             }
1678             return false;
1679         }
1680 
isSpecialAppInternal(final int userId, @NonNull String packageName)1681         private boolean isSpecialAppInternal(final int userId, @NonNull String packageName) {
1682             synchronized (mSatLock) {
1683                 if (mPowerAllowlistedApps.contains(packageName)) {
1684                     return true;
1685                 }
1686                 for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) {
1687                     if (mCarrierPrivilegedApps.contains(
1688                             mCarrierPrivilegedApps.keyAt(l), packageName)) {
1689                         return true;
1690                     }
1691                 }
1692             }
1693             return false;
1694         }
1695 
onAppRemoved(final int userId, String packageName)1696         private void onAppRemoved(final int userId, String packageName) {
1697             synchronized (mSatLock) {
1698                 // Don't touch the USER_ALL set here. If the app is completely removed from the
1699                 // device, any list that affects USER_ALL should update and this would eventually
1700                 // be updated with those lists no longer containing the app.
1701                 mSpecialApps.remove(userId, packageName);
1702             }
1703         }
1704 
onSystemServicesReady()1705         private void onSystemServicesReady() {
1706             mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
1707             mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
1708 
1709             synchronized (mLock) {
1710                 if (mFlexibilityEnabled) {
1711                     mHandler.post(
1712                             SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration);
1713                     mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
1714                 }
1715             }
1716         }
1717 
onUserRemoved(final int userId)1718         private void onUserRemoved(final int userId) {
1719             synchronized (mSatLock) {
1720                 mSpecialApps.remove(userId);
1721             }
1722         }
1723 
startTracking()1724         private void startTracking() {
1725             IntentFilter filter = new IntentFilter(
1726                     PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
1727 
1728             if (mHasFeatureTelephonySubscription) {
1729                 filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
1730 
1731                 updateCarrierPrivilegedCallbackRegistration();
1732             }
1733 
1734             mContext.registerReceiver(mBroadcastReceiver, filter);
1735 
1736             updatePowerAllowlistCache();
1737         }
1738 
stopTracking()1739         private void stopTracking() {
1740             mContext.unregisterReceiver(mBroadcastReceiver);
1741 
1742             synchronized (mSatLock) {
1743                 mCarrierPrivilegedApps.clear();
1744                 mPowerAllowlistedApps.clear();
1745                 mSpecialApps.clear();
1746 
1747                 for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
1748                     mTelephonyManager.unregisterCarrierPrivilegesCallback(
1749                             mCarrierPrivilegedCallbacks.valueAt(i));
1750                 }
1751                 mCarrierPrivilegedCallbacks.clear();
1752             }
1753         }
1754 
updateCarrierPrivilegedCallbackRegistration()1755         private void updateCarrierPrivilegedCallbackRegistration() {
1756             if (mTelephonyManager == null) {
1757                 return;
1758             }
1759             if (!mHasFeatureTelephonySubscription) {
1760                 return;
1761             }
1762 
1763             Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
1764             final ArraySet<String> changedPkgs = new ArraySet<>();
1765             synchronized (mSatLock) {
1766                 final IntArray callbacksToRemove = new IntArray();
1767                 for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
1768                     callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i));
1769                 }
1770                 for (UiccSlotMapping mapping : simSlotMapping) {
1771                     final int logicalIndex = mapping.getLogicalSlotIndex();
1772                     if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
1773                         // Callback already exists. No need to create a new one or remove it.
1774                         for (int i = callbacksToRemove.size() - 1; i >= 0; i--) {
1775                             if (callbacksToRemove.get(i) == logicalIndex) {
1776                                 callbacksToRemove.remove(i);
1777                                 break;
1778                             }
1779                         }
1780 
1781                         continue;
1782                     }
1783                     final LogicalIndexCarrierPrivilegesCallback callback =
1784                             new LogicalIndexCarrierPrivilegesCallback(logicalIndex);
1785                     mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
1786                     // Upon registration, the callbacks will be called with the current list of
1787                     // apps, so there's no need to query the app list synchronously.
1788                     mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex,
1789                             AppSchedulingModuleThread.getExecutor(), callback);
1790                 }
1791 
1792                 for (int i = callbacksToRemove.size() - 1; i >= 0; --i) {
1793                     final int logicalIndex = callbacksToRemove.get(i);
1794                     final LogicalIndexCarrierPrivilegesCallback callback =
1795                             mCarrierPrivilegedCallbacks.get(logicalIndex);
1796                     mTelephonyManager.unregisterCarrierPrivilegesCallback(callback);
1797                     mCarrierPrivilegedCallbacks.remove(logicalIndex);
1798                     changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex));
1799                     mCarrierPrivilegedApps.remove(logicalIndex);
1800                 }
1801             }
1802 
1803             updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
1804         }
1805 
1806         /**
1807          * Update the processed special app set for the specified user ID, only looking at the
1808          * specified set of apps. This method must <b>NEVER</b> be called while holding
1809          * {@link #mSatLock}.
1810          */
updateSpecialAppSetUnlocked(final int userId, @NonNull ArraySet<String> pkgs)1811         private void updateSpecialAppSetUnlocked(final int userId, @NonNull ArraySet<String> pkgs) {
1812             // This method may need to acquire mLock, so ensure that mSatLock isn't held to avoid
1813             // lock inversion.
1814             if (Thread.holdsLock(mSatLock)) {
1815                 throw new IllegalStateException("Must never hold local mSatLock");
1816             }
1817             if (pkgs.size() == 0) {
1818                 return;
1819             }
1820             final ArraySet<String> changedPkgs = new ArraySet<>();
1821 
1822             synchronized (mSatLock) {
1823                 for (int i = pkgs.size() - 1; i >= 0; --i) {
1824                     final String pkgName = pkgs.valueAt(i);
1825                     if (isSpecialAppInternal(userId, pkgName)) {
1826                         if (mSpecialApps.add(userId, pkgName)) {
1827                             changedPkgs.add(pkgName);
1828                         }
1829                     } else if (mSpecialApps.remove(userId, pkgName)) {
1830                         changedPkgs.add(pkgName);
1831                     }
1832                 }
1833             }
1834 
1835             if (changedPkgs.size() > 0) {
1836                 synchronized (mLock) {
1837                     mPackagesToCheck.addAll(changedPkgs);
1838                     mHandler.sendEmptyMessage(MSG_CHECK_PACKAGES);
1839                 }
1840             }
1841         }
1842 
updatePowerAllowlistCache()1843         private void updatePowerAllowlistCache() {
1844             if (mDeviceIdleInternal == null) {
1845                 return;
1846             }
1847 
1848             // Don't call out to DeviceIdleController with the lock held.
1849             final String[] allowlistedPkgs = mDeviceIdleInternal.getFullPowerWhitelistExceptIdle();
1850             final ArraySet<String> changedPkgs = new ArraySet<>();
1851             synchronized (mSatLock) {
1852                 changedPkgs.addAll(mPowerAllowlistedApps);
1853                 mPowerAllowlistedApps.clear();
1854                 for (String pkgName : allowlistedPkgs) {
1855                     mPowerAllowlistedApps.add(pkgName);
1856                     if (!changedPkgs.remove(pkgName)) {
1857                         // The package wasn't in the previous set of allowlisted apps. Add it
1858                         // since its state has changed.
1859                         changedPkgs.add(pkgName);
1860                     }
1861                 }
1862             }
1863 
1864             // The full allowlist is currently user-agnostic, so use USER_ALL for these packages.
1865             updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
1866         }
1867 
1868         class LogicalIndexCarrierPrivilegesCallback implements
1869                 TelephonyManager.CarrierPrivilegesCallback {
1870             public final int logicalIndex;
1871 
LogicalIndexCarrierPrivilegesCallback(int logicalIndex)1872             LogicalIndexCarrierPrivilegesCallback(int logicalIndex) {
1873                 this.logicalIndex = logicalIndex;
1874             }
1875 
1876             @Override
onCarrierPrivilegesChanged(@onNull Set<String> privilegedPackageNames, @NonNull Set<Integer> privilegedUids)1877             public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames,
1878                     @NonNull Set<Integer> privilegedUids) {
1879                 final ArraySet<String> changedPkgs = new ArraySet<>();
1880                 synchronized (mSatLock) {
1881                     final ArraySet<String> oldPrivilegedSet =
1882                             mCarrierPrivilegedApps.get(logicalIndex);
1883                     if (oldPrivilegedSet != null) {
1884                         changedPkgs.addAll(oldPrivilegedSet);
1885                         mCarrierPrivilegedApps.remove(logicalIndex);
1886                     }
1887                     for (String pkgName : privilegedPackageNames) {
1888                         mCarrierPrivilegedApps.add(logicalIndex, pkgName);
1889                         if (!changedPkgs.remove(pkgName)) {
1890                             // The package wasn't in the previous set of privileged apps. Add it
1891                             // since its state has changed.
1892                             changedPkgs.add(pkgName);
1893                         }
1894                     }
1895                 }
1896 
1897                 // The carrier privileged list doesn't provide a simple userId correlation,
1898                 // so for now, use USER_ALL for these packages.
1899                 // TODO(141645789): use the UID list to narrow down to specific userIds
1900                 updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
1901             }
1902         }
1903 
dump(@onNull IndentingPrintWriter pw)1904         public void dump(@NonNull IndentingPrintWriter pw) {
1905             pw.println("Special apps:");
1906             pw.increaseIndent();
1907 
1908             synchronized (mSatLock) {
1909                 for (int u = 0; u < mSpecialApps.size(); ++u) {
1910                     pw.print("User ");
1911                     pw.print(mSpecialApps.keyAt(u));
1912                     pw.print(": ");
1913                     pw.println(mSpecialApps.valuesAt(u));
1914                 }
1915 
1916                 pw.println();
1917                 pw.println("Carrier privileged packages:");
1918                 pw.increaseIndent();
1919                 for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) {
1920                     pw.print(mCarrierPrivilegedApps.keyAt(i));
1921                     pw.print(": ");
1922                     pw.println(mCarrierPrivilegedApps.valuesAt(i));
1923                 }
1924                 pw.decreaseIndent();
1925 
1926                 pw.println();
1927                 pw.print("Power allowlisted packages: ");
1928                 pw.println(mPowerAllowlistedApps);
1929             }
1930 
1931             pw.decreaseIndent();
1932         }
1933     }
1934 
1935     /**
1936      * If {@code override} is true, uses {@code appliedConstraints} for flex policy evaluation,
1937      * overriding anything else that was set. If {@code override} is false, any previous calls
1938      * will be discarded and the policy will be reset to the normal default policy.
1939      */
setLocalPolicyForTesting(boolean override, int appliedConstraints)1940     public void setLocalPolicyForTesting(boolean override, int appliedConstraints) {
1941         synchronized (mLock) {
1942             final boolean recheckJobs = mLocalOverride != override
1943                     || mAppliedConstraints != appliedConstraints;
1944             mLocalOverride = override;
1945             if (mLocalOverride) {
1946                 mAppliedConstraints = appliedConstraints;
1947             } else {
1948                 mAppliedConstraints = mFcConfig.APPLIED_CONSTRAINTS;
1949             }
1950             if (recheckJobs) {
1951                 mHandler.obtainMessage(MSG_CHECK_ALL_JOBS).sendToTarget();
1952             }
1953         }
1954     }
1955 
1956     @Override
1957     @GuardedBy("mLock")
dumpConstants(IndentingPrintWriter pw)1958     public void dumpConstants(IndentingPrintWriter pw) {
1959         mFcConfig.dump(pw);
1960     }
1961 
1962     @Override
1963     @GuardedBy("mLock")
dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)1964     public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
1965         if (mLocalOverride) {
1966             pw.println("Local override active");
1967         }
1968         pw.print("Applied Flexible Constraints:");
1969         JobStatus.dumpConstraints(pw, mAppliedConstraints);
1970         pw.println();
1971         pw.print("Satisfied Flexible Constraints:");
1972         JobStatus.dumpConstraints(pw, mSatisfiedFlexibleConstraints);
1973         pw.println();
1974         pw.println();
1975 
1976         final long nowElapsed = sElapsedRealtimeClock.millis();
1977         pw.println("Time since constraint combos last seen:");
1978         pw.increaseIndent();
1979         for (int i = 0; i < mLastSeenConstraintTimesElapsed.size(); ++i) {
1980             final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i);
1981             if (constraints == mSatisfiedFlexibleConstraints) {
1982                 pw.print("0ms");
1983             } else {
1984                 TimeUtils.formatDuration(
1985                         mLastSeenConstraintTimesElapsed.valueAt(i), nowElapsed, pw);
1986             }
1987             pw.print(":");
1988             if (constraints != 0) {
1989                 // dumpConstraints prepends with a space, so no need to add a space after the :
1990                 JobStatus.dumpConstraints(pw, constraints);
1991             } else {
1992                 pw.print(" none");
1993             }
1994             pw.println();
1995         }
1996         pw.decreaseIndent();
1997 
1998         pw.println();
1999         mSpecialAppTracker.dump(pw);
2000 
2001         pw.println();
2002         mFlexibilityTracker.dump(pw, predicate, nowElapsed);
2003 
2004         pw.println();
2005         pw.println("Job scores:");
2006         pw.increaseIndent();
2007         mJobScoreTrackers.forEach((uid, pkgName, jobScoreTracker) -> {
2008             pw.print(uid);
2009             pw.print("/");
2010             pw.print(pkgName);
2011             pw.print(": ");
2012             jobScoreTracker.dump(pw, nowElapsed);
2013             pw.println();
2014         });
2015         pw.decreaseIndent();
2016 
2017         pw.println();
2018         mFlexibilityAlarmQueue.dump(pw);
2019     }
2020 }
2021