1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.job;
18 
19 import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
20 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
21 
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.ActivityManager;
26 import android.app.ActivityManagerInternal;
27 import android.app.UserSwitchObserver;
28 import android.app.job.JobInfo;
29 import android.app.job.JobParameters;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.pm.UserInfo;
35 import android.os.Handler;
36 import android.os.PowerManager;
37 import android.os.RemoteException;
38 import android.os.UserHandle;
39 import android.provider.DeviceConfig;
40 import android.util.ArraySet;
41 import android.util.IndentingPrintWriter;
42 import android.util.Pair;
43 import android.util.Pools;
44 import android.util.Slog;
45 import android.util.SparseArrayMap;
46 import android.util.SparseIntArray;
47 import android.util.SparseLongArray;
48 import android.util.TimeUtils;
49 import android.util.proto.ProtoOutputStream;
50 
51 import com.android.internal.R;
52 import com.android.internal.annotations.GuardedBy;
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.internal.app.procstats.ProcessStats;
55 import com.android.internal.util.StatLogger;
56 import com.android.server.JobSchedulerBackgroundThread;
57 import com.android.server.LocalServices;
58 import com.android.server.job.controllers.JobStatus;
59 import com.android.server.job.controllers.StateController;
60 import com.android.server.pm.UserManagerInternal;
61 
62 import java.lang.annotation.Retention;
63 import java.lang.annotation.RetentionPolicy;
64 import java.util.Iterator;
65 import java.util.List;
66 import java.util.function.Consumer;
67 
68 /**
69  * This class decides, given the various configuration and the system status, which jobs can start
70  * and which {@link JobServiceContext} to run each job on.
71  */
72 class JobConcurrencyManager {
73     private static final String TAG = JobSchedulerService.TAG + ".Concurrency";
74     private static final boolean DEBUG = JobSchedulerService.DEBUG;
75 
76     static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
77     private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS =
78             CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
79     private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
80     private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ =
81             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej";
82     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3;
83     private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
84             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
85     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2;
86 
87     /**
88      * Set of possible execution types that a job can have. The actual type(s) of a job are based
89      * on the {@link JobStatus#lastEvaluatedPriority}, which is typically evaluated right before
90      * execution (when we're trying to determine which jobs to run next) and won't change after the
91      * job has started executing.
92      *
93      * Try to give higher priority types lower values.
94      *
95      * @see #getJobWorkTypes(JobStatus)
96      */
97 
98     /** Job shouldn't run or qualify as any other work type. */
99     static final int WORK_TYPE_NONE = 0;
100     /** The job is for an app in the TOP state for a currently active user. */
101     static final int WORK_TYPE_TOP = 1 << 0;
102     /**
103      * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
104      * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user.
105      */
106     static final int WORK_TYPE_FGS = 1 << 1;
107     /** The job is allowed to run as an expedited job for a currently active user. */
108     static final int WORK_TYPE_EJ = 1 << 2;
109     /**
110      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
111      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so
112      * can run as a background job.
113      */
114     static final int WORK_TYPE_BG = 1 << 3;
115     /**
116      * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
117      * state, or is allowed to run as an expedited job, but is for a completely background user.
118      */
119     static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4;
120     /**
121      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
122      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user,
123      * so can run as a background user job.
124      */
125     static final int WORK_TYPE_BGUSER = 1 << 5;
126     @VisibleForTesting
127     static final int NUM_WORK_TYPES = 6;
128     private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1;
129 
130     @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
131             WORK_TYPE_NONE,
132             WORK_TYPE_TOP,
133             WORK_TYPE_FGS,
134             WORK_TYPE_EJ,
135             WORK_TYPE_BG,
136             WORK_TYPE_BGUSER_IMPORTANT,
137             WORK_TYPE_BGUSER
138     })
139     @Retention(RetentionPolicy.SOURCE)
140     public @interface WorkType {
141     }
142 
143     @VisibleForTesting
workTypeToString(@orkType int workType)144     static String workTypeToString(@WorkType int workType) {
145         switch (workType) {
146             case WORK_TYPE_NONE:
147                 return "NONE";
148             case WORK_TYPE_TOP:
149                 return "TOP";
150             case WORK_TYPE_FGS:
151                 return "FGS";
152             case WORK_TYPE_EJ:
153                 return "EJ";
154             case WORK_TYPE_BG:
155                 return "BG";
156             case WORK_TYPE_BGUSER:
157                 return "BGUSER";
158             case WORK_TYPE_BGUSER_IMPORTANT:
159                 return "BGUSER_IMPORTANT";
160             default:
161                 return "WORK(" + workType + ")";
162         }
163     }
164 
165     private final Object mLock;
166     private final JobSchedulerService mService;
167     private final Context mContext;
168     private final Handler mHandler;
169 
170     private PowerManager mPowerManager;
171 
172     private boolean mCurrentInteractiveState;
173     private boolean mEffectiveInteractiveState;
174 
175     private long mLastScreenOnRealtime;
176     private long mLastScreenOffRealtime;
177 
178     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
179             new WorkConfigLimitsPerMemoryTrimLevel(
180                     new WorkTypeConfig("screen_on_normal", 11,
181                             // defaultMin
182                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
183                                     Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2),
184                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
185                             // defaultMax
186                             List.of(Pair.create(WORK_TYPE_BG, 6),
187                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2),
188                                     Pair.create(WORK_TYPE_BGUSER, 3))
189                     ),
190                     new WorkTypeConfig("screen_on_moderate", 9,
191                             // defaultMin
192                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
193                                     Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1),
194                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
195                             // defaultMax
196                             List.of(Pair.create(WORK_TYPE_BG, 4),
197                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
198                                     Pair.create(WORK_TYPE_BGUSER, 1))
199                     ),
200                     new WorkTypeConfig("screen_on_low", 6,
201                             // defaultMin
202                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
203                                     Pair.create(WORK_TYPE_EJ, 1)),
204                             // defaultMax
205                             List.of(Pair.create(WORK_TYPE_BG, 2),
206                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
207                                     Pair.create(WORK_TYPE_BGUSER, 1))
208                     ),
209                     new WorkTypeConfig("screen_on_critical", 6,
210                             // defaultMin
211                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
212                                     Pair.create(WORK_TYPE_EJ, 1)),
213                             // defaultMax
214                             List.of(Pair.create(WORK_TYPE_BG, 1),
215                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
216                                     Pair.create(WORK_TYPE_BGUSER, 1))
217                     )
218             );
219     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
220             new WorkConfigLimitsPerMemoryTrimLevel(
221                     new WorkTypeConfig("screen_off_normal", 16,
222                             // defaultMin
223                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2),
224                                     Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2),
225                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
226                             // defaultMax
227                             List.of(Pair.create(WORK_TYPE_BG, 10),
228                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2),
229                                     Pair.create(WORK_TYPE_BGUSER, 3))
230                     ),
231                     new WorkTypeConfig("screen_off_moderate", 14,
232                             // defaultMin
233                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2),
234                                     Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2),
235                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
236                             // defaultMax
237                             List.of(Pair.create(WORK_TYPE_BG, 7),
238                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
239                                     Pair.create(WORK_TYPE_BGUSER, 1))
240                     ),
241                     new WorkTypeConfig("screen_off_low", 9,
242                             // defaultMin
243                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
244                                     Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)),
245                             // defaultMax
246                             List.of(Pair.create(WORK_TYPE_BG, 3),
247                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
248                                     Pair.create(WORK_TYPE_BGUSER, 1))
249                     ),
250                     new WorkTypeConfig("screen_off_critical", 6,
251                             // defaultMin
252                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
253                                     Pair.create(WORK_TYPE_EJ, 1)),
254                             // defaultMax
255                             List.of(Pair.create(WORK_TYPE_BG, 1),
256                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
257                                     Pair.create(WORK_TYPE_BGUSER, 1))
258                     )
259             );
260 
261     /**
262      * This array essentially stores the state of mActiveServices array.
263      * The ith index stores the job present on the ith JobServiceContext.
264      * We manipulate this array until we arrive at what jobs should be running on
265      * what JobServiceContext.
266      */
267     JobStatus[] mRecycledAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
268 
269     boolean[] mRecycledSlotChanged = new boolean[MAX_JOB_CONTEXTS_COUNT];
270 
271     int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
272 
273     int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
274 
275     String[] mRecycledPreemptReasonForContext = new String[MAX_JOB_CONTEXTS_COUNT];
276 
277     int[] mRecycledPreemptReasonCodeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
278 
279     String[] mRecycledShouldStopJobReason = new String[MAX_JOB_CONTEXTS_COUNT];
280 
281     private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
282 
283     private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
284 
285     private final Pools.Pool<PackageStats> mPkgStatsPool =
286             new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT);
287 
288     private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>();
289 
290     private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;
291 
292     /** Wait for this long after screen off before adjusting the job concurrency. */
293     private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
294 
295     /**
296      * The maximum number of expedited jobs a single userId-package can have running simultaneously.
297      * TOP apps are not limited.
298      */
299     private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ;
300 
301     /**
302      * The maximum number of regular jobs a single userId-package can have running simultaneously.
303      * TOP apps are not limited.
304      */
305     private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
306 
307     /** Current memory trim level. */
308     private int mLastMemoryTrimLevel;
309 
310     /** Used to throttle heavy API calls. */
311     private long mNextSystemStateRefreshTime;
312     private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
313 
314     private final Consumer<PackageStats> mPackageStatsStagingCountClearer =
315             PackageStats::resetStagedCount;
316 
317     private final StatLogger mStatLogger = new StatLogger(new String[]{
318             "assignJobsToContexts",
319             "refreshSystemState",
320     });
321     @VisibleForTesting
322     GracePeriodObserver mGracePeriodObserver;
323     @VisibleForTesting
324     boolean mShouldRestrictBgUser;
325 
326     interface Stats {
327         int ASSIGN_JOBS_TO_CONTEXTS = 0;
328         int REFRESH_SYSTEM_STATE = 1;
329 
330         int COUNT = REFRESH_SYSTEM_STATE + 1;
331     }
332 
JobConcurrencyManager(JobSchedulerService service)333     JobConcurrencyManager(JobSchedulerService service) {
334         mService = service;
335         mLock = mService.mLock;
336         mContext = service.getTestableContext();
337 
338         mHandler = JobSchedulerBackgroundThread.getHandler();
339 
340         mGracePeriodObserver = new GracePeriodObserver(mContext);
341         mShouldRestrictBgUser = mContext.getResources().getBoolean(
342                 R.bool.config_jobSchedulerRestrictBackgroundUser);
343     }
344 
onSystemReady()345     public void onSystemReady() {
346         mPowerManager = mContext.getSystemService(PowerManager.class);
347 
348         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
349         filter.addAction(Intent.ACTION_SCREEN_OFF);
350         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
351         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
352         mContext.registerReceiver(mReceiver, filter);
353         try {
354             ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG);
355         } catch (RemoteException e) {
356         }
357 
358         onInteractiveStateChanged(mPowerManager.isInteractive());
359     }
360 
361     @GuardedBy("mLock")
onAppRemovedLocked(String pkgName, int uid)362     void onAppRemovedLocked(String pkgName, int uid) {
363         final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName);
364         if (packageStats != null) {
365             if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) {
366                 // Don't delete the object just yet. We'll remove it in onJobCompleted() when the
367                 // jobs officially stop running.
368                 Slog.w(TAG,
369                         pkgName + "(" + uid + ") marked as removed before jobs stopped running");
370             } else {
371                 mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName);
372             }
373         }
374     }
375 
onUserRemoved(int userId)376     void onUserRemoved(int userId) {
377         mGracePeriodObserver.onUserRemoved(userId);
378     }
379 
380     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
381         @Override
382         public void onReceive(Context context, Intent intent) {
383             switch (intent.getAction()) {
384                 case Intent.ACTION_SCREEN_ON:
385                     onInteractiveStateChanged(true);
386                     break;
387                 case Intent.ACTION_SCREEN_OFF:
388                     onInteractiveStateChanged(false);
389                     break;
390                 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
391                     if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) {
392                         synchronized (mLock) {
393                             stopLongRunningJobsLocked("deep doze");
394                         }
395                     }
396                     break;
397                 case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
398                     if (mPowerManager != null && mPowerManager.isPowerSaveMode()) {
399                         synchronized (mLock) {
400                             stopLongRunningJobsLocked("battery saver");
401                         }
402                     }
403                     break;
404             }
405         }
406     };
407 
408     /**
409      * Called when the screen turns on / off.
410      */
onInteractiveStateChanged(boolean interactive)411     private void onInteractiveStateChanged(boolean interactive) {
412         synchronized (mLock) {
413             if (mCurrentInteractiveState == interactive) {
414                 return;
415             }
416             mCurrentInteractiveState = interactive;
417             if (DEBUG) {
418                 Slog.d(TAG, "Interactive: " + interactive);
419             }
420 
421             final long nowRealtime = sElapsedRealtimeClock.millis();
422             if (interactive) {
423                 mLastScreenOnRealtime = nowRealtime;
424                 mEffectiveInteractiveState = true;
425 
426                 mHandler.removeCallbacks(mRampUpForScreenOff);
427             } else {
428                 mLastScreenOffRealtime = nowRealtime;
429 
430                 // Set mEffectiveInteractiveState to false after the delay, when we may increase
431                 // the concurrency.
432                 // We don't need a wakeup alarm here. When there's a pending job, there should
433                 // also be jobs running too, meaning the device should be awake.
434 
435                 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
436                 // we need the exact same instance for removeCallbacks().
437                 mHandler.postDelayed(mRampUpForScreenOff, mScreenOffAdjustmentDelayMs);
438             }
439         }
440     }
441 
442     private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
443 
444     /**
445      * Called in {@link #mScreenOffAdjustmentDelayMs} after
446      * the screen turns off, in order to increase concurrency.
447      */
rampUpForScreenOff()448     private void rampUpForScreenOff() {
449         synchronized (mLock) {
450             // Make sure the screen has really been off for the configured duration.
451             // (There could be a race.)
452             if (!mEffectiveInteractiveState) {
453                 return;
454             }
455             if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
456                 return;
457             }
458             final long now = sElapsedRealtimeClock.millis();
459             if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) {
460                 return;
461             }
462 
463             mEffectiveInteractiveState = false;
464 
465             if (DEBUG) {
466                 Slog.d(TAG, "Ramping up concurrency");
467             }
468 
469             mService.maybeRunPendingJobsLocked();
470         }
471     }
472 
473     @GuardedBy("mLock")
isJobRunningLocked(JobStatus job)474     boolean isJobRunningLocked(JobStatus job) {
475         return mRunningJobs.contains(job);
476     }
477 
478     /** Return {@code true} if the state was updated. */
479     @GuardedBy("mLock")
refreshSystemStateLocked()480     private boolean refreshSystemStateLocked() {
481         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
482 
483         // Only refresh the information every so often.
484         if (nowUptime < mNextSystemStateRefreshTime) {
485             return false;
486         }
487 
488         final long start = mStatLogger.getTime();
489         mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL;
490 
491         mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
492         try {
493             mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel();
494         } catch (RemoteException e) {
495         }
496 
497         mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
498         return true;
499     }
500 
501     @GuardedBy("mLock")
updateCounterConfigLocked()502     private void updateCounterConfigLocked() {
503         if (!refreshSystemStateLocked()) {
504             return;
505         }
506 
507         final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
508                 ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
509 
510         switch (mLastMemoryTrimLevel) {
511             case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
512                 mWorkTypeConfig = workConfigs.moderate;
513                 break;
514             case ProcessStats.ADJ_MEM_FACTOR_LOW:
515                 mWorkTypeConfig = workConfigs.low;
516                 break;
517             case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
518                 mWorkTypeConfig = workConfigs.critical;
519                 break;
520             default:
521                 mWorkTypeConfig = workConfigs.normal;
522                 break;
523         }
524 
525         mWorkCountTracker.setConfig(mWorkTypeConfig);
526     }
527 
528     /**
529      * Takes jobs from pending queue and runs them on available contexts.
530      * If no contexts are available, preempts lower priority jobs to
531      * run higher priority ones.
532      * Lock on mJobs before calling this function.
533      */
534     @GuardedBy("mLock")
assignJobsToContextsLocked()535     void assignJobsToContextsLocked() {
536         final long start = mStatLogger.getTime();
537 
538         assignJobsToContextsInternalLocked();
539 
540         mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
541     }
542 
543     @GuardedBy("mLock")
assignJobsToContextsInternalLocked()544     private void assignJobsToContextsInternalLocked() {
545         if (DEBUG) {
546             Slog.d(TAG, printPendingQueueLocked());
547         }
548 
549         final List<JobStatus> pendingJobs = mService.mPendingJobs;
550         final List<JobServiceContext> activeServices = mService.mActiveServices;
551 
552         // To avoid GC churn, we recycle the arrays.
553         JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
554         boolean[] slotChanged = mRecycledSlotChanged;
555         int[] preferredUidForContext = mRecycledPreferredUidForContext;
556         int[] workTypeForContext = mRecycledWorkTypeForContext;
557         String[] preemptReasonForContext = mRecycledPreemptReasonForContext;
558         int[] preemptReasonCodeForContext = mRecycledPreemptReasonCodeForContext;
559         String[] shouldStopJobReason = mRecycledShouldStopJobReason;
560 
561         updateCounterConfigLocked();
562         // Reset everything since we'll re-evaluate the current state.
563         mWorkCountTracker.resetCounts();
564 
565         // Update the priorities of jobs that aren't running, and also count the pending work types.
566         // Do this before the following loop to hopefully reduce the cost of
567         // shouldStopRunningJobLocked().
568         updateNonRunningPrioritiesLocked(pendingJobs, true);
569 
570         for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
571             final JobServiceContext js = activeServices.get(i);
572             final JobStatus status = js.getRunningJobLocked();
573 
574             if ((contextIdToJobMap[i] = status) != null) {
575                 mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType());
576                 workTypeForContext[i] = js.getRunningJobWorkType();
577             }
578 
579             slotChanged[i] = false;
580             preferredUidForContext[i] = js.getPreferredUid();
581             preemptReasonForContext[i] = null;
582             preemptReasonCodeForContext[i] = JobParameters.STOP_REASON_UNDEFINED;
583             shouldStopJobReason[i] = shouldStopRunningJobLocked(js);
584         }
585         if (DEBUG) {
586             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
587         }
588 
589         mWorkCountTracker.onCountDone();
590 
591         for (int i = 0; i < pendingJobs.size(); i++) {
592             final JobStatus nextPending = pendingJobs.get(i);
593 
594             if (mRunningJobs.contains(nextPending)) {
595                 continue;
596             }
597 
598             // Find an available slot for nextPending. The context should be available OR
599             // it should have lowest priority among all running jobs
600             // (sharing the same Uid as nextPending)
601             int minPriorityForPreemption = Integer.MAX_VALUE;
602             int selectedContextId = -1;
603             int allWorkTypes = getJobWorkTypes(nextPending);
604             int workType = mWorkCountTracker.canJobStart(allWorkTypes);
605             boolean startingJob = false;
606             int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
607             String preemptReason = null;
608             final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
609             // TODO(141645789): rewrite this to look at empty contexts first so we don't
610             // unnecessarily preempt
611             for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
612                 JobStatus job = contextIdToJobMap[j];
613                 int preferredUid = preferredUidForContext[j];
614                 if (job == null) {
615                     final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
616                             || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
617 
618                     if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
619                         // This slot is free, and we haven't yet hit the limit on
620                         // concurrent jobs...  we can just throw the job in to here.
621                         selectedContextId = j;
622                         startingJob = true;
623                         break;
624                     }
625                     // No job on this context, but nextPending can't run here because
626                     // the context has a preferred Uid or we have reached the limit on
627                     // concurrent jobs.
628                     continue;
629                 }
630                 if (job.getUid() != nextPending.getUid()) {
631                     // Maybe stop the job if it has had its day in the sun. Don't let a different
632                     // app preempt jobs started for TOP apps though.
633                     final String reason = shouldStopJobReason[j];
634                     if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP
635                             && reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
636                             activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
637                         // Right now, the way the code is set up, we don't need to explicitly
638                         // assign the new job to this context since we'll reassign when the
639                         // preempted job finally stops.
640                         preemptReason = reason;
641                         preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
642                     }
643                     continue;
644                 }
645 
646                 final int jobPriority = mService.evaluateJobPriorityLocked(job);
647                 if (jobPriority >= nextPending.lastEvaluatedPriority) {
648                     continue;
649                 }
650 
651                 if (minPriorityForPreemption > jobPriority) {
652                     // Step down the preemption threshold - wind up replacing
653                     // the lowest-priority running job
654                     minPriorityForPreemption = jobPriority;
655                     selectedContextId = j;
656                     preemptReason = "higher priority job found";
657                     preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
658                     // In this case, we're just going to preempt a low priority job, we're not
659                     // actually starting a job, so don't set startingJob.
660                 }
661             }
662             final PackageStats packageStats = getPkgStatsLocked(
663                     nextPending.getSourceUserId(), nextPending.getSourcePackageName());
664             if (selectedContextId != -1) {
665                 contextIdToJobMap[selectedContextId] = nextPending;
666                 slotChanged[selectedContextId] = true;
667                 preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
668                 preemptReasonForContext[selectedContextId] = preemptReason;
669                 packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
670             }
671             if (startingJob) {
672                 // Increase the counters when we're going to start a job.
673                 workTypeForContext[selectedContextId] = workType;
674                 mWorkCountTracker.stageJob(workType, allWorkTypes);
675                 mActivePkgStats.add(
676                         nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
677                         packageStats);
678             }
679         }
680         if (DEBUG) {
681             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
682 
683             Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
684         }
685 
686         for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
687             boolean preservePreferredUid = false;
688             if (slotChanged[i]) {
689                 JobStatus js = activeServices.get(i).getRunningJobLocked();
690                 if (js != null) {
691                     if (DEBUG) {
692                         Slog.d(TAG, "preempting job: "
693                                 + activeServices.get(i).getRunningJobLocked());
694                     }
695                     // preferredUid will be set to uid of currently running job.
696                     activeServices.get(i).cancelExecutingJobLocked(
697                             preemptReasonCodeForContext[i],
698                             JobParameters.INTERNAL_STOP_REASON_PREEMPT, preemptReasonForContext[i]);
699                     // Only preserve the UID if we're preempting for the same UID. If we're stopping
700                     // the job because something is pending (eg. EJs), then we shouldn't preserve
701                     // the UID.
702                     preservePreferredUid =
703                             preemptReasonCodeForContext[i] == JobParameters.STOP_REASON_PREEMPT;
704                 } else {
705                     final JobStatus pendingJob = contextIdToJobMap[i];
706                     if (DEBUG) {
707                         Slog.d(TAG, "About to run job on context "
708                                 + i + ", job: " + pendingJob);
709                     }
710                     startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]);
711                 }
712             }
713             if (!preservePreferredUid) {
714                 activeServices.get(i).clearPreferredUid();
715             }
716         }
717         mWorkCountTracker.resetStagingCount();
718         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
719         noteConcurrency();
720     }
721 
722     @GuardedBy("mLock")
stopLongRunningJobsLocked(@onNull String debugReason)723     private void stopLongRunningJobsLocked(@NonNull String debugReason) {
724         for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; ++i) {
725             final JobServiceContext jsc = mService.mActiveServices.get(i);
726             final JobStatus jobStatus = jsc.getRunningJobLocked();
727 
728             if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) {
729                 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
730                         JobParameters.INTERNAL_STOP_REASON_TIMEOUT, debugReason);
731             }
732         }
733     }
734 
noteConcurrency()735     private void noteConcurrency() {
736         mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
737                 // TODO: log per type instead of only TOP
738                 mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
739     }
740 
741     @GuardedBy("mLock")
updateNonRunningPrioritiesLocked(@onNull final List<JobStatus> pendingJobs, boolean updateCounter)742     private void updateNonRunningPrioritiesLocked(@NonNull final List<JobStatus> pendingJobs,
743             boolean updateCounter) {
744         for (int i = 0; i < pendingJobs.size(); i++) {
745             final JobStatus pending = pendingJobs.get(i);
746 
747             // If job is already running, go to next job.
748             if (mRunningJobs.contains(pending)) {
749                 continue;
750             }
751 
752             pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending);
753 
754             if (updateCounter) {
755                 mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
756             }
757         }
758     }
759 
760     @GuardedBy("mLock")
761     @NonNull
getPkgStatsLocked(int userId, @NonNull String packageName)762     private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) {
763         PackageStats packageStats = mActivePkgStats.get(userId, packageName);
764         if (packageStats == null) {
765             packageStats = mPkgStatsPool.acquire();
766             if (packageStats == null) {
767                 packageStats = new PackageStats();
768             }
769             packageStats.setPackage(userId, packageName);
770         }
771         return packageStats;
772     }
773 
774     @GuardedBy("mLock")
isPkgConcurrencyLimitedLocked(@onNull JobStatus jobStatus)775     private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
776         if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
777             // Don't restrict top apps' concurrency. The work type limits will make sure
778             // background jobs have slots to run if the system has resources.
779             return false;
780         }
781         // Use < instead of <= as that gives us a little wiggle room in case a new job comes
782         // along very shortly.
783         if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) {
784             // Don't artificially limit a single package if we don't even have enough jobs to use
785             // the maximum number of slots. We'll preempt the job later if we need the slot.
786             return false;
787         }
788         final PackageStats packageStats =
789                 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
790         if (packageStats == null) {
791             // No currently running jobs.
792             return false;
793         }
794         if (jobStatus.shouldTreatAsExpeditedJob()) {
795             return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj;
796         } else {
797             return packageStats.numRunningRegular + packageStats.numStagedRegular
798                     < mPkgConcurrencyLimitRegular;
799         }
800     }
801 
802     @GuardedBy("mLock")
startJobLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)803     private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
804             @WorkType final int workType) {
805         final List<StateController> controllers = mService.mControllers;
806         final int numControllers = controllers.size();
807         for (int ic = 0; ic < numControllers; ic++) {
808             controllers.get(ic).prepareForExecutionLocked(jobStatus);
809         }
810         final PackageStats packageStats =
811                 getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
812         packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob());
813         if (!worker.executeRunnableJob(jobStatus, workType)) {
814             Slog.e(TAG, "Error executing " + jobStatus);
815             mWorkCountTracker.onStagedJobFailed(workType);
816             for (int ic = 0; ic < numControllers; ic++) {
817                 controllers.get(ic).unprepareFromExecutionLocked(jobStatus);
818             }
819         } else {
820             mRunningJobs.add(jobStatus);
821             mWorkCountTracker.onJobStarted(workType);
822             packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
823             mActivePkgStats.add(
824                     jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats);
825         }
826         final List<JobStatus> pendingJobs = mService.mPendingJobs;
827         if (pendingJobs.remove(jobStatus)) {
828             mService.mJobPackageTracker.noteNonpending(jobStatus);
829         }
830     }
831 
832     @GuardedBy("mLock")
onJobCompletedLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)833     void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
834             @WorkType final int workType) {
835         mWorkCountTracker.onJobFinished(workType);
836         mRunningJobs.remove(jobStatus);
837         final PackageStats packageStats =
838                 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
839         if (packageStats == null) {
840             Slog.wtf(TAG, "Running job didn't have an active PackageStats object");
841         } else {
842             packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob);
843             if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) {
844                 mActivePkgStats.delete(packageStats.userId, packageStats.packageName);
845                 mPkgStatsPool.release(packageStats);
846             }
847         }
848 
849         final List<JobStatus> pendingJobs = mService.mPendingJobs;
850         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
851             updateCounterConfigLocked();
852             // Preemption case needs special care.
853             updateNonRunningPrioritiesLocked(pendingJobs, false);
854 
855             JobStatus highestPriorityJob = null;
856             int highPriWorkType = workType;
857             int highPriAllWorkTypes = workType;
858             JobStatus backupJob = null;
859             int backupWorkType = WORK_TYPE_NONE;
860             int backupAllWorkTypes = WORK_TYPE_NONE;
861             for (int i = 0; i < pendingJobs.size(); i++) {
862                 final JobStatus nextPending = pendingJobs.get(i);
863 
864                 if (mRunningJobs.contains(nextPending)) {
865                     continue;
866                 }
867 
868                 if (worker.getPreferredUid() != nextPending.getUid()) {
869                     if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
870                         int allWorkTypes = getJobWorkTypes(nextPending);
871                         int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
872                         if (workAsType != WORK_TYPE_NONE) {
873                             backupJob = nextPending;
874                             backupWorkType = workAsType;
875                             backupAllWorkTypes = allWorkTypes;
876                         }
877                     }
878                     continue;
879                 }
880 
881                 // Only bypass the concurrent limit if we had preempted the job due to a higher
882                 // priority job.
883                 if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority
884                         && isPkgConcurrencyLimitedLocked(nextPending)) {
885                     continue;
886                 }
887 
888                 if (highestPriorityJob == null
889                         || highestPriorityJob.lastEvaluatedPriority
890                         < nextPending.lastEvaluatedPriority) {
891                     highestPriorityJob = nextPending;
892                 } else {
893                     continue;
894                 }
895 
896                 // In this path, we pre-empted an existing job. We don't fully care about the
897                 // reserved slots. We should just run the highest priority job we can find,
898                 // though it would be ideal to use an available WorkType slot instead of
899                 // overloading slots.
900                 highPriAllWorkTypes = getJobWorkTypes(nextPending);
901                 final int workAsType = mWorkCountTracker.canJobStart(highPriAllWorkTypes);
902                 if (workAsType == WORK_TYPE_NONE) {
903                     // Just use the preempted job's work type since this new one is technically
904                     // replacing it anyway.
905                     highPriWorkType = workType;
906                 } else {
907                     highPriWorkType = workAsType;
908                 }
909             }
910             if (highestPriorityJob != null) {
911                 if (DEBUG) {
912                     Slog.d(TAG, "Running job " + jobStatus + " as preemption");
913                 }
914                 mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
915                 startJobLocked(worker, highestPriorityJob, highPriWorkType);
916             } else {
917                 if (DEBUG) {
918                     Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid());
919                 }
920                 worker.clearPreferredUid();
921                 if (backupJob != null) {
922                     if (DEBUG) {
923                         Slog.d(TAG, "Running job " + jobStatus + " instead");
924                     }
925                     mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
926                     startJobLocked(worker, backupJob, backupWorkType);
927                 }
928             }
929         } else if (pendingJobs.size() > 0) {
930             updateCounterConfigLocked();
931             updateNonRunningPrioritiesLocked(pendingJobs, false);
932 
933             // This slot is now free and we have pending jobs. Start the highest priority job we
934             // find.
935             JobStatus highestPriorityJob = null;
936             int highPriWorkType = workType;
937             int highPriAllWorkTypes = workType;
938             for (int i = 0; i < pendingJobs.size(); i++) {
939                 final JobStatus nextPending = pendingJobs.get(i);
940 
941                 if (mRunningJobs.contains(nextPending)) {
942                     continue;
943                 }
944 
945                 if (isPkgConcurrencyLimitedLocked(nextPending)) {
946                     continue;
947                 }
948 
949                 final int allWorkTypes = getJobWorkTypes(nextPending);
950                 final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
951                 if (workAsType == WORK_TYPE_NONE) {
952                     continue;
953                 }
954                 if (highestPriorityJob == null
955                         || highestPriorityJob.lastEvaluatedPriority
956                         < nextPending.lastEvaluatedPriority) {
957                     highestPriorityJob = nextPending;
958                     highPriWorkType = workAsType;
959                     highPriAllWorkTypes = allWorkTypes;
960                 }
961             }
962 
963             if (highestPriorityJob != null) {
964                 // This slot is free, and we haven't yet hit the limit on
965                 // concurrent jobs...  we can just throw the job in to here.
966                 if (DEBUG) {
967                     Slog.d(TAG, "About to run job: " + jobStatus);
968                 }
969                 mWorkCountTracker.stageJob(highPriWorkType, highPriAllWorkTypes);
970                 startJobLocked(worker, highestPriorityJob, highPriWorkType);
971             }
972         }
973 
974         noteConcurrency();
975     }
976 
977     /**
978      * Returns {@code null} if the job can continue running and a non-null String if the job should
979      * be stopped. The non-null String details the reason for stopping the job. A job will generally
980      * be stopped if there similar job types waiting to be run and stopping this job would allow
981      * another job to run, or if system state suggests the job should stop.
982      */
983     @Nullable
984     @GuardedBy("mLock")
shouldStopRunningJobLocked(@onNull JobServiceContext context)985     String shouldStopRunningJobLocked(@NonNull JobServiceContext context) {
986         final JobStatus js = context.getRunningJobLocked();
987         if (js == null) {
988             // This can happen when we try to assign newly found pending jobs to contexts.
989             return null;
990         }
991 
992         if (context.isWithinExecutionGuaranteeTime()) {
993             return null;
994         }
995 
996         // We're over the minimum guaranteed runtime. Stop the job if we're over config limits,
997         // there are pending jobs that could replace this one, or the device state is not conducive
998         // to long runs.
999 
1000         if (mPowerManager.isPowerSaveMode()) {
1001             return "battery saver";
1002         }
1003         if (mPowerManager.isDeviceIdleMode()) {
1004             return "deep doze";
1005         }
1006 
1007         // Update config in case memory usage has changed significantly.
1008         updateCounterConfigLocked();
1009 
1010         @WorkType final int workType = context.getRunningJobWorkType();
1011 
1012         if (mRunningJobs.size() > mWorkTypeConfig.getMaxTotal()
1013                 || mWorkCountTracker.isOverTypeLimit(workType)) {
1014             return "too many jobs running";
1015         }
1016 
1017         final List<JobStatus> pendingJobs = mService.mPendingJobs;
1018         final int numPending = pendingJobs.size();
1019         if (numPending == 0) {
1020             // All quiet. We can let this job run to completion.
1021             return null;
1022         }
1023 
1024         // Only expedited jobs can replace expedited jobs.
1025         if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) {
1026             // Keep fg/bg user distinction.
1027             if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) {
1028                 // Let any important bg user job replace a bg user expedited job.
1029                 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER_IMPORTANT) > 0) {
1030                     return "blocking " + workTypeToString(WORK_TYPE_BGUSER_IMPORTANT) + " queue";
1031                 }
1032                 // Let a fg user EJ preempt a bg user EJ (if able), but not the other way around.
1033                 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0
1034                         && mWorkCountTracker.canJobStart(WORK_TYPE_EJ, workType)
1035                         != WORK_TYPE_NONE) {
1036                     return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
1037                 }
1038             } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
1039                 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
1040             }
1041             // No other pending EJs. Return null so we don't let regular jobs preempt an EJ.
1042             return null;
1043         }
1044 
1045         // Easy check. If there are pending jobs of the same work type, then we know that
1046         // something will replace this.
1047         if (mWorkCountTracker.getPendingJobCount(workType) > 0) {
1048             return "blocking " + workTypeToString(workType) + " queue";
1049         }
1050 
1051         // Harder check. We need to see if a different work type can replace this job.
1052         int remainingWorkTypes = ALL_WORK_TYPES;
1053         for (int i = 0; i < numPending; ++i) {
1054             final JobStatus pending = pendingJobs.get(i);
1055             final int workTypes = getJobWorkTypes(pending);
1056             if ((workTypes & remainingWorkTypes) > 0
1057                     && mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) {
1058                 return "blocking other pending jobs";
1059             }
1060 
1061             remainingWorkTypes = remainingWorkTypes & ~workTypes;
1062             if (remainingWorkTypes == 0) {
1063                 break;
1064             }
1065         }
1066 
1067         return null;
1068     }
1069 
1070     @GuardedBy("mLock")
printPendingQueueLocked()1071     private String printPendingQueueLocked() {
1072         StringBuilder s = new StringBuilder("Pending queue: ");
1073         Iterator<JobStatus> it = mService.mPendingJobs.iterator();
1074         while (it.hasNext()) {
1075             JobStatus js = it.next();
1076             s.append("(")
1077                     .append(js.getJob().getId())
1078                     .append(", ")
1079                     .append(js.getUid())
1080                     .append(") ");
1081         }
1082         return s.toString();
1083     }
1084 
printContextIdToJobMap(JobStatus[] map, String initial)1085     private static String printContextIdToJobMap(JobStatus[] map, String initial) {
1086         StringBuilder s = new StringBuilder(initial + ": ");
1087         for (int i=0; i<map.length; i++) {
1088             s.append("(")
1089                     .append(map[i] == null? -1: map[i].getJobId())
1090                     .append(map[i] == null? -1: map[i].getUid())
1091                     .append(")" );
1092         }
1093         return s.toString();
1094     }
1095 
1096     @GuardedBy("mLock")
updateConfigLocked()1097     void updateConfigLocked() {
1098         DeviceConfig.Properties properties =
1099                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
1100 
1101         mScreenOffAdjustmentDelayMs = properties.getLong(
1102                 KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS);
1103 
1104         CONFIG_LIMITS_SCREEN_ON.normal.update(properties);
1105         CONFIG_LIMITS_SCREEN_ON.moderate.update(properties);
1106         CONFIG_LIMITS_SCREEN_ON.low.update(properties);
1107         CONFIG_LIMITS_SCREEN_ON.critical.update(properties);
1108 
1109         CONFIG_LIMITS_SCREEN_OFF.normal.update(properties);
1110         CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
1111         CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
1112         CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
1113 
1114         // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT].
1115         mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
1116                 properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ)));
1117         mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
1118                 properties.getInt(
1119                         KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
1120     }
1121 
1122     @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)1123     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
1124         pw.println("Concurrency:");
1125 
1126         pw.increaseIndent();
1127         try {
1128             pw.println("Configuration:");
1129             pw.increaseIndent();
1130             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
1131             pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
1132             pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
1133             pw.println();
1134             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
1135             pw.println();
1136             CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
1137             pw.println();
1138             CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
1139             pw.println();
1140             CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
1141             pw.println();
1142             CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
1143             pw.println();
1144             CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
1145             pw.println();
1146             CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
1147             pw.println();
1148             CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
1149             pw.println();
1150             pw.decreaseIndent();
1151 
1152             pw.print("Screen state: current ");
1153             pw.print(mCurrentInteractiveState ? "ON" : "OFF");
1154             pw.print("  effective ");
1155             pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
1156             pw.println();
1157 
1158             pw.print("Last screen ON: ");
1159             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
1160             pw.println();
1161 
1162             pw.print("Last screen OFF: ");
1163             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
1164             pw.println();
1165 
1166             pw.println();
1167 
1168             pw.print("Current work counts: ");
1169             pw.println(mWorkCountTracker);
1170 
1171             pw.println();
1172 
1173             pw.print("mLastMemoryTrimLevel: ");
1174             pw.println(mLastMemoryTrimLevel);
1175             pw.println();
1176 
1177             pw.println("Active Package stats:");
1178             pw.increaseIndent();
1179             mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw));
1180             pw.decreaseIndent();
1181             pw.println();
1182 
1183             pw.print("User Grace Period: ");
1184             pw.println(mGracePeriodObserver.mGracePeriodExpiration);
1185             pw.println();
1186 
1187             mStatLogger.dump(pw);
1188         } finally {
1189             pw.decreaseIndent();
1190         }
1191     }
1192 
dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)1193     public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
1194         final long token = proto.start(tag);
1195 
1196         proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE_STATE, mCurrentInteractiveState);
1197         proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE_STATE,
1198                 mEffectiveInteractiveState);
1199 
1200         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
1201                 nowRealtime - mLastScreenOnRealtime);
1202         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
1203                 nowRealtime - mLastScreenOffRealtime);
1204 
1205         proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
1206 
1207         mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
1208 
1209         proto.end(token);
1210     }
1211 
1212     /**
1213      * Decides whether a job is from the current foreground user or the equivalent.
1214      */
1215     @VisibleForTesting
shouldRunAsFgUserJob(JobStatus job)1216     boolean shouldRunAsFgUserJob(JobStatus job) {
1217         if (!mShouldRestrictBgUser) return true;
1218         int userId = job.getSourceUserId();
1219         UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
1220         UserInfo userInfo = um.getUserInfo(userId);
1221 
1222         // If the user has a parent user (e.g. a work profile of another user), the user should be
1223         // treated equivalent as its parent user.
1224         if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
1225                 && userInfo.profileGroupId != userId) {
1226             userId = userInfo.profileGroupId;
1227             userInfo = um.getUserInfo(userId);
1228         }
1229 
1230         int currentUser = LocalServices.getService(ActivityManagerInternal.class)
1231                 .getCurrentUserId();
1232         // A user is treated as foreground user if any of the followings is true:
1233         // 1. The user is current user
1234         // 2. The user is primary user
1235         // 3. The user's grace period has not expired
1236         return currentUser == userId || userInfo.isPrimary()
1237                 || mGracePeriodObserver.isWithinGracePeriodForUser(userId);
1238     }
1239 
getJobWorkTypes(@onNull JobStatus js)1240     int getJobWorkTypes(@NonNull JobStatus js) {
1241         int classification = 0;
1242 
1243         if (shouldRunAsFgUserJob(js)) {
1244             if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
1245                 classification |= WORK_TYPE_TOP;
1246             } else if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE) {
1247                 classification |= WORK_TYPE_FGS;
1248             } else {
1249                 classification |= WORK_TYPE_BG;
1250             }
1251 
1252             if (js.shouldTreatAsExpeditedJob()) {
1253                 classification |= WORK_TYPE_EJ;
1254             }
1255         } else {
1256             if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_FOREGROUND_SERVICE
1257                     || js.shouldTreatAsExpeditedJob()) {
1258                 classification |= WORK_TYPE_BGUSER_IMPORTANT;
1259             }
1260             // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here.
1261             classification |= WORK_TYPE_BGUSER;
1262         }
1263 
1264         return classification;
1265     }
1266 
1267     @VisibleForTesting
1268     static class WorkTypeConfig {
1269         private static final String KEY_PREFIX_MAX_TOTAL =
1270                 CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
1271         private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
1272         private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_";
1273         private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_";
1274         private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
1275         private static final String KEY_PREFIX_MAX_BGUSER =
1276                 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_";
1277         private static final String KEY_PREFIX_MAX_BGUSER_IMPORTANT =
1278                 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_important_";
1279         private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_";
1280         private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_";
1281         private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_";
1282         private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
1283         private static final String KEY_PREFIX_MIN_BGUSER =
1284                 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_";
1285         private static final String KEY_PREFIX_MIN_BGUSER_IMPORTANT =
1286                 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_important_";
1287         private final String mConfigIdentifier;
1288 
1289         private int mMaxTotal;
1290         private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1291         private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
1292         private final int mDefaultMaxTotal;
1293         private final SparseIntArray mDefaultMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1294         private final SparseIntArray mDefaultMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
1295 
WorkTypeConfig(@onNull String configIdentifier, int defaultMaxTotal, List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax)1296         WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal,
1297                 List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) {
1298             mConfigIdentifier = configIdentifier;
1299             mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, MAX_JOB_CONTEXTS_COUNT);
1300             int numReserved = 0;
1301             for (int i = defaultMin.size() - 1; i >= 0; --i) {
1302                 mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second);
1303                 numReserved += defaultMin.get(i).second;
1304             }
1305             if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
1306                 // We only create new configs on boot, so this should trigger during development
1307                 // (before the code gets checked in), so this makes sure the hard-coded defaults
1308                 // make sense. DeviceConfig values will be handled gracefully in update().
1309                 throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
1310                         + " min=" + defaultMin + " max=" + defaultMax);
1311             }
1312             for (int i = defaultMax.size() - 1; i >= 0; --i) {
1313                 mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second);
1314             }
1315             update(new DeviceConfig.Properties.Builder(
1316                     DeviceConfig.NAMESPACE_JOB_SCHEDULER).build());
1317         }
1318 
update(@onNull DeviceConfig.Properties properties)1319         void update(@NonNull DeviceConfig.Properties properties) {
1320             // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
1321             mMaxTotal = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
1322                     properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal)));
1323 
1324             mMaxAllowedSlots.clear();
1325             // Ensure they're in the range [1, total].
1326             final int maxTop = Math.max(1, Math.min(mMaxTotal,
1327                     properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier,
1328                             mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal))));
1329             mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop);
1330             final int maxFgs = Math.max(1, Math.min(mMaxTotal,
1331                     properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier,
1332                             mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal))));
1333             mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
1334             final int maxEj = Math.max(1, Math.min(mMaxTotal,
1335                     properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier,
1336                             mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal))));
1337             mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj);
1338             final int maxBg = Math.max(1, Math.min(mMaxTotal,
1339                     properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier,
1340                             mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal))));
1341             mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
1342             final int maxBgUserImp = Math.max(1, Math.min(mMaxTotal,
1343                     properties.getInt(KEY_PREFIX_MAX_BGUSER_IMPORTANT + mConfigIdentifier,
1344                             mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, mMaxTotal))));
1345             mMaxAllowedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, maxBgUserImp);
1346             final int maxBgUser = Math.max(1, Math.min(mMaxTotal,
1347                     properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
1348                             mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal))));
1349             mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser);
1350 
1351             int remaining = mMaxTotal;
1352             mMinReservedSlots.clear();
1353             // Ensure top is in the range [1, min(maxTop, total)]
1354             final int minTop = Math.max(1, Math.min(Math.min(maxTop, mMaxTotal),
1355                     properties.getInt(KEY_PREFIX_MIN_TOP + mConfigIdentifier,
1356                             mDefaultMinReservedSlots.get(WORK_TYPE_TOP))));
1357             mMinReservedSlots.put(WORK_TYPE_TOP, minTop);
1358             remaining -= minTop;
1359             // Ensure fgs is in the range [0, min(maxFgs, remaining)]
1360             final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining),
1361                     properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier,
1362                             mDefaultMinReservedSlots.get(WORK_TYPE_FGS))));
1363             mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
1364             remaining -= minFgs;
1365             // Ensure ej is in the range [0, min(maxEj, remaining)]
1366             final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining),
1367                     properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier,
1368                             mDefaultMinReservedSlots.get(WORK_TYPE_EJ))));
1369             mMinReservedSlots.put(WORK_TYPE_EJ, minEj);
1370             remaining -= minEj;
1371             // Ensure bg is in the range [0, min(maxBg, remaining)]
1372             final int minBg = Math.max(0, Math.min(Math.min(maxBg, remaining),
1373                     properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier,
1374                             mDefaultMinReservedSlots.get(WORK_TYPE_BG))));
1375             mMinReservedSlots.put(WORK_TYPE_BG, minBg);
1376             remaining -= minBg;
1377             // Ensure bg user imp is in the range [0, min(maxBgUserImp, remaining)]
1378             final int minBgUserImp = Math.max(0, Math.min(Math.min(maxBgUserImp, remaining),
1379                     properties.getInt(KEY_PREFIX_MIN_BGUSER_IMPORTANT + mConfigIdentifier,
1380                             mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, 0))));
1381             mMinReservedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, minBgUserImp);
1382             // Ensure bg user is in the range [0, min(maxBgUser, remaining)]
1383             final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining),
1384                     properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
1385                             mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER, 0))));
1386             mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser);
1387         }
1388 
getMaxTotal()1389         int getMaxTotal() {
1390             return mMaxTotal;
1391         }
1392 
getMax(@orkType int workType)1393         int getMax(@WorkType int workType) {
1394             return mMaxAllowedSlots.get(workType, mMaxTotal);
1395         }
1396 
getMinReserved(@orkType int workType)1397         int getMinReserved(@WorkType int workType) {
1398             return mMinReservedSlots.get(workType);
1399         }
1400 
dump(IndentingPrintWriter pw)1401         void dump(IndentingPrintWriter pw) {
1402             pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println();
1403             pw.print(KEY_PREFIX_MIN_TOP + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_TOP))
1404                     .println();
1405             pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP))
1406                     .println();
1407             pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS))
1408                     .println();
1409             pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS))
1410                     .println();
1411             pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ))
1412                     .println();
1413             pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ))
1414                     .println();
1415             pw.print(KEY_PREFIX_MIN_BG + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BG))
1416                     .println();
1417             pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG))
1418                     .println();
1419             pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
1420                     mMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
1421             pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
1422                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
1423             pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
1424                     mMinReservedSlots.get(WORK_TYPE_BGUSER)).println();
1425             pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
1426                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println();
1427         }
1428     }
1429 
1430     /** {@link WorkTypeConfig} for each memory trim level. */
1431     static class WorkConfigLimitsPerMemoryTrimLevel {
1432         public final WorkTypeConfig normal;
1433         public final WorkTypeConfig moderate;
1434         public final WorkTypeConfig low;
1435         public final WorkTypeConfig critical;
1436 
WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, WorkTypeConfig low, WorkTypeConfig critical)1437         WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate,
1438                 WorkTypeConfig low, WorkTypeConfig critical) {
1439             this.normal = normal;
1440             this.moderate = moderate;
1441             this.low = low;
1442             this.critical = critical;
1443         }
1444     }
1445 
1446     /**
1447      * This class keeps the track of when a user's grace period expires.
1448      */
1449     @VisibleForTesting
1450     static class GracePeriodObserver extends UserSwitchObserver {
1451         // Key is UserId and Value is the time when grace period expires
1452         @VisibleForTesting
1453         final SparseLongArray mGracePeriodExpiration = new SparseLongArray();
1454         private int mCurrentUserId;
1455         @VisibleForTesting
1456         int mGracePeriod;
1457         private final UserManagerInternal mUserManagerInternal;
1458         final Object mLock = new Object();
1459 
1460 
GracePeriodObserver(Context context)1461         GracePeriodObserver(Context context) {
1462             mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class)
1463                     .getCurrentUserId();
1464             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
1465             mGracePeriod = Math.max(0, context.getResources().getInteger(
1466                     R.integer.config_jobSchedulerUserGracePeriod));
1467         }
1468 
1469         @Override
onUserSwitchComplete(int newUserId)1470         public void onUserSwitchComplete(int newUserId) {
1471             final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod;
1472             synchronized (mLock) {
1473                 if (mCurrentUserId != UserHandle.USER_NULL
1474                         && mUserManagerInternal.exists(mCurrentUserId)) {
1475                     mGracePeriodExpiration.append(mCurrentUserId, expiration);
1476                 }
1477                 mGracePeriodExpiration.delete(newUserId);
1478                 mCurrentUserId = newUserId;
1479             }
1480         }
1481 
onUserRemoved(int userId)1482         void onUserRemoved(int userId) {
1483             synchronized (mLock) {
1484                 mGracePeriodExpiration.delete(userId);
1485             }
1486         }
1487 
1488         @VisibleForTesting
isWithinGracePeriodForUser(int userId)1489         public boolean isWithinGracePeriodForUser(int userId) {
1490             synchronized (mLock) {
1491                 return userId == mCurrentUserId
1492                         || sElapsedRealtimeClock.millis()
1493                         < mGracePeriodExpiration.get(userId, Long.MAX_VALUE);
1494             }
1495         }
1496     }
1497 
1498     /**
1499      * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
1500      * are running/pending, how many more job can start.
1501      *
1502      * Extracted for testing and logging.
1503      */
1504     @VisibleForTesting
1505     static class WorkCountTracker {
1506         private int mConfigMaxTotal;
1507         private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1508         private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
1509         private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES);
1510 
1511         /**
1512          * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't
1513          * enough ready jobs of a type to take up all of the desired reserved slots.
1514          */
1515         private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1516         private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES);
1517         private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES);
1518         private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES);
1519         private int mNumUnspecializedRemaining = 0;
1520 
setConfig(@onNull WorkTypeConfig workTypeConfig)1521         void setConfig(@NonNull WorkTypeConfig workTypeConfig) {
1522             mConfigMaxTotal = workTypeConfig.getMaxTotal();
1523             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
1524                 mConfigNumReservedSlots.put(workType, workTypeConfig.getMinReserved(workType));
1525                 mConfigAbsoluteMaxSlots.put(workType, workTypeConfig.getMax(workType));
1526             }
1527 
1528             mNumUnspecializedRemaining = mConfigMaxTotal;
1529             for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
1530                 mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
1531                         mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
1532             }
1533         }
1534 
resetCounts()1535         void resetCounts() {
1536             mNumActuallyReservedSlots.clear();
1537             mNumPendingJobs.clear();
1538             mNumRunningJobs.clear();
1539             resetStagingCount();
1540         }
1541 
resetStagingCount()1542         void resetStagingCount() {
1543             mNumStartingJobs.clear();
1544         }
1545 
incrementRunningJobCount(@orkType int workType)1546         void incrementRunningJobCount(@WorkType int workType) {
1547             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
1548         }
1549 
incrementPendingJobCount(int workTypes)1550         void incrementPendingJobCount(int workTypes) {
1551             adjustPendingJobCount(workTypes, true);
1552         }
1553 
decrementPendingJobCount(int workTypes)1554         void decrementPendingJobCount(int workTypes) {
1555             if (adjustPendingJobCount(workTypes, false) > 1) {
1556                 // We don't need to adjust reservations if only one work type was modified
1557                 // because that work type is the one we're using.
1558 
1559                 for (int workType = 1; workType <= workTypes; workType <<= 1) {
1560                     if ((workType & workTypes) == workType) {
1561                         maybeAdjustReservations(workType);
1562                     }
1563                 }
1564             }
1565         }
1566 
1567         /** Returns the number of WorkTypes that were modified. */
adjustPendingJobCount(int workTypes, boolean add)1568         private int adjustPendingJobCount(int workTypes, boolean add) {
1569             final int adj = add ? 1 : -1;
1570 
1571             int numAdj = 0;
1572             // We don't know which type we'll classify the job as when we run it yet, so make sure
1573             // we have space in all applicable slots.
1574             for (int workType = 1; workType <= workTypes; workType <<= 1) {
1575                 if ((workTypes & workType) == workType) {
1576                     mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj);
1577                     numAdj++;
1578                 }
1579             }
1580 
1581             return numAdj;
1582         }
1583 
stageJob(@orkType int workType, int allWorkTypes)1584         void stageJob(@WorkType int workType, int allWorkTypes) {
1585             final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
1586             mNumStartingJobs.put(workType, newNumStartingJobs);
1587             decrementPendingJobCount(allWorkTypes);
1588             if (newNumStartingJobs + mNumRunningJobs.get(workType)
1589                     > mNumActuallyReservedSlots.get(workType)) {
1590                 mNumUnspecializedRemaining--;
1591             }
1592         }
1593 
onStagedJobFailed(@orkType int workType)1594         void onStagedJobFailed(@WorkType int workType) {
1595             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
1596             if (oldNumStartingJobs == 0) {
1597                 Slog.e(TAG, "# staged jobs for " + workType + " went negative.");
1598                 // We are in a bad state. We will eventually recover when the pending list is
1599                 // regenerated.
1600                 return;
1601             }
1602             mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
1603             maybeAdjustReservations(workType);
1604         }
1605 
maybeAdjustReservations(@orkType int workType)1606         private void maybeAdjustReservations(@WorkType int workType) {
1607             // Always make sure we reserve the minimum number of slots in case new jobs become ready
1608             // soon.
1609             final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType),
1610                     mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
1611                             + mNumPendingJobs.get(workType));
1612             if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) {
1613                 // We've run all jobs for this type. Let another type use it now.
1614                 mNumActuallyReservedSlots.put(workType, numRemainingForType);
1615                 int assignWorkType = WORK_TYPE_NONE;
1616                 for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
1617                     int wt = mNumActuallyReservedSlots.keyAt(i);
1618                     if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
1619                         // Try to give this slot to the highest priority one within its limits.
1620                         int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
1621                                 + mNumPendingJobs.get(wt);
1622                         if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
1623                                 && total > mNumActuallyReservedSlots.valueAt(i)) {
1624                             assignWorkType = wt;
1625                         }
1626                     }
1627                 }
1628                 if (assignWorkType != WORK_TYPE_NONE) {
1629                     mNumActuallyReservedSlots.put(assignWorkType,
1630                             mNumActuallyReservedSlots.get(assignWorkType) + 1);
1631                 } else {
1632                     mNumUnspecializedRemaining++;
1633                 }
1634             }
1635         }
1636 
onJobStarted(@orkType int workType)1637         void onJobStarted(@WorkType int workType) {
1638             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
1639             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
1640             if (oldNumStartingJobs == 0) {
1641                 Slog.e(TAG, "# stated jobs for " + workType + " went negative.");
1642                 // We are in a bad state. We will eventually recover when the pending list is
1643                 // regenerated. For now, only modify the running count.
1644             } else {
1645                 mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
1646             }
1647         }
1648 
onJobFinished(@orkType int workType)1649         void onJobFinished(@WorkType int workType) {
1650             final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1;
1651             if (newNumRunningJobs < 0) {
1652                 // We are in a bad state. We will eventually recover when the pending list is
1653                 // regenerated.
1654                 Slog.e(TAG, "# running jobs for " + workType + " went negative.");
1655                 return;
1656             }
1657             mNumRunningJobs.put(workType, newNumRunningJobs);
1658             maybeAdjustReservations(workType);
1659         }
1660 
onCountDone()1661         void onCountDone() {
1662             // Calculate how many slots to reserve for each work type. "Unspecialized" slots will
1663             // be reserved for higher importance types first (ie. top before ej before bg).
1664             // Steps:
1665             //   1. Account for slots for already running jobs
1666             //   2. Use remaining unaccounted slots to try and ensure minimum reserved slots
1667             //   3. Allocate remaining up to max, based on importance
1668 
1669             mNumUnspecializedRemaining = mConfigMaxTotal;
1670 
1671             // Step 1
1672             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
1673                 int run = mNumRunningJobs.get(workType);
1674                 mRecycledReserved.put(workType, run);
1675                 mNumUnspecializedRemaining -= run;
1676             }
1677 
1678             // Step 2
1679             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
1680                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
1681                 int res = mRecycledReserved.get(workType);
1682                 int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
1683                         Math.min(num, mConfigNumReservedSlots.get(workType) - res)));
1684                 res += fillUp;
1685                 mRecycledReserved.put(workType, res);
1686                 mNumUnspecializedRemaining -= fillUp;
1687             }
1688 
1689             // Step 3
1690             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
1691                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
1692                 int res = mRecycledReserved.get(workType);
1693                 int unspecializedAssigned = Math.max(0,
1694                         Math.min(mNumUnspecializedRemaining,
1695                                 Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res));
1696                 mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned);
1697                 mNumUnspecializedRemaining -= unspecializedAssigned;
1698             }
1699         }
1700 
canJobStart(int workTypes)1701         int canJobStart(int workTypes) {
1702             for (int workType = 1; workType <= workTypes; workType <<= 1) {
1703                 if ((workTypes & workType) == workType) {
1704                     final int maxAllowed = Math.min(
1705                             mConfigAbsoluteMaxSlots.get(workType),
1706                             mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining);
1707                     if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
1708                             < maxAllowed) {
1709                         return workType;
1710                     }
1711                 }
1712             }
1713             return WORK_TYPE_NONE;
1714         }
1715 
canJobStart(int workTypes, @WorkType int replacingWorkType)1716         int canJobStart(int workTypes, @WorkType int replacingWorkType) {
1717             final boolean changedNums;
1718             int oldNumRunning = mNumRunningJobs.get(replacingWorkType);
1719             if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) {
1720                 mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1);
1721                 // Lazy implementation to avoid lots of processing. Best way would be to go
1722                 // through the whole process of adjusting reservations, but the processing cost
1723                 // is likely not worth it.
1724                 mNumUnspecializedRemaining++;
1725                 changedNums = true;
1726             } else {
1727                 changedNums = false;
1728             }
1729 
1730             final int ret = canJobStart(workTypes);
1731             if (changedNums) {
1732                 mNumRunningJobs.put(replacingWorkType, oldNumRunning);
1733                 mNumUnspecializedRemaining--;
1734             }
1735             return ret;
1736         }
1737 
getPendingJobCount(@orkType final int workType)1738         int getPendingJobCount(@WorkType final int workType) {
1739             return mNumPendingJobs.get(workType, 0);
1740         }
1741 
getRunningJobCount(@orkType final int workType)1742         int getRunningJobCount(@WorkType final int workType) {
1743             return mNumRunningJobs.get(workType, 0);
1744         }
1745 
isOverTypeLimit(@orkType final int workType)1746         boolean isOverTypeLimit(@WorkType final int workType) {
1747             return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType);
1748         }
1749 
toString()1750         public String toString() {
1751             StringBuilder sb = new StringBuilder();
1752 
1753             sb.append("Config={");
1754             sb.append("tot=").append(mConfigMaxTotal);
1755             sb.append(" mins=");
1756             sb.append(mConfigNumReservedSlots);
1757             sb.append(" maxs=");
1758             sb.append(mConfigAbsoluteMaxSlots);
1759             sb.append("}");
1760 
1761             sb.append(", act res=").append(mNumActuallyReservedSlots);
1762             sb.append(", Pending=").append(mNumPendingJobs);
1763             sb.append(", Running=").append(mNumRunningJobs);
1764             sb.append(", Staged=").append(mNumStartingJobs);
1765             sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining);
1766 
1767             return sb.toString();
1768         }
1769     }
1770 
1771     private static class PackageStats {
1772         public int userId;
1773         public String packageName;
1774         public int numRunningEj;
1775         public int numRunningRegular;
1776         public int numStagedEj;
1777         public int numStagedRegular;
1778 
setPackage(int userId, @NonNull String packageName)1779         private void setPackage(int userId, @NonNull String packageName) {
1780             this.userId = userId;
1781             this.packageName = packageName;
1782             numRunningEj = numRunningRegular = 0;
1783             resetStagedCount();
1784         }
1785 
resetStagedCount()1786         private void resetStagedCount() {
1787             numStagedEj = numStagedRegular = 0;
1788         }
1789 
adjustRunningCount(boolean add, boolean forEj)1790         private void adjustRunningCount(boolean add, boolean forEj) {
1791             if (forEj) {
1792                 numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1));
1793             } else {
1794                 numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1));
1795             }
1796         }
1797 
adjustStagedCount(boolean add, boolean forEj)1798         private void adjustStagedCount(boolean add, boolean forEj) {
1799             if (forEj) {
1800                 numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1));
1801             } else {
1802                 numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1));
1803             }
1804         }
1805 
1806         @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw)1807         private void dumpLocked(IndentingPrintWriter pw) {
1808             pw.print("PackageStats{");
1809             pw.print(userId);
1810             pw.print("-");
1811             pw.print(packageName);
1812             pw.print("#runEJ", numRunningEj);
1813             pw.print("#runReg", numRunningRegular);
1814             pw.print("#stagedEJ", numStagedEj);
1815             pw.print("#stagedReg", numStagedRegular);
1816             pw.println("}");
1817         }
1818     }
1819 }
1820