• Home
  • History
  • Annotate
  • 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 android.text.format.DateUtils.MINUTE_IN_MILLIS;
20 import static android.util.DataUnit.GIGABYTES;
21 
22 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
23 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
24 
25 import android.annotation.IntDef;
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.ActivityManager;
29 import android.app.ActivityManagerInternal;
30 import android.app.BackgroundStartPrivileges;
31 import android.app.UserSwitchObserver;
32 import android.app.job.JobInfo;
33 import android.app.job.JobParameters;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.content.pm.UserInfo;
39 import android.os.BatteryStats;
40 import android.os.Handler;
41 import android.os.Looper;
42 import android.os.PowerManager;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.os.UserHandle;
46 import android.provider.DeviceConfig;
47 import android.util.ArraySet;
48 import android.util.IndentingPrintWriter;
49 import android.util.Pair;
50 import android.util.Pools;
51 import android.util.Slog;
52 import android.util.SparseArrayMap;
53 import android.util.SparseIntArray;
54 import android.util.SparseLongArray;
55 import android.util.TimeUtils;
56 import android.util.proto.ProtoOutputStream;
57 
58 import com.android.internal.R;
59 import com.android.internal.annotations.GuardedBy;
60 import com.android.internal.annotations.VisibleForTesting;
61 import com.android.internal.app.IBatteryStats;
62 import com.android.internal.app.procstats.ProcessStats;
63 import com.android.internal.util.MemInfoReader;
64 import com.android.internal.util.StatLogger;
65 import com.android.modules.expresslog.Histogram;
66 import com.android.server.AppSchedulingModuleThread;
67 import com.android.server.LocalServices;
68 import com.android.server.job.controllers.JobStatus;
69 import com.android.server.job.controllers.StateController;
70 import com.android.server.job.restrictions.JobRestriction;
71 import com.android.server.pm.UserManagerInternal;
72 
73 import java.io.PrintWriter;
74 import java.lang.annotation.Retention;
75 import java.lang.annotation.RetentionPolicy;
76 import java.util.ArrayList;
77 import java.util.Collection;
78 import java.util.Comparator;
79 import java.util.List;
80 import java.util.function.Consumer;
81 import java.util.function.Predicate;
82 
83 /**
84  * This class decides, given the various configuration and the system status, which jobs can start
85  * and which {@link JobServiceContext} to run each job on.
86  */
87 class JobConcurrencyManager {
88     private static final String TAG = JobSchedulerService.TAG + ".Concurrency";
89     private static final boolean DEBUG = JobSchedulerService.DEBUG;
90 
91     /** The maximum number of concurrent jobs we'll aim to run at one time. */
92     @VisibleForTesting
93     static final int MAX_CONCURRENCY_LIMIT = 64;
94     /** The maximum number of objects we should retain in memory when not in use. */
95     private static final int MAX_RETAINED_OBJECTS = (int) (1.5 * MAX_CONCURRENCY_LIMIT);
96 
97     static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
98     private static final String KEY_CONCURRENCY_LIMIT = CONFIG_KEY_PREFIX_CONCURRENCY + "limit";
99     static final int DEFAULT_CONCURRENCY_LIMIT;
100 
101     static {
102         if (ActivityManager.isLowRamDeviceStatic()) {
103             DEFAULT_CONCURRENCY_LIMIT = 8;
104         } else {
105             final long ramBytes = new MemInfoReader().getTotalSize();
106             if (ramBytes <= GIGABYTES.toBytes(6)) {
107                 DEFAULT_CONCURRENCY_LIMIT = 16;
108             } else if (ramBytes <= GIGABYTES.toBytes(8)) {
109                 DEFAULT_CONCURRENCY_LIMIT = 20;
110             } else if (ramBytes <= GIGABYTES.toBytes(12)) {
111                 DEFAULT_CONCURRENCY_LIMIT = 32;
112             } else {
113                 DEFAULT_CONCURRENCY_LIMIT = 40;
114             }
115         }
116     }
117 
118     private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS =
119             CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
120     private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
121     @VisibleForTesting
122     static final String KEY_PKG_CONCURRENCY_LIMIT_EJ =
123             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej";
124     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3;
125     @VisibleForTesting
126     static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
127             CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
128     private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = DEFAULT_CONCURRENCY_LIMIT / 2;
129     @VisibleForTesting
130     static final String KEY_ENABLE_MAX_WAIT_TIME_BYPASS =
131             CONFIG_KEY_PREFIX_CONCURRENCY + "enable_max_wait_time_bypass";
132     private static final boolean DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS = true;
133     @VisibleForTesting
134     static final String KEY_MAX_WAIT_UI_MS = CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_ui_ms";
135     @VisibleForTesting
136     static final long DEFAULT_MAX_WAIT_UI_MS = 5 * MINUTE_IN_MILLIS;
137     private static final String KEY_MAX_WAIT_EJ_MS =
138             CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_ej_ms";
139     @VisibleForTesting
140     static final long DEFAULT_MAX_WAIT_EJ_MS = 5 * MINUTE_IN_MILLIS;
141     private static final String KEY_MAX_WAIT_REGULAR_MS =
142             CONFIG_KEY_PREFIX_CONCURRENCY + "max_wait_regular_ms";
143     @VisibleForTesting
144     static final long DEFAULT_MAX_WAIT_REGULAR_MS = 30 * MINUTE_IN_MILLIS;
145 
146     /**
147      * Set of possible execution types that a job can have. The actual type(s) of a job are based
148      * on the {@link JobStatus#lastEvaluatedBias}, which is typically evaluated right before
149      * execution (when we're trying to determine which jobs to run next) and won't change after the
150      * job has started executing.
151      *
152      * Try to give higher priority types lower values.
153      *
154      * @see #getJobWorkTypes(JobStatus)
155      */
156 
157     /** Job shouldn't run or qualify as any other work type. */
158     static final int WORK_TYPE_NONE = 0;
159     /** The job is for an app in the TOP state for a currently active user. */
160     static final int WORK_TYPE_TOP = 1 << 0;
161     /**
162      * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
163      * state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user.
164      */
165     static final int WORK_TYPE_FGS = 1 << 1;
166     /** The job is allowed to run as a user-initiated job for a currently active user. */
167     static final int WORK_TYPE_UI = 1 << 2;
168     /** The job is allowed to run as an expedited job for a currently active user. */
169     static final int WORK_TYPE_EJ = 1 << 3;
170     /**
171      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
172      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so
173      * can run as a background job.
174      */
175     static final int WORK_TYPE_BG = 1 << 4;
176     /**
177      * The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
178      * state, or is allowed to run as an expedited or user-initiated job,
179      * but is for a completely background user.
180      */
181     static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 5;
182     /**
183      * The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
184      * {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user,
185      * so can run as a background user job.
186      */
187     static final int WORK_TYPE_BGUSER = 1 << 6;
188     @VisibleForTesting
189     static final int NUM_WORK_TYPES = 7;
190     private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1;
191 
192     @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
193             WORK_TYPE_NONE,
194             WORK_TYPE_TOP,
195             WORK_TYPE_FGS,
196             WORK_TYPE_UI,
197             WORK_TYPE_EJ,
198             WORK_TYPE_BG,
199             WORK_TYPE_BGUSER_IMPORTANT,
200             WORK_TYPE_BGUSER
201     })
202     @Retention(RetentionPolicy.SOURCE)
203     public @interface WorkType {
204     }
205 
206     @VisibleForTesting
workTypeToString(@orkType int workType)207     static String workTypeToString(@WorkType int workType) {
208         switch (workType) {
209             case WORK_TYPE_NONE:
210                 return "NONE";
211             case WORK_TYPE_TOP:
212                 return "TOP";
213             case WORK_TYPE_FGS:
214                 return "FGS";
215             case WORK_TYPE_UI:
216                 return "UI";
217             case WORK_TYPE_EJ:
218                 return "EJ";
219             case WORK_TYPE_BG:
220                 return "BG";
221             case WORK_TYPE_BGUSER:
222                 return "BGUSER";
223             case WORK_TYPE_BGUSER_IMPORTANT:
224                 return "BGUSER_IMPORTANT";
225             default:
226                 return "WORK(" + workType + ")";
227         }
228     }
229 
230     private final Object mLock;
231     private final JobNotificationCoordinator mNotificationCoordinator;
232     private final JobSchedulerService mService;
233     private final Context mContext;
234     private final Handler mHandler;
235     private final Injector mInjector;
236 
237     private final ActivityManagerInternal mActivityManagerInternal;
238     private PowerManager mPowerManager;
239     private final UserManagerInternal mUserManagerInternal;
240 
241     private boolean mCurrentInteractiveState;
242     private boolean mEffectiveInteractiveState;
243 
244     private long mLastScreenOnRealtime;
245     private long mLastScreenOffRealtime;
246 
247     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
248             new WorkConfigLimitsPerMemoryTrimLevel(
249                     new WorkTypeConfig("screen_on_normal", DEFAULT_CONCURRENCY_LIMIT,
250                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 3 / 4,
251                             // defaultMin
252                             List.of(Pair.create(WORK_TYPE_TOP, .4f),
253                                     Pair.create(WORK_TYPE_FGS, .2f),
254                                     Pair.create(WORK_TYPE_UI, .1f),
255                                     Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
256                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
257                             // defaultMax
258                             List.of(Pair.create(WORK_TYPE_BG, .5f),
259                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .25f),
260                                     Pair.create(WORK_TYPE_BGUSER, .2f))
261                     ),
262                     new WorkTypeConfig("screen_on_moderate", DEFAULT_CONCURRENCY_LIMIT,
263                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT / 2,
264                             // defaultMin
265                             List.of(Pair.create(WORK_TYPE_TOP, .4f),
266                                     Pair.create(WORK_TYPE_FGS, .1f),
267                                     Pair.create(WORK_TYPE_UI, .1f),
268                                     Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .1f),
269                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
270                             // defaultMax
271                             List.of(Pair.create(WORK_TYPE_BG, .4f),
272                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
273                                     Pair.create(WORK_TYPE_BGUSER, .1f))
274                     ),
275                     new WorkTypeConfig("screen_on_low", DEFAULT_CONCURRENCY_LIMIT,
276                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
277                             // defaultMin
278                             List.of(Pair.create(WORK_TYPE_TOP, .6f),
279                                     Pair.create(WORK_TYPE_FGS, .1f),
280                                     Pair.create(WORK_TYPE_UI, .1f),
281                                     Pair.create(WORK_TYPE_EJ, .1f)),
282                             // defaultMax
283                             List.of(Pair.create(WORK_TYPE_BG, 1.0f / 3),
284                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 6),
285                                     Pair.create(WORK_TYPE_BGUSER, 1.0f / 6))
286                     ),
287                     new WorkTypeConfig("screen_on_critical", DEFAULT_CONCURRENCY_LIMIT,
288                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
289                             // defaultMin
290                             List.of(Pair.create(WORK_TYPE_TOP, .7f),
291                                     Pair.create(WORK_TYPE_FGS, .1f),
292                                     Pair.create(WORK_TYPE_UI, .1f),
293                                     Pair.create(WORK_TYPE_EJ, .05f)),
294                             // defaultMax
295                             List.of(Pair.create(WORK_TYPE_BG, 1.0f / 6),
296                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 6),
297                                     Pair.create(WORK_TYPE_BGUSER, 1.0f / 6))
298                     )
299             );
300     private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
301             new WorkConfigLimitsPerMemoryTrimLevel(
302                     new WorkTypeConfig("screen_off_normal", DEFAULT_CONCURRENCY_LIMIT,
303                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT,
304                             // defaultMin
305                             List.of(Pair.create(WORK_TYPE_TOP, .3f),
306                                     Pair.create(WORK_TYPE_FGS, .2f),
307                                     Pair.create(WORK_TYPE_UI, .2f),
308                                     Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
309                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
310                             // defaultMax
311                             List.of(Pair.create(WORK_TYPE_BG, .6f),
312                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .2f),
313                                     Pair.create(WORK_TYPE_BGUSER, .2f))
314                     ),
315                     new WorkTypeConfig("screen_off_moderate", DEFAULT_CONCURRENCY_LIMIT,
316                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 9 / 10,
317                             // defaultMin
318                             List.of(Pair.create(WORK_TYPE_TOP, .3f),
319                                     Pair.create(WORK_TYPE_FGS, .2f),
320                                     Pair.create(WORK_TYPE_UI, .2f),
321                                     Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
322                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
323                             // defaultMax
324                             List.of(Pair.create(WORK_TYPE_BG, .5f),
325                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
326                                     Pair.create(WORK_TYPE_BGUSER, .1f))
327                     ),
328                     new WorkTypeConfig("screen_off_low", DEFAULT_CONCURRENCY_LIMIT,
329                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 6 / 10,
330                             // defaultMin
331                             List.of(Pair.create(WORK_TYPE_TOP, .3f),
332                                     Pair.create(WORK_TYPE_FGS, .15f),
333                                     Pair.create(WORK_TYPE_UI, .15f),
334                                     Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
335                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
336                             // defaultMax
337                             List.of(Pair.create(WORK_TYPE_BG, .25f),
338                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
339                                     Pair.create(WORK_TYPE_BGUSER, .1f))
340                     ),
341                     new WorkTypeConfig("screen_off_critical", DEFAULT_CONCURRENCY_LIMIT,
342                             /* defaultMaxTotal */  DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
343                             // defaultMin
344                             List.of(Pair.create(WORK_TYPE_TOP, .3f),
345                                     Pair.create(WORK_TYPE_FGS, .1f),
346                                     Pair.create(WORK_TYPE_UI, .1f),
347                                     Pair.create(WORK_TYPE_EJ, .05f)),
348                             // defaultMax
349                             List.of(Pair.create(WORK_TYPE_BG, .1f),
350                                     Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
351                                     Pair.create(WORK_TYPE_BGUSER, .1f))
352                     )
353             );
354 
355     /**
356      * Comparator to sort the determination lists, putting the ContextAssignments that we most
357      * prefer to use at the end of the list.
358      */
359     private static final Comparator<ContextAssignment> sDeterminationComparator = (ca1, ca2) -> {
360         if (ca1 == ca2) {
361             return 0;
362         }
363         final JobStatus js1 = ca1.context.getRunningJobLocked();
364         final JobStatus js2 = ca2.context.getRunningJobLocked();
365         // Prefer using an empty context over one with a running job.
366         if (js1 == null) {
367             if (js2 == null) {
368                 return 0;
369             }
370             return 1;
371         } else if (js2 == null) {
372             return -1;
373         }
374         // We would prefer to replace bg jobs over TOP jobs.
375         if (js1.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
376             if (js2.lastEvaluatedBias != JobInfo.BIAS_TOP_APP) {
377                 return -1;
378             }
379         } else if (js2.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
380             return 1;
381         }
382         // Prefer replacing the job that has been running the longest.
383         return Long.compare(
384                 ca2.context.getExecutionStartTimeElapsed(),
385                 ca1.context.getExecutionStartTimeElapsed());
386     };
387 
388     // We reuse the lists to avoid GC churn.
389     private final ArraySet<ContextAssignment> mRecycledChanged = new ArraySet<>();
390     private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
391     private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
392     private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
393     private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();
394     private final SparseIntArray mRecycledPrivilegedState = new SparseIntArray();
395 
396     private static final int PRIVILEGED_STATE_UNDEFINED = 0;
397     private static final int PRIVILEGED_STATE_NONE = 1;
398     private static final int PRIVILEGED_STATE_BAL = 2;
399     private static final int PRIVILEGED_STATE_TOP = 3;
400 
401     private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
402             new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
403 
404     /**
405      * Set of JobServiceContexts that are actively running jobs.
406      */
407     final List<JobServiceContext> mActiveServices = new ArrayList<>();
408 
409     /** Set of JobServiceContexts that aren't currently running any jobs. */
410     private final ArraySet<JobServiceContext> mIdleContexts = new ArraySet<>();
411 
412     private int mNumDroppedContexts = 0;
413 
414     private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
415 
416     private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
417 
418     private final Pools.Pool<PackageStats> mPkgStatsPool =
419             new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
420 
421     private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>();
422 
423     private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;
424 
425     /** Wait for this long after screen off before adjusting the job concurrency. */
426     private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
427 
428     /**
429      * The maximum number of jobs we'll attempt to have running at one time. This may occasionally
430      * be exceeded based on other factors.
431      */
432     private int mSteadyStateConcurrencyLimit = DEFAULT_CONCURRENCY_LIMIT;
433 
434     /**
435      * The maximum number of expedited jobs a single userId-package can have running simultaneously.
436      * TOP apps are not limited.
437      */
438     private int mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ;
439 
440     /**
441      * The maximum number of regular jobs a single userId-package can have running simultaneously.
442      * TOP apps are not limited.
443      */
444     private int mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
445 
446     private boolean mMaxWaitTimeBypassEnabled = DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS;
447 
448     /**
449      * The maximum time a user-initiated job would have to be potentially waiting for an available
450      * slot before we would consider creating a new slot for it.
451      */
452     private long mMaxWaitUIMs = DEFAULT_MAX_WAIT_UI_MS;
453 
454     /**
455      * The maximum time an expedited job would have to be potentially waiting for an available
456      * slot before we would consider creating a new slot for it.
457      */
458     private long mMaxWaitEjMs = DEFAULT_MAX_WAIT_EJ_MS;
459 
460     /**
461      * The maximum time a regular job would have to be potentially waiting for an available
462      * slot before we would consider creating a new slot for it.
463      */
464     private long mMaxWaitRegularMs = DEFAULT_MAX_WAIT_REGULAR_MS;
465 
466     /** Current memory trim level. */
467     private int mLastMemoryTrimLevel;
468 
469     /** Used to throttle heavy API calls. */
470     private long mNextSystemStateRefreshTime;
471     private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
472 
473     private final Consumer<PackageStats> mPackageStatsStagingCountClearer =
474             PackageStats::resetStagedCount;
475 
476     private static final Histogram sConcurrencyHistogramLogger = new Histogram(
477             "job_scheduler.value_hist_job_concurrency",
478             // Create a histogram that expects values in the range [0, 99].
479             // Include more buckets than MAX_CONCURRENCY_LIMIT to account for/identify the cases
480             // where we may create additional slots for TOP-started EJs and UIJs
481             new Histogram.UniformOptions(100, 0, 99));
482 
483     private final StatLogger mStatLogger = new StatLogger(new String[]{
484             "assignJobsToContexts",
485             "refreshSystemState",
486     });
487     @VisibleForTesting
488     GracePeriodObserver mGracePeriodObserver;
489     @VisibleForTesting
490     boolean mShouldRestrictBgUser;
491 
492     interface Stats {
493         int ASSIGN_JOBS_TO_CONTEXTS = 0;
494         int REFRESH_SYSTEM_STATE = 1;
495 
496         int COUNT = REFRESH_SYSTEM_STATE + 1;
497     }
498 
JobConcurrencyManager(JobSchedulerService service)499     JobConcurrencyManager(JobSchedulerService service) {
500         this(service, new Injector());
501     }
502 
503     @VisibleForTesting
JobConcurrencyManager(JobSchedulerService service, Injector injector)504     JobConcurrencyManager(JobSchedulerService service, Injector injector) {
505         mService = service;
506         mLock = mService.getLock();
507         mContext = service.getTestableContext();
508         mInjector = injector;
509         mNotificationCoordinator = new JobNotificationCoordinator();
510 
511         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
512         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
513 
514         mHandler = AppSchedulingModuleThread.getHandler();
515 
516         mGracePeriodObserver = new GracePeriodObserver(mContext);
517         mShouldRestrictBgUser = mContext.getResources().getBoolean(
518                 R.bool.config_jobSchedulerRestrictBackgroundUser);
519     }
520 
onSystemReady()521     public void onSystemReady() {
522         mPowerManager = mContext.getSystemService(PowerManager.class);
523 
524         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
525         filter.addAction(Intent.ACTION_SCREEN_OFF);
526         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
527         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
528         mContext.registerReceiver(mReceiver, filter);
529         try {
530             ActivityManager.getService().registerUserSwitchObserver(mGracePeriodObserver, TAG);
531         } catch (RemoteException e) {
532         }
533 
534         onInteractiveStateChanged(mPowerManager.isInteractive());
535     }
536 
537     /**
538      * Called when the boot phase reaches
539      * {@link com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START}.
540      */
onThirdPartyAppsCanStart()541     void onThirdPartyAppsCanStart() {
542         final IBatteryStats batteryStats = IBatteryStats.Stub.asInterface(
543                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
544         for (int i = 0; i < mSteadyStateConcurrencyLimit; ++i) {
545             mIdleContexts.add(
546                     mInjector.createJobServiceContext(mService, this,
547                             mNotificationCoordinator, batteryStats,
548                             mService.mJobPackageTracker,
549                             AppSchedulingModuleThread.get().getLooper()));
550         }
551     }
552 
553     @GuardedBy("mLock")
onAppRemovedLocked(String pkgName, int uid)554     void onAppRemovedLocked(String pkgName, int uid) {
555         final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName);
556         if (packageStats != null) {
557             if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) {
558                 // Don't delete the object just yet. We'll remove it in onJobCompleted() when the
559                 // jobs officially stop running.
560                 Slog.w(TAG,
561                         pkgName + "(" + uid + ") marked as removed before jobs stopped running");
562             } else {
563                 mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName);
564             }
565         }
566     }
567 
onUserRemoved(int userId)568     void onUserRemoved(int userId) {
569         mGracePeriodObserver.onUserRemoved(userId);
570     }
571 
572     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
573         @Override
574         public void onReceive(Context context, Intent intent) {
575             switch (intent.getAction()) {
576                 case Intent.ACTION_SCREEN_ON:
577                     onInteractiveStateChanged(true);
578                     break;
579                 case Intent.ACTION_SCREEN_OFF:
580                     onInteractiveStateChanged(false);
581                     break;
582                 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
583                     if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) {
584                         synchronized (mLock) {
585                             stopUnexemptedJobsForDoze();
586                             stopOvertimeJobsLocked("deep doze");
587                         }
588                     }
589                     break;
590                 case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
591                     if (mPowerManager != null && mPowerManager.isPowerSaveMode()) {
592                         synchronized (mLock) {
593                             stopOvertimeJobsLocked("battery saver");
594                         }
595                     }
596                     break;
597             }
598         }
599     };
600 
601     /**
602      * Called when the screen turns on / off.
603      */
onInteractiveStateChanged(boolean interactive)604     private void onInteractiveStateChanged(boolean interactive) {
605         synchronized (mLock) {
606             if (mCurrentInteractiveState == interactive) {
607                 return;
608             }
609             mCurrentInteractiveState = interactive;
610             if (DEBUG) {
611                 Slog.d(TAG, "Interactive: " + interactive);
612             }
613 
614             final long nowRealtime = sElapsedRealtimeClock.millis();
615             if (interactive) {
616                 mLastScreenOnRealtime = nowRealtime;
617                 mEffectiveInteractiveState = true;
618 
619                 mHandler.removeCallbacks(mRampUpForScreenOff);
620             } else {
621                 mLastScreenOffRealtime = nowRealtime;
622 
623                 // Set mEffectiveInteractiveState to false after the delay, when we may increase
624                 // the concurrency.
625                 // We don't need a wakeup alarm here. When there's a pending job, there should
626                 // also be jobs running too, meaning the device should be awake.
627 
628                 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
629                 // we need the exact same instance for removeCallbacks().
630                 mHandler.postDelayed(mRampUpForScreenOff, mScreenOffAdjustmentDelayMs);
631             }
632         }
633     }
634 
635     private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
636 
637     /**
638      * Called in {@link #mScreenOffAdjustmentDelayMs} after
639      * the screen turns off, in order to increase concurrency.
640      */
rampUpForScreenOff()641     private void rampUpForScreenOff() {
642         synchronized (mLock) {
643             // Make sure the screen has really been off for the configured duration.
644             // (There could be a race.)
645             if (!mEffectiveInteractiveState) {
646                 return;
647             }
648             if (mLastScreenOnRealtime > mLastScreenOffRealtime) {
649                 return;
650             }
651             final long now = sElapsedRealtimeClock.millis();
652             if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) {
653                 return;
654             }
655 
656             mEffectiveInteractiveState = false;
657 
658             if (DEBUG) {
659                 Slog.d(TAG, "Ramping up concurrency");
660             }
661 
662             mService.maybeRunPendingJobsLocked();
663         }
664     }
665 
666     @GuardedBy("mLock")
getRunningJobsLocked()667     ArraySet<JobStatus> getRunningJobsLocked() {
668         return mRunningJobs;
669     }
670 
671     @GuardedBy("mLock")
isJobRunningLocked(JobStatus job)672     boolean isJobRunningLocked(JobStatus job) {
673         return mRunningJobs.contains(job);
674     }
675 
676     /**
677      * Return {@code true} if the specified job has been executing for longer than the minimum
678      * execution guarantee.
679      */
680     @GuardedBy("mLock")
isJobInOvertimeLocked(@onNull JobStatus job)681     boolean isJobInOvertimeLocked(@NonNull JobStatus job) {
682         if (!mRunningJobs.contains(job)) {
683             return false;
684         }
685 
686         for (int i = mActiveServices.size() - 1; i >= 0; --i) {
687             final JobServiceContext jsc = mActiveServices.get(i);
688             final JobStatus jobStatus = jsc.getRunningJobLocked();
689 
690             if (jobStatus == job) {
691                 return !jsc.isWithinExecutionGuaranteeTime();
692             }
693         }
694 
695         Slog.wtf(TAG, "Couldn't find long running job on a context");
696         mRunningJobs.remove(job);
697         return false;
698     }
699 
700     /**
701      * Returns true if a job that is "similar" to the provided job is currently running.
702      * "Similar" in this context means any job that the {@link JobStore} would consider equivalent
703      * and replace one with the other.
704      */
705     @GuardedBy("mLock")
isSimilarJobRunningLocked(JobStatus job)706     private boolean isSimilarJobRunningLocked(JobStatus job) {
707         for (int i = mRunningJobs.size() - 1; i >= 0; --i) {
708             JobStatus js = mRunningJobs.valueAt(i);
709             if (job.matches(js.getUid(), js.getNamespace(), js.getJobId())) {
710                 return true;
711             }
712         }
713         return false;
714     }
715 
716     /** Return {@code true} if the state was updated. */
717     @GuardedBy("mLock")
refreshSystemStateLocked()718     private boolean refreshSystemStateLocked() {
719         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
720 
721         // Only refresh the information every so often.
722         if (nowUptime < mNextSystemStateRefreshTime) {
723             return false;
724         }
725 
726         final long start = mStatLogger.getTime();
727         mNextSystemStateRefreshTime = nowUptime + SYSTEM_STATE_REFRESH_MIN_INTERVAL;
728 
729         mLastMemoryTrimLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
730         try {
731             mLastMemoryTrimLevel = ActivityManager.getService().getMemoryTrimLevel();
732         } catch (RemoteException e) {
733         }
734 
735         mStatLogger.logDurationStat(Stats.REFRESH_SYSTEM_STATE, start);
736         return true;
737     }
738 
739     @GuardedBy("mLock")
updateCounterConfigLocked()740     private void updateCounterConfigLocked() {
741         if (!refreshSystemStateLocked()) {
742             return;
743         }
744 
745         final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
746                 ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
747 
748         switch (mLastMemoryTrimLevel) {
749             case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
750                 mWorkTypeConfig = workConfigs.moderate;
751                 break;
752             case ProcessStats.ADJ_MEM_FACTOR_LOW:
753                 mWorkTypeConfig = workConfigs.low;
754                 break;
755             case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
756                 mWorkTypeConfig = workConfigs.critical;
757                 break;
758             default:
759                 mWorkTypeConfig = workConfigs.normal;
760                 break;
761         }
762 
763         mWorkCountTracker.setConfig(mWorkTypeConfig);
764     }
765 
766     /**
767      * Takes jobs from pending queue and runs them on available contexts.
768      * If no contexts are available, preempts lower bias jobs to run higher bias ones.
769      * Lock on mLock before calling this function.
770      */
771     @GuardedBy("mLock")
assignJobsToContextsLocked()772     void assignJobsToContextsLocked() {
773         final long start = mStatLogger.getTime();
774 
775         assignJobsToContextsInternalLocked();
776 
777         mStatLogger.logDurationStat(Stats.ASSIGN_JOBS_TO_CONTEXTS, start);
778     }
779 
780     @GuardedBy("mLock")
assignJobsToContextsInternalLocked()781     private void assignJobsToContextsInternalLocked() {
782         if (DEBUG) {
783             Slog.d(TAG, printPendingQueueLocked());
784         }
785 
786         if (mService.getPendingJobQueue().size() == 0) {
787             // Nothing to do.
788             return;
789         }
790 
791         prepareForAssignmentDeterminationLocked(
792                 mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
793                 mRecycledAssignmentInfo);
794 
795         if (DEBUG) {
796             Slog.d(TAG, printAssignments("running jobs initial",
797                     mRecycledStoppable, mRecycledPreferredUidOnly));
798         }
799 
800         determineAssignmentsLocked(
801                 mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
802                 mRecycledAssignmentInfo);
803 
804         if (DEBUG) {
805             Slog.d(TAG, printAssignments("running jobs final",
806                     mRecycledStoppable, mRecycledPreferredUidOnly, mRecycledChanged));
807 
808             Slog.d(TAG, "work count results: " + mWorkCountTracker);
809         }
810 
811         carryOutAssignmentChangesLocked(mRecycledChanged);
812 
813         cleanUpAfterAssignmentChangesLocked(
814                 mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
815                 mRecycledAssignmentInfo, mRecycledPrivilegedState);
816 
817         noteConcurrency(true);
818     }
819 
820     @VisibleForTesting
821     @GuardedBy("mLock")
prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable, final AssignmentInfo info)822     void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
823             final List<ContextAssignment> preferredUidOnly,
824             final List<ContextAssignment> stoppable,
825             final AssignmentInfo info) {
826         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
827         final List<JobServiceContext> activeServices = mActiveServices;
828 
829         updateCounterConfigLocked();
830         // Reset everything since we'll re-evaluate the current state.
831         mWorkCountTracker.resetCounts();
832 
833         // Update the priorities of jobs that aren't running, and also count the pending work types.
834         // Do this before the following loop to hopefully reduce the cost of
835         // shouldStopRunningJobLocked().
836         updateNonRunningPrioritiesLocked(pendingJobQueue, true);
837 
838         final int numRunningJobs = activeServices.size();
839         final long nowElapsed = sElapsedRealtimeClock.millis();
840         long minPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
841         for (int i = 0; i < numRunningJobs; ++i) {
842             final JobServiceContext jsc = activeServices.get(i);
843             final JobStatus js = jsc.getRunningJobLocked();
844 
845             ContextAssignment assignment = mContextAssignmentPool.acquire();
846             if (assignment == null) {
847                 assignment = new ContextAssignment();
848             }
849 
850             assignment.context = jsc;
851 
852             if (js != null) {
853                 mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
854                 assignment.workType = jsc.getRunningJobWorkType();
855                 if (js.startedWithImmediacyPrivilege) {
856                     info.numRunningImmediacyPrivileged++;
857                 }
858                 if (js.shouldTreatAsUserInitiatedJob()) {
859                     info.numRunningUi++;
860                 } else if (js.startedAsExpeditedJob) {
861                     info.numRunningEj++;
862                 } else {
863                     info.numRunningReg++;
864                 }
865             }
866 
867             assignment.preferredUid = jsc.getPreferredUid();
868             if ((assignment.shouldStopJobReason = shouldStopRunningJobLocked(jsc)) != null) {
869                 stoppable.add(assignment);
870             } else {
871                 assignment.timeUntilStoppableMs = jsc.getRemainingGuaranteedTimeMs(nowElapsed);
872                 minPreferredUidOnlyWaitingTimeMs =
873                         Math.min(minPreferredUidOnlyWaitingTimeMs, assignment.timeUntilStoppableMs);
874                 preferredUidOnly.add(assignment);
875             }
876         }
877         preferredUidOnly.sort(sDeterminationComparator);
878         stoppable.sort(sDeterminationComparator);
879         for (int i = numRunningJobs; i < mSteadyStateConcurrencyLimit; ++i) {
880             final JobServiceContext jsc;
881             final int numIdleContexts = mIdleContexts.size();
882             if (numIdleContexts > 0) {
883                 jsc = mIdleContexts.removeAt(numIdleContexts - 1);
884             } else {
885                 // This could happen if the config is changed at runtime.
886                 Slog.w(TAG, "Had fewer than " + mSteadyStateConcurrencyLimit + " in existence");
887                 jsc = createNewJobServiceContext();
888             }
889 
890             ContextAssignment assignment = mContextAssignmentPool.acquire();
891             if (assignment == null) {
892                 assignment = new ContextAssignment();
893             }
894 
895             assignment.context = jsc;
896             idle.add(assignment);
897         }
898 
899         mWorkCountTracker.onCountDone();
900         // Set 0 if there were no preferred UID only contexts to indicate no waiting time due
901         // to such jobs.
902         info.minPreferredUidOnlyWaitingTimeMs =
903                 minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
904                         ? 0 : minPreferredUidOnlyWaitingTimeMs;
905     }
906 
907     @VisibleForTesting
908     @GuardedBy("mLock")
determineAssignmentsLocked(final ArraySet<ContextAssignment> changed, final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable, @NonNull AssignmentInfo info)909     void determineAssignmentsLocked(final ArraySet<ContextAssignment> changed,
910             final ArraySet<ContextAssignment> idle,
911             final List<ContextAssignment> preferredUidOnly,
912             final List<ContextAssignment> stoppable,
913             @NonNull AssignmentInfo info) {
914         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
915         final List<JobServiceContext> activeServices = mActiveServices;
916         pendingJobQueue.resetIterator();
917         JobStatus nextPending;
918         int projectedRunningCount = activeServices.size();
919         long minChangedWaitingTimeMs = Long.MAX_VALUE;
920         // Only allow the Context creation bypass for each type if one of that type isn't already
921         // running. That way, we don't run into issues (creating too many additional contexts)
922         // if new jobs become ready to run in rapid succession and we end up going through this
923         // loop many times before running jobs have had a decent chance to finish.
924         boolean allowMaxWaitContextBypassUi = info.numRunningUi == 0;
925         boolean allowMaxWaitContextBypassEj = info.numRunningEj == 0;
926         boolean allowMaxWaitContextBypassOthers = info.numRunningReg == 0;
927         while ((nextPending = pendingJobQueue.next()) != null) {
928             if (mRunningJobs.contains(nextPending)) {
929                 // Should never happen.
930                 Slog.wtf(TAG, "Pending queue contained a running job");
931                 if (DEBUG) {
932                     Slog.e(TAG, "Pending+running job: " + nextPending);
933                 }
934                 pendingJobQueue.remove(nextPending);
935                 continue;
936             }
937 
938             final boolean hasImmediacyPrivilege =
939                     hasImmediacyPrivilegeLocked(nextPending, mRecycledPrivilegedState);
940             if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
941                 Slog.w(TAG, "Already running similar job to: " + nextPending);
942             }
943 
944             // Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
945             // the number of additional contexts that are created due to long waiting times.
946             // By factoring it in, we imply that the new slot will be available for other
947             // pending jobs that could be designated as waiting too long, and those other jobs
948             // would only have to wait for the new slots to become available.
949             final long minWaitingTimeMs =
950                     Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
951 
952             // Find an available slot for nextPending. The context should be one of the following:
953             // 1. Unused
954             // 2. Its job should have used up its minimum execution guarantee so it
955             // 3. Its job should have the lowest bias among all running jobs (sharing the same UID
956             //    as nextPending)
957             ContextAssignment selectedContext = null;
958             final int allWorkTypes = getJobWorkTypes(nextPending);
959             final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
960             final boolean isInOverage = projectedRunningCount > mSteadyStateConcurrencyLimit;
961             boolean startingJob = false;
962             if (idle.size() > 0) {
963                 final int idx = idle.size() - 1;
964                 final ContextAssignment assignment = idle.valueAt(idx);
965                 final boolean preferredUidOkay = (assignment.preferredUid == nextPending.getUid())
966                         || (assignment.preferredUid == JobServiceContext.NO_PREFERRED_UID);
967                 int workType = mWorkCountTracker.canJobStart(allWorkTypes);
968                 if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
969                     // This slot is free, and we haven't yet hit the limit on
970                     // concurrent jobs...  we can just throw the job in to here.
971                     selectedContext = assignment;
972                     startingJob = true;
973                     idle.removeAt(idx);
974                     assignment.newJob = nextPending;
975                     assignment.newWorkType = workType;
976                 }
977             }
978             if (selectedContext == null && stoppable.size() > 0) {
979                 for (int s = stoppable.size() - 1; s >= 0; --s) {
980                     final ContextAssignment assignment = stoppable.get(s);
981                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
982                     // Maybe stop the job if it has had its day in the sun. Only allow replacing
983                     // for one of the following conditions:
984                     // 1. We're putting in a job that has the privilege of running immediately
985                     // 2. There aren't too many jobs running AND the current job started when the
986                     //    app was in the background
987                     // 3. There aren't too many jobs running AND the current job started when the
988                     //    app was on TOP, but the app has since left TOP
989                     // 4. There aren't too many jobs running AND the current job started when the
990                     //    app was on TOP, the app is still TOP, but there are too many
991                     //    immediacy-privileged jobs
992                     //    running (because we don't want them to starve out other apps and the
993                     //    current job has already run for the minimum guaranteed time).
994                     // 5. This new job could be waiting for too long for a slot to open up
995                     boolean canReplace = hasImmediacyPrivilege; // Case 1
996                     if (!canReplace && !isInOverage) {
997                         final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
998                         canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
999                                 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
1000                                 // Case 4
1001                                 || info.numRunningImmediacyPrivileged
1002                                         > (mWorkTypeConfig.getMaxTotal() / 2);
1003                     }
1004                     if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
1005                         if (nextPending.shouldTreatAsUserInitiatedJob()) {
1006                             canReplace = minWaitingTimeMs >= mMaxWaitUIMs;
1007                         } else if (nextPending.shouldTreatAsExpeditedJob()) {
1008                             canReplace = minWaitingTimeMs >= mMaxWaitEjMs;
1009                         } else {
1010                             canReplace = minWaitingTimeMs >= mMaxWaitRegularMs;
1011                         }
1012                     }
1013                     if (canReplace) {
1014                         int replaceWorkType = mWorkCountTracker.canJobStart(allWorkTypes,
1015                                 assignment.context.getRunningJobWorkType());
1016                         if (replaceWorkType != WORK_TYPE_NONE) {
1017                             // Right now, the way the code is set up, we don't need to explicitly
1018                             // assign the new job to this context since we'll reassign when the
1019                             // preempted job finally stops.
1020                             assignment.preemptReason = assignment.shouldStopJobReason;
1021                             assignment.preemptReasonCode = JobParameters.STOP_REASON_DEVICE_STATE;
1022                             selectedContext = assignment;
1023                             stoppable.remove(s);
1024                             assignment.newJob = nextPending;
1025                             assignment.newWorkType = replaceWorkType;
1026                             break;
1027                         }
1028                     }
1029                 }
1030             }
1031             if (selectedContext == null && (!isInOverage || hasImmediacyPrivilege)) {
1032                 int lowestBiasSeen = Integer.MAX_VALUE;
1033                 long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
1034                 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
1035                     final ContextAssignment assignment = preferredUidOnly.get(p);
1036                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
1037                     if (runningJob.getUid() != nextPending.getUid()) {
1038                         continue;
1039                     }
1040                     final int jobBias = mService.evaluateJobBiasLocked(runningJob);
1041                     if (jobBias >= nextPending.lastEvaluatedBias) {
1042                         continue;
1043                     }
1044 
1045                     if (selectedContext == null || lowestBiasSeen > jobBias) {
1046                         if (selectedContext != null) {
1047                             // We're no longer using the previous context, so factor it into the
1048                             // calculation.
1049                             newMinPreferredUidOnlyWaitingTimeMs = Math.min(
1050                                     newMinPreferredUidOnlyWaitingTimeMs,
1051                                     selectedContext.timeUntilStoppableMs);
1052                         }
1053                         // Step down the preemption threshold - wind up replacing
1054                         // the lowest-bias running job
1055                         lowestBiasSeen = jobBias;
1056                         selectedContext = assignment;
1057                         assignment.preemptReason = "higher bias job found";
1058                         assignment.preemptReasonCode = JobParameters.STOP_REASON_PREEMPT;
1059                         // In this case, we're just going to preempt a low bias job, we're not
1060                         // actually starting a job, so don't set startingJob to true.
1061                     } else {
1062                         // We're not going to use this context, so factor it into the calculation.
1063                         newMinPreferredUidOnlyWaitingTimeMs = Math.min(
1064                                 newMinPreferredUidOnlyWaitingTimeMs,
1065                                 assignment.timeUntilStoppableMs);
1066                     }
1067                 }
1068                 if (selectedContext != null) {
1069                     selectedContext.newJob = nextPending;
1070                     preferredUidOnly.remove(selectedContext);
1071                     info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
1072                 }
1073             }
1074             // Make sure to run jobs with special privilege immediately.
1075             if (hasImmediacyPrivilege) {
1076                 if (selectedContext != null
1077                         && selectedContext.context.getRunningJobLocked() != null) {
1078                     // We're "replacing" a currently running job, but we want immediacy-privileged
1079                     // jobs to start immediately, so we'll start the privileged jobs on a fresh
1080                     // available context and
1081                     // stop this currently running job to replace in two steps.
1082                     changed.add(selectedContext);
1083                     projectedRunningCount--;
1084                     selectedContext.newJob = null;
1085                     selectedContext.newWorkType = WORK_TYPE_NONE;
1086                     selectedContext = null;
1087                 }
1088                 if (selectedContext == null) {
1089                     if (DEBUG) {
1090                         Slog.d(TAG, "Allowing additional context because EJ would wait too long");
1091                     }
1092                     selectedContext = mContextAssignmentPool.acquire();
1093                     if (selectedContext == null) {
1094                         selectedContext = new ContextAssignment();
1095                     }
1096                     selectedContext.context = mIdleContexts.size() > 0
1097                             ? mIdleContexts.removeAt(mIdleContexts.size() - 1)
1098                             : createNewJobServiceContext();
1099                     selectedContext.newJob = nextPending;
1100                     final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
1101                     selectedContext.newWorkType =
1102                             (workType != WORK_TYPE_NONE) ? workType : WORK_TYPE_TOP;
1103                 }
1104             } else if (selectedContext == null && mMaxWaitTimeBypassEnabled) {
1105                 final boolean wouldBeWaitingTooLong;
1106                 if (nextPending.shouldTreatAsUserInitiatedJob() && allowMaxWaitContextBypassUi) {
1107                     wouldBeWaitingTooLong = minWaitingTimeMs >= mMaxWaitUIMs;
1108                     // We want to create at most one additional context for each type.
1109                     allowMaxWaitContextBypassUi = !wouldBeWaitingTooLong;
1110                 } else if (nextPending.shouldTreatAsExpeditedJob() && allowMaxWaitContextBypassEj) {
1111                     wouldBeWaitingTooLong = minWaitingTimeMs >= mMaxWaitEjMs;
1112                     // We want to create at most one additional context for each type.
1113                     allowMaxWaitContextBypassEj = !wouldBeWaitingTooLong;
1114                 } else if (allowMaxWaitContextBypassOthers) {
1115                     // The way things are set up a UIJ or EJ could end up here and create a 2nd
1116                     // context as if it were a "regular" job. That's fine for now since they would
1117                     // still be subject to the higher waiting time threshold here.
1118                     wouldBeWaitingTooLong = minWaitingTimeMs >= mMaxWaitRegularMs;
1119                     // We want to create at most one additional context for each type.
1120                     allowMaxWaitContextBypassOthers = !wouldBeWaitingTooLong;
1121                 } else {
1122                     wouldBeWaitingTooLong = false;
1123                 }
1124                 if (wouldBeWaitingTooLong) {
1125                     if (DEBUG) {
1126                         Slog.d(TAG, "Allowing additional context because job would wait too long");
1127                     }
1128                     selectedContext = mContextAssignmentPool.acquire();
1129                     if (selectedContext == null) {
1130                         selectedContext = new ContextAssignment();
1131                     }
1132                     selectedContext.context = mIdleContexts.size() > 0
1133                             ? mIdleContexts.removeAt(mIdleContexts.size() - 1)
1134                             : createNewJobServiceContext();
1135                     selectedContext.newJob = nextPending;
1136                     final int workType = mWorkCountTracker.canJobStart(allWorkTypes);
1137                     if (workType != WORK_TYPE_NONE) {
1138                         selectedContext.newWorkType = workType;
1139                     } else {
1140                         // Use the strongest work type possible for this job.
1141                         for (int type = 1; type <= ALL_WORK_TYPES; type = type << 1) {
1142                             if ((type & allWorkTypes) != 0) {
1143                                 selectedContext.newWorkType = type;
1144                                 break;
1145                             }
1146                         }
1147                     }
1148                 }
1149             }
1150             final PackageStats packageStats = getPkgStatsLocked(
1151                     nextPending.getSourceUserId(), nextPending.getSourcePackageName());
1152             if (selectedContext != null) {
1153                 changed.add(selectedContext);
1154                 if (selectedContext.context.getRunningJobLocked() != null) {
1155                     projectedRunningCount--;
1156                 }
1157                 if (selectedContext.newJob != null) {
1158                     selectedContext.newJob.startedWithImmediacyPrivilege = hasImmediacyPrivilege;
1159                     projectedRunningCount++;
1160                     minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
1161                             mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
1162                 }
1163                 packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
1164             }
1165             if (startingJob) {
1166                 // Increase the counters when we're going to start a job.
1167                 mWorkCountTracker.stageJob(selectedContext.newWorkType, allWorkTypes);
1168                 mActivePkgStats.add(
1169                         nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
1170                         packageStats);
1171             }
1172         }
1173     }
1174 
1175     @GuardedBy("mLock")
carryOutAssignmentChangesLocked(final ArraySet<ContextAssignment> changed)1176     private void carryOutAssignmentChangesLocked(final ArraySet<ContextAssignment> changed) {
1177         for (int c = changed.size() - 1; c >= 0; --c) {
1178             final ContextAssignment assignment = changed.valueAt(c);
1179             final JobStatus js = assignment.context.getRunningJobLocked();
1180             if (js != null) {
1181                 if (DEBUG) {
1182                     Slog.d(TAG, "preempting job: " + js);
1183                 }
1184                 // preferredUid will be set to uid of currently running job, if appropriate.
1185                 assignment.context.cancelExecutingJobLocked(
1186                         assignment.preemptReasonCode,
1187                         JobParameters.INTERNAL_STOP_REASON_PREEMPT, assignment.preemptReason);
1188             } else {
1189                 final JobStatus pendingJob = assignment.newJob;
1190                 if (DEBUG) {
1191                     Slog.d(TAG, "About to run job on context "
1192                             + assignment.context.getId() + ", job: " + pendingJob);
1193                 }
1194                 startJobLocked(assignment.context, pendingJob, assignment.newWorkType);
1195             }
1196 
1197             assignment.clear();
1198             mContextAssignmentPool.release(assignment);
1199         }
1200     }
1201 
1202     @GuardedBy("mLock")
cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed, final ArraySet<ContextAssignment> idle, final List<ContextAssignment> preferredUidOnly, final List<ContextAssignment> stoppable, final AssignmentInfo assignmentInfo, final SparseIntArray privilegedState)1203     private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
1204             final ArraySet<ContextAssignment> idle,
1205             final List<ContextAssignment> preferredUidOnly,
1206             final List<ContextAssignment> stoppable,
1207             final AssignmentInfo assignmentInfo,
1208             final SparseIntArray privilegedState) {
1209         for (int s = stoppable.size() - 1; s >= 0; --s) {
1210             final ContextAssignment assignment = stoppable.get(s);
1211             assignment.clear();
1212             mContextAssignmentPool.release(assignment);
1213         }
1214         for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
1215             final ContextAssignment assignment = preferredUidOnly.get(p);
1216             assignment.clear();
1217             mContextAssignmentPool.release(assignment);
1218         }
1219         for (int i = idle.size() - 1; i >= 0; --i) {
1220             final ContextAssignment assignment = idle.valueAt(i);
1221             mIdleContexts.add(assignment.context);
1222             assignment.clear();
1223             mContextAssignmentPool.release(assignment);
1224         }
1225         changed.clear();
1226         idle.clear();
1227         stoppable.clear();
1228         preferredUidOnly.clear();
1229         assignmentInfo.clear();
1230         privilegedState.clear();
1231         mWorkCountTracker.resetStagingCount();
1232         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
1233     }
1234 
1235     @VisibleForTesting
1236     @GuardedBy("mLock")
hasImmediacyPrivilegeLocked(@onNull JobStatus job, @NonNull SparseIntArray cachedPrivilegedState)1237     boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job,
1238             @NonNull SparseIntArray cachedPrivilegedState) {
1239         if (!job.shouldTreatAsExpeditedJob() && !job.shouldTreatAsUserInitiatedJob()) {
1240             return false;
1241         }
1242         // EJs & user-initiated jobs for the TOP app should run immediately.
1243         // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL
1244         // state, we don't give the immediacy privilege so that we can try and maintain
1245         // reasonably concurrency behavior.
1246         if (job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
1247             return true;
1248         }
1249         final int uid = job.getSourceUid();
1250         final int privilegedState = cachedPrivilegedState.get(uid, PRIVILEGED_STATE_UNDEFINED);
1251         switch (privilegedState) {
1252             case PRIVILEGED_STATE_TOP:
1253                 return true;
1254             case PRIVILEGED_STATE_BAL:
1255                 return job.shouldTreatAsUserInitiatedJob();
1256             case PRIVILEGED_STATE_NONE:
1257                 return false;
1258             case PRIVILEGED_STATE_UNDEFINED:
1259             default:
1260                 final int procState = mActivityManagerInternal.getUidProcessState(uid);
1261                 if (procState == ActivityManager.PROCESS_STATE_TOP) {
1262                     cachedPrivilegedState.put(uid, PRIVILEGED_STATE_TOP);
1263                     return true;
1264                 }
1265                 if (job.shouldTreatAsExpeditedJob()) {
1266                     // EJs only get the TOP privilege.
1267                     return false;
1268                 }
1269 
1270                 final BackgroundStartPrivileges bsp =
1271                         mActivityManagerInternal.getBackgroundStartPrivileges(uid);
1272                 if (DEBUG) {
1273                     Slog.d(TAG, "Job " + job.toShortString() + " bsp state: " + bsp);
1274                 }
1275                 // Intentionally use the background activity start BSP here instead of
1276                 // the full BAL check since the former is transient and better indicates that the
1277                 // user recently interacted with the app, while the latter includes
1278                 // permanent exceptions that don't warrant bypassing normal concurrency policy.
1279                 final boolean balAllowed = bsp.allowsBackgroundActivityStarts();
1280                 cachedPrivilegedState.put(uid,
1281                         balAllowed ? PRIVILEGED_STATE_BAL : PRIVILEGED_STATE_NONE);
1282                 return balAllowed;
1283         }
1284     }
1285 
1286     @GuardedBy("mLock")
onUidBiasChangedLocked(int prevBias, int newBias)1287     void onUidBiasChangedLocked(int prevBias, int newBias) {
1288         if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
1289             // TOP app didn't change. Nothing to do.
1290             return;
1291         }
1292         if (mService.getPendingJobQueue().size() == 0) {
1293             // Nothing waiting for the top app to leave. Nothing to do.
1294             return;
1295         }
1296         // Don't stop the TOP jobs directly. Instead, see if they would be replaced by some
1297         // pending job (there may not always be something to replace them).
1298         assignJobsToContextsLocked();
1299     }
1300 
1301     @Nullable
1302     @GuardedBy("mLock")
getRunningJobServiceContextLocked(JobStatus job)1303     JobServiceContext getRunningJobServiceContextLocked(JobStatus job) {
1304         if (!mRunningJobs.contains(job)) {
1305             return null;
1306         }
1307 
1308         for (int i = 0; i < mActiveServices.size(); i++) {
1309             JobServiceContext jsc = mActiveServices.get(i);
1310             final JobStatus executing = jsc.getRunningJobLocked();
1311             if (executing == job) {
1312                 return jsc;
1313             }
1314         }
1315         Slog.wtf(TAG, "Couldn't find running job on a context");
1316         mRunningJobs.remove(job);
1317         return null;
1318     }
1319 
1320     @GuardedBy("mLock")
stopJobOnServiceContextLocked(JobStatus job, @JobParameters.StopReason int reason, int internalReasonCode, String debugReason)1321     boolean stopJobOnServiceContextLocked(JobStatus job,
1322             @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
1323         if (!mRunningJobs.contains(job)) {
1324             return false;
1325         }
1326 
1327         for (int i = 0; i < mActiveServices.size(); i++) {
1328             JobServiceContext jsc = mActiveServices.get(i);
1329             final JobStatus executing = jsc.getRunningJobLocked();
1330             if (executing == job) {
1331                 jsc.cancelExecutingJobLocked(reason, internalReasonCode, debugReason);
1332                 return true;
1333             }
1334         }
1335         Slog.wtf(TAG, "Couldn't find running job on a context");
1336         mRunningJobs.remove(job);
1337         return false;
1338     }
1339 
1340     @GuardedBy("mLock")
stopUnexemptedJobsForDoze()1341     private void stopUnexemptedJobsForDoze() {
1342         // When becoming idle, make sure no jobs are actively running,
1343         // except those using the idle exemption flag.
1344         for (int i = 0; i < mActiveServices.size(); i++) {
1345             JobServiceContext jsc = mActiveServices.get(i);
1346             final JobStatus executing = jsc.getRunningJobLocked();
1347             if (executing != null && !executing.canRunInDoze()) {
1348                 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
1349                         JobParameters.INTERNAL_STOP_REASON_DEVICE_IDLE,
1350                         "cancelled due to doze");
1351             }
1352         }
1353     }
1354 
1355     @GuardedBy("mLock")
stopOvertimeJobsLocked(@onNull String debugReason)1356     private void stopOvertimeJobsLocked(@NonNull String debugReason) {
1357         for (int i = 0; i < mActiveServices.size(); ++i) {
1358             final JobServiceContext jsc = mActiveServices.get(i);
1359             final JobStatus jobStatus = jsc.getRunningJobLocked();
1360 
1361             if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()) {
1362                 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE,
1363                         JobParameters.INTERNAL_STOP_REASON_TIMEOUT, debugReason);
1364             }
1365         }
1366     }
1367 
1368     /**
1369      * Stops any jobs that have run for more than their minimum execution guarantee and are
1370      * restricted by the given {@link JobRestriction}.
1371      */
1372     @GuardedBy("mLock")
maybeStopOvertimeJobsLocked(@onNull JobRestriction restriction)1373     void maybeStopOvertimeJobsLocked(@NonNull JobRestriction restriction) {
1374         for (int i = mActiveServices.size() - 1; i >= 0; --i) {
1375             final JobServiceContext jsc = mActiveServices.get(i);
1376             final JobStatus jobStatus = jsc.getRunningJobLocked();
1377 
1378             if (jobStatus != null
1379                     && !jsc.isWithinExecutionGuaranteeTime()
1380                     && restriction.isJobRestricted(
1381                             jobStatus, mService.evaluateJobBiasLocked(jobStatus))) {
1382                 jsc.cancelExecutingJobLocked(restriction.getStopReason(),
1383                         restriction.getInternalReason(),
1384                         JobParameters.getInternalReasonCodeDescription(
1385                                 restriction.getInternalReason()));
1386             }
1387         }
1388     }
1389 
1390     @GuardedBy("mLock")
markJobsForUserStopLocked(int userId, @NonNull String packageName, @Nullable String debugReason)1391     void markJobsForUserStopLocked(int userId, @NonNull String packageName,
1392             @Nullable String debugReason) {
1393         for (int i = mActiveServices.size() - 1; i >= 0; --i) {
1394             final JobServiceContext jsc = mActiveServices.get(i);
1395             final JobStatus jobStatus = jsc.getRunningJobLocked();
1396 
1397             // Normally, we handle jobs primarily using the source package and userId,
1398             // however, user-visible jobs are shown as coming from the calling app, so we
1399             // need to operate on the jobs from that perspective here.
1400             if (jobStatus != null && userId == jobStatus.getUserId()
1401                     && jobStatus.getServiceComponent().getPackageName().equals(packageName)) {
1402                 jsc.markForProcessDeathLocked(JobParameters.STOP_REASON_USER,
1403                         JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP,
1404                         debugReason);
1405             }
1406         }
1407     }
1408 
1409     @GuardedBy("mLock")
stopNonReadyActiveJobsLocked()1410     void stopNonReadyActiveJobsLocked() {
1411         for (int i = 0; i < mActiveServices.size(); i++) {
1412             JobServiceContext serviceContext = mActiveServices.get(i);
1413             final JobStatus running = serviceContext.getRunningJobLocked();
1414             if (running == null) {
1415                 continue;
1416             }
1417             if (!running.isReady()) {
1418                 if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX
1419                         && running.getStopReason() == JobParameters.STOP_REASON_APP_STANDBY) {
1420                     serviceContext.cancelExecutingJobLocked(
1421                             running.getStopReason(),
1422                             JobParameters.INTERNAL_STOP_REASON_RESTRICTED_BUCKET,
1423                             "cancelled due to restricted bucket");
1424                 } else {
1425                     serviceContext.cancelExecutingJobLocked(
1426                             running.getStopReason(),
1427                             JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED,
1428                             "cancelled due to unsatisfied constraints");
1429                 }
1430             } else {
1431                 final JobRestriction restriction = mService.checkIfRestricted(running);
1432                 if (restriction != null) {
1433                     final int internalReasonCode = restriction.getInternalReason();
1434                     serviceContext.cancelExecutingJobLocked(restriction.getStopReason(),
1435                             internalReasonCode,
1436                             "restricted due to "
1437                                     + JobParameters.getInternalReasonCodeDescription(
1438                                     internalReasonCode));
1439                 }
1440             }
1441         }
1442     }
1443 
noteConcurrency(boolean logForHistogram)1444     private void noteConcurrency(boolean logForHistogram) {
1445         mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
1446                 // TODO: log per type instead of only TOP
1447                 mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
1448         if (logForHistogram) {
1449             sConcurrencyHistogramLogger.logSample(mActiveServices.size());
1450         }
1451     }
1452 
1453     @GuardedBy("mLock")
updateNonRunningPrioritiesLocked(@onNull final PendingJobQueue jobQueue, boolean updateCounter)1454     private void updateNonRunningPrioritiesLocked(@NonNull final PendingJobQueue jobQueue,
1455             boolean updateCounter) {
1456         JobStatus pending;
1457         jobQueue.resetIterator();
1458         while ((pending = jobQueue.next()) != null) {
1459 
1460             // If job is already running, go to next job.
1461             if (mRunningJobs.contains(pending)) {
1462                 continue;
1463             }
1464 
1465             pending.lastEvaluatedBias = mService.evaluateJobBiasLocked(pending);
1466 
1467             if (updateCounter) {
1468                 mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
1469             }
1470         }
1471     }
1472 
1473     @GuardedBy("mLock")
1474     @NonNull
getPkgStatsLocked(int userId, @NonNull String packageName)1475     private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) {
1476         PackageStats packageStats = mActivePkgStats.get(userId, packageName);
1477         if (packageStats == null) {
1478             packageStats = mPkgStatsPool.acquire();
1479             if (packageStats == null) {
1480                 packageStats = new PackageStats();
1481             }
1482             packageStats.setPackage(userId, packageName);
1483         }
1484         return packageStats;
1485     }
1486 
1487     @GuardedBy("mLock")
1488     @VisibleForTesting
isPkgConcurrencyLimitedLocked(@onNull JobStatus jobStatus)1489     boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
1490         if (jobStatus.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
1491             // Don't restrict top apps' concurrency. The work type limits will make sure
1492             // background jobs have slots to run if the system has resources.
1493             return false;
1494         }
1495         // Use < instead of <= as that gives us a little wiggle room in case a new job comes
1496         // along very shortly.
1497         if (mService.getPendingJobQueue().size() + mRunningJobs.size()
1498                 < mWorkTypeConfig.getMaxTotal()) {
1499             // Don't artificially limit a single package if we don't even have enough jobs to use
1500             // the maximum number of slots. We'll preempt the job later if we need the slot.
1501             return false;
1502         }
1503         final PackageStats packageStats =
1504                 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
1505         if (packageStats == null) {
1506             // No currently running jobs.
1507             return false;
1508         }
1509         if (jobStatus.shouldTreatAsExpeditedJob()) {
1510             return packageStats.numRunningEj + packageStats.numStagedEj >= mPkgConcurrencyLimitEj;
1511         } else {
1512             return packageStats.numRunningRegular + packageStats.numStagedRegular
1513                     >= mPkgConcurrencyLimitRegular;
1514         }
1515     }
1516 
1517     @GuardedBy("mLock")
startJobLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)1518     private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
1519             @WorkType final int workType) {
1520         final List<StateController> controllers = mService.mControllers;
1521         final int numControllers = controllers.size();
1522         final PowerManager.WakeLock wl = mPowerManager.newWakeLock(
1523                 PowerManager.PARTIAL_WAKE_LOCK, jobStatus.getWakelockTag());
1524         wl.setWorkSource(mService.deriveWorkSource(
1525                 jobStatus.getSourceUid(), jobStatus.getSourcePackageName()));
1526         wl.setReferenceCounted(false);
1527         // Since the quota controller will start counting from the time prepareForExecutionLocked()
1528         // is called, hold a wakelock to make sure the CPU doesn't suspend between that call and
1529         // when the service actually starts.
1530         wl.acquire();
1531         try {
1532             for (int ic = 0; ic < numControllers; ic++) {
1533                 controllers.get(ic).prepareForExecutionLocked(jobStatus);
1534             }
1535             final PackageStats packageStats = getPkgStatsLocked(
1536                     jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
1537             packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob());
1538             if (!worker.executeRunnableJob(jobStatus, workType)) {
1539                 Slog.e(TAG, "Error executing " + jobStatus);
1540                 mWorkCountTracker.onStagedJobFailed(workType);
1541                 for (int ic = 0; ic < numControllers; ic++) {
1542                     controllers.get(ic).unprepareFromExecutionLocked(jobStatus);
1543                 }
1544             } else {
1545                 mRunningJobs.add(jobStatus);
1546                 mActiveServices.add(worker);
1547                 mIdleContexts.remove(worker);
1548                 mWorkCountTracker.onJobStarted(workType);
1549                 packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
1550                 mActivePkgStats.add(
1551                         jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
1552                         packageStats);
1553                 mService.resetPendingJobReasonCache(jobStatus);
1554             }
1555             if (mService.getPendingJobQueue().remove(jobStatus)) {
1556                 mService.mJobPackageTracker.noteNonpending(jobStatus);
1557             }
1558         } finally {
1559             wl.release();
1560         }
1561     }
1562 
1563     @GuardedBy("mLock")
onJobCompletedLocked(@onNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType)1564     void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
1565             @WorkType final int workType) {
1566         mWorkCountTracker.onJobFinished(workType);
1567         mRunningJobs.remove(jobStatus);
1568         mActiveServices.remove(worker);
1569         if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) {
1570             // Don't need to save all new contexts, but keep some extra around in case we need
1571             // extras for another immediacy privileged overage.
1572             mIdleContexts.add(worker);
1573         } else {
1574             mNumDroppedContexts++;
1575         }
1576         final PackageStats packageStats =
1577                 mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
1578         if (packageStats == null) {
1579             Slog.wtf(TAG, "Running job didn't have an active PackageStats object");
1580         } else {
1581             packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob);
1582             if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) {
1583                 mActivePkgStats.delete(packageStats.userId, packageStats.packageName);
1584                 mPkgStatsPool.release(packageStats);
1585             }
1586         }
1587 
1588         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
1589         if (pendingJobQueue.size() == 0) {
1590             worker.clearPreferredUid();
1591             // Don't log the drop in concurrency to the histogram, otherwise, we'll end up
1592             // overcounting lower concurrency values as jobs end execution.
1593             noteConcurrency(false);
1594             return;
1595         }
1596         if (mActiveServices.size() >= mSteadyStateConcurrencyLimit) {
1597             final boolean respectConcurrencyLimit;
1598             if (!mMaxWaitTimeBypassEnabled) {
1599                 respectConcurrencyLimit = true;
1600             } else {
1601                 long minWaitingTimeMs = Long.MAX_VALUE;
1602                 final long nowElapsed = sElapsedRealtimeClock.millis();
1603                 for (int i = mActiveServices.size() - 1; i >= 0; --i) {
1604                     minWaitingTimeMs = Math.min(minWaitingTimeMs,
1605                             mActiveServices.get(i).getRemainingGuaranteedTimeMs(nowElapsed));
1606                 }
1607                 final boolean wouldBeWaitingTooLong;
1608                 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_UI) > 0) {
1609                     wouldBeWaitingTooLong = minWaitingTimeMs >= mMaxWaitUIMs;
1610                 } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
1611                     wouldBeWaitingTooLong = minWaitingTimeMs >= mMaxWaitEjMs;
1612                 } else {
1613                     wouldBeWaitingTooLong = minWaitingTimeMs >= mMaxWaitRegularMs;
1614                 }
1615                 respectConcurrencyLimit = !wouldBeWaitingTooLong;
1616             }
1617             if (respectConcurrencyLimit) {
1618                 worker.clearPreferredUid();
1619                 // We're over the limit (because there were a lot of immediacy-privileged jobs
1620                 // scheduled), but we should
1621                 // be able to stop the other jobs soon so don't start running anything new until we
1622                 // get back below the limit.
1623                 // Don't log the drop in concurrency to the histogram, otherwise, we'll end up
1624                 // overcounting lower concurrency values as jobs end execution.
1625                 noteConcurrency(false);
1626                 return;
1627             }
1628         }
1629 
1630         if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
1631             updateCounterConfigLocked();
1632             // Preemption case needs special care.
1633             updateNonRunningPrioritiesLocked(pendingJobQueue, false);
1634 
1635             JobStatus highestBiasJob = null;
1636             int highBiasWorkType = workType;
1637             int highBiasAllWorkTypes = workType;
1638             JobStatus backupJob = null;
1639             int backupWorkType = WORK_TYPE_NONE;
1640             int backupAllWorkTypes = WORK_TYPE_NONE;
1641 
1642             JobStatus nextPending;
1643             pendingJobQueue.resetIterator();
1644             while ((nextPending = pendingJobQueue.next()) != null) {
1645                 if (mRunningJobs.contains(nextPending)) {
1646                     // Should never happen.
1647                     Slog.wtf(TAG, "Pending queue contained a running job");
1648                     if (DEBUG) {
1649                         Slog.e(TAG, "Pending+running job: " + nextPending);
1650                     }
1651                     pendingJobQueue.remove(nextPending);
1652                     continue;
1653                 }
1654 
1655                 if (Flags.countQuotaFix() && !nextPending.isReady()) {
1656                     // This could happen when the constraints for the job have been marked
1657                     // as unsatisfiled but hasn't been removed from the pending queue yet.
1658                     if (DEBUG) {
1659                         Slog.w(TAG, "Pending+not ready job: " + nextPending);
1660                     }
1661                     pendingJobQueue.remove(nextPending);
1662                     continue;
1663                 }
1664 
1665                 if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
1666                     Slog.w(TAG, "Already running similar job to: " + nextPending);
1667                 }
1668 
1669                 if (worker.getPreferredUid() != nextPending.getUid()) {
1670                     if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
1671                         int allWorkTypes = getJobWorkTypes(nextPending);
1672                         int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
1673                         if (workAsType != WORK_TYPE_NONE) {
1674                             backupJob = nextPending;
1675                             backupWorkType = workAsType;
1676                             backupAllWorkTypes = allWorkTypes;
1677                         }
1678                     }
1679                     continue;
1680                 }
1681 
1682                 // Only bypass the concurrent limit if we had preempted the job due to a higher
1683                 // bias job.
1684                 if (nextPending.lastEvaluatedBias <= jobStatus.lastEvaluatedBias
1685                         && isPkgConcurrencyLimitedLocked(nextPending)) {
1686                     continue;
1687                 }
1688 
1689                 if (highestBiasJob == null
1690                         || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
1691                     highestBiasJob = nextPending;
1692                 } else {
1693                     continue;
1694                 }
1695 
1696                 // In this path, we pre-empted an existing job. We don't fully care about the
1697                 // reserved slots. We should just run the highest bias job we can find,
1698                 // though it would be ideal to use an available WorkType slot instead of
1699                 // overloading slots.
1700                 highBiasAllWorkTypes = getJobWorkTypes(nextPending);
1701                 final int workAsType = mWorkCountTracker.canJobStart(highBiasAllWorkTypes);
1702                 if (workAsType == WORK_TYPE_NONE) {
1703                     // Just use the preempted job's work type since this new one is technically
1704                     // replacing it anyway.
1705                     highBiasWorkType = workType;
1706                 } else {
1707                     highBiasWorkType = workAsType;
1708                 }
1709             }
1710             if (highestBiasJob != null) {
1711                 if (DEBUG) {
1712                     Slog.d(TAG, "Running job " + highestBiasJob + " as preemption");
1713                 }
1714                 mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
1715                 startJobLocked(worker, highestBiasJob, highBiasWorkType);
1716             } else {
1717                 if (DEBUG) {
1718                     Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid());
1719                 }
1720                 worker.clearPreferredUid();
1721                 if (backupJob != null) {
1722                     if (DEBUG) {
1723                         Slog.d(TAG, "Running job " + backupJob + " instead");
1724                     }
1725                     mWorkCountTracker.stageJob(backupWorkType, backupAllWorkTypes);
1726                     startJobLocked(worker, backupJob, backupWorkType);
1727                 }
1728             }
1729         } else if (pendingJobQueue.size() > 0) {
1730             updateCounterConfigLocked();
1731             updateNonRunningPrioritiesLocked(pendingJobQueue, false);
1732 
1733             // This slot is now free and we have pending jobs. Start the highest bias job we find.
1734             JobStatus highestBiasJob = null;
1735             int highBiasWorkType = workType;
1736             int highBiasAllWorkTypes = workType;
1737 
1738             JobStatus nextPending;
1739             pendingJobQueue.resetIterator();
1740             while ((nextPending = pendingJobQueue.next()) != null) {
1741 
1742                 if (mRunningJobs.contains(nextPending)) {
1743                     // Should never happen.
1744                     Slog.wtf(TAG, "Pending queue contained a running job");
1745                     if (DEBUG) {
1746                         Slog.e(TAG, "Pending+running job: " + nextPending);
1747                     }
1748                     pendingJobQueue.remove(nextPending);
1749                     continue;
1750                 }
1751 
1752                 if (Flags.countQuotaFix() && !nextPending.isReady()) {
1753                     // This could happen when the constraints for the job have been marked
1754                     // as unsatisfiled but hasn't been removed from the pending queue yet.
1755                     if (DEBUG) {
1756                         Slog.w(TAG, "Pending+not ready job: " + nextPending);
1757                     }
1758                     pendingJobQueue.remove(nextPending);
1759                     continue;
1760                 }
1761 
1762                 if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
1763                     Slog.w(TAG, "Already running similar job to: " + nextPending);
1764                 }
1765 
1766                 if (isPkgConcurrencyLimitedLocked(nextPending)) {
1767                     continue;
1768                 }
1769 
1770                 final int allWorkTypes = getJobWorkTypes(nextPending);
1771                 final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
1772                 if (workAsType == WORK_TYPE_NONE) {
1773                     continue;
1774                 }
1775                 if (highestBiasJob == null
1776                         || highestBiasJob.lastEvaluatedBias < nextPending.lastEvaluatedBias) {
1777                     highestBiasJob = nextPending;
1778                     highBiasWorkType = workAsType;
1779                     highBiasAllWorkTypes = allWorkTypes;
1780                 }
1781             }
1782 
1783             if (highestBiasJob != null) {
1784                 // This slot is free, and we haven't yet hit the limit on
1785                 // concurrent jobs...  we can just throw the job in to here.
1786                 if (DEBUG) {
1787                     Slog.d(TAG, "About to run job: " + highestBiasJob);
1788                 }
1789                 mWorkCountTracker.stageJob(highBiasWorkType, highBiasAllWorkTypes);
1790                 startJobLocked(worker, highestBiasJob, highBiasWorkType);
1791             }
1792         }
1793 
1794         // Don't log the drop in concurrency to the histogram, otherwise, we'll end up
1795         // overcounting lower concurrency values as jobs end execution.
1796         noteConcurrency(false);
1797     }
1798 
1799     /**
1800      * Returns {@code null} if the job can continue running and a non-null String if the job should
1801      * be stopped. The non-null String details the reason for stopping the job. A job will generally
1802      * be stopped if there are similar job types waiting to be run and stopping this job would allow
1803      * another job to run, or if system state suggests the job should stop.
1804      */
1805     @Nullable
1806     @GuardedBy("mLock")
shouldStopRunningJobLocked(@onNull JobServiceContext context)1807     String shouldStopRunningJobLocked(@NonNull JobServiceContext context) {
1808         final JobStatus js = context.getRunningJobLocked();
1809         if (js == null) {
1810             // This can happen when we try to assign newly found pending jobs to contexts.
1811             return null;
1812         }
1813 
1814         if (context.isWithinExecutionGuaranteeTime()) {
1815             return null;
1816         }
1817 
1818         // We're over the minimum guaranteed runtime. Stop the job if we're over config limits,
1819         // there are pending jobs that could replace this one, or the device state is not conducive
1820         // to long runs.
1821 
1822         if (mPowerManager.isPowerSaveMode()) {
1823             return "battery saver";
1824         }
1825         if (mPowerManager.isDeviceIdleMode()) {
1826             return "deep doze";
1827         }
1828         final JobRestriction jobRestriction;
1829         if ((jobRestriction = mService.checkIfRestricted(js)) != null) {
1830             return "restriction:"
1831                     + JobParameters.getInternalReasonCodeDescription(
1832                             jobRestriction.getInternalReason());
1833         }
1834 
1835         // Update config in case memory usage has changed significantly.
1836         updateCounterConfigLocked();
1837 
1838         @WorkType final int workType = context.getRunningJobWorkType();
1839 
1840         if (mRunningJobs.size() > mWorkTypeConfig.getMaxTotal()
1841                 || mWorkCountTracker.isOverTypeLimit(workType)) {
1842             return "too many jobs running";
1843         }
1844 
1845         final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
1846         final int numPending = pendingJobQueue.size();
1847         if (numPending == 0) {
1848             // All quiet. We can let this job run to completion.
1849             return null;
1850         }
1851 
1852         // Only expedited jobs can replace expedited jobs.
1853         if (js.shouldTreatAsExpeditedJob() || js.startedAsExpeditedJob) {
1854             // Keep fg/bg user distinction.
1855             if (workType == WORK_TYPE_BGUSER_IMPORTANT || workType == WORK_TYPE_BGUSER) {
1856                 // Let any important bg user job replace a bg user expedited job.
1857                 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_BGUSER_IMPORTANT) > 0) {
1858                     return "blocking " + workTypeToString(WORK_TYPE_BGUSER_IMPORTANT) + " queue";
1859                 }
1860                 // Let a fg user EJ preempt a bg user EJ (if able), but not the other way around.
1861                 if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0
1862                         && mWorkCountTracker.canJobStart(WORK_TYPE_EJ, workType)
1863                         != WORK_TYPE_NONE) {
1864                     return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
1865                 }
1866             } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
1867                 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
1868             } else if (js.startedWithImmediacyPrivilege) {
1869                 // Try not to let jobs with immediacy privilege starve out other apps.
1870                 int immediacyPrivilegeCount = 0;
1871                 for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
1872                     JobStatus j = mRunningJobs.valueAt(r);
1873                     if (j.startedWithImmediacyPrivilege) {
1874                         immediacyPrivilegeCount++;
1875                     }
1876                 }
1877                 if (immediacyPrivilegeCount > mWorkTypeConfig.getMaxTotal() / 2) {
1878                     return "prevent immediacy privilege dominance";
1879                 }
1880             }
1881             // No other pending EJs. Return null so we don't let regular jobs preempt an EJ.
1882             return null;
1883         }
1884 
1885         // Easy check. If there are pending jobs of the same work type, then we know that
1886         // something will replace this.
1887         if (mWorkCountTracker.getPendingJobCount(workType) > 0) {
1888             return "blocking " + workTypeToString(workType) + " queue";
1889         }
1890 
1891         // Harder check. We need to see if a different work type can replace this job.
1892         int remainingWorkTypes = ALL_WORK_TYPES;
1893         JobStatus pending;
1894         pendingJobQueue.resetIterator();
1895         while ((pending = pendingJobQueue.next()) != null) {
1896             final int workTypes = getJobWorkTypes(pending);
1897             if ((workTypes & remainingWorkTypes) > 0
1898                     && mWorkCountTracker.canJobStart(workTypes, workType) != WORK_TYPE_NONE) {
1899                 return "blocking other pending jobs";
1900             }
1901 
1902             remainingWorkTypes = remainingWorkTypes & ~workTypes;
1903             if (remainingWorkTypes == 0) {
1904                 break;
1905             }
1906         }
1907 
1908         return null;
1909     }
1910 
1911     @GuardedBy("mLock")
executeStopCommandLocked(PrintWriter pw, String pkgName, int userId, @Nullable String namespace, boolean matchJobId, int jobId, int stopReason, int internalStopReason)1912     boolean executeStopCommandLocked(PrintWriter pw, String pkgName, int userId,
1913             @Nullable String namespace, boolean matchJobId, int jobId,
1914             int stopReason, int internalStopReason) {
1915         boolean foundSome = false;
1916         for (int i = 0; i < mActiveServices.size(); i++) {
1917             final JobServiceContext jc = mActiveServices.get(i);
1918             final JobStatus js = jc.getRunningJobLocked();
1919             if (jc.stopIfExecutingLocked(pkgName, userId, namespace, matchJobId, jobId,
1920                     stopReason, internalStopReason)) {
1921                 foundSome = true;
1922                 pw.print("Stopping job: ");
1923                 js.printUniqueId(pw);
1924                 pw.print(" ");
1925                 pw.println(js.getServiceComponent().flattenToShortString());
1926             }
1927         }
1928         return foundSome;
1929     }
1930 
1931     /**
1932      * Returns the estimated network bytes if the job is running. Returns {@code null} if the job
1933      * isn't running.
1934      */
1935     @Nullable
1936     @GuardedBy("mLock")
getEstimatedNetworkBytesLocked(String pkgName, int uid, String namespace, int jobId)1937     Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid,
1938             String namespace, int jobId) {
1939         for (int i = 0; i < mActiveServices.size(); i++) {
1940             final JobServiceContext jc = mActiveServices.get(i);
1941             final JobStatus js = jc.getRunningJobLocked();
1942             if (js != null && js.matches(uid, namespace, jobId)
1943                     && js.getSourcePackageName().equals(pkgName)) {
1944                 return jc.getEstimatedNetworkBytes();
1945             }
1946         }
1947         return null;
1948     }
1949 
1950     /**
1951      * Returns the transferred network bytes if the job is running. Returns {@code null} if the job
1952      * isn't running.
1953      */
1954     @Nullable
1955     @GuardedBy("mLock")
getTransferredNetworkBytesLocked(String pkgName, int uid, String namespace, int jobId)1956     Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid,
1957             String namespace, int jobId) {
1958         for (int i = 0; i < mActiveServices.size(); i++) {
1959             final JobServiceContext jc = mActiveServices.get(i);
1960             final JobStatus js = jc.getRunningJobLocked();
1961             if (js != null && js.matches(uid, namespace, jobId)
1962                     && js.getSourcePackageName().equals(pkgName)) {
1963                 return jc.getTransferredNetworkBytes();
1964             }
1965         }
1966         return null;
1967     }
1968 
isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId, @NonNull String packageName)1969     boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId,
1970             @NonNull String packageName) {
1971         return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs(
1972                 notificationId, userId, packageName);
1973     }
1974 
isNotificationChannelAssociatedWithAnyUserInitiatedJobs( @onNull String notificationChannel, int userId, @NonNull String packageName)1975     boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
1976             @NonNull String notificationChannel, int userId, @NonNull String packageName) {
1977         return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
1978                 notificationChannel, userId, packageName);
1979     }
1980 
1981     @NonNull
createNewJobServiceContext()1982     private JobServiceContext createNewJobServiceContext() {
1983         return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
1984                 IBatteryStats.Stub.asInterface(
1985                         ServiceManager.getService(BatteryStats.SERVICE_NAME)),
1986                 mService.mJobPackageTracker, AppSchedulingModuleThread.get().getLooper());
1987     }
1988 
1989     @GuardedBy("mLock")
printPendingQueueLocked()1990     private String printPendingQueueLocked() {
1991         StringBuilder s = new StringBuilder("Pending queue: ");
1992         PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
1993         JobStatus js;
1994         pendingJobQueue.resetIterator();
1995         while ((js = pendingJobQueue.next()) != null) {
1996             s.append("(")
1997                     .append("{")
1998                     .append(js.getNamespace())
1999                     .append("} ")
2000                     .append(js.getJob().getId())
2001                     .append(", ")
2002                     .append(js.getUid())
2003                     .append(") ");
2004         }
2005         return s.toString();
2006     }
2007 
printAssignments(String header, Collection<ContextAssignment>... list)2008     private static String printAssignments(String header, Collection<ContextAssignment>... list) {
2009         final StringBuilder s = new StringBuilder(header + ": ");
2010         for (int l = 0; l < list.length; ++l) {
2011             final Collection<ContextAssignment> assignments = list[l];
2012             int c = 0;
2013             for (final ContextAssignment assignment : assignments) {
2014                 final JobStatus job = assignment.newJob == null
2015                         ? assignment.context.getRunningJobLocked() : assignment.newJob;
2016 
2017                 if (l > 0 || c > 0) {
2018                     s.append(" ");
2019                 }
2020                 s.append("(").append(assignment.context.getId()).append("=");
2021                 if (job == null) {
2022                     s.append("nothing");
2023                 } else {
2024                     if (job.getNamespace() != null) {
2025                         s.append(job.getNamespace()).append(":");
2026                     }
2027                     s.append(job.getJobId()).append("/").append(job.getUid());
2028                 }
2029                 s.append(")");
2030                 c++;
2031             }
2032         }
2033         return s.toString();
2034     }
2035 
2036     @GuardedBy("mLock")
updateConfigLocked()2037     void updateConfigLocked() {
2038         DeviceConfig.Properties properties =
2039                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
2040 
2041         // Concurrency limit should be in the range [8, MAX_CONCURRENCY_LIMIT].
2042         mSteadyStateConcurrencyLimit = Math.max(8, Math.min(MAX_CONCURRENCY_LIMIT,
2043                 properties.getInt(KEY_CONCURRENCY_LIMIT, DEFAULT_CONCURRENCY_LIMIT)));
2044 
2045         mScreenOffAdjustmentDelayMs = properties.getLong(
2046                 KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS);
2047 
2048         CONFIG_LIMITS_SCREEN_ON.normal.update(properties, mSteadyStateConcurrencyLimit);
2049         CONFIG_LIMITS_SCREEN_ON.moderate.update(properties, mSteadyStateConcurrencyLimit);
2050         CONFIG_LIMITS_SCREEN_ON.low.update(properties, mSteadyStateConcurrencyLimit);
2051         CONFIG_LIMITS_SCREEN_ON.critical.update(properties, mSteadyStateConcurrencyLimit);
2052 
2053         CONFIG_LIMITS_SCREEN_OFF.normal.update(properties, mSteadyStateConcurrencyLimit);
2054         CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties, mSteadyStateConcurrencyLimit);
2055         CONFIG_LIMITS_SCREEN_OFF.low.update(properties, mSteadyStateConcurrencyLimit);
2056         CONFIG_LIMITS_SCREEN_OFF.critical.update(properties, mSteadyStateConcurrencyLimit);
2057 
2058         // Package concurrency limits must in the range [1, mSteadyStateConcurrencyLimit].
2059         mPkgConcurrencyLimitEj = Math.max(1, Math.min(mSteadyStateConcurrencyLimit,
2060                 properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ)));
2061         mPkgConcurrencyLimitRegular = Math.max(1, Math.min(mSteadyStateConcurrencyLimit,
2062                 properties.getInt(
2063                         KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
2064 
2065         mMaxWaitTimeBypassEnabled = properties.getBoolean(
2066                 KEY_ENABLE_MAX_WAIT_TIME_BYPASS, DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS);
2067         // UI max wait must be in the range [0, infinity).
2068         mMaxWaitUIMs = Math.max(0, properties.getLong(KEY_MAX_WAIT_UI_MS, DEFAULT_MAX_WAIT_UI_MS));
2069         // EJ max wait must be in the range [UI max wait, infinity).
2070         mMaxWaitEjMs = Math.max(mMaxWaitUIMs,
2071                 properties.getLong(KEY_MAX_WAIT_EJ_MS, DEFAULT_MAX_WAIT_EJ_MS));
2072         // Regular max wait must be in the range [EJ max wait, infinity).
2073         mMaxWaitRegularMs = Math.max(mMaxWaitEjMs,
2074                 properties.getLong(KEY_MAX_WAIT_REGULAR_MS, DEFAULT_MAX_WAIT_REGULAR_MS));
2075     }
2076 
2077     @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)2078     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
2079         pw.println("Concurrency:");
2080 
2081         pw.increaseIndent();
2082         try {
2083             pw.println("Configuration:");
2084             pw.increaseIndent();
2085             pw.print(KEY_CONCURRENCY_LIMIT, mSteadyStateConcurrencyLimit).println();
2086             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
2087             pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
2088             pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
2089             pw.print(KEY_ENABLE_MAX_WAIT_TIME_BYPASS, mMaxWaitTimeBypassEnabled).println();
2090             pw.print(KEY_MAX_WAIT_UI_MS, mMaxWaitUIMs).println();
2091             pw.print(KEY_MAX_WAIT_EJ_MS, mMaxWaitEjMs).println();
2092             pw.print(KEY_MAX_WAIT_REGULAR_MS, mMaxWaitRegularMs).println();
2093             pw.println();
2094             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
2095             pw.println();
2096             CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
2097             pw.println();
2098             CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
2099             pw.println();
2100             CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
2101             pw.println();
2102             CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
2103             pw.println();
2104             CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
2105             pw.println();
2106             CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
2107             pw.println();
2108             CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
2109             pw.println();
2110             pw.decreaseIndent();
2111 
2112             pw.print("Screen state: current ");
2113             pw.print(mCurrentInteractiveState ? "ON" : "OFF");
2114             pw.print("  effective ");
2115             pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
2116             pw.println();
2117 
2118             pw.print("Last screen ON: ");
2119             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
2120             pw.println();
2121 
2122             pw.print("Last screen OFF: ");
2123             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
2124             pw.println();
2125 
2126             pw.println();
2127 
2128             pw.print("Current work counts: ");
2129             pw.println(mWorkCountTracker);
2130 
2131             pw.println();
2132 
2133             pw.print("mLastMemoryTrimLevel: ");
2134             pw.println(mLastMemoryTrimLevel);
2135             pw.println();
2136 
2137             pw.println("Active Package stats:");
2138             pw.increaseIndent();
2139             mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw));
2140             pw.decreaseIndent();
2141             pw.println();
2142 
2143             pw.print("User Grace Period: ");
2144             pw.println(mGracePeriodObserver.mGracePeriodExpiration);
2145             pw.println();
2146 
2147             mStatLogger.dump(pw);
2148         } finally {
2149             pw.decreaseIndent();
2150         }
2151     }
2152 
2153     @GuardedBy("mLock")
dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed, long nowUptime)2154     void dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate,
2155             long nowElapsed, long nowUptime) {
2156         pw.println("Active jobs:");
2157         pw.increaseIndent();
2158         if (mActiveServices.size() == 0) {
2159             pw.println("N/A");
2160         }
2161         for (int i = 0; i < mActiveServices.size(); i++) {
2162             JobServiceContext jsc = mActiveServices.get(i);
2163             final JobStatus job = jsc.getRunningJobLocked();
2164 
2165             if (job != null && !predicate.test(job)) {
2166                 continue;
2167             }
2168 
2169             pw.print("Slot #"); pw.print(i);
2170             pw.print("(ID="); pw.print(jsc.getId()); pw.print("): ");
2171             jsc.dumpLocked(pw, nowElapsed);
2172 
2173             if (job != null) {
2174                 pw.increaseIndent();
2175 
2176                 pw.increaseIndent();
2177                 job.dump(pw, false, nowElapsed);
2178                 pw.decreaseIndent();
2179 
2180                 pw.print("Evaluated bias: ");
2181                 pw.println(JobInfo.getBiasString(job.lastEvaluatedBias));
2182 
2183                 pw.print("Active at ");
2184                 TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
2185                 pw.print(", pending for ");
2186                 TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
2187                 pw.decreaseIndent();
2188                 pw.println();
2189             }
2190         }
2191         pw.decreaseIndent();
2192 
2193         pw.println();
2194         pw.print("Idle contexts (");
2195         pw.print(mIdleContexts.size());
2196         pw.println("):");
2197         pw.increaseIndent();
2198         for (int i = 0; i < mIdleContexts.size(); i++) {
2199             JobServiceContext jsc = mIdleContexts.valueAt(i);
2200 
2201             pw.print("ID="); pw.print(jsc.getId()); pw.print(": ");
2202             jsc.dumpLocked(pw, nowElapsed);
2203         }
2204         pw.decreaseIndent();
2205 
2206         if (mNumDroppedContexts > 0) {
2207             pw.println();
2208             pw.print("Dropped ");
2209             pw.print(mNumDroppedContexts);
2210             pw.println(" contexts");
2211         }
2212     }
2213 
dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)2214     public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
2215         final long token = proto.start(tag);
2216 
2217         proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE_STATE, mCurrentInteractiveState);
2218         proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE_STATE,
2219                 mEffectiveInteractiveState);
2220 
2221         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
2222                 nowRealtime - mLastScreenOnRealtime);
2223         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
2224                 nowRealtime - mLastScreenOffRealtime);
2225 
2226         proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
2227 
2228         mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
2229 
2230         proto.end(token);
2231     }
2232 
2233     /**
2234      * Decides whether a job is from the current foreground user or the equivalent.
2235      */
2236     @VisibleForTesting
shouldRunAsFgUserJob(JobStatus job)2237     boolean shouldRunAsFgUserJob(JobStatus job) {
2238         if (!mShouldRestrictBgUser) return true;
2239         int userId = job.getSourceUserId();
2240         UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
2241 
2242         // If the user has a parent user (e.g. a work profile of another user), the user should be
2243         // treated equivalent as its parent user.
2244         if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
2245                 && userInfo.profileGroupId != userId) {
2246             userId = userInfo.profileGroupId;
2247             userInfo = mUserManagerInternal.getUserInfo(userId);
2248         }
2249 
2250         int currentUser = mActivityManagerInternal.getCurrentUserId();
2251         // A user is treated as foreground user if any of the followings is true:
2252         // 1. The user is current user
2253         // 2. The user is primary user
2254         // 3. The user's grace period has not expired
2255         return currentUser == userId || userInfo.isPrimary()
2256                 || mGracePeriodObserver.isWithinGracePeriodForUser(userId);
2257     }
2258 
getJobWorkTypes(@onNull JobStatus js)2259     int getJobWorkTypes(@NonNull JobStatus js) {
2260         int classification = 0;
2261 
2262         if (shouldRunAsFgUserJob(js)) {
2263             if (js.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
2264                 classification |= WORK_TYPE_TOP;
2265             } else if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE) {
2266                 classification |= WORK_TYPE_FGS;
2267             } else {
2268                 classification |= WORK_TYPE_BG;
2269             }
2270 
2271             if (js.shouldTreatAsExpeditedJob()) {
2272                 classification |= WORK_TYPE_EJ;
2273             } else if (js.shouldTreatAsUserInitiatedJob()) {
2274                 classification |= WORK_TYPE_UI;
2275             }
2276         } else {
2277             if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
2278                     || js.shouldTreatAsExpeditedJob() || js.shouldTreatAsUserInitiatedJob()) {
2279                 classification |= WORK_TYPE_BGUSER_IMPORTANT;
2280             }
2281             // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here.
2282             classification |= WORK_TYPE_BGUSER;
2283         }
2284 
2285         return classification;
2286     }
2287 
2288     @VisibleForTesting
2289     static class WorkTypeConfig {
2290         private static final String KEY_PREFIX_MAX = CONFIG_KEY_PREFIX_CONCURRENCY + "max_";
2291         private static final String KEY_PREFIX_MIN = CONFIG_KEY_PREFIX_CONCURRENCY + "min_";
2292         @VisibleForTesting
2293         static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
2294         @VisibleForTesting
2295         static final String KEY_PREFIX_MAX_RATIO = KEY_PREFIX_MAX + "ratio_";
2296         private static final String KEY_PREFIX_MAX_RATIO_TOP = KEY_PREFIX_MAX_RATIO + "top_";
2297         private static final String KEY_PREFIX_MAX_RATIO_FGS = KEY_PREFIX_MAX_RATIO + "fgs_";
2298         private static final String KEY_PREFIX_MAX_RATIO_UI = KEY_PREFIX_MAX_RATIO + "ui_";
2299         private static final String KEY_PREFIX_MAX_RATIO_EJ = KEY_PREFIX_MAX_RATIO + "ej_";
2300         private static final String KEY_PREFIX_MAX_RATIO_BG = KEY_PREFIX_MAX_RATIO + "bg_";
2301         private static final String KEY_PREFIX_MAX_RATIO_BGUSER = KEY_PREFIX_MAX_RATIO + "bguser_";
2302         private static final String KEY_PREFIX_MAX_RATIO_BGUSER_IMPORTANT =
2303                 KEY_PREFIX_MAX_RATIO + "bguser_important_";
2304         @VisibleForTesting
2305         static final String KEY_PREFIX_MIN_RATIO = KEY_PREFIX_MIN + "ratio_";
2306         private static final String KEY_PREFIX_MIN_RATIO_TOP = KEY_PREFIX_MIN_RATIO + "top_";
2307         private static final String KEY_PREFIX_MIN_RATIO_FGS = KEY_PREFIX_MIN_RATIO + "fgs_";
2308         private static final String KEY_PREFIX_MIN_RATIO_UI = KEY_PREFIX_MIN_RATIO + "ui_";
2309         private static final String KEY_PREFIX_MIN_RATIO_EJ = KEY_PREFIX_MIN_RATIO + "ej_";
2310         private static final String KEY_PREFIX_MIN_RATIO_BG = KEY_PREFIX_MIN_RATIO + "bg_";
2311         private static final String KEY_PREFIX_MIN_RATIO_BGUSER = KEY_PREFIX_MIN_RATIO + "bguser_";
2312         private static final String KEY_PREFIX_MIN_RATIO_BGUSER_IMPORTANT =
2313                 KEY_PREFIX_MIN_RATIO + "bguser_important_";
2314         private final String mConfigIdentifier;
2315 
2316         private int mMaxTotal;
2317         private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
2318         private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
2319         private final int mDefaultMaxTotal;
2320         // We use SparseIntArrays to store floats because there is currently no SparseFloatArray
2321         // available, and it doesn't seem worth it to add such a data structure just for this
2322         // use case. We don't use SparseDoubleArrays because DeviceConfig only supports floats and
2323         // converting between floats and ints is more straightforward than floats and doubles.
2324         private final SparseIntArray mDefaultMinReservedSlotsRatio =
2325                 new SparseIntArray(NUM_WORK_TYPES);
2326         private final SparseIntArray mDefaultMaxAllowedSlotsRatio =
2327                 new SparseIntArray(NUM_WORK_TYPES);
2328 
WorkTypeConfig(@onNull String configIdentifier, int steadyStateConcurrencyLimit, int defaultMaxTotal, List<Pair<Integer, Float>> defaultMinRatio, List<Pair<Integer, Float>> defaultMaxRatio)2329         WorkTypeConfig(@NonNull String configIdentifier,
2330                 int steadyStateConcurrencyLimit, int defaultMaxTotal,
2331                 List<Pair<Integer, Float>> defaultMinRatio,
2332                 List<Pair<Integer, Float>> defaultMaxRatio) {
2333             mConfigIdentifier = configIdentifier;
2334             mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, steadyStateConcurrencyLimit);
2335             int numReserved = 0;
2336             for (int i = defaultMinRatio.size() - 1; i >= 0; --i) {
2337                 final float ratio = defaultMinRatio.get(i).second;
2338                 final int wt = defaultMinRatio.get(i).first;
2339                 if (ratio < 0 || 1 <= ratio) {
2340                     // 1 means to reserve everything. This shouldn't be allowed.
2341                     // We only create new configs on boot, so this should trigger during development
2342                     // (before the code gets checked in), so this makes sure the hard-coded defaults
2343                     // make sense. DeviceConfig values will be handled gracefully in update().
2344                     throw new IllegalArgumentException("Invalid default min ratio: wt=" + wt
2345                             + " minRatio=" + ratio);
2346                 }
2347                 mDefaultMinReservedSlotsRatio.put(wt, Float.floatToRawIntBits(ratio));
2348                 numReserved += mMaxTotal * ratio;
2349             }
2350             if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
2351                 // We only create new configs on boot, so this should trigger during development
2352                 // (before the code gets checked in), so this makes sure the hard-coded defaults
2353                 // make sense. DeviceConfig values will be handled gracefully in update().
2354                 throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
2355                         + " min=" + defaultMinRatio + " max=" + defaultMaxRatio);
2356             }
2357             for (int i = defaultMaxRatio.size() - 1; i >= 0; --i) {
2358                 final float ratio = defaultMaxRatio.get(i).second;
2359                 final int wt = defaultMaxRatio.get(i).first;
2360                 final float minRatio =
2361                         Float.intBitsToFloat(mDefaultMinReservedSlotsRatio.get(wt, 0));
2362                 if (ratio < minRatio || ratio <= 0) {
2363                     // Max ratio shouldn't be <= 0 or less than minRatio.
2364                     throw new IllegalArgumentException("Invalid default config:"
2365                             + " t=" + defaultMaxTotal
2366                             + " min=" + defaultMinRatio + " max=" + defaultMaxRatio);
2367                 }
2368                 mDefaultMaxAllowedSlotsRatio.put(wt, Float.floatToRawIntBits(ratio));
2369             }
2370             update(new DeviceConfig.Properties.Builder(
2371                     DeviceConfig.NAMESPACE_JOB_SCHEDULER).build(), steadyStateConcurrencyLimit);
2372         }
2373 
update(@onNull DeviceConfig.Properties properties, int steadyStateConcurrencyLimit)2374         void update(@NonNull DeviceConfig.Properties properties, int steadyStateConcurrencyLimit) {
2375             // Ensure total in the range [1, mSteadyStateConcurrencyLimit].
2376             mMaxTotal = Math.max(1, Math.min(steadyStateConcurrencyLimit,
2377                     properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal)));
2378 
2379             final int oneIntBits = Float.floatToIntBits(1);
2380 
2381             mMaxAllowedSlots.clear();
2382             // Ensure they're in the range [1, total].
2383             final int maxTop = getMaxValue(properties,
2384                     KEY_PREFIX_MAX_RATIO_TOP + mConfigIdentifier, WORK_TYPE_TOP, oneIntBits);
2385             mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop);
2386             final int maxFgs = getMaxValue(properties,
2387                     KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier, WORK_TYPE_FGS, oneIntBits);
2388             mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
2389             final int maxUi = getMaxValue(properties,
2390                     KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI, oneIntBits);
2391             mMaxAllowedSlots.put(WORK_TYPE_UI, maxUi);
2392             final int maxEj = getMaxValue(properties,
2393                     KEY_PREFIX_MAX_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ, oneIntBits);
2394             mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj);
2395             final int maxBg = getMaxValue(properties,
2396                     KEY_PREFIX_MAX_RATIO_BG + mConfigIdentifier, WORK_TYPE_BG, oneIntBits);
2397             mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
2398             final int maxBgUserImp = getMaxValue(properties,
2399                     KEY_PREFIX_MAX_RATIO_BGUSER_IMPORTANT + mConfigIdentifier,
2400                     WORK_TYPE_BGUSER_IMPORTANT, oneIntBits);
2401             mMaxAllowedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, maxBgUserImp);
2402             final int maxBgUser = getMaxValue(properties,
2403                     KEY_PREFIX_MAX_RATIO_BGUSER + mConfigIdentifier, WORK_TYPE_BGUSER, oneIntBits);
2404             mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser);
2405 
2406             int remaining = mMaxTotal;
2407             mMinReservedSlots.clear();
2408             // Ensure top is in the range [1, min(maxTop, total)]
2409             final int minTop = getMinValue(properties,
2410                     KEY_PREFIX_MIN_RATIO_TOP + mConfigIdentifier, WORK_TYPE_TOP,
2411                     1, Math.min(maxTop, mMaxTotal));
2412             mMinReservedSlots.put(WORK_TYPE_TOP, minTop);
2413             remaining -= minTop;
2414             // Ensure fgs is in the range [0, min(maxFgs, remaining)]
2415             final int minFgs = getMinValue(properties,
2416                     KEY_PREFIX_MIN_RATIO_FGS + mConfigIdentifier, WORK_TYPE_FGS,
2417                     0, Math.min(maxFgs, remaining));
2418             mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
2419             remaining -= minFgs;
2420             // Ensure ui is in the range [0, min(maxUi, remaining)]
2421             final int minUi = getMinValue(properties,
2422                     KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI,
2423                     0, Math.min(maxUi, remaining));
2424             mMinReservedSlots.put(WORK_TYPE_UI, minUi);
2425             remaining -= minUi;
2426             // Ensure ej is in the range [0, min(maxEj, remaining)]
2427             final int minEj = getMinValue(properties,
2428                     KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ,
2429                     0, Math.min(maxEj, remaining));
2430             mMinReservedSlots.put(WORK_TYPE_EJ, minEj);
2431             remaining -= minEj;
2432             // Ensure bg is in the range [0, min(maxBg, remaining)]
2433             final int minBg = getMinValue(properties,
2434                     KEY_PREFIX_MIN_RATIO_BG + mConfigIdentifier, WORK_TYPE_BG,
2435                     0, Math.min(maxBg, remaining));
2436             mMinReservedSlots.put(WORK_TYPE_BG, minBg);
2437             remaining -= minBg;
2438             // Ensure bg user imp is in the range [0, min(maxBgUserImp, remaining)]
2439             final int minBgUserImp = getMinValue(properties,
2440                     KEY_PREFIX_MIN_RATIO_BGUSER_IMPORTANT + mConfigIdentifier,
2441                     WORK_TYPE_BGUSER_IMPORTANT, 0, Math.min(maxBgUserImp, remaining));
2442             mMinReservedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, minBgUserImp);
2443             remaining -= minBgUserImp;
2444             // Ensure bg user is in the range [0, min(maxBgUser, remaining)]
2445             final int minBgUser = getMinValue(properties,
2446                     KEY_PREFIX_MIN_RATIO_BGUSER + mConfigIdentifier, WORK_TYPE_BGUSER,
2447                     0, Math.min(maxBgUser, remaining));
2448             mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser);
2449         }
2450 
2451         /**
2452          * Return the calculated max value for the work type.
2453          * @param defaultFloatInIntBits A {@code float} value in int bits representation (using
2454          *                              {@link Float#floatToIntBits(float)}.
2455          */
getMaxValue(@onNull DeviceConfig.Properties properties, @NonNull String key, int workType, int defaultFloatInIntBits)2456         private int getMaxValue(@NonNull DeviceConfig.Properties properties, @NonNull String key,
2457                 int workType, int defaultFloatInIntBits) {
2458             final float maxRatio = Math.min(1, properties.getFloat(key,
2459                     Float.intBitsToFloat(
2460                             mDefaultMaxAllowedSlotsRatio.get(workType, defaultFloatInIntBits))));
2461             // Max values should be in  the range [1, total].
2462             return Math.max(1, (int) (mMaxTotal * maxRatio));
2463         }
2464 
2465         /**
2466          * Return the calculated min value for the work type.
2467          */
getMinValue(@onNull DeviceConfig.Properties properties, @NonNull String key, int workType, int lowerLimit, int upperLimit)2468         private int getMinValue(@NonNull DeviceConfig.Properties properties, @NonNull String key,
2469                 int workType, int lowerLimit, int upperLimit) {
2470             final float minRatio = Math.min(1,
2471                     properties.getFloat(key,
2472                             Float.intBitsToFloat(mDefaultMinReservedSlotsRatio.get(workType))));
2473             return Math.max(lowerLimit, Math.min(upperLimit, (int) (mMaxTotal * minRatio)));
2474         }
2475 
getMaxTotal()2476         int getMaxTotal() {
2477             return mMaxTotal;
2478         }
2479 
getMax(@orkType int workType)2480         int getMax(@WorkType int workType) {
2481             return mMaxAllowedSlots.get(workType, mMaxTotal);
2482         }
2483 
getMinReserved(@orkType int workType)2484         int getMinReserved(@WorkType int workType) {
2485             return mMinReservedSlots.get(workType);
2486         }
2487 
dump(IndentingPrintWriter pw)2488         void dump(IndentingPrintWriter pw) {
2489             pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println();
2490             pw.print(KEY_PREFIX_MIN_RATIO_TOP + mConfigIdentifier,
2491                             mMinReservedSlots.get(WORK_TYPE_TOP))
2492                     .println();
2493             pw.print(KEY_PREFIX_MAX_RATIO_TOP + mConfigIdentifier,
2494                             mMaxAllowedSlots.get(WORK_TYPE_TOP))
2495                     .println();
2496             pw.print(KEY_PREFIX_MIN_RATIO_FGS + mConfigIdentifier,
2497                             mMinReservedSlots.get(WORK_TYPE_FGS))
2498                     .println();
2499             pw.print(KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier,
2500                             mMaxAllowedSlots.get(WORK_TYPE_FGS))
2501                     .println();
2502             pw.print(KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier,
2503                             mMinReservedSlots.get(WORK_TYPE_UI))
2504                     .println();
2505             pw.print(KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier,
2506                             mMaxAllowedSlots.get(WORK_TYPE_UI))
2507                     .println();
2508             pw.print(KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier,
2509                             mMinReservedSlots.get(WORK_TYPE_EJ))
2510                     .println();
2511             pw.print(KEY_PREFIX_MAX_RATIO_EJ + mConfigIdentifier,
2512                             mMaxAllowedSlots.get(WORK_TYPE_EJ))
2513                     .println();
2514             pw.print(KEY_PREFIX_MIN_RATIO_BG + mConfigIdentifier,
2515                             mMinReservedSlots.get(WORK_TYPE_BG))
2516                     .println();
2517             pw.print(KEY_PREFIX_MAX_RATIO_BG + mConfigIdentifier,
2518                             mMaxAllowedSlots.get(WORK_TYPE_BG))
2519                     .println();
2520             pw.print(KEY_PREFIX_MIN_RATIO_BGUSER + mConfigIdentifier,
2521                     mMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
2522             pw.print(KEY_PREFIX_MAX_RATIO_BGUSER + mConfigIdentifier,
2523                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
2524             pw.print(KEY_PREFIX_MIN_RATIO_BGUSER + mConfigIdentifier,
2525                     mMinReservedSlots.get(WORK_TYPE_BGUSER)).println();
2526             pw.print(KEY_PREFIX_MAX_RATIO_BGUSER + mConfigIdentifier,
2527                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println();
2528         }
2529     }
2530 
2531     /** {@link WorkTypeConfig} for each memory trim level. */
2532     static class WorkConfigLimitsPerMemoryTrimLevel {
2533         public final WorkTypeConfig normal;
2534         public final WorkTypeConfig moderate;
2535         public final WorkTypeConfig low;
2536         public final WorkTypeConfig critical;
2537 
WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, WorkTypeConfig low, WorkTypeConfig critical)2538         WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate,
2539                 WorkTypeConfig low, WorkTypeConfig critical) {
2540             this.normal = normal;
2541             this.moderate = moderate;
2542             this.low = low;
2543             this.critical = critical;
2544         }
2545     }
2546 
2547     /**
2548      * This class keeps the track of when a user's grace period expires.
2549      */
2550     @VisibleForTesting
2551     static class GracePeriodObserver extends UserSwitchObserver {
2552         // Key is UserId and Value is the time when grace period expires
2553         @VisibleForTesting
2554         final SparseLongArray mGracePeriodExpiration = new SparseLongArray();
2555         private int mCurrentUserId;
2556         @VisibleForTesting
2557         int mGracePeriod;
2558         private final UserManagerInternal mUserManagerInternal;
2559         final Object mLock = new Object();
2560 
2561 
GracePeriodObserver(Context context)2562         GracePeriodObserver(Context context) {
2563             mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class)
2564                     .getCurrentUserId();
2565             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
2566             mGracePeriod = Math.max(0, context.getResources().getInteger(
2567                     R.integer.config_jobSchedulerUserGracePeriod));
2568         }
2569 
2570         @Override
onUserSwitchComplete(int newUserId)2571         public void onUserSwitchComplete(int newUserId) {
2572             final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod;
2573             synchronized (mLock) {
2574                 if (mCurrentUserId != UserHandle.USER_NULL
2575                         && mUserManagerInternal.exists(mCurrentUserId)) {
2576                     mGracePeriodExpiration.append(mCurrentUserId, expiration);
2577                 }
2578                 mGracePeriodExpiration.delete(newUserId);
2579                 mCurrentUserId = newUserId;
2580             }
2581         }
2582 
onUserRemoved(int userId)2583         void onUserRemoved(int userId) {
2584             synchronized (mLock) {
2585                 mGracePeriodExpiration.delete(userId);
2586             }
2587         }
2588 
2589         @VisibleForTesting
isWithinGracePeriodForUser(int userId)2590         public boolean isWithinGracePeriodForUser(int userId) {
2591             synchronized (mLock) {
2592                 return userId == mCurrentUserId
2593                         || sElapsedRealtimeClock.millis()
2594                         < mGracePeriodExpiration.get(userId, Long.MAX_VALUE);
2595             }
2596         }
2597     }
2598 
2599     /**
2600      * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
2601      * are running/pending, how many more job can start.
2602      *
2603      * Extracted for testing and logging.
2604      */
2605     @VisibleForTesting
2606     static class WorkCountTracker {
2607         private int mConfigMaxTotal;
2608         private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
2609         private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
2610         private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES);
2611 
2612         /**
2613          * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't
2614          * enough ready jobs of a type to take up all of the desired reserved slots.
2615          */
2616         private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
2617         private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES);
2618         private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES);
2619         private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES);
2620         private int mNumUnspecializedRemaining = 0;
2621 
setConfig(@onNull WorkTypeConfig workTypeConfig)2622         void setConfig(@NonNull WorkTypeConfig workTypeConfig) {
2623             mConfigMaxTotal = workTypeConfig.getMaxTotal();
2624             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2625                 mConfigNumReservedSlots.put(workType, workTypeConfig.getMinReserved(workType));
2626                 mConfigAbsoluteMaxSlots.put(workType, workTypeConfig.getMax(workType));
2627             }
2628 
2629             mNumUnspecializedRemaining = mConfigMaxTotal;
2630             for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
2631                 mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
2632                         mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
2633             }
2634         }
2635 
resetCounts()2636         void resetCounts() {
2637             mNumActuallyReservedSlots.clear();
2638             mNumPendingJobs.clear();
2639             mNumRunningJobs.clear();
2640             resetStagingCount();
2641         }
2642 
resetStagingCount()2643         void resetStagingCount() {
2644             mNumStartingJobs.clear();
2645         }
2646 
incrementRunningJobCount(@orkType int workType)2647         void incrementRunningJobCount(@WorkType int workType) {
2648             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
2649         }
2650 
incrementPendingJobCount(int workTypes)2651         void incrementPendingJobCount(int workTypes) {
2652             adjustPendingJobCount(workTypes, true);
2653         }
2654 
decrementPendingJobCount(int workTypes)2655         void decrementPendingJobCount(int workTypes) {
2656             if (adjustPendingJobCount(workTypes, false) > 1) {
2657                 // We don't need to adjust reservations if only one work type was modified
2658                 // because that work type is the one we're using.
2659 
2660                 for (int workType = 1; workType <= workTypes; workType <<= 1) {
2661                     if ((workType & workTypes) == workType) {
2662                         maybeAdjustReservations(workType);
2663                     }
2664                 }
2665             }
2666         }
2667 
2668         /** Returns the number of WorkTypes that were modified. */
adjustPendingJobCount(int workTypes, boolean add)2669         private int adjustPendingJobCount(int workTypes, boolean add) {
2670             final int adj = add ? 1 : -1;
2671 
2672             int numAdj = 0;
2673             // We don't know which type we'll classify the job as when we run it yet, so make sure
2674             // we have space in all applicable slots.
2675             for (int workType = 1; workType <= workTypes; workType <<= 1) {
2676                 if ((workTypes & workType) == workType) {
2677                     mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj);
2678                     numAdj++;
2679                 }
2680             }
2681 
2682             return numAdj;
2683         }
2684 
stageJob(@orkType int workType, int allWorkTypes)2685         void stageJob(@WorkType int workType, int allWorkTypes) {
2686             final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
2687             mNumStartingJobs.put(workType, newNumStartingJobs);
2688             decrementPendingJobCount(allWorkTypes);
2689             if (newNumStartingJobs + mNumRunningJobs.get(workType)
2690                     > mNumActuallyReservedSlots.get(workType)) {
2691                 mNumUnspecializedRemaining--;
2692             }
2693         }
2694 
onStagedJobFailed(@orkType int workType)2695         void onStagedJobFailed(@WorkType int workType) {
2696             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
2697             if (oldNumStartingJobs == 0) {
2698                 Slog.e(TAG, "# staged jobs for " + workType + " went negative.");
2699                 // We are in a bad state. We will eventually recover when the pending list is
2700                 // regenerated.
2701                 return;
2702             }
2703             mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
2704             maybeAdjustReservations(workType);
2705         }
2706 
maybeAdjustReservations(@orkType int workType)2707         private void maybeAdjustReservations(@WorkType int workType) {
2708             // Always make sure we reserve the minimum number of slots in case new jobs become ready
2709             // soon.
2710             final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType),
2711                     mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
2712                             + mNumPendingJobs.get(workType));
2713             if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) {
2714                 // We've run all jobs for this type. Let another type use it now.
2715                 mNumActuallyReservedSlots.put(workType, numRemainingForType);
2716                 int assignWorkType = WORK_TYPE_NONE;
2717                 for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
2718                     int wt = mNumActuallyReservedSlots.keyAt(i);
2719                     if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
2720                         // Try to give this slot to the highest bias one within its limits.
2721                         int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
2722                                 + mNumPendingJobs.get(wt);
2723                         if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
2724                                 && total > mNumActuallyReservedSlots.valueAt(i)) {
2725                             assignWorkType = wt;
2726                         }
2727                     }
2728                 }
2729                 if (assignWorkType != WORK_TYPE_NONE) {
2730                     mNumActuallyReservedSlots.put(assignWorkType,
2731                             mNumActuallyReservedSlots.get(assignWorkType) + 1);
2732                 } else {
2733                     mNumUnspecializedRemaining++;
2734                 }
2735             }
2736         }
2737 
onJobStarted(@orkType int workType)2738         void onJobStarted(@WorkType int workType) {
2739             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
2740             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
2741             if (oldNumStartingJobs == 0) {
2742                 Slog.e(TAG, "# stated jobs for " + workType + " went negative.");
2743                 // We are in a bad state. We will eventually recover when the pending list is
2744                 // regenerated. For now, only modify the running count.
2745             } else {
2746                 mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
2747             }
2748         }
2749 
onJobFinished(@orkType int workType)2750         void onJobFinished(@WorkType int workType) {
2751             final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1;
2752             if (newNumRunningJobs < 0) {
2753                 // We are in a bad state. We will eventually recover when the pending list is
2754                 // regenerated.
2755                 Slog.e(TAG, "# running jobs for " + workType + " went negative.");
2756                 return;
2757             }
2758             mNumRunningJobs.put(workType, newNumRunningJobs);
2759             maybeAdjustReservations(workType);
2760         }
2761 
onCountDone()2762         void onCountDone() {
2763             // Calculate how many slots to reserve for each work type. "Unspecialized" slots will
2764             // be reserved for higher importance types first (ie. top before ej before bg).
2765             // Steps:
2766             //   1. Account for slots for already running jobs
2767             //   2. Use remaining unaccounted slots to try and ensure minimum reserved slots
2768             //   3. Allocate remaining up to max, based on importance
2769 
2770             mNumUnspecializedRemaining = mConfigMaxTotal;
2771 
2772             // Step 1
2773             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2774                 int run = mNumRunningJobs.get(workType);
2775                 mRecycledReserved.put(workType, run);
2776                 mNumUnspecializedRemaining -= run;
2777             }
2778 
2779             // Step 2
2780             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2781                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
2782                 int res = mRecycledReserved.get(workType);
2783                 int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
2784                         Math.min(num, mConfigNumReservedSlots.get(workType) - res)));
2785                 res += fillUp;
2786                 mRecycledReserved.put(workType, res);
2787                 mNumUnspecializedRemaining -= fillUp;
2788             }
2789 
2790             // Step 3
2791             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2792                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
2793                 int res = mRecycledReserved.get(workType);
2794                 int unspecializedAssigned = Math.max(0,
2795                         Math.min(mNumUnspecializedRemaining,
2796                                 Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res));
2797                 mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned);
2798                 mNumUnspecializedRemaining -= unspecializedAssigned;
2799             }
2800         }
2801 
canJobStart(int workTypes)2802         int canJobStart(int workTypes) {
2803             for (int workType = 1; workType <= workTypes; workType <<= 1) {
2804                 if ((workTypes & workType) == workType) {
2805                     final int maxAllowed = Math.min(
2806                             mConfigAbsoluteMaxSlots.get(workType),
2807                             mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining);
2808                     if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
2809                             < maxAllowed) {
2810                         return workType;
2811                     }
2812                 }
2813             }
2814             return WORK_TYPE_NONE;
2815         }
2816 
canJobStart(int workTypes, @WorkType int replacingWorkType)2817         int canJobStart(int workTypes, @WorkType int replacingWorkType) {
2818             final boolean changedNums;
2819             int oldNumRunning = mNumRunningJobs.get(replacingWorkType);
2820             if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) {
2821                 mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1);
2822                 // Lazy implementation to avoid lots of processing. Best way would be to go
2823                 // through the whole process of adjusting reservations, but the processing cost
2824                 // is likely not worth it.
2825                 mNumUnspecializedRemaining++;
2826                 changedNums = true;
2827             } else {
2828                 changedNums = false;
2829             }
2830 
2831             final int ret = canJobStart(workTypes);
2832             if (changedNums) {
2833                 mNumRunningJobs.put(replacingWorkType, oldNumRunning);
2834                 mNumUnspecializedRemaining--;
2835             }
2836             return ret;
2837         }
2838 
getPendingJobCount(@orkType final int workType)2839         int getPendingJobCount(@WorkType final int workType) {
2840             return mNumPendingJobs.get(workType, 0);
2841         }
2842 
getRunningJobCount(@orkType final int workType)2843         int getRunningJobCount(@WorkType final int workType) {
2844             return mNumRunningJobs.get(workType, 0);
2845         }
2846 
isOverTypeLimit(@orkType final int workType)2847         boolean isOverTypeLimit(@WorkType final int workType) {
2848             return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType);
2849         }
2850 
toString()2851         public String toString() {
2852             StringBuilder sb = new StringBuilder();
2853 
2854             sb.append("Config={");
2855             sb.append("tot=").append(mConfigMaxTotal);
2856             sb.append(" mins=");
2857             sb.append(mConfigNumReservedSlots);
2858             sb.append(" maxs=");
2859             sb.append(mConfigAbsoluteMaxSlots);
2860             sb.append("}");
2861 
2862             sb.append(", act res=").append(mNumActuallyReservedSlots);
2863             sb.append(", Pending=").append(mNumPendingJobs);
2864             sb.append(", Running=").append(mNumRunningJobs);
2865             sb.append(", Staged=").append(mNumStartingJobs);
2866             sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining);
2867 
2868             return sb.toString();
2869         }
2870     }
2871 
2872     @VisibleForTesting
2873     static class PackageStats {
2874         public int userId;
2875         public String packageName;
2876         public int numRunningEj;
2877         public int numRunningRegular;
2878         public int numStagedEj;
2879         public int numStagedRegular;
2880 
setPackage(int userId, @NonNull String packageName)2881         private void setPackage(int userId, @NonNull String packageName) {
2882             this.userId = userId;
2883             this.packageName = packageName;
2884             numRunningEj = numRunningRegular = 0;
2885             resetStagedCount();
2886         }
2887 
resetStagedCount()2888         private void resetStagedCount() {
2889             numStagedEj = numStagedRegular = 0;
2890         }
2891 
adjustRunningCount(boolean add, boolean forEj)2892         private void adjustRunningCount(boolean add, boolean forEj) {
2893             if (forEj) {
2894                 numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1));
2895             } else {
2896                 numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1));
2897             }
2898         }
2899 
adjustStagedCount(boolean add, boolean forEj)2900         private void adjustStagedCount(boolean add, boolean forEj) {
2901             if (forEj) {
2902                 numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1));
2903             } else {
2904                 numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1));
2905             }
2906         }
2907 
2908         @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw)2909         private void dumpLocked(IndentingPrintWriter pw) {
2910             pw.print("PackageStats{");
2911             pw.print(userId);
2912             pw.print("-");
2913             pw.print(packageName);
2914             pw.print("#runEJ", numRunningEj);
2915             pw.print("#runReg", numRunningRegular);
2916             pw.print("#stagedEJ", numStagedEj);
2917             pw.print("#stagedReg", numStagedRegular);
2918             pw.println("}");
2919         }
2920     }
2921 
2922     @VisibleForTesting
2923     static final class ContextAssignment {
2924         public JobServiceContext context;
2925         public int preferredUid = JobServiceContext.NO_PREFERRED_UID;
2926         public int workType = WORK_TYPE_NONE;
2927         public String preemptReason;
2928         public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
2929         public long timeUntilStoppableMs;
2930         public String shouldStopJobReason;
2931         public JobStatus newJob;
2932         public int newWorkType = WORK_TYPE_NONE;
2933 
clear()2934         void clear() {
2935             context = null;
2936             preferredUid = JobServiceContext.NO_PREFERRED_UID;
2937             workType = WORK_TYPE_NONE;
2938             preemptReason = null;
2939             preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
2940             timeUntilStoppableMs = 0;
2941             shouldStopJobReason = null;
2942             newJob = null;
2943             newWorkType = WORK_TYPE_NONE;
2944         }
2945     }
2946 
2947     @VisibleForTesting
2948     static final class AssignmentInfo {
2949         public long minPreferredUidOnlyWaitingTimeMs;
2950         public int numRunningImmediacyPrivileged;
2951         public int numRunningUi;
2952         public int numRunningEj;
2953         public int numRunningReg;
2954 
clear()2955         void clear() {
2956             minPreferredUidOnlyWaitingTimeMs = 0;
2957             numRunningImmediacyPrivileged = 0;
2958             numRunningUi = 0;
2959             numRunningEj = 0;
2960             numRunningReg = 0;
2961         }
2962     }
2963 
2964     // TESTING HELPERS
2965 
2966     @VisibleForTesting
addRunningJobForTesting(@onNull JobStatus job)2967     void addRunningJobForTesting(@NonNull JobStatus job) {
2968         mRunningJobs.add(job);
2969         final PackageStats packageStats =
2970                 getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName());
2971         packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob());
2972 
2973         final JobServiceContext context;
2974         if (mIdleContexts.size() > 0) {
2975             context = mIdleContexts.removeAt(mIdleContexts.size() - 1);
2976         } else {
2977             context = createNewJobServiceContext();
2978         }
2979         context.executeRunnableJob(job, mWorkCountTracker.canJobStart(getJobWorkTypes(job)));
2980         mActiveServices.add(context);
2981     }
2982 
2983     @VisibleForTesting
getPackageConcurrencyLimitEj()2984     int getPackageConcurrencyLimitEj() {
2985         return mPkgConcurrencyLimitEj;
2986     }
2987 
getPackageConcurrencyLimitRegular()2988     int getPackageConcurrencyLimitRegular() {
2989         return mPkgConcurrencyLimitRegular;
2990     }
2991 
2992     /** Gets the {@link PackageStats} object for the app and saves it for testing use. */
2993     @NonNull
2994     @VisibleForTesting
getPackageStatsForTesting(int userId, @NonNull String packageName)2995     PackageStats getPackageStatsForTesting(int userId, @NonNull String packageName) {
2996         final PackageStats packageStats = getPkgStatsLocked(userId, packageName);
2997         mActivePkgStats.add(userId, packageName, packageStats);
2998         return packageStats;
2999     }
3000 
3001     @VisibleForTesting
3002     static class Injector {
3003         @NonNull
createJobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper)3004         JobServiceContext createJobServiceContext(JobSchedulerService service,
3005                 JobConcurrencyManager concurrencyManager,
3006                 JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats,
3007                 JobPackageTracker tracker, Looper looper) {
3008             return new JobServiceContext(service, concurrencyManager, notificationCoordinator,
3009                     batteryStats, tracker, looper);
3010         }
3011     }
3012 }
3013