• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.job;
18 
19 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
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.BatteryStats;
36 import android.os.Handler;
37 import android.os.PowerManager;
38 import android.os.RemoteException;
39 import android.os.ServiceManager;
40 import android.os.UserHandle;
41 import android.provider.DeviceConfig;
42 import android.util.ArraySet;
43 import android.util.IndentingPrintWriter;
44 import android.util.Pair;
45 import android.util.Pools;
46 import android.util.Slog;
47 import android.util.SparseArrayMap;
48 import android.util.SparseIntArray;
49 import android.util.SparseLongArray;
50 import android.util.TimeUtils;
51 import android.util.proto.ProtoOutputStream;
52 
53 import com.android.internal.R;
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.app.IBatteryStats;
57 import com.android.internal.app.procstats.ProcessStats;
58 import com.android.internal.util.StatLogger;
59 import com.android.server.JobSchedulerBackgroundThread;
60 import com.android.server.LocalServices;
61 import com.android.server.job.controllers.JobStatus;
62 import com.android.server.job.controllers.StateController;
63 import com.android.server.job.restrictions.JobRestriction;
64 import com.android.server.pm.UserManagerInternal;
65 
66 import java.io.PrintWriter;
67 import java.lang.annotation.Retention;
68 import java.lang.annotation.RetentionPolicy;
69 import java.util.ArrayList;
70 import java.util.Collection;
71 import java.util.Comparator;
72 import java.util.List;
73 import java.util.function.Consumer;
74 import java.util.function.Predicate;
75 
76 /**
77  * This class decides, given the various configuration and the system status, which jobs can start
78  * and which {@link JobServiceContext} to run each job on.
79  */
80 class JobConcurrencyManager {
81     private static final String TAG = JobSchedulerService.TAG + ".Concurrency";
82     private static final boolean DEBUG = JobSchedulerService.DEBUG;
83 
84     /** The maximum number of concurrent jobs we'll aim to run at one time. */
85     public static final int STANDARD_CONCURRENCY_LIMIT = 16;
86     /** The maximum number of objects we should retain in memory when not in use. */
87     private static final int MAX_RETAINED_OBJECTS = (int) (1.5 * STANDARD_CONCURRENCY_LIMIT);
88 
89     static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
90     private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS =
91             CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
92     private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
93     @VisibleForTesting
94     static final String KEY_PKG_CONCURRENCY_LIMIT_EJ =
95             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej";
96     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3;
97     @VisibleForTesting
98     static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
99             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
100     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = STANDARD_CONCURRENCY_LIMIT / 2;
101 
102     /**
103      * Set of possible execution types that a job can have. The actual type(s) of a job are based
104      * on the {@link JobStatus#lastEvaluatedBias}, which is typically evaluated right before
105      * execution (when we're trying to determine which jobs to run next) and won't change after the
106      * job has started executing.
107      *
108      * Try to give higher priority types lower values.
109      *
110      * @see #getJobWorkTypes(JobStatus)
111      */
112 
113     /** Job shouldn't run or qualify as any other work type. */
114     static final int WORK_TYPE_NONE = 0;
115     /** The job is for an app in the TOP state for a currently active user. */
116     static final int WORK_TYPE_TOP = 1 << 0;
117     /**
118      * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
119      * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user.
120      */
121     static final int WORK_TYPE_FGS = 1 << 1;
122     /** The job is allowed to run as an expedited job for a currently active user. */
123     static final int WORK_TYPE_EJ = 1 << 2;
124     /**
125      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
126      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so
127      * can run as a background job.
128      */
129     static final int WORK_TYPE_BG = 1 << 3;
130     /**
131      * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
132      * state, or is allowed to run as an expedited job, but is for a completely background user.
133      */
134     static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4;
135     /**
136      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
137      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user,
138      * so can run as a background user job.
139      */
140     static final int WORK_TYPE_BGUSER = 1 << 5;
141     @VisibleForTesting
142     static final int NUM_WORK_TYPES = 6;
143     private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1;
144 
145     @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
146             WORK_TYPE_NONE,
147             WORK_TYPE_TOP,
148             WORK_TYPE_FGS,
149             WORK_TYPE_EJ,
150             WORK_TYPE_BG,
151             WORK_TYPE_BGUSER_IMPORTANT,
152             WORK_TYPE_BGUSER
153     })
154     @Retention(RetentionPolicy.SOURCE)
155     public @interface WorkType {
156     }
157 
158     @VisibleForTesting
workTypeToString(@orkType int workType)159     static String workTypeToString(@WorkType int workType) {
160         switch (workType) {
161             case WORK_TYPE_NONE:
162                 return "NONE";
163             case WORK_TYPE_TOP:
164                 return "TOP";
165             case WORK_TYPE_FGS:
166                 return "FGS";
167             case WORK_TYPE_EJ:
168                 return "EJ";
169             case WORK_TYPE_BG:
170                 return "BG";
171             case WORK_TYPE_BGUSER:
172                 return "BGUSER";
173             case WORK_TYPE_BGUSER_IMPORTANT:
174                 return "BGUSER_IMPORTANT";
175             default:
176                 return "WORK(" + workType + ")";
177         }
178     }
179 
180     private final Object mLock;
181     private final JobSchedulerService mService;
182     private final Context mContext;
183     private final Handler mHandler;
184 
185     private PowerManager mPowerManager;
186 
187     private boolean mCurrentInteractiveState;
188     private boolean mEffectiveInteractiveState;
189 
190     private long mLastScreenOnRealtime;
191     private long mLastScreenOffRealtime;
192 
193     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
194             new WorkConfigLimitsPerMemoryTrimLevel(
195                     new WorkTypeConfig("screen_on_normal", 11,
196                             // defaultMin
197                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
198                                     Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2),
199                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
200                             // defaultMax
201                             List.of(Pair.create(WORK_TYPE_BG, 6),
202                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2),
203                                     Pair.create(WORK_TYPE_BGUSER, 3))
204                     ),
205                     new WorkTypeConfig("screen_on_moderate", 9,
206                             // defaultMin
207                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
208                                     Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1),
209                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
210                             // defaultMax
211                             List.of(Pair.create(WORK_TYPE_BG, 4),
212                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
213                                     Pair.create(WORK_TYPE_BGUSER, 1))
214                     ),
215                     new WorkTypeConfig("screen_on_low", 6,
216                             // defaultMin
217                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
218                                     Pair.create(WORK_TYPE_EJ, 1)),
219                             // defaultMax
220                             List.of(Pair.create(WORK_TYPE_BG, 2),
221                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
222                                     Pair.create(WORK_TYPE_BGUSER, 1))
223                     ),
224                     new WorkTypeConfig("screen_on_critical", 6,
225                             // defaultMin
226                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
227                                     Pair.create(WORK_TYPE_EJ, 1)),
228                             // defaultMax
229                             List.of(Pair.create(WORK_TYPE_BG, 1),
230                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
231                                     Pair.create(WORK_TYPE_BGUSER, 1))
232                     )
233             );
234     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
235             new WorkConfigLimitsPerMemoryTrimLevel(
236                     new WorkTypeConfig("screen_off_normal", 16,
237                             // defaultMin
238                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2),
239                                     Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2),
240                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
241                             // defaultMax
242                             List.of(Pair.create(WORK_TYPE_BG, 10),
243                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2),
244                                     Pair.create(WORK_TYPE_BGUSER, 3))
245                     ),
246                     new WorkTypeConfig("screen_off_moderate", 14,
247                             // defaultMin
248                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 2),
249                                     Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 2),
250                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1)),
251                             // defaultMax
252                             List.of(Pair.create(WORK_TYPE_BG, 7),
253                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
254                                     Pair.create(WORK_TYPE_BGUSER, 1))
255                     ),
256                     new WorkTypeConfig("screen_off_low", 9,
257                             // defaultMin
258                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
259                                     Pair.create(WORK_TYPE_EJ, 2), Pair.create(WORK_TYPE_BG, 1)),
260                             // defaultMax
261                             List.of(Pair.create(WORK_TYPE_BG, 3),
262                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
263                                     Pair.create(WORK_TYPE_BGUSER, 1))
264                     ),
265                     new WorkTypeConfig("screen_off_critical", 6,
266                             // defaultMin
267                             List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_FGS, 1),
268                                     Pair.create(WORK_TYPE_EJ, 1)),
269                             // defaultMax
270                             List.of(Pair.create(WORK_TYPE_BG, 1),
271                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1),
272                                     Pair.create(WORK_TYPE_BGUSER, 1))
273                     )
274             );
275 
276     /**
277      * Comparator to sort the determination lists, putting the ContextAssignments that we most
278      * prefer to use at the end of the list.
279      */
280     private static final Comparator<ContextAssignment> sDeterminationComparator = (ca1, ca2) -> {
281         if (ca1 == ca2) {
282             return 0;
283         }
284         final JobStatus js1 = ca1.context.getRunningJobLocked();
285         final JobStatus js2 = ca2.context.getRunningJobLocked();
286         // Prefer using an empty context over one with a running job.
287         if (js1 == null) {
288             if (js2 == null) {
289                 return 0;
290             }
291             return 1;
292         } else if (js2 == null) {
293             return -1;
294         }
295         // We would prefer to replace bg jobs over TOP jobs.
296         if (js1.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
297             if (js2.lastEvaluatedBias != JobInfo.BIAS_TOP_APP) {
298                 return -1;
299             }
300         } else if (js2.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
301             return 1;
302         }
303         // Prefer replacing the job that has been running the longest.
304         return Long.compare(
305                 ca2.context.getExecutionStartTimeElapsed(),
306                 ca1.context.getExecutionStartTimeElapsed());
307     };
308 
309     // We reuse the lists to avoid GC churn.
310     private final ArraySet<ContextAssignment> mRecycledChanged = new ArraySet<>();
311     private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
312     private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
313     private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
314 
315     private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
316             new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
317 
318     /**
319      * Set of JobServiceContexts that are actively running jobs.
320      */
321     final List<JobServiceContext> mActiveServices = new ArrayList<>();
322 
323     /** Set of JobServiceContexts that aren't currently running any jobs. */
324     private final ArraySet<JobServiceContext> mIdleContexts = new ArraySet<>();
325 
326     private int mNumDroppedContexts = 0;
327 
328     private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
329 
330     private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
331 
332     private final Pools.Pool<PackageStats> mPkgStatsPool =
333             new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
334 
335     private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>();
336 
337     private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;
338 
339     /** Wait for this long after screen off before adjusting the job concurrency. */
340     private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
341 
342     /**
343      * The maximum number of expedited jobs a single userId-package can have running simultaneously.
344      * TOP apps are not limited.
345      */
346     private int mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ;
347 
348     /**
349      * The maximum number of regular jobs a single userId-package can have running simultaneously.
350      * TOP apps are not limited.
351      */
352     private int mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
353 
354     /** Current memory trim level. */
355     private int mLastMemoryTrimLevel;
356 
357     /** Used to throttle heavy API calls. */
358     private long mNextSystemStateRefreshTime;
359     private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
360 
361     private final Consumer<PackageStats> mPackageStatsStagingCountClearer =
362             PackageStats::resetStagedCount;
363 
364     private final StatLogger mStatLogger = new StatLogger(new String[]{
365             "assignJobsToContexts",
366             "refreshSystemState",
367     });
368     @VisibleForTesting
369     GracePeriodObserver mGracePeriodObserver;
370     @VisibleForTesting
371     boolean mShouldRestrictBgUser;
372 
373     interface Stats {
374         int ASSIGN_JOBS_TO_CONTEXTS = 0;
375         int REFRESH_SYSTEM_STATE = 1;
376 
377         int COUNT = REFRESH_SYSTEM_STATE + 1;
378     }
379 
JobConcurrencyManager(JobSchedulerService service)380     JobConcurrencyManager(JobSchedulerService service) {
381         mService = service;
382         mLock = mService.mLock;
383         mContext = service.getTestableContext();
384 
385         mHandler = JobSchedulerBackgroundThread.getHandler();
386 
387         mGracePeriodObserver = new GracePeriodObserver(mContext);
388         mShouldRestrictBgUser = mContext.getResources().getBoolean(
389                 R.bool.config_jobSchedulerRestrictBackgroundUser);
390     }
391 
onSystemReady()392     public void onSystemReady() {
393         mPowerManager = mContext.getSystemService(PowerManager.class);
394 
395         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
396         filter.addAction(Intent.ACTION_SCREEN_OFF);
397         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
398         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
399         mContext.registerReceiver(mReceiver, filter);
400         try {
401             ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG);
402         } catch (RemoteException e) {
403         }
404 
405         onInteractiveStateChanged(mPowerManager.isInteractive());
406     }
407 
408     /**
409      * Called when the boot phase reaches
410      * {@link com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START}.
411      */
onThirdPartyAppsCanStart()412     void onThirdPartyAppsCanStart() {
413         final IBatteryStats batteryStats = IBatteryStats.Stub.asInterface(
414                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
415         for (int i = 0; i < STANDARD_CONCURRENCY_LIMIT; i++) {
416             mIdleContexts.add(
417                     new JobServiceContext(mService, this, batteryStats,
418                             mService.mJobPackageTracker, mContext.getMainLooper()));
419         }
420     }
421 
422     @GuardedBy("mLock")
onAppRemovedLocked(String pkgName, int uid)423     void onAppRemovedLocked(String pkgName, int uid) {
424         final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName);
425         if (packageStats != null) {
426             if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) {
427                 // Don't delete the object just yet. We'll remove it in onJobCompleted() when the
428                 // jobs officially stop running.
429                 Slog.w(TAG,
430                         pkgName + "(" + uid + ") marked as removed before jobs stopped running");
431             } else {
432                 mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName);
433             }
434         }
435     }
436 
onUserRemoved(int userId)437     void onUserRemoved(int userId) {
438         mGracePeriodObserver.onUserRemoved(userId);
439     }
440 
441     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
442         @Override
443         public void onReceive(Context context, Intent intent) {
444             switch (intent.getAction()) {
445                 case Intent.ACTION_SCREEN_ON:
446                     onInteractiveStateChanged(true);
447                     break;
448                 case Intent.ACTION_SCREEN_OFF:
449                     onInteractiveStateChanged(false);
450                     break;
451                 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
452                     if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) {
453                         synchronized (mLock) {
454                             stopUnexemptedJobsForDoze();
455                             stopLongRunningJobsLocked("deep doze");
456                         }
457                     }
458                     break;
459                 case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
460                     if (mPowerManager != null && mPowerManager.isPowerSaveMode()) {
461                         synchronized (mLock) {
462                             stopLongRunningJobsLocked("battery saver");
463                         }
464                     }
465                     break;
466             }
467         }
468     };
469 
470     /**
471      * Called when the screen turns on / off.
472      */
onInteractiveStateChanged(boolean interactive)473     private void onInteractiveStateChanged(boolean interactive) {
474         synchronized (mLock) {
475             if (mCurrentInteractiveState == interactive) {
476                 return;
477             }
478             mCurrentInteractiveState = interactive;
479             if (DEBUG) {
480                 Slog.d(TAG, "Interactive: " + interactive);
481             }
482 
483             final long nowRealtime = sElapsedRealtimeClock.millis();
484             if (interactive) {
485                 mLastScreenOnRealtime = nowRealtime;
486                 mEffectiveInteractiveState = true;
487 
488                 mHandler.removeCallbacks(mRampUpForScreenOff);
489             } else {
490                 mLastScreenOffRealtime = nowRealtime;
491 
492                 // Set mEffectiveInteractiveState to false after the delay, when we may increase
493                 // the concurrency.
494                 // We don't need a wakeup alarm here. When there's a pending job, there should
495                 // also be jobs running too, meaning the device should be awake.
496 
497                 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
498                 // we need the exact same instance for removeCallbacks().
499                 mHandler.postDelayed(mRampUpForScreenOff, mScreenOffAdjustmentDelayMs);
500             }
501         }
502     }
503 
504     private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
505 
506     /**
507      * Called in {@link #mScreenOffAdjustmentDelayMs} after
508      * the screen turns off, in order to increase concurrency.
509      */
rampUpForScreenOff()510     private void rampUpForScreenOff() {
511         synchronized (mLock) {
512             // Make sure the screen has really been off for the configured duration.
513             // (There could be a race.)
514             if (!mEffectiveInteractiveState) {
515                 return;
516             }
517             if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
518                 return;
519             }
520             final long now = sElapsedRealtimeClock.millis();
521             if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) {
522                 return;
523             }
524 
525             mEffectiveInteractiveState = false;
526 
527             if (DEBUG) {
528                 Slog.d(TAG, "Ramping up concurrency");
529             }
530 
531             mService.maybeRunPendingJobsLocked();
532         }
533     }
534 
535     @GuardedBy("mLock")
getRunningJobsLocked()536     ArraySet<JobStatus> getRunningJobsLocked() {
537         return mRunningJobs;
538     }
539 
540     @GuardedBy("mLock")
isJobRunningLocked(JobStatus job)541     boolean isJobRunningLocked(JobStatus job) {
542         return mRunningJobs.contains(job);
543     }
544 
545     /**
546      * Returns true if a job that is "similar" to the provided job is currently running.
547      * "Similar" in this context means any job that the {@link JobStore} would consider equivalent
548      * and replace one with the other.
549      */
550     @GuardedBy("mLock")
isSimilarJobRunningLocked(JobStatus job)551     private boolean isSimilarJobRunningLocked(JobStatus job) {
552         for (int i = mRunningJobs.size() - 1; i >= 0; --i) {
553             JobStatus js = mRunningJobs.valueAt(i);
554             if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) {
555                 return true;
556             }
557         }
558         return false;
559     }
560 
561     /** Return {@code true} if the state was updated. */
562     @GuardedBy("mLock")
refreshSystemStateLocked()563     private boolean refreshSystemStateLocked() {
564         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
565 
566         // Only refresh the information every so often.
567         if (nowUptime < mNextSystemStateRefreshTime) {
568             return false;
569         }
570 
571         final long start = mStatLogger.getTime();
572         mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL;
573 
574         mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
575         try {
576             mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel();
577         } catch (RemoteException e) {
578         }
579 
580         mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
581         return true;
582     }
583 
584     @GuardedBy("mLock")
updateCounterConfigLocked()585     private void updateCounterConfigLocked() {
586         if (!refreshSystemStateLocked()) {
587             return;
588         }
589 
590         final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
591                 ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
592 
593         switch (mLastMemoryTrimLevel) {
594             case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
595                 mWorkTypeConfig = workConfigs.moderate;
596                 break;
597             case ProcessStats.ADJ_MEM_FACTOR_LOW:
598                 mWorkTypeConfig = workConfigs.low;
599                 break;
600             case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
601                 mWorkTypeConfig = workConfigs.critical;
602                 break;
603             default:
604                 mWorkTypeConfig = workConfigs.normal;
605                 break;
606         }
607 
608         mWorkCountTracker.setConfig(mWorkTypeConfig);
609     }
610 
611     /**
612      * Takes jobs from pending queue and runs them on available contexts.
613      * If no contexts are available, preempts lower bias jobs to run higher bias ones.
614      * Lock on mLock before calling this function.
615      */
616     @GuardedBy("mLock")
assignJobsToContextsLocked()617     void assignJobsToContextsLocked() {
618         final long start = mStatLogger.getTime();
619 
620         assignJobsToContextsInternalLocked();
621 
622         mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
623     }
624 
625     @GuardedBy("mLock")
assignJobsToContextsInternalLocked()626     private void assignJobsToContextsInternalLocked() {
627         if (DEBUG) {
628             Slog.d(TAG, printPendingQueueLocked());
629         }
630 
631         if (mService.getPendingJobQueue().size() == 0) {
632             // Nothing to do.
633             return;
634         }
635 
636         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
637         final List<JobServiceContext> activeServices = mActiveServices;
638 
639         // To avoid GC churn, we recycle the arrays.
640         final ArraySet<ContextAssignment> changed = mRecycledChanged;
641         final ArraySet<ContextAssignment> idle = mRecycledIdle;
642         final ArrayList<ContextAssignment> preferredUidOnly = mRecycledPreferredUidOnly;
643         final ArrayList<ContextAssignment> stoppable = mRecycledStoppable;
644 
645         updateCounterConfigLocked();
646         // Reset everything since we'll re-evaluate the current state.
647         mWorkCountTracker.resetCounts();
648 
649         // Update the priorities of jobs that aren't running, and also count the pending work types.
650         // Do this before the following loop to hopefully reduce the cost of
651         // shouldStopRunningJobLocked().
652         updateNonRunningPrioritiesLocked(pendingJobQueue, true);
653 
654         final int numRunningJobs = activeServices.size();
655         for (int i = 0; i < numRunningJobs; ++i) {
656             final JobServiceContext jsc = activeServices.get(i);
657             final JobStatus js = jsc.getRunningJobLocked();
658 
659             ContextAssignment assignment = mContextAssignmentPool.acquire();
660             if (assignment == null) {
661                 assignment = new ContextAssignment();
662             }
663 
664             assignment.context = jsc;
665 
666             if (js != null) {
667                 mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
668                 assignment.workType = jsc.getRunningJobWorkType();
669             }
670 
671             assignment.preferredUid = jsc.getPreferredUid();
672             if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
673                 stoppable.add(assignment);
674             } else {
675                 preferredUidOnly.add(assignment);
676             }
677         }
678         preferredUidOnly.sort(sDeterminationComparator);
679         stoppable.sort(sDeterminationComparator);
680         for (int i = numRunningJobs; i < STANDARD_CONCURRENCY_LIMIT; ++i) {
681             final JobServiceContext jsc;
682             final int numIdleContexts = mIdleContexts.size();
683             if (numIdleContexts > 0) {
684                 jsc = mIdleContexts.removeAt(numIdleContexts - 1);
685             } else {
686                 Slog.wtf(TAG, "Had fewer than " + STANDARD_CONCURRENCY_LIMIT + " in existence");
687                 jsc = createNewJobServiceContext();
688             }
689 
690             ContextAssignment assignment = mContextAssignmentPool.acquire();
691             if (assignment == null) {
692                 assignment = new ContextAssignment();
693             }
694 
695             assignment.context = jsc;
696             idle.add(assignment);
697         }
698         if (DEBUG) {
699             Slog.d(TAG, printAssignments("running jobs initial", stoppable, preferredUidOnly));
700         }
701 
702         mWorkCountTracker.onCountDone();
703 
704         JobStatus nextPending;
705         pendingJobQueue.resetIterator();
706         int projectedRunningCount = numRunningJobs;
707         while ((nextPending = pendingJobQueue.next()) != null) {
708             if (mRunningJobs.contains(nextPending)) {
709                 // Should never happen.
710                 Slog.wtf(TAG, "Pending queue contained a running job");
711                 if (DEBUG) {
712                     Slog.e(TAG, "Pending+running job: " + nextPending);
713                 }
714                 pendingJobQueue.remove(nextPending);
715                 continue;
716             }
717 
718             final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
719                     && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
720             if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
721                 Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job")
722                         + " to: " + nextPending);
723             }
724 
725             // Find an available slot for nextPending. The context should be one of the following:
726             // 1. Unused
727             // 2. Its job should have used up its minimum execution guarantee so it
728             // 3. Its job should have the lowest bias among all running jobs (sharing the same UID
729             //    as nextPending)
730             ContextAssignment selectedContext = null;
731             final int allWorkTypes = getJobWorkTypes(nextPending);
732             final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
733             final boolean isInOverage = projectedRunningCount > STANDARD_CONCURRENCY_LIMIT;
734             boolean startingJob = false;
735             if (idle.size() > 0) {
736                 final int idx = idle.size() - 1;
737                 final ContextAssignment assignment = idle.valueAt(idx);
738                 final boolean preferredUidOkay = (assignment.preferredUid == nextPending.getUid())
739                         || (assignment.preferredUid == JobServiceContext.NO_PREFERRED_UID);
740                 int workType = mWorkCountTracker.canJobStart(allWorkTypes);
741                 if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
742                     // This slot is free, and we haven't yet hit the limit on
743                     // concurrent jobs...  we can just throw the job in to here.
744                     selectedContext = assignment;
745                     startingJob = true;
746                     idle.removeAt(idx);
747                     assignment.newJob = nextPending;
748                     assignment.newWorkType = workType;
749                 }
750             }
751             if (selectedContext == null && stoppable.size() > 0) {
752                 int topEjCount = 0;
753                 for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
754                     JobStatus js = mRunningJobs.valueAt(r);
755                     if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
756                         topEjCount++;
757                     }
758                 }
759                 for (int s = stoppable.size() - 1; s >= 0; --s) {
760                     final ContextAssignment assignment = stoppable.get(s);
761                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
762                     // Maybe stop the job if it has had its day in the sun. Only allow replacing
763                     // for one of the following conditions:
764                     // 1. We're putting in the current TOP app's EJ
765                     // 2. There aren't too many jobs running AND the current job started when the
766                     //    app was in the background
767                     // 3. There aren't too many jobs running AND the current job started when the
768                     //    app was on TOP, but the app has since left TOP
769                     // 4. There aren't too many jobs running AND the current job started when the
770                     //    app was on TOP, the app is still TOP, but there are too many TOP+EJs
771                     //    running (because we don't want them to starve out other apps and the
772                     //    current job has already run for the minimum guaranteed time).
773                     boolean canReplace = isTopEj; // Case 1
774                     if (!canReplace && !isInOverage) {
775                         final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
776                         canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
777                                 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
778                                 || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
779                     }
780                     if (canReplace) {
781                         int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
782                                 assignment.context.getRunningJobWorkType());
783                         if (replaceWorkType != WORK_TYPE_NONE) {
784                             // Right now, the way the code is set up, we don't need to explicitly
785                             // assign the new job to this context since we'll reassign when the
786                             // preempted job finally stops.
787                             assignment.preemptReason = assignment.shouldStopJobReason;
788                             assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
789                             selectedContext = assignment;
790                             stoppable.remove(s);
791                             assignment.newJob = nextPending;
792                             assignment.newWorkType = replaceWorkType;
793                             break;
794                         }
795                     }
796                 }
797             }
798             if (selectedContext == null && (!isInOverage || isTopEj)) {
799                 int lowestBiasSeen = Integer.MAX_VALUE;
800                 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
801                     final ContextAssignment assignment = preferredUidOnly.get(p);
802                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
803                     if (runningJob.getUid() != nextPending.getUid()) {
804                         continue;
805                     }
806                     final int jobBias = mService.evaluateJobBiasLocked(runningJob);
807                     if (jobBias >= nextPending.lastEvaluatedBias) {
808                         continue;
809                     }
810 
811                     if (selectedContext == null || lowestBiasSeen > jobBias) {
812                         // Step down the preemption threshold - wind up replacing
813                         // the lowest-bias running job
814                         lowestBiasSeen = jobBias;
815                         selectedContext = assignment;
816                         assignment.preemptReason = "higher bias job found";
817                         assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
818                         // In this case, we're just going to preempt a low bias job, we're not
819                         // actually starting a job, so don't set startingJob to true.
820                     }
821                 }
822                 if (selectedContext != null) {
823                     selectedContext.newJob = nextPending;
824                     preferredUidOnly.remove(selectedContext);
825                 }
826             }
827             // Make sure to run EJs for the TOP app immediately.
828             if (isTopEj) {
829                 if (selectedContext != null
830                         && selectedContext.context.getRunningJobLocked() != null) {
831                     // We're "replacing" a currently running job, but we want TOP EJs to start
832                     // immediately, so we'll start the EJ on a fresh available context and
833                     // stop this currently running job to replace in two steps.
834                     changed.add(selectedContext);
835                     projectedRunningCount--;
836                     selectedContext.newJob = null;
837                     selectedContext.newWorkType = WORK_TYPE_NONE;
838                     selectedContext = null;
839                 }
840                 if (selectedContext == null) {
841                     selectedContext = mContextAssignmentPool.acquire();
842                     if (selectedContext == null) {
843                         selectedContext = new ContextAssignment();
844                     }
845                     selectedContext.context = mIdleContexts.size() > 0
846                             ? mIdleContexts.removeAt(mIdleContexts.size() - 1)
847                             : createNewJobServiceContext();
848                     selectedContext.newJob = nextPending;
849                     final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
850                     selectedContext.newWorkType =
851                             (workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP;
852                 }
853             }
854             final PackageStats packageStats = getPkgStatsLocked(
855                     nextPending.getSourceUserId(), nextPending.getSourcePackageName());
856             if (selectedContext != null) {
857                 changed.add(selectedContext);
858                 if (selectedContext.context.getRunningJobLocked() != null) {
859                     projectedRunningCount--;
860                 }
861                 if (selectedContext.newJob != null) {
862                     projectedRunningCount++;
863                 }
864                 packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
865             }
866             if (startingJob) {
867                 // Increase the counters when we're going to start a job.
868                 mWorkCountTracker.stageJob(selectedContext.newWorkType, allWorkTypes);
869                 mActivePkgStats.add(
870                         nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
871                         packageStats);
872             }
873         }
874         if (DEBUG) {
875             Slog.d(TAG, printAssignments("running jobs final",
876                     stoppable, preferredUidOnly, changed));
877 
878             Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
879         }
880 
881         for (int c = changed.size() - 1; c >= 0; --c) {
882             final ContextAssignment assignment = changed.valueAt(c);
883             final JobStatus js = assignment.context.getRunningJobLocked();
884             if (js != null) {
885                 if (DEBUG) {
886                     Slog.d(TAG, "preempting job: " + js);
887                 }
888                 // preferredUid will be set to uid of currently running job, if appropriate.
889                 assignment.context.cancelExecutingJobLocked(
890                         assignment.preemptReasonCode,
891                         JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason);
892             } else {
893                 final JobStatus pendingJob = assignment.newJob;
894                 if (DEBUG) {
895                     Slog.d(TAG, "About to run job on context "
896                             + assignment.context.getId() + ", job: " + pendingJob);
897                 }
898                 startJobLocked(assignment.context, pendingJob, assignment.newWorkType);
899             }
900 
901             assignment.clear();
902             mContextAssignmentPool.release(assignment);
903         }
904         for (int s = stoppable.size() - 1; s >= 0; --s) {
905             final ContextAssignment assignment = stoppable.get(s);
906             assignment.clear();
907             mContextAssignmentPool.release(assignment);
908         }
909         for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
910             final ContextAssignment assignment = preferredUidOnly.get(p);
911             assignment.clear();
912             mContextAssignmentPool.release(assignment);
913         }
914         for (int i = idle.size() - 1; i >= 0; --i) {
915             final ContextAssignment assignment = idle.valueAt(i);
916             mIdleContexts.add(assignment.context);
917             assignment.clear();
918             mContextAssignmentPool.release(assignment);
919         }
920         changed.clear();
921         idle.clear();
922         stoppable.clear();
923         preferredUidOnly.clear();
924         mWorkCountTracker.resetStagingCount();
925         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
926         noteConcurrency();
927     }
928 
929     @GuardedBy("mLock")
onUidBiasChangedLocked(int prevBias, int newBias)930     void onUidBiasChangedLocked(int prevBias, int newBias) {
931         if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
932             // TOP app didn't change. Nothing to do.
933             return;
934         }
935         if (mService.getPendingJobQueue().size() == 0) {
936             // Nothing waiting for the top app to leave. Nothing to do.
937             return;
938         }
939         // Don't stop the TOP jobs directly. Instead, see if they would be replaced by some
940         // pending job (there may not always be something to replace them).
941         assignJobsToContextsLocked();
942     }
943 
944     @GuardedBy("mLock")
stopJobOnServiceContextLocked(JobStatus job, @JobParameters.StopReason int reason, int internalReasonCode, String debugReason)945     boolean stopJobOnServiceContextLocked(JobStatus job,
946             @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
947         if (!mRunningJobs.contains(job)) {
948             return false;
949         }
950 
951         for (int i = 0; i < mActiveServices.size(); i++) {
952             JobServiceContext jsc = mActiveServices.get(i);
953             final JobStatus executing = jsc.getRunningJobLocked();
954             if (executing == job) {
955                 jsc.cancelExecutingJobLocked(reason, internalReasonCode, debugReason);
956                 return true;
957             }
958         }
959         Slog.wtf(TAG, "Couldn't find running job on a context");
960         mRunningJobs.remove(job);
961         return false;
962     }
963 
964     @GuardedBy("mLock")
stopUnexemptedJobsForDoze()965     private void stopUnexemptedJobsForDoze() {
966         // When becoming idle, make sure no jobs are actively running,
967         // except those using the idle exemption flag.
968         for (int i = 0; i < mActiveServices.size(); i++) {
969             JobServiceContext jsc = mActiveServices.get(i);
970             final JobStatus executing = jsc.getRunningJobLocked();
971             if (executing != null && !executing.canRunInDoze()) {
972                 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
973                         JobParameters.INTERNAL_STOP_REASON_DEVICE_IDLE,
974                         "cancelled due to doze");
975             }
976         }
977     }
978 
979     @GuardedBy("mLock")
stopLongRunningJobsLocked(@onNull String debugReason)980     private void stopLongRunningJobsLocked(@NonNull String debugReason) {
981         for (int i = 0; i < mActiveServices.size(); ++i) {
982             final JobServiceContext jsc = mActiveServices.get(i);
983             final JobStatus jobStatus = jsc.getRunningJobLocked();
984 
985             if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) {
986                 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
987                         JobParameters.INTERNAL_STOP_REASON_TIMEOUT, debugReason);
988             }
989         }
990     }
991 
992     @GuardedBy("mLock")
stopNonReadyActiveJobsLocked()993     void stopNonReadyActiveJobsLocked() {
994         for (int i = 0; i < mActiveServices.size(); i++) {
995             JobServiceContext serviceContext = mActiveServices.get(i);
996             final JobStatus running = serviceContext.getRunningJobLocked();
997             if (running == null) {
998                 continue;
999             }
1000             if (!running.isReady()) {
1001                 if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX
1002                         && running.getStopReason() == JobParameters.STOP_REASON_APP_STANDBY) {
1003                     serviceContext.cancelExecutingJobLocked(
1004                             running.getStopReason(),
1005                             JobParameters.INTERNAL_STOP_REASON_RESTRICTED_BUCKET,
1006                             "cancelled due to restricted bucket");
1007                 } else {
1008                     serviceContext.cancelExecutingJobLocked(
1009                             running.getStopReason(),
1010                             JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED,
1011                             "cancelled due to unsatisfied constraints");
1012                 }
1013             } else {
1014                 final JobRestriction restriction = mService.checkIfRestricted(running);
1015                 if (restriction != null) {
1016                     final int internalReasonCode = restriction.getInternalReason();
1017                     serviceContext.cancelExecutingJobLocked(restriction.getReason(),
1018                             internalReasonCode,
1019                             "restricted due to "
1020                                     + JobParameters.getInternalReasonCodeDescription(
1021                                     internalReasonCode));
1022                 }
1023             }
1024         }
1025     }
1026 
noteConcurrency()1027     private void noteConcurrency() {
1028         mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
1029                 // TODO: log per type instead of only TOP
1030                 mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
1031     }
1032 
1033     @GuardedBy("mLock")
updateNonRunningPrioritiesLocked(@onNull final PendingJobQueue jobQueue, boolean updateCounter)1034     private void updateNonRunningPrioritiesLocked(@NonNull final PendingJobQueue jobQueue,
1035             boolean updateCounter) {
1036         JobStatus pending;
1037         jobQueue.resetIterator();
1038         while ((pending = jobQueue.next()) != null) {
1039 
1040             // If job is already running, go to next job.
1041             if (mRunningJobs.contains(pending)) {
1042                 continue;
1043             }
1044 
1045             pending.lastEvaluatedBias = mService.evaluateJobBiasLocked(pending);
1046 
1047             if (updateCounter) {
1048                 mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
1049             }
1050         }
1051     }
1052 
1053     @GuardedBy("mLock")
1054     @NonNull
getPkgStatsLocked(int userId, @NonNull String packageName)1055     private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) {
1056         PackageStats packageStats = mActivePkgStats.get(userId, packageName);
1057         if (packageStats == null) {
1058             packageStats = mPkgStatsPool.acquire();
1059             if (packageStats == null) {
1060                 packageStats = new PackageStats();
1061             }
1062             packageStats.setPackage(userId, packageName);
1063         }
1064         return packageStats;
1065     }
1066 
1067     @GuardedBy("mLock")
1068     @VisibleForTesting
isPkgConcurrencyLimitedLocked(@onNull JobStatus jobStatus)1069     boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
1070         if (jobStatus.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
1071             // Don't restrict top apps' concurrency. The work type limits will make sure
1072             // background jobs have slots to run if the system has resources.
1073             return false;
1074         }
1075         // Use < instead of <= as that gives us a little wiggle room in case a new job comes
1076         // along very shortly.
1077         if (mService.getPendingJobQueue().size() + mRunningJobs.size()
1078                 < mWorkTypeConfig.getMaxTotal()) {
1079             // Don't artificially limit a single package if we don't even have enough jobs to use
1080             // the maximum number of slots. We'll preempt the job later if we need the slot.
1081             return false;
1082         }
1083         final PackageStats packageStats =
1084                 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
1085         if (packageStats == null) {
1086             // No currently running jobs.
1087             return false;
1088         }
1089         if (jobStatus.shouldTreatAsExpeditedJob()) {
1090             return packageStats.numRunningEj + packageStats.numStagedEj >= mPkgConcurrencyLimitEj;
1091         } else {
1092             return packageStats.numRunningRegular + packageStats.numStagedRegular
1093                     >= mPkgConcurrencyLimitRegular;
1094         }
1095     }
1096 
1097     @GuardedBy("mLock")
startJobLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)1098     private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
1099             @WorkType final int workType) {
1100         final List<StateController> controllers = mService.mControllers;
1101         final int numControllers = controllers.size();
1102         final PowerManager.WakeLock wl =
1103                 mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getTag());
1104         wl.setWorkSource(mService.deriveWorkSource(
1105                 jobStatus.getSourceUid(), jobStatus.getSourcePackageName()));
1106         wl.setReferenceCounted(false);
1107         // Since the quota controller will start counting from the time prepareForExecutionLocked()
1108         // is called, hold a wakelock to make sure the CPU doesn't suspend between that call and
1109         // when the service actually starts.
1110         wl.acquire();
1111         try {
1112             for (int ic = 0; ic < numControllers; ic++) {
1113                 controllers.get(ic).prepareForExecutionLocked(jobStatus);
1114             }
1115             final PackageStats packageStats = getPkgStatsLocked(
1116                     jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
1117             packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob());
1118             if (!worker.executeRunnableJob(jobStatus, workType)) {
1119                 Slog.e(TAG, "Error executing " + jobStatus);
1120                 mWorkCountTracker.onStagedJobFailed(workType);
1121                 for (int ic = 0; ic < numControllers; ic++) {
1122                     controllers.get(ic).unprepareFromExecutionLocked(jobStatus);
1123                 }
1124             } else {
1125                 mRunningJobs.add(jobStatus);
1126                 mActiveServices.add(worker);
1127                 mIdleContexts.remove(worker);
1128                 mWorkCountTracker.onJobStarted(workType);
1129                 packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
1130                 mActivePkgStats.add(
1131                         jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
1132                         packageStats);
1133             }
1134             if (mService.getPendingJobQueue().remove(jobStatus)) {
1135                 mService.mJobPackageTracker.noteNonpending(jobStatus);
1136             }
1137         } finally {
1138             wl.release();
1139         }
1140     }
1141 
1142     @GuardedBy("mLock")
onJobCompletedLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)1143     void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
1144             @WorkType final int workType) {
1145         mWorkCountTracker.onJobFinished(workType);
1146         mRunningJobs.remove(jobStatus);
1147         mActiveServices.remove(worker);
1148         if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) {
1149             // Don't need to save all new contexts, but keep some extra around in case we need
1150             // extras for another TOP+EJ overage.
1151             mIdleContexts.add(worker);
1152         } else {
1153             mNumDroppedContexts++;
1154         }
1155         final PackageStats packageStats =
1156                 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
1157         if (packageStats == null) {
1158             Slog.wtf(TAG, "Running job didn't have an active PackageStats object");
1159         } else {
1160             packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob);
1161             if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) {
1162                 mActivePkgStats.delete(packageStats.userId, packageStats.packageName);
1163                 mPkgStatsPool.release(packageStats);
1164             }
1165         }
1166 
1167         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
1168         if (mActiveServices.size() >= STANDARD_CONCURRENCY_LIMIT || pendingJobQueue.size() == 0) {
1169             worker.clearPreferredUid();
1170             // We're over the limit (because the TOP app scheduled a lot of EJs). Don't start
1171             // running anything new until we get back below the limit.
1172             noteConcurrency();
1173             return;
1174         }
1175 
1176         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
1177             updateCounterConfigLocked();
1178             // Preemption case needs special care.
1179             updateNonRunningPrioritiesLocked(pendingJobQueue, false);
1180 
1181             JobStatus highestBiasJob = null;
1182             int highBiasWorkType = workType;
1183             int highBiasAllWorkTypes = workType;
1184             JobStatus backupJob = null;
1185             int backupWorkType = WORK_TYPE_NONE;
1186             int backupAllWorkTypes = WORK_TYPE_NONE;
1187 
1188             JobStatus nextPending;
1189             pendingJobQueue.resetIterator();
1190             while ((nextPending = pendingJobQueue.next()) != null) {
1191                 if (mRunningJobs.contains(nextPending)) {
1192                     // Should never happen.
1193                     Slog.wtf(TAG, "Pending queue contained a running job");
1194                     if (DEBUG) {
1195                         Slog.e(TAG, "Pending+running job: " + nextPending);
1196                     }
1197                     pendingJobQueue.remove(nextPending);
1198                     continue;
1199                 }
1200 
1201                 if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
1202                     Slog.w(TAG, "Already running similar job to: " + nextPending);
1203                 }
1204 
1205                 if (worker.getPreferredUid() != nextPending.getUid()) {
1206                     if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
1207                         int allWorkTypes = getJobWorkTypes(nextPending);
1208                         int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
1209                         if (workAsType != WORK_TYPE_NONE) {
1210                             backupJob = nextPending;
1211                             backupWorkType = workAsType;
1212                             backupAllWorkTypes = allWorkTypes;
1213                         }
1214                     }
1215                     continue;
1216                 }
1217 
1218                 // Only bypass the concurrent limit if we had preempted the job due to a higher
1219                 // bias job.
1220                 if (nextPending.lastEvaluatedBias <= jobStatus.lastEvaluatedBias
1221                         && isPkgConcurrencyLimitedLocked(nextPending)) {
1222                     continue;
1223                 }
1224 
1225                 if (highestBiasJob == null
1226                         || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
1227                     highestBiasJob = nextPending;
1228                 } else {
1229                     continue;
1230                 }
1231 
1232                 // In this path, we pre-empted an existing job. We don't fully care about the
1233                 // reserved slots. We should just run the highest bias job we can find,
1234                 // though it would be ideal to use an available WorkType slot instead of
1235                 // overloading slots.
1236                 highBiasAllWorkTypes = getJobWorkTypes(nextPending);
1237                 final int workAsType = mWorkCountTracker.canJobStart(highBiasAllWorkTypes);
1238                 if (workAsType == WORK_TYPE_NONE) {
1239                     // Just use the preempted job's work type since this new one is technically
1240                     // replacing it anyway.
1241                     highBiasWorkType = workType;
1242                 } else {
1243                     highBiasWorkType = workAsType;
1244                 }
1245             }
1246             if (highestBiasJob != null) {
1247                 if (DEBUG) {
1248                     Slog.d(TAG, "Running job " + highestBiasJob + " as preemption");
1249                 }
1250                 mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
1251                 startJobLocked(worker, highestBiasJob, highBiasWorkType);
1252             } else {
1253                 if (DEBUG) {
1254                     Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid());
1255                 }
1256                 worker.clearPreferredUid();
1257                 if (backupJob != null) {
1258                     if (DEBUG) {
1259                         Slog.d(TAG, "Running job " + backupJob + " instead");
1260                     }
1261                     mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
1262                     startJobLocked(worker, backupJob, backupWorkType);
1263                 }
1264             }
1265         } else if (pendingJobQueue.size() > 0) {
1266             updateCounterConfigLocked();
1267             updateNonRunningPrioritiesLocked(pendingJobQueue, false);
1268 
1269             // This slot is now free and we have pending jobs. Start the highest bias job we find.
1270             JobStatus highestBiasJob = null;
1271             int highBiasWorkType = workType;
1272             int highBiasAllWorkTypes = workType;
1273 
1274             JobStatus nextPending;
1275             pendingJobQueue.resetIterator();
1276             while ((nextPending = pendingJobQueue.next()) != null) {
1277 
1278                 if (mRunningJobs.contains(nextPending)) {
1279                     // Should never happen.
1280                     Slog.wtf(TAG, "Pending queue contained a running job");
1281                     if (DEBUG) {
1282                         Slog.e(TAG, "Pending+running job: " + nextPending);
1283                     }
1284                     pendingJobQueue.remove(nextPending);
1285                     continue;
1286                 }
1287 
1288                 if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
1289                     Slog.w(TAG, "Already running similar job to: " + nextPending);
1290                 }
1291 
1292                 if (isPkgConcurrencyLimitedLocked(nextPending)) {
1293                     continue;
1294                 }
1295 
1296                 final int allWorkTypes = getJobWorkTypes(nextPending);
1297                 final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
1298                 if (workAsType == WORK_TYPE_NONE) {
1299                     continue;
1300                 }
1301                 if (highestBiasJob == null
1302                         || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
1303                     highestBiasJob = nextPending;
1304                     highBiasWorkType = workAsType;
1305                     highBiasAllWorkTypes = allWorkTypes;
1306                 }
1307             }
1308 
1309             if (highestBiasJob != null) {
1310                 // This slot is free, and we haven't yet hit the limit on
1311                 // concurrent jobs...  we can just throw the job in to here.
1312                 if (DEBUG) {
1313                     Slog.d(TAG, "About to run job: " + highestBiasJob);
1314                 }
1315                 mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
1316                 startJobLocked(worker, highestBiasJob, highBiasWorkType);
1317             }
1318         }
1319 
1320         noteConcurrency();
1321     }
1322 
1323     /**
1324      * Returns {@code null} if the job can continue running and a non-null String if the job should
1325      * be stopped. The non-null String details the reason for stopping the job. A job will generally
1326      * be stopped if there are similar job types waiting to be run and stopping this job would allow
1327      * another job to run, or if system state suggests the job should stop.
1328      */
1329     @Nullable
1330     @GuardedBy("mLock")
shouldStopRunningJobLocked(@onNull JobServiceContext context)1331     String shouldStopRunningJobLocked(@NonNull JobServiceContext context) {
1332         final JobStatus js = context.getRunningJobLocked();
1333         if (js == null) {
1334             // This can happen when we try to assign newly found pending jobs to contexts.
1335             return null;
1336         }
1337 
1338         if (context.isWithinExecutionGuaranteeTime()) {
1339             return null;
1340         }
1341 
1342         // We're over the minimum guaranteed runtime. Stop the job if we're over config limits,
1343         // there are pending jobs that could replace this one, or the device state is not conducive
1344         // to long runs.
1345 
1346         if (mPowerManager.isPowerSaveMode()) {
1347             return "battery saver";
1348         }
1349         if (mPowerManager.isDeviceIdleMode()) {
1350             return "deep doze";
1351         }
1352 
1353         // Update config in case memory usage has changed significantly.
1354         updateCounterConfigLocked();
1355 
1356         @WorkType final int workType = context.getRunningJobWorkType();
1357 
1358         if (mRunningJobs.size() > mWorkTypeConfig.getMaxTotal()
1359                 || mWorkCountTracker.isOverTypeLimit(workType)) {
1360             return "too many jobs running";
1361         }
1362 
1363         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
1364         final int numPending = pendingJobQueue.size();
1365         if (numPending == 0) {
1366             // All quiet. We can let this job run to completion.
1367             return null;
1368         }
1369 
1370         // Only expedited jobs can replace expedited jobs.
1371         if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) {
1372             // Keep fg/bg user distinction.
1373             if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) {
1374                 // Let any important bg user job replace a bg user expedited job.
1375                 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER_IMPORTANT) > 0) {
1376                     return "blocking " + workTypeToString(WORK_TYPE_BGUSER_IMPORTANT) + " queue";
1377                 }
1378                 // Let a fg user EJ preempt a bg user EJ (if able), but not the other way around.
1379                 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0
1380                         && mWorkCountTracker.canJobStart(WORK_TYPE_EJ, workType)
1381                         != WORK_TYPE_NONE) {
1382                     return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
1383                 }
1384             } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
1385                 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
1386             } else if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
1387                 // Try not to let TOP + EJ starve out other apps.
1388                 int topEjCount = 0;
1389                 for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
1390                     JobStatus j = mRunningJobs.valueAt(r);
1391                     if (j.startedAsExpeditedJob && j.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
1392                         topEjCount++;
1393                     }
1394                 }
1395                 if (topEjCount > .5 * mWorkTypeConfig.getMaxTotal()) {
1396                     return "prevent top EJ dominance";
1397                 }
1398             }
1399             // No other pending EJs. Return null so we don't let regular jobs preempt an EJ.
1400             return null;
1401         }
1402 
1403         // Easy check. If there are pending jobs of the same work type, then we know that
1404         // something will replace this.
1405         if (mWorkCountTracker.getPendingJobCount(workType) > 0) {
1406             return "blocking " + workTypeToString(workType) + " queue";
1407         }
1408 
1409         // Harder check. We need to see if a different work type can replace this job.
1410         int remainingWorkTypes = ALL_WORK_TYPES;
1411         JobStatus pending;
1412         pendingJobQueue.resetIterator();
1413         while ((pending = pendingJobQueue.next()) != null) {
1414             final int workTypes = getJobWorkTypes(pending);
1415             if ((workTypes & remainingWorkTypes) > 0
1416                     && mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) {
1417                 return "blocking other pending jobs";
1418             }
1419 
1420             remainingWorkTypes = remainingWorkTypes & ~workTypes;
1421             if (remainingWorkTypes == 0) {
1422                 break;
1423             }
1424         }
1425 
1426         return null;
1427     }
1428 
1429     @GuardedBy("mLock")
executeTimeoutCommandLocked(PrintWriter pw, String pkgName, int userId, boolean hasJobId, int jobId)1430     boolean executeTimeoutCommandLocked(PrintWriter pw, String pkgName, int userId,
1431             boolean hasJobId, int jobId) {
1432         boolean foundSome = false;
1433         for (int i = 0; i < mActiveServices.size(); i++) {
1434             final JobServiceContext jc = mActiveServices.get(i);
1435             final JobStatus js = jc.getRunningJobLocked();
1436             if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
1437                 foundSome = true;
1438                 pw.print("Timing out: ");
1439                 js.printUniqueId(pw);
1440                 pw.print(" ");
1441                 pw.println(js.getServiceComponent().flattenToShortString());
1442             }
1443         }
1444         return foundSome;
1445     }
1446 
1447     @NonNull
createNewJobServiceContext()1448     private JobServiceContext createNewJobServiceContext() {
1449         return new JobServiceContext(mService, this,
1450                 IBatteryStats.Stub.asInterface(
1451                         ServiceManager.getService(BatteryStats.SERVICE_NAME)),
1452                 mService.mJobPackageTracker, mContext.getMainLooper());
1453     }
1454 
1455     @GuardedBy("mLock")
printPendingQueueLocked()1456     private String printPendingQueueLocked() {
1457         StringBuilder s = new StringBuilder("Pending queue: ");
1458         PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
1459         JobStatus js;
1460         pendingJobQueue.resetIterator();
1461         while ((js = pendingJobQueue.next()) != null) {
1462             s.append("(")
1463                     .append(js.getJob().getId())
1464                     .append(", ")
1465                     .append(js.getUid())
1466                     .append(") ");
1467         }
1468         return s.toString();
1469     }
1470 
printAssignments(String header, Collection<ContextAssignment>... list)1471     private static String printAssignments(String header, Collection<ContextAssignment>... list) {
1472         final StringBuilder s = new StringBuilder(header + ": ");
1473         for (int l = 0; l < list.length; ++l) {
1474             final Collection<ContextAssignment> assignments = list[l];
1475             int c = 0;
1476             for (final ContextAssignment assignment : assignments) {
1477                 final JobStatus job = assignment.newJob == null
1478                         ? assignment.context.getRunningJobLocked() : assignment.newJob;
1479 
1480                 if (l > 0 || c > 0) {
1481                     s.append(" ");
1482                 }
1483                 s.append("(").append(assignment.context.getId()).append("=");
1484                 if (job == null) {
1485                     s.append("nothing");
1486                 } else {
1487                     s.append(job.getJobId()).append("/").append(job.getUid());
1488                 }
1489                 s.append(")");
1490                 c++;
1491             }
1492         }
1493         return s.toString();
1494     }
1495 
1496     @GuardedBy("mLock")
updateConfigLocked()1497     void updateConfigLocked() {
1498         DeviceConfig.Properties properties =
1499                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
1500 
1501         mScreenOffAdjustmentDelayMs = properties.getLong(
1502                 KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS);
1503 
1504         CONFIG_LIMITS_SCREEN_ON.normal.update(properties);
1505         CONFIG_LIMITS_SCREEN_ON.moderate.update(properties);
1506         CONFIG_LIMITS_SCREEN_ON.low.update(properties);
1507         CONFIG_LIMITS_SCREEN_ON.critical.update(properties);
1508 
1509         CONFIG_LIMITS_SCREEN_OFF.normal.update(properties);
1510         CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
1511         CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
1512         CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
1513 
1514         // Package concurrency limits must in the range [1, STANDARD_CONCURRENCY_LIMIT].
1515         mPkgConcurrencyLimitEj = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT,
1516                 properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ)));
1517         mPkgConcurrencyLimitRegular = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT,
1518                 properties.getInt(
1519                         KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
1520     }
1521 
1522     @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)1523     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
1524         pw.println("Concurrency:");
1525 
1526         pw.increaseIndent();
1527         try {
1528             pw.println("Configuration:");
1529             pw.increaseIndent();
1530             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
1531             pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
1532             pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
1533             pw.println();
1534             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
1535             pw.println();
1536             CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
1537             pw.println();
1538             CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
1539             pw.println();
1540             CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
1541             pw.println();
1542             CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
1543             pw.println();
1544             CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
1545             pw.println();
1546             CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
1547             pw.println();
1548             CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
1549             pw.println();
1550             pw.decreaseIndent();
1551 
1552             pw.print("Screen state: current ");
1553             pw.print(mCurrentInteractiveState ? "ON" : "OFF");
1554             pw.print("  effective ");
1555             pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
1556             pw.println();
1557 
1558             pw.print("Last screen ON: ");
1559             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
1560             pw.println();
1561 
1562             pw.print("Last screen OFF: ");
1563             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
1564             pw.println();
1565 
1566             pw.println();
1567 
1568             pw.print("Current work counts: ");
1569             pw.println(mWorkCountTracker);
1570 
1571             pw.println();
1572 
1573             pw.print("mLastMemoryTrimLevel: ");
1574             pw.println(mLastMemoryTrimLevel);
1575             pw.println();
1576 
1577             pw.println("Active Package stats:");
1578             pw.increaseIndent();
1579             mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw));
1580             pw.decreaseIndent();
1581             pw.println();
1582 
1583             pw.print("User Grace Period: ");
1584             pw.println(mGracePeriodObserver.mGracePeriodExpiration);
1585             pw.println();
1586 
1587             mStatLogger.dump(pw);
1588         } finally {
1589             pw.decreaseIndent();
1590         }
1591     }
1592 
1593     @GuardedBy("mLock")
dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed, long nowUptime)1594     void dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate,
1595             long nowElapsed, long nowUptime) {
1596         pw.println("Active jobs:");
1597         pw.increaseIndent();
1598         if (mActiveServices.size() == 0) {
1599             pw.println("N/A");
1600         }
1601         for (int i = 0; i < mActiveServices.size(); i++) {
1602             JobServiceContext jsc = mActiveServices.get(i);
1603             final JobStatus job = jsc.getRunningJobLocked();
1604 
1605             if (job != null && !predicate.test(job)) {
1606                 continue;
1607             }
1608 
1609             pw.print("Slot #"); pw.print(i);
1610             pw.print("(ID="); pw.print(jsc.getId()); pw.print("): ");
1611             jsc.dumpLocked(pw, nowElapsed);
1612 
1613             if (job != null) {
1614                 pw.increaseIndent();
1615 
1616                 pw.increaseIndent();
1617                 job.dump(pw, false, nowElapsed);
1618                 pw.decreaseIndent();
1619 
1620                 pw.print("Evaluated bias: ");
1621                 pw.println(JobInfo.getBiasString(job.lastEvaluatedBias));
1622 
1623                 pw.print("Active at ");
1624                 TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
1625                 pw.print(", pending for ");
1626                 TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
1627                 pw.decreaseIndent();
1628                 pw.println();
1629             }
1630         }
1631         pw.decreaseIndent();
1632 
1633         pw.println();
1634         pw.print("Idle contexts (");
1635         pw.print(mIdleContexts.size());
1636         pw.println("):");
1637         pw.increaseIndent();
1638         for (int i = 0; i < mIdleContexts.size(); i++) {
1639             JobServiceContext jsc = mIdleContexts.valueAt(i);
1640 
1641             pw.print("ID="); pw.print(jsc.getId()); pw.print(": ");
1642             jsc.dumpLocked(pw, nowElapsed);
1643         }
1644         pw.decreaseIndent();
1645 
1646         if (mNumDroppedContexts > 0) {
1647             pw.println();
1648             pw.print("Dropped ");
1649             pw.print(mNumDroppedContexts);
1650             pw.println(" contexts");
1651         }
1652     }
1653 
dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)1654     public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
1655         final long token = proto.start(tag);
1656 
1657         proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE_STATE, mCurrentInteractiveState);
1658         proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE_STATE,
1659                 mEffectiveInteractiveState);
1660 
1661         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
1662                 nowRealtime - mLastScreenOnRealtime);
1663         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
1664                 nowRealtime - mLastScreenOffRealtime);
1665 
1666         proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
1667 
1668         mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
1669 
1670         proto.end(token);
1671     }
1672 
1673     /**
1674      * Decides whether a job is from the current foreground user or the equivalent.
1675      */
1676     @VisibleForTesting
shouldRunAsFgUserJob(JobStatus job)1677     boolean shouldRunAsFgUserJob(JobStatus job) {
1678         if (!mShouldRestrictBgUser) return true;
1679         int userId = job.getSourceUserId();
1680         UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
1681         UserInfo userInfo = um.getUserInfo(userId);
1682 
1683         // If the user has a parent user (e.g. a work profile of another user), the user should be
1684         // treated equivalent as its parent user.
1685         if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
1686                 && userInfo.profileGroupId != userId) {
1687             userId = userInfo.profileGroupId;
1688             userInfo = um.getUserInfo(userId);
1689         }
1690 
1691         int currentUser = LocalServices.getService(ActivityManagerInternal.class)
1692                 .getCurrentUserId();
1693         // A user is treated as foreground user if any of the followings is true:
1694         // 1. The user is current user
1695         // 2. The user is primary user
1696         // 3. The user's grace period has not expired
1697         return currentUser == userId || userInfo.isPrimary()
1698                 || mGracePeriodObserver.isWithinGracePeriodForUser(userId);
1699     }
1700 
getJobWorkTypes(@onNull JobStatus js)1701     int getJobWorkTypes(@NonNull JobStatus js) {
1702         int classification = 0;
1703 
1704         if (shouldRunAsFgUserJob(js)) {
1705             if (js.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
1706                 classification |= WORK_TYPE_TOP;
1707             } else if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE) {
1708                 classification |= WORK_TYPE_FGS;
1709             } else {
1710                 classification |= WORK_TYPE_BG;
1711             }
1712 
1713             if (js.shouldTreatAsExpeditedJob()) {
1714                 classification |= WORK_TYPE_EJ;
1715             }
1716         } else {
1717             if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
1718                     || js.shouldTreatAsExpeditedJob()) {
1719                 classification |= WORK_TYPE_BGUSER_IMPORTANT;
1720             }
1721             // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here.
1722             classification |= WORK_TYPE_BGUSER;
1723         }
1724 
1725         return classification;
1726     }
1727 
1728     @VisibleForTesting
1729     static class WorkTypeConfig {
1730         @VisibleForTesting
1731         static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
1732         private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
1733         private static final String KEY_PREFIX_MAX_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_fgs_";
1734         private static final String KEY_PREFIX_MAX_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "max_ej_";
1735         private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
1736         private static final String KEY_PREFIX_MAX_BGUSER =
1737                 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_";
1738         private static final String KEY_PREFIX_MAX_BGUSER_IMPORTANT =
1739                 CONFIG_KEY_PREFIX_CONCURRENCY + "max_bguser_important_";
1740         private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_";
1741         private static final String KEY_PREFIX_MIN_FGS = CONFIG_KEY_PREFIX_CONCURRENCY + "min_fgs_";
1742         private static final String KEY_PREFIX_MIN_EJ = CONFIG_KEY_PREFIX_CONCURRENCY + "min_ej_";
1743         private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
1744         private static final String KEY_PREFIX_MIN_BGUSER =
1745                 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_";
1746         private static final String KEY_PREFIX_MIN_BGUSER_IMPORTANT =
1747                 CONFIG_KEY_PREFIX_CONCURRENCY + "min_bguser_important_";
1748         private final String mConfigIdentifier;
1749 
1750         private int mMaxTotal;
1751         private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1752         private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
1753         private final int mDefaultMaxTotal;
1754         private final SparseIntArray mDefaultMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1755         private final SparseIntArray mDefaultMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
1756 
WorkTypeConfig(@onNull String configIdentifier, int defaultMaxTotal, List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax)1757         WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal,
1758                 List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) {
1759             mConfigIdentifier = configIdentifier;
1760             mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, STANDARD_CONCURRENCY_LIMIT);
1761             int numReserved = 0;
1762             for (int i = defaultMin.size() - 1; i >= 0; --i) {
1763                 mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second);
1764                 numReserved += defaultMin.get(i).second;
1765             }
1766             if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
1767                 // We only create new configs on boot, so this should trigger during development
1768                 // (before the code gets checked in), so this makes sure the hard-coded defaults
1769                 // make sense. DeviceConfig values will be handled gracefully in update().
1770                 throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
1771                         + " min=" + defaultMin + " max=" + defaultMax);
1772             }
1773             for (int i = defaultMax.size() - 1; i >= 0; --i) {
1774                 mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second);
1775             }
1776             update(new DeviceConfig.Properties.Builder(
1777                     DeviceConfig.NAMESPACE_JOB_SCHEDULER).build());
1778         }
1779 
update(@onNull DeviceConfig.Properties properties)1780         void update(@NonNull DeviceConfig.Properties properties) {
1781             // Ensure total in the range [1, STANDARD_CONCURRENCY_LIMIT].
1782             mMaxTotal = Math.max(1, Math.min(STANDARD_CONCURRENCY_LIMIT,
1783                     properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal)));
1784 
1785             mMaxAllowedSlots.clear();
1786             // Ensure they're in the range [1, total].
1787             final int maxTop = Math.max(1, Math.min(mMaxTotal,
1788                     properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier,
1789                             mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal))));
1790             mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop);
1791             final int maxFgs = Math.max(1, Math.min(mMaxTotal,
1792                     properties.getInt(KEY_PREFIX_MAX_FGS + mConfigIdentifier,
1793                             mDefaultMaxAllowedSlots.get(WORK_TYPE_FGS, mMaxTotal))));
1794             mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
1795             final int maxEj = Math.max(1, Math.min(mMaxTotal,
1796                     properties.getInt(KEY_PREFIX_MAX_EJ + mConfigIdentifier,
1797                             mDefaultMaxAllowedSlots.get(WORK_TYPE_EJ, mMaxTotal))));
1798             mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj);
1799             final int maxBg = Math.max(1, Math.min(mMaxTotal,
1800                     properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier,
1801                             mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal))));
1802             mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
1803             final int maxBgUserImp = Math.max(1, Math.min(mMaxTotal,
1804                     properties.getInt(KEY_PREFIX_MAX_BGUSER_IMPORTANT + mConfigIdentifier,
1805                             mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, mMaxTotal))));
1806             mMaxAllowedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, maxBgUserImp);
1807             final int maxBgUser = Math.max(1, Math.min(mMaxTotal,
1808                     properties.getInt(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
1809                             mDefaultMaxAllowedSlots.get(WORK_TYPE_BGUSER, mMaxTotal))));
1810             mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser);
1811 
1812             int remaining = mMaxTotal;
1813             mMinReservedSlots.clear();
1814             // Ensure top is in the range [1, min(maxTop, total)]
1815             final int minTop = Math.max(1, Math.min(Math.min(maxTop, mMaxTotal),
1816                     properties.getInt(KEY_PREFIX_MIN_TOP + mConfigIdentifier,
1817                             mDefaultMinReservedSlots.get(WORK_TYPE_TOP))));
1818             mMinReservedSlots.put(WORK_TYPE_TOP, minTop);
1819             remaining -= minTop;
1820             // Ensure fgs is in the range [0, min(maxFgs, remaining)]
1821             final int minFgs = Math.max(0, Math.min(Math.min(maxFgs, remaining),
1822                     properties.getInt(KEY_PREFIX_MIN_FGS + mConfigIdentifier,
1823                             mDefaultMinReservedSlots.get(WORK_TYPE_FGS))));
1824             mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
1825             remaining -= minFgs;
1826             // Ensure ej is in the range [0, min(maxEj, remaining)]
1827             final int minEj = Math.max(0, Math.min(Math.min(maxEj, remaining),
1828                     properties.getInt(KEY_PREFIX_MIN_EJ + mConfigIdentifier,
1829                             mDefaultMinReservedSlots.get(WORK_TYPE_EJ))));
1830             mMinReservedSlots.put(WORK_TYPE_EJ, minEj);
1831             remaining -= minEj;
1832             // Ensure bg is in the range [0, min(maxBg, remaining)]
1833             final int minBg = Math.max(0, Math.min(Math.min(maxBg, remaining),
1834                     properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier,
1835                             mDefaultMinReservedSlots.get(WORK_TYPE_BG))));
1836             mMinReservedSlots.put(WORK_TYPE_BG, minBg);
1837             remaining -= minBg;
1838             // Ensure bg user imp is in the range [0, min(maxBgUserImp, remaining)]
1839             final int minBgUserImp = Math.max(0, Math.min(Math.min(maxBgUserImp, remaining),
1840                     properties.getInt(KEY_PREFIX_MIN_BGUSER_IMPORTANT + mConfigIdentifier,
1841                             mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT, 0))));
1842             mMinReservedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, minBgUserImp);
1843             // Ensure bg user is in the range [0, min(maxBgUser, remaining)]
1844             final int minBgUser = Math.max(0, Math.min(Math.min(maxBgUser, remaining),
1845                     properties.getInt(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
1846                             mDefaultMinReservedSlots.get(WORK_TYPE_BGUSER, 0))));
1847             mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser);
1848         }
1849 
getMaxTotal()1850         int getMaxTotal() {
1851             return mMaxTotal;
1852         }
1853 
getMax(@orkType int workType)1854         int getMax(@WorkType int workType) {
1855             return mMaxAllowedSlots.get(workType, mMaxTotal);
1856         }
1857 
getMinReserved(@orkType int workType)1858         int getMinReserved(@WorkType int workType) {
1859             return mMinReservedSlots.get(workType);
1860         }
1861 
dump(IndentingPrintWriter pw)1862         void dump(IndentingPrintWriter pw) {
1863             pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println();
1864             pw.print(KEY_PREFIX_MIN_TOP + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_TOP))
1865                     .println();
1866             pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP))
1867                     .println();
1868             pw.print(KEY_PREFIX_MIN_FGS + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_FGS))
1869                     .println();
1870             pw.print(KEY_PREFIX_MAX_FGS + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_FGS))
1871                     .println();
1872             pw.print(KEY_PREFIX_MIN_EJ + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_EJ))
1873                     .println();
1874             pw.print(KEY_PREFIX_MAX_EJ + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_EJ))
1875                     .println();
1876             pw.print(KEY_PREFIX_MIN_BG + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BG))
1877                     .println();
1878             pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG))
1879                     .println();
1880             pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
1881                     mMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
1882             pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
1883                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
1884             pw.print(KEY_PREFIX_MIN_BGUSER + mConfigIdentifier,
1885                     mMinReservedSlots.get(WORK_TYPE_BGUSER)).println();
1886             pw.print(KEY_PREFIX_MAX_BGUSER + mConfigIdentifier,
1887                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println();
1888         }
1889     }
1890 
1891     /** {@link WorkTypeConfig} for each memory trim level. */
1892     static class WorkConfigLimitsPerMemoryTrimLevel {
1893         public final WorkTypeConfig normal;
1894         public final WorkTypeConfig moderate;
1895         public final WorkTypeConfig low;
1896         public final WorkTypeConfig critical;
1897 
WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, WorkTypeConfig low, WorkTypeConfig critical)1898         WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate,
1899                 WorkTypeConfig low, WorkTypeConfig critical) {
1900             this.normal = normal;
1901             this.moderate = moderate;
1902             this.low = low;
1903             this.critical = critical;
1904         }
1905     }
1906 
1907     /**
1908      * This class keeps the track of when a user's grace period expires.
1909      */
1910     @VisibleForTesting
1911     static class GracePeriodObserver extends UserSwitchObserver {
1912         // Key is UserId and Value is the time when grace period expires
1913         @VisibleForTesting
1914         final SparseLongArray mGracePeriodExpiration = new SparseLongArray();
1915         private int mCurrentUserId;
1916         @VisibleForTesting
1917         int mGracePeriod;
1918         private final UserManagerInternal mUserManagerInternal;
1919         final Object mLock = new Object();
1920 
1921 
GracePeriodObserver(Context context)1922         GracePeriodObserver(Context context) {
1923             mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class)
1924                     .getCurrentUserId();
1925             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
1926             mGracePeriod = Math.max(0, context.getResources().getInteger(
1927                     R.integer.config_jobSchedulerUserGracePeriod));
1928         }
1929 
1930         @Override
onUserSwitchComplete(int newUserId)1931         public void onUserSwitchComplete(int newUserId) {
1932             final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod;
1933             synchronized (mLock) {
1934                 if (mCurrentUserId != UserHandle.USER_NULL
1935                         && mUserManagerInternal.exists(mCurrentUserId)) {
1936                     mGracePeriodExpiration.append(mCurrentUserId, expiration);
1937                 }
1938                 mGracePeriodExpiration.delete(newUserId);
1939                 mCurrentUserId = newUserId;
1940             }
1941         }
1942 
onUserRemoved(int userId)1943         void onUserRemoved(int userId) {
1944             synchronized (mLock) {
1945                 mGracePeriodExpiration.delete(userId);
1946             }
1947         }
1948 
1949         @VisibleForTesting
isWithinGracePeriodForUser(int userId)1950         public boolean isWithinGracePeriodForUser(int userId) {
1951             synchronized (mLock) {
1952                 return userId == mCurrentUserId
1953                         || sElapsedRealtimeClock.millis()
1954                         < mGracePeriodExpiration.get(userId, Long.MAX_VALUE);
1955             }
1956         }
1957     }
1958 
1959     /**
1960      * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
1961      * are running/pending, how many more job can start.
1962      *
1963      * Extracted for testing and logging.
1964      */
1965     @VisibleForTesting
1966     static class WorkCountTracker {
1967         private int mConfigMaxTotal;
1968         private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1969         private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
1970         private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES);
1971 
1972         /**
1973          * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't
1974          * enough ready jobs of a type to take up all of the desired reserved slots.
1975          */
1976         private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
1977         private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES);
1978         private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES);
1979         private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES);
1980         private int mNumUnspecializedRemaining = 0;
1981 
setConfig(@onNull WorkTypeConfig workTypeConfig)1982         void setConfig(@NonNull WorkTypeConfig workTypeConfig) {
1983             mConfigMaxTotal = workTypeConfig.getMaxTotal();
1984             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
1985                 mConfigNumReservedSlots.put(workType, workTypeConfig.getMinReserved(workType));
1986                 mConfigAbsoluteMaxSlots.put(workType, workTypeConfig.getMax(workType));
1987             }
1988 
1989             mNumUnspecializedRemaining = mConfigMaxTotal;
1990             for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
1991                 mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
1992                         mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
1993             }
1994         }
1995 
resetCounts()1996         void resetCounts() {
1997             mNumActuallyReservedSlots.clear();
1998             mNumPendingJobs.clear();
1999             mNumRunningJobs.clear();
2000             resetStagingCount();
2001         }
2002 
resetStagingCount()2003         void resetStagingCount() {
2004             mNumStartingJobs.clear();
2005         }
2006 
incrementRunningJobCount(@orkType int workType)2007         void incrementRunningJobCount(@WorkType int workType) {
2008             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
2009         }
2010 
incrementPendingJobCount(int workTypes)2011         void incrementPendingJobCount(int workTypes) {
2012             adjustPendingJobCount(workTypes, true);
2013         }
2014 
decrementPendingJobCount(int workTypes)2015         void decrementPendingJobCount(int workTypes) {
2016             if (adjustPendingJobCount(workTypes, false) > 1) {
2017                 // We don't need to adjust reservations if only one work type was modified
2018                 // because that work type is the one we're using.
2019 
2020                 for (int workType = 1; workType <= workTypes; workType <<= 1) {
2021                     if ((workType & workTypes) == workType) {
2022                         maybeAdjustReservations(workType);
2023                     }
2024                 }
2025             }
2026         }
2027 
2028         /** Returns the number of WorkTypes that were modified. */
adjustPendingJobCount(int workTypes, boolean add)2029         private int adjustPendingJobCount(int workTypes, boolean add) {
2030             final int adj = add ? 1 : -1;
2031 
2032             int numAdj = 0;
2033             // We don't know which type we'll classify the job as when we run it yet, so make sure
2034             // we have space in all applicable slots.
2035             for (int workType = 1; workType <= workTypes; workType <<= 1) {
2036                 if ((workTypes & workType) == workType) {
2037                     mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj);
2038                     numAdj++;
2039                 }
2040             }
2041 
2042             return numAdj;
2043         }
2044 
stageJob(@orkType int workType, int allWorkTypes)2045         void stageJob(@WorkType int workType, int allWorkTypes) {
2046             final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
2047             mNumStartingJobs.put(workType, newNumStartingJobs);
2048             decrementPendingJobCount(allWorkTypes);
2049             if (newNumStartingJobs + mNumRunningJobs.get(workType)
2050                     > mNumActuallyReservedSlots.get(workType)) {
2051                 mNumUnspecializedRemaining--;
2052             }
2053         }
2054 
onStagedJobFailed(@orkType int workType)2055         void onStagedJobFailed(@WorkType int workType) {
2056             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
2057             if (oldNumStartingJobs == 0) {
2058                 Slog.e(TAG, "# staged jobs for " + workType + " went negative.");
2059                 // We are in a bad state. We will eventually recover when the pending list is
2060                 // regenerated.
2061                 return;
2062             }
2063             mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
2064             maybeAdjustReservations(workType);
2065         }
2066 
maybeAdjustReservations(@orkType int workType)2067         private void maybeAdjustReservations(@WorkType int workType) {
2068             // Always make sure we reserve the minimum number of slots in case new jobs become ready
2069             // soon.
2070             final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType),
2071                     mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
2072                             + mNumPendingJobs.get(workType));
2073             if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) {
2074                 // We've run all jobs for this type. Let another type use it now.
2075                 mNumActuallyReservedSlots.put(workType, numRemainingForType);
2076                 int assignWorkType = WORK_TYPE_NONE;
2077                 for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
2078                     int wt = mNumActuallyReservedSlots.keyAt(i);
2079                     if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
2080                         // Try to give this slot to the highest bias one within its limits.
2081                         int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
2082                                 + mNumPendingJobs.get(wt);
2083                         if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
2084                                 && total > mNumActuallyReservedSlots.valueAt(i)) {
2085                             assignWorkType = wt;
2086                         }
2087                     }
2088                 }
2089                 if (assignWorkType != WORK_TYPE_NONE) {
2090                     mNumActuallyReservedSlots.put(assignWorkType,
2091                             mNumActuallyReservedSlots.get(assignWorkType) + 1);
2092                 } else {
2093                     mNumUnspecializedRemaining++;
2094                 }
2095             }
2096         }
2097 
onJobStarted(@orkType int workType)2098         void onJobStarted(@WorkType int workType) {
2099             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
2100             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
2101             if (oldNumStartingJobs == 0) {
2102                 Slog.e(TAG, "# stated jobs for " + workType + " went negative.");
2103                 // We are in a bad state. We will eventually recover when the pending list is
2104                 // regenerated. For now, only modify the running count.
2105             } else {
2106                 mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
2107             }
2108         }
2109 
onJobFinished(@orkType int workType)2110         void onJobFinished(@WorkType int workType) {
2111             final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1;
2112             if (newNumRunningJobs < 0) {
2113                 // We are in a bad state. We will eventually recover when the pending list is
2114                 // regenerated.
2115                 Slog.e(TAG, "# running jobs for " + workType + " went negative.");
2116                 return;
2117             }
2118             mNumRunningJobs.put(workType, newNumRunningJobs);
2119             maybeAdjustReservations(workType);
2120         }
2121 
onCountDone()2122         void onCountDone() {
2123             // Calculate how many slots to reserve for each work type. "Unspecialized" slots will
2124             // be reserved for higher importance types first (ie. top before ej before bg).
2125             // Steps:
2126             //   1. Account for slots for already running jobs
2127             //   2. Use remaining unaccounted slots to try and ensure minimum reserved slots
2128             //   3. Allocate remaining up to max, based on importance
2129 
2130             mNumUnspecializedRemaining = mConfigMaxTotal;
2131 
2132             // Step 1
2133             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2134                 int run = mNumRunningJobs.get(workType);
2135                 mRecycledReserved.put(workType, run);
2136                 mNumUnspecializedRemaining -= run;
2137             }
2138 
2139             // Step 2
2140             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2141                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
2142                 int res = mRecycledReserved.get(workType);
2143                 int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
2144                         Math.min(num, mConfigNumReservedSlots.get(workType) - res)));
2145                 res += fillUp;
2146                 mRecycledReserved.put(workType, res);
2147                 mNumUnspecializedRemaining -= fillUp;
2148             }
2149 
2150             // Step 3
2151             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2152                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
2153                 int res = mRecycledReserved.get(workType);
2154                 int unspecializedAssigned = Math.max(0,
2155                         Math.min(mNumUnspecializedRemaining,
2156                                 Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res));
2157                 mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned);
2158                 mNumUnspecializedRemaining -= unspecializedAssigned;
2159             }
2160         }
2161 
canJobStart(int workTypes)2162         int canJobStart(int workTypes) {
2163             for (int workType = 1; workType <= workTypes; workType <<= 1) {
2164                 if ((workTypes & workType) == workType) {
2165                     final int maxAllowed = Math.min(
2166                             mConfigAbsoluteMaxSlots.get(workType),
2167                             mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining);
2168                     if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
2169                             < maxAllowed) {
2170                         return workType;
2171                     }
2172                 }
2173             }
2174             return WORK_TYPE_NONE;
2175         }
2176 
canJobStart(int workTypes, @WorkType int replacingWorkType)2177         int canJobStart(int workTypes, @WorkType int replacingWorkType) {
2178             final boolean changedNums;
2179             int oldNumRunning = mNumRunningJobs.get(replacingWorkType);
2180             if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) {
2181                 mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1);
2182                 // Lazy implementation to avoid lots of processing. Best way would be to go
2183                 // through the whole process of adjusting reservations, but the processing cost
2184                 // is likely not worth it.
2185                 mNumUnspecializedRemaining++;
2186                 changedNums = true;
2187             } else {
2188                 changedNums = false;
2189             }
2190 
2191             final int ret = canJobStart(workTypes);
2192             if (changedNums) {
2193                 mNumRunningJobs.put(replacingWorkType, oldNumRunning);
2194                 mNumUnspecializedRemaining--;
2195             }
2196             return ret;
2197         }
2198 
getPendingJobCount(@orkType final int workType)2199         int getPendingJobCount(@WorkType final int workType) {
2200             return mNumPendingJobs.get(workType, 0);
2201         }
2202 
getRunningJobCount(@orkType final int workType)2203         int getRunningJobCount(@WorkType final int workType) {
2204             return mNumRunningJobs.get(workType, 0);
2205         }
2206 
isOverTypeLimit(@orkType final int workType)2207         boolean isOverTypeLimit(@WorkType final int workType) {
2208             return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType);
2209         }
2210 
toString()2211         public String toString() {
2212             StringBuilder sb = new StringBuilder();
2213 
2214             sb.append("Config={");
2215             sb.append("tot=").append(mConfigMaxTotal);
2216             sb.append(" mins=");
2217             sb.append(mConfigNumReservedSlots);
2218             sb.append(" maxs=");
2219             sb.append(mConfigAbsoluteMaxSlots);
2220             sb.append("}");
2221 
2222             sb.append(", act res=").append(mNumActuallyReservedSlots);
2223             sb.append(", Pending=").append(mNumPendingJobs);
2224             sb.append(", Running=").append(mNumRunningJobs);
2225             sb.append(", Staged=").append(mNumStartingJobs);
2226             sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining);
2227 
2228             return sb.toString();
2229         }
2230     }
2231 
2232     @VisibleForTesting
2233     static class PackageStats {
2234         public int userId;
2235         public String packageName;
2236         public int numRunningEj;
2237         public int numRunningRegular;
2238         public int numStagedEj;
2239         public int numStagedRegular;
2240 
setPackage(int userId, @NonNull String packageName)2241         private void setPackage(int userId, @NonNull String packageName) {
2242             this.userId = userId;
2243             this.packageName = packageName;
2244             numRunningEj = numRunningRegular = 0;
2245             resetStagedCount();
2246         }
2247 
resetStagedCount()2248         private void resetStagedCount() {
2249             numStagedEj = numStagedRegular = 0;
2250         }
2251 
adjustRunningCount(boolean add, boolean forEj)2252         private void adjustRunningCount(boolean add, boolean forEj) {
2253             if (forEj) {
2254                 numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1));
2255             } else {
2256                 numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1));
2257             }
2258         }
2259 
adjustStagedCount(boolean add, boolean forEj)2260         private void adjustStagedCount(boolean add, boolean forEj) {
2261             if (forEj) {
2262                 numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1));
2263             } else {
2264                 numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1));
2265             }
2266         }
2267 
2268         @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw)2269         private void dumpLocked(IndentingPrintWriter pw) {
2270             pw.print("PackageStats{");
2271             pw.print(userId);
2272             pw.print("-");
2273             pw.print(packageName);
2274             pw.print("#runEJ", numRunningEj);
2275             pw.print("#runReg", numRunningRegular);
2276             pw.print("#stagedEJ", numStagedEj);
2277             pw.print("#stagedReg", numStagedRegular);
2278             pw.println("}");
2279         }
2280     }
2281 
2282     private static final class ContextAssignment {
2283         public JobServiceContext context;
2284         public int preferredUid = JobServiceContext.NO_PREFERRED_UID;
2285         public int workType = WORK_TYPE_NONE;
2286         public String preemptReason;
2287         public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
2288         public String shouldStopJobReason;
2289         public JobStatus newJob;
2290         public int newWorkType = WORK_TYPE_NONE;
2291 
clear()2292         void clear() {
2293             context = null;
2294             preferredUid = JobServiceContext.NO_PREFERRED_UID;
2295             workType = WORK_TYPE_NONE;
2296             preemptReason = null;
2297             preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
2298             shouldStopJobReason = null;
2299             newJob = null;
2300             newWorkType = WORK_TYPE_NONE;
2301         }
2302     }
2303 
2304     // TESTING HELPERS
2305 
2306     @VisibleForTesting
addRunningJobForTesting(@onNull JobStatus job)2307     void addRunningJobForTesting(@NonNull JobStatus job) {
2308         mRunningJobs.add(job);
2309         final PackageStats packageStats =
2310                 getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName());
2311         packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob());
2312     }
2313 
2314     @VisibleForTesting
getPackageConcurrencyLimitEj()2315     int getPackageConcurrencyLimitEj() {
2316         return mPkgConcurrencyLimitEj;
2317     }
2318 
getPackageConcurrencyLimitRegular()2319     int getPackageConcurrencyLimitRegular() {
2320         return mPkgConcurrencyLimitRegular;
2321     }
2322 
2323     /** Gets the {@link PackageStats} object for the app and saves it for testing use. */
2324     @NonNull
2325     @VisibleForTesting
getPackageStatsForTesting(int userId, @NonNull String packageName)2326     PackageStats getPackageStatsForTesting(int userId, @NonNull String packageName) {
2327         final PackageStats packageStats = getPkgStatsLocked(userId, packageName);
2328         mActivePkgStats.add(userId, packageName, packageStats);
2329         return packageStats;
2330     }
2331 }
2332