• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.job;
18 
19 import static 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 == null || 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.resetPendingJobReasonsCache(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 (js != null &&
1920                     jc.stopIfExecutingLocked(pkgName, userId, namespace,
1921                         matchJobId, jobId, stopReason, internalStopReason)) {
1922                 foundSome = true;
1923                 pw.print("Stopping job: ");
1924                 js.printUniqueId(pw);
1925                 pw.print(" ");
1926                 pw.println(js.getServiceComponent().flattenToShortString());
1927             }
1928         }
1929         return foundSome;
1930     }
1931 
1932     /**
1933      * Returns the estimated network bytes if the job is running. Returns {@code null} if the job
1934      * isn't running.
1935      */
1936     @Nullable
1937     @GuardedBy("mLock")
getEstimatedNetworkBytesLocked(String pkgName, int uid, String namespace, int jobId)1938     Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid,
1939             String namespace, int jobId) {
1940         for (int i = 0; i < mActiveServices.size(); i++) {
1941             final JobServiceContext jc = mActiveServices.get(i);
1942             final JobStatus js = jc.getRunningJobLocked();
1943             if (js != null && js.matches(uid, namespace, jobId)
1944                     && js.getSourcePackageName().equals(pkgName)) {
1945                 return jc.getEstimatedNetworkBytes();
1946             }
1947         }
1948         return null;
1949     }
1950 
1951     /**
1952      * Returns the transferred network bytes if the job is running. Returns {@code null} if the job
1953      * isn't running.
1954      */
1955     @Nullable
1956     @GuardedBy("mLock")
getTransferredNetworkBytesLocked(String pkgName, int uid, String namespace, int jobId)1957     Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid,
1958             String namespace, int jobId) {
1959         for (int i = 0; i < mActiveServices.size(); i++) {
1960             final JobServiceContext jc = mActiveServices.get(i);
1961             final JobStatus js = jc.getRunningJobLocked();
1962             if (js != null && js.matches(uid, namespace, jobId)
1963                     && js.getSourcePackageName().equals(pkgName)) {
1964                 return jc.getTransferredNetworkBytes();
1965             }
1966         }
1967         return null;
1968     }
1969 
isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId, @NonNull String packageName)1970     boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId,
1971             @NonNull String packageName) {
1972         return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs(
1973                 notificationId, userId, packageName);
1974     }
1975 
isNotificationChannelAssociatedWithAnyUserInitiatedJobs( @onNull String notificationChannel, int userId, @NonNull String packageName)1976     boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
1977             @NonNull String notificationChannel, int userId, @NonNull String packageName) {
1978         return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs(
1979                 notificationChannel, userId, packageName);
1980     }
1981 
1982     @NonNull
createNewJobServiceContext()1983     private JobServiceContext createNewJobServiceContext() {
1984         return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
1985                 IBatteryStats.Stub.asInterface(
1986                         ServiceManager.getService(BatteryStats.SERVICE_NAME)),
1987                 mService.mJobPackageTracker, AppSchedulingModuleThread.get().getLooper());
1988     }
1989 
1990     @GuardedBy("mLock")
printPendingQueueLocked()1991     private String printPendingQueueLocked() {
1992         StringBuilder s = new StringBuilder("Pending queue: ");
1993         PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
1994         JobStatus js;
1995         pendingJobQueue.resetIterator();
1996         while ((js = pendingJobQueue.next()) != null) {
1997             s.append("(")
1998                     .append("{")
1999                     .append(js.getNamespace())
2000                     .append("} ")
2001                     .append(js.getJob().getId())
2002                     .append(", ")
2003                     .append(js.getUid())
2004                     .append(") ");
2005         }
2006         return s.toString();
2007     }
2008 
printAssignments(String header, Collection<ContextAssignment>... list)2009     private static String printAssignments(String header, Collection<ContextAssignment>... list) {
2010         final StringBuilder s = new StringBuilder(header + ": ");
2011         for (int l = 0; l < list.length; ++l) {
2012             final Collection<ContextAssignment> assignments = list[l];
2013             int c = 0;
2014             for (final ContextAssignment assignment : assignments) {
2015                 final JobStatus job = assignment.newJob == null
2016                         ? assignment.context.getRunningJobLocked() : assignment.newJob;
2017 
2018                 if (l > 0 || c > 0) {
2019                     s.append(" ");
2020                 }
2021                 s.append("(").append(assignment.context.getId()).append("=");
2022                 if (job == null) {
2023                     s.append("nothing");
2024                 } else {
2025                     if (job.getNamespace() != null) {
2026                         s.append(job.getNamespace()).append(":");
2027                     }
2028                     s.append(job.getJobId()).append("/").append(job.getUid());
2029                 }
2030                 s.append(")");
2031                 c++;
2032             }
2033         }
2034         return s.toString();
2035     }
2036 
2037     @GuardedBy("mLock")
updateConfigLocked()2038     void updateConfigLocked() {
2039         DeviceConfig.Properties properties =
2040                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
2041 
2042         // Concurrency limit should be in the range [1, MAX_CONCURRENCY_LIMIT].
2043         mSteadyStateConcurrencyLimit = Math.max(1, Math.min(MAX_CONCURRENCY_LIMIT,
2044                 properties.getInt(KEY_CONCURRENCY_LIMIT, DEFAULT_CONCURRENCY_LIMIT)));
2045 
2046         mScreenOffAdjustmentDelayMs = properties.getLong(
2047                 KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS);
2048 
2049         CONFIG_LIMITS_SCREEN_ON.normal.update(properties, mSteadyStateConcurrencyLimit);
2050         CONFIG_LIMITS_SCREEN_ON.moderate.update(properties, mSteadyStateConcurrencyLimit);
2051         CONFIG_LIMITS_SCREEN_ON.low.update(properties, mSteadyStateConcurrencyLimit);
2052         CONFIG_LIMITS_SCREEN_ON.critical.update(properties, mSteadyStateConcurrencyLimit);
2053 
2054         CONFIG_LIMITS_SCREEN_OFF.normal.update(properties, mSteadyStateConcurrencyLimit);
2055         CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties, mSteadyStateConcurrencyLimit);
2056         CONFIG_LIMITS_SCREEN_OFF.low.update(properties, mSteadyStateConcurrencyLimit);
2057         CONFIG_LIMITS_SCREEN_OFF.critical.update(properties, mSteadyStateConcurrencyLimit);
2058 
2059         // Package concurrency limits must in the range [1, mSteadyStateConcurrencyLimit].
2060         mPkgConcurrencyLimitEj = Math.max(1, Math.min(mSteadyStateConcurrencyLimit,
2061                 properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ)));
2062         mPkgConcurrencyLimitRegular = Math.max(1, Math.min(mSteadyStateConcurrencyLimit,
2063                 properties.getInt(
2064                         KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
2065 
2066         mMaxWaitTimeBypassEnabled = properties.getBoolean(
2067                 KEY_ENABLE_MAX_WAIT_TIME_BYPASS, DEFAULT_ENABLE_MAX_WAIT_TIME_BYPASS);
2068         // UI max wait must be in the range [0, infinity).
2069         mMaxWaitUIMs = Math.max(0, properties.getLong(KEY_MAX_WAIT_UI_MS, DEFAULT_MAX_WAIT_UI_MS));
2070         // EJ max wait must be in the range [UI max wait, infinity).
2071         mMaxWaitEjMs = Math.max(mMaxWaitUIMs,
2072                 properties.getLong(KEY_MAX_WAIT_EJ_MS, DEFAULT_MAX_WAIT_EJ_MS));
2073         // Regular max wait must be in the range [EJ max wait, infinity).
2074         mMaxWaitRegularMs = Math.max(mMaxWaitEjMs,
2075                 properties.getLong(KEY_MAX_WAIT_REGULAR_MS, DEFAULT_MAX_WAIT_REGULAR_MS));
2076     }
2077 
2078     @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime)2079     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
2080         pw.println("Concurrency:");
2081 
2082         pw.increaseIndent();
2083         try {
2084             pw.println("Configuration:");
2085             pw.increaseIndent();
2086             pw.print(KEY_CONCURRENCY_LIMIT, mSteadyStateConcurrencyLimit).println();
2087             pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
2088             pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
2089             pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
2090             pw.print(KEY_ENABLE_MAX_WAIT_TIME_BYPASS, mMaxWaitTimeBypassEnabled).println();
2091             pw.print(KEY_MAX_WAIT_UI_MS, mMaxWaitUIMs).println();
2092             pw.print(KEY_MAX_WAIT_EJ_MS, mMaxWaitEjMs).println();
2093             pw.print(KEY_MAX_WAIT_REGULAR_MS, mMaxWaitRegularMs).println();
2094             pw.println();
2095             CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
2096             pw.println();
2097             CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
2098             pw.println();
2099             CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
2100             pw.println();
2101             CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
2102             pw.println();
2103             CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
2104             pw.println();
2105             CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
2106             pw.println();
2107             CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
2108             pw.println();
2109             CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
2110             pw.println();
2111             pw.decreaseIndent();
2112 
2113             pw.print("Screen state: current ");
2114             pw.print(mCurrentInteractiveState ? "ON" : "OFF");
2115             pw.print("  effective ");
2116             pw.print(mEffectiveInteractiveState ? "ON" : "OFF");
2117             pw.println();
2118 
2119             pw.print("Last screen ON: ");
2120             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOnRealtime, now);
2121             pw.println();
2122 
2123             pw.print("Last screen OFF: ");
2124             TimeUtils.dumpTimeWithDelta(pw, now - nowRealtime + mLastScreenOffRealtime, now);
2125             pw.println();
2126 
2127             pw.println();
2128 
2129             pw.print("Current work counts: ");
2130             pw.println(mWorkCountTracker);
2131 
2132             pw.println();
2133 
2134             pw.print("mLastMemoryTrimLevel: ");
2135             pw.println(mLastMemoryTrimLevel);
2136             pw.println();
2137 
2138             pw.println("Active Package stats:");
2139             pw.increaseIndent();
2140             mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw));
2141             pw.decreaseIndent();
2142             pw.println();
2143 
2144             pw.print("User Grace Period: ");
2145             pw.println(mGracePeriodObserver.mGracePeriodExpiration);
2146             pw.println();
2147 
2148             mStatLogger.dump(pw);
2149         } finally {
2150             pw.decreaseIndent();
2151         }
2152     }
2153 
2154     @GuardedBy("mLock")
dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate, long nowElapsed, long nowUptime)2155     void dumpContextInfoLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate,
2156             long nowElapsed, long nowUptime) {
2157         pw.println("Active jobs:");
2158         pw.increaseIndent();
2159         if (mActiveServices.size() == 0) {
2160             pw.println("N/A");
2161         }
2162         for (int i = 0; i < mActiveServices.size(); i++) {
2163             JobServiceContext jsc = mActiveServices.get(i);
2164             final JobStatus job = jsc.getRunningJobLocked();
2165 
2166             if (job != null && !predicate.test(job)) {
2167                 continue;
2168             }
2169 
2170             pw.print("Slot #"); pw.print(i);
2171             pw.print("(ID="); pw.print(jsc.getId()); pw.print("): ");
2172             jsc.dumpLocked(pw, nowElapsed);
2173 
2174             if (job != null) {
2175                 pw.increaseIndent();
2176 
2177                 pw.increaseIndent();
2178                 job.dump(pw, false, nowElapsed);
2179                 pw.decreaseIndent();
2180 
2181                 pw.print("Evaluated bias: ");
2182                 pw.println(JobInfo.getBiasString(job.lastEvaluatedBias));
2183 
2184                 pw.print("Active at ");
2185                 TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
2186                 pw.print(", pending for ");
2187                 TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
2188                 pw.decreaseIndent();
2189                 pw.println();
2190             }
2191         }
2192         pw.decreaseIndent();
2193 
2194         pw.println();
2195         pw.print("Idle contexts (");
2196         pw.print(mIdleContexts.size());
2197         pw.println("):");
2198         pw.increaseIndent();
2199         for (int i = 0; i < mIdleContexts.size(); i++) {
2200             JobServiceContext jsc = mIdleContexts.valueAt(i);
2201 
2202             pw.print("ID="); pw.print(jsc.getId()); pw.print(": ");
2203             jsc.dumpLocked(pw, nowElapsed);
2204         }
2205         pw.decreaseIndent();
2206 
2207         if (mNumDroppedContexts > 0) {
2208             pw.println();
2209             pw.print("Dropped ");
2210             pw.print(mNumDroppedContexts);
2211             pw.println(" contexts");
2212         }
2213     }
2214 
dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime)2215     public void dumpProtoLocked(ProtoOutputStream proto, long tag, long now, long nowRealtime) {
2216         final long token = proto.start(tag);
2217 
2218         proto.write(JobConcurrencyManagerProto.CURRENT_INTERACTIVE_STATE, mCurrentInteractiveState);
2219         proto.write(JobConcurrencyManagerProto.EFFECTIVE_INTERACTIVE_STATE,
2220                 mEffectiveInteractiveState);
2221 
2222         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_ON_MS,
2223                 nowRealtime - mLastScreenOnRealtime);
2224         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
2225                 nowRealtime - mLastScreenOffRealtime);
2226 
2227         proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
2228 
2229         mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
2230 
2231         proto.end(token);
2232     }
2233 
2234     /**
2235      * Decides whether a job is from the current foreground user or the equivalent.
2236      */
2237     @VisibleForTesting
shouldRunAsFgUserJob(JobStatus job)2238     boolean shouldRunAsFgUserJob(JobStatus job) {
2239         if (!mShouldRestrictBgUser) return true;
2240         int userId = job.getSourceUserId();
2241         UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
2242 
2243         // If the user has a parent user (e.g. a work profile of another user), the user should be
2244         // treated equivalent as its parent user.
2245         if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
2246                 && userInfo.profileGroupId != userId) {
2247             userId = userInfo.profileGroupId;
2248             userInfo = mUserManagerInternal.getUserInfo(userId);
2249         }
2250 
2251         int currentUser = mActivityManagerInternal.getCurrentUserId();
2252         // A user is treated as foreground user if any of the followings is true:
2253         // 1. The user is current user
2254         // 2. The user is primary user
2255         // 3. The user's grace period has not expired
2256         return currentUser == userId || userInfo.isPrimary()
2257                 || mGracePeriodObserver.isWithinGracePeriodForUser(userId);
2258     }
2259 
getJobWorkTypes(@onNull JobStatus js)2260     int getJobWorkTypes(@NonNull JobStatus js) {
2261         int classification = 0;
2262 
2263         if (shouldRunAsFgUserJob(js)) {
2264             if (js.lastEvaluatedBias >= JobInfo.BIAS_TOP_APP) {
2265                 classification |= WORK_TYPE_TOP;
2266             } else if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE) {
2267                 classification |= WORK_TYPE_FGS;
2268             } else {
2269                 classification |= WORK_TYPE_BG;
2270             }
2271 
2272             if (js.shouldTreatAsExpeditedJob()) {
2273                 classification |= WORK_TYPE_EJ;
2274             } else if (js.shouldTreatAsUserInitiatedJob()) {
2275                 classification |= WORK_TYPE_UI;
2276             }
2277         } else {
2278             if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
2279                     || js.shouldTreatAsExpeditedJob() || js.shouldTreatAsUserInitiatedJob()) {
2280                 classification |= WORK_TYPE_BGUSER_IMPORTANT;
2281             }
2282             // BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here.
2283             classification |= WORK_TYPE_BGUSER;
2284         }
2285 
2286         return classification;
2287     }
2288 
2289     @VisibleForTesting
2290     static class WorkTypeConfig {
2291         private static final String KEY_PREFIX_MAX = CONFIG_KEY_PREFIX_CONCURRENCY + "max_";
2292         private static final String KEY_PREFIX_MIN = CONFIG_KEY_PREFIX_CONCURRENCY + "min_";
2293         @VisibleForTesting
2294         static final String KEY_PREFIX_MAX_TOTAL = CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
2295         @VisibleForTesting
2296         static final String KEY_PREFIX_MAX_RATIO = KEY_PREFIX_MAX + "ratio_";
2297         private static final String KEY_PREFIX_MAX_RATIO_TOP = KEY_PREFIX_MAX_RATIO + "top_";
2298         private static final String KEY_PREFIX_MAX_RATIO_FGS = KEY_PREFIX_MAX_RATIO + "fgs_";
2299         private static final String KEY_PREFIX_MAX_RATIO_UI = KEY_PREFIX_MAX_RATIO + "ui_";
2300         private static final String KEY_PREFIX_MAX_RATIO_EJ = KEY_PREFIX_MAX_RATIO + "ej_";
2301         private static final String KEY_PREFIX_MAX_RATIO_BG = KEY_PREFIX_MAX_RATIO + "bg_";
2302         private static final String KEY_PREFIX_MAX_RATIO_BGUSER = KEY_PREFIX_MAX_RATIO + "bguser_";
2303         private static final String KEY_PREFIX_MAX_RATIO_BGUSER_IMPORTANT =
2304                 KEY_PREFIX_MAX_RATIO + "bguser_important_";
2305         @VisibleForTesting
2306         static final String KEY_PREFIX_MIN_RATIO = KEY_PREFIX_MIN + "ratio_";
2307         private static final String KEY_PREFIX_MIN_RATIO_TOP = KEY_PREFIX_MIN_RATIO + "top_";
2308         private static final String KEY_PREFIX_MIN_RATIO_FGS = KEY_PREFIX_MIN_RATIO + "fgs_";
2309         private static final String KEY_PREFIX_MIN_RATIO_UI = KEY_PREFIX_MIN_RATIO + "ui_";
2310         private static final String KEY_PREFIX_MIN_RATIO_EJ = KEY_PREFIX_MIN_RATIO + "ej_";
2311         private static final String KEY_PREFIX_MIN_RATIO_BG = KEY_PREFIX_MIN_RATIO + "bg_";
2312         private static final String KEY_PREFIX_MIN_RATIO_BGUSER = KEY_PREFIX_MIN_RATIO + "bguser_";
2313         private static final String KEY_PREFIX_MIN_RATIO_BGUSER_IMPORTANT =
2314                 KEY_PREFIX_MIN_RATIO + "bguser_important_";
2315         private final String mConfigIdentifier;
2316 
2317         private int mMaxTotal;
2318         private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
2319         private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
2320         private final int mDefaultMaxTotal;
2321         // We use SparseIntArrays to store floats because there is currently no SparseFloatArray
2322         // available, and it doesn't seem worth it to add such a data structure just for this
2323         // use case. We don't use SparseDoubleArrays because DeviceConfig only supports floats and
2324         // converting between floats and ints is more straightforward than floats and doubles.
2325         private final SparseIntArray mDefaultMinReservedSlotsRatio =
2326                 new SparseIntArray(NUM_WORK_TYPES);
2327         private final SparseIntArray mDefaultMaxAllowedSlotsRatio =
2328                 new SparseIntArray(NUM_WORK_TYPES);
2329 
WorkTypeConfig(@onNull String configIdentifier, int steadyStateConcurrencyLimit, int defaultMaxTotal, List<Pair<Integer, Float>> defaultMinRatio, List<Pair<Integer, Float>> defaultMaxRatio)2330         WorkTypeConfig(@NonNull String configIdentifier,
2331                 int steadyStateConcurrencyLimit, int defaultMaxTotal,
2332                 List<Pair<Integer, Float>> defaultMinRatio,
2333                 List<Pair<Integer, Float>> defaultMaxRatio) {
2334             mConfigIdentifier = configIdentifier;
2335             mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, steadyStateConcurrencyLimit);
2336             int numReserved = 0;
2337             for (int i = defaultMinRatio.size() - 1; i >= 0; --i) {
2338                 final float ratio = defaultMinRatio.get(i).second;
2339                 final int wt = defaultMinRatio.get(i).first;
2340                 if (ratio < 0 || 1 <= ratio) {
2341                     // 1 means to reserve everything. This shouldn't be allowed.
2342                     // We only create new configs on boot, so this should trigger during development
2343                     // (before the code gets checked in), so this makes sure the hard-coded defaults
2344                     // make sense. DeviceConfig values will be handled gracefully in update().
2345                     throw new IllegalArgumentException("Invalid default min ratio: wt=" + wt
2346                             + " minRatio=" + ratio);
2347                 }
2348                 mDefaultMinReservedSlotsRatio.put(wt, Float.floatToRawIntBits(ratio));
2349                 numReserved += mMaxTotal * ratio;
2350             }
2351             if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
2352                 // We only create new configs on boot, so this should trigger during development
2353                 // (before the code gets checked in), so this makes sure the hard-coded defaults
2354                 // make sense. DeviceConfig values will be handled gracefully in update().
2355                 throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
2356                         + " min=" + defaultMinRatio + " max=" + defaultMaxRatio);
2357             }
2358             for (int i = defaultMaxRatio.size() - 1; i >= 0; --i) {
2359                 final float ratio = defaultMaxRatio.get(i).second;
2360                 final int wt = defaultMaxRatio.get(i).first;
2361                 final float minRatio =
2362                         Float.intBitsToFloat(mDefaultMinReservedSlotsRatio.get(wt, 0));
2363                 if (ratio < minRatio || ratio <= 0) {
2364                     // Max ratio shouldn't be <= 0 or less than minRatio.
2365                     throw new IllegalArgumentException("Invalid default config:"
2366                             + " t=" + defaultMaxTotal
2367                             + " min=" + defaultMinRatio + " max=" + defaultMaxRatio);
2368                 }
2369                 mDefaultMaxAllowedSlotsRatio.put(wt, Float.floatToRawIntBits(ratio));
2370             }
2371             update(new DeviceConfig.Properties.Builder(
2372                     DeviceConfig.NAMESPACE_JOB_SCHEDULER).build(), steadyStateConcurrencyLimit);
2373         }
2374 
update(@onNull DeviceConfig.Properties properties, int steadyStateConcurrencyLimit)2375         void update(@NonNull DeviceConfig.Properties properties, int steadyStateConcurrencyLimit) {
2376             // Ensure total in the range [1, mSteadyStateConcurrencyLimit].
2377             mMaxTotal = Math.max(1, Math.min(steadyStateConcurrencyLimit,
2378                     properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal)));
2379 
2380             final int oneIntBits = Float.floatToIntBits(1);
2381 
2382             mMaxAllowedSlots.clear();
2383             // Ensure they're in the range [1, total].
2384             final int maxTop = getMaxValue(properties,
2385                     KEY_PREFIX_MAX_RATIO_TOP + mConfigIdentifier, WORK_TYPE_TOP, oneIntBits);
2386             mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop);
2387             final int maxFgs = getMaxValue(properties,
2388                     KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier, WORK_TYPE_FGS, oneIntBits);
2389             mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
2390             final int maxUi = getMaxValue(properties,
2391                     KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI, oneIntBits);
2392             mMaxAllowedSlots.put(WORK_TYPE_UI, maxUi);
2393             final int maxEj = getMaxValue(properties,
2394                     KEY_PREFIX_MAX_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ, oneIntBits);
2395             mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj);
2396             final int maxBg = getMaxValue(properties,
2397                     KEY_PREFIX_MAX_RATIO_BG + mConfigIdentifier, WORK_TYPE_BG, oneIntBits);
2398             mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
2399             final int maxBgUserImp = getMaxValue(properties,
2400                     KEY_PREFIX_MAX_RATIO_BGUSER_IMPORTANT + mConfigIdentifier,
2401                     WORK_TYPE_BGUSER_IMPORTANT, oneIntBits);
2402             mMaxAllowedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, maxBgUserImp);
2403             final int maxBgUser = getMaxValue(properties,
2404                     KEY_PREFIX_MAX_RATIO_BGUSER + mConfigIdentifier, WORK_TYPE_BGUSER, oneIntBits);
2405             mMaxAllowedSlots.put(WORK_TYPE_BGUSER, maxBgUser);
2406 
2407             int remaining = mMaxTotal;
2408             mMinReservedSlots.clear();
2409             // Ensure top is in the range [1, min(maxTop, total)]
2410             final int minTop = getMinValue(properties,
2411                     KEY_PREFIX_MIN_RATIO_TOP + mConfigIdentifier, WORK_TYPE_TOP,
2412                     1, Math.min(maxTop, mMaxTotal));
2413             mMinReservedSlots.put(WORK_TYPE_TOP, minTop);
2414             remaining -= minTop;
2415             // Ensure fgs is in the range [0, min(maxFgs, remaining)]
2416             final int minFgs = getMinValue(properties,
2417                     KEY_PREFIX_MIN_RATIO_FGS + mConfigIdentifier, WORK_TYPE_FGS,
2418                     0, Math.min(maxFgs, remaining));
2419             mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
2420             remaining -= minFgs;
2421             // Ensure ui is in the range [0, min(maxUi, remaining)]
2422             final int minUi = getMinValue(properties,
2423                     KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI,
2424                     0, Math.min(maxUi, remaining));
2425             mMinReservedSlots.put(WORK_TYPE_UI, minUi);
2426             remaining -= minUi;
2427             // Ensure ej is in the range [0, min(maxEj, remaining)]
2428             final int minEj = getMinValue(properties,
2429                     KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ,
2430                     0, Math.min(maxEj, remaining));
2431             mMinReservedSlots.put(WORK_TYPE_EJ, minEj);
2432             remaining -= minEj;
2433             // Ensure bg is in the range [0, min(maxBg, remaining)]
2434             final int minBg = getMinValue(properties,
2435                     KEY_PREFIX_MIN_RATIO_BG + mConfigIdentifier, WORK_TYPE_BG,
2436                     0, Math.min(maxBg, remaining));
2437             mMinReservedSlots.put(WORK_TYPE_BG, minBg);
2438             remaining -= minBg;
2439             // Ensure bg user imp is in the range [0, min(maxBgUserImp, remaining)]
2440             final int minBgUserImp = getMinValue(properties,
2441                     KEY_PREFIX_MIN_RATIO_BGUSER_IMPORTANT + mConfigIdentifier,
2442                     WORK_TYPE_BGUSER_IMPORTANT, 0, Math.min(maxBgUserImp, remaining));
2443             mMinReservedSlots.put(WORK_TYPE_BGUSER_IMPORTANT, minBgUserImp);
2444             remaining -= minBgUserImp;
2445             // Ensure bg user is in the range [0, min(maxBgUser, remaining)]
2446             final int minBgUser = getMinValue(properties,
2447                     KEY_PREFIX_MIN_RATIO_BGUSER + mConfigIdentifier, WORK_TYPE_BGUSER,
2448                     0, Math.min(maxBgUser, remaining));
2449             mMinReservedSlots.put(WORK_TYPE_BGUSER, minBgUser);
2450         }
2451 
2452         /**
2453          * Return the calculated max value for the work type.
2454          * @param defaultFloatInIntBits A {@code float} value in int bits representation (using
2455          *                              {@link Float#floatToIntBits(float)}.
2456          */
getMaxValue(@onNull DeviceConfig.Properties properties, @NonNull String key, int workType, int defaultFloatInIntBits)2457         private int getMaxValue(@NonNull DeviceConfig.Properties properties, @NonNull String key,
2458                 int workType, int defaultFloatInIntBits) {
2459             final float maxRatio = Math.min(1, properties.getFloat(key,
2460                     Float.intBitsToFloat(
2461                             mDefaultMaxAllowedSlotsRatio.get(workType, defaultFloatInIntBits))));
2462             // Max values should be in  the range [1, total].
2463             return Math.max(1, (int) (mMaxTotal * maxRatio));
2464         }
2465 
2466         /**
2467          * Return the calculated min value for the work type.
2468          */
getMinValue(@onNull DeviceConfig.Properties properties, @NonNull String key, int workType, int lowerLimit, int upperLimit)2469         private int getMinValue(@NonNull DeviceConfig.Properties properties, @NonNull String key,
2470                 int workType, int lowerLimit, int upperLimit) {
2471             final float minRatio = Math.min(1,
2472                     properties.getFloat(key,
2473                             Float.intBitsToFloat(mDefaultMinReservedSlotsRatio.get(workType))));
2474             return Math.max(lowerLimit, Math.min(upperLimit, (int) (mMaxTotal * minRatio)));
2475         }
2476 
getMaxTotal()2477         int getMaxTotal() {
2478             return mMaxTotal;
2479         }
2480 
getMax(@orkType int workType)2481         int getMax(@WorkType int workType) {
2482             return mMaxAllowedSlots.get(workType, mMaxTotal);
2483         }
2484 
getMinReserved(@orkType int workType)2485         int getMinReserved(@WorkType int workType) {
2486             return mMinReservedSlots.get(workType);
2487         }
2488 
dump(IndentingPrintWriter pw)2489         void dump(IndentingPrintWriter pw) {
2490             pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println();
2491             pw.print(KEY_PREFIX_MIN_RATIO_TOP + mConfigIdentifier,
2492                             mMinReservedSlots.get(WORK_TYPE_TOP))
2493                     .println();
2494             pw.print(KEY_PREFIX_MAX_RATIO_TOP + mConfigIdentifier,
2495                             mMaxAllowedSlots.get(WORK_TYPE_TOP))
2496                     .println();
2497             pw.print(KEY_PREFIX_MIN_RATIO_FGS + mConfigIdentifier,
2498                             mMinReservedSlots.get(WORK_TYPE_FGS))
2499                     .println();
2500             pw.print(KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier,
2501                             mMaxAllowedSlots.get(WORK_TYPE_FGS))
2502                     .println();
2503             pw.print(KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier,
2504                             mMinReservedSlots.get(WORK_TYPE_UI))
2505                     .println();
2506             pw.print(KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier,
2507                             mMaxAllowedSlots.get(WORK_TYPE_UI))
2508                     .println();
2509             pw.print(KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier,
2510                             mMinReservedSlots.get(WORK_TYPE_EJ))
2511                     .println();
2512             pw.print(KEY_PREFIX_MAX_RATIO_EJ + mConfigIdentifier,
2513                             mMaxAllowedSlots.get(WORK_TYPE_EJ))
2514                     .println();
2515             pw.print(KEY_PREFIX_MIN_RATIO_BG + mConfigIdentifier,
2516                             mMinReservedSlots.get(WORK_TYPE_BG))
2517                     .println();
2518             pw.print(KEY_PREFIX_MAX_RATIO_BG + mConfigIdentifier,
2519                             mMaxAllowedSlots.get(WORK_TYPE_BG))
2520                     .println();
2521             pw.print(KEY_PREFIX_MIN_RATIO_BGUSER + mConfigIdentifier,
2522                     mMinReservedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
2523             pw.print(KEY_PREFIX_MAX_RATIO_BGUSER + mConfigIdentifier,
2524                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER_IMPORTANT)).println();
2525             pw.print(KEY_PREFIX_MIN_RATIO_BGUSER + mConfigIdentifier,
2526                     mMinReservedSlots.get(WORK_TYPE_BGUSER)).println();
2527             pw.print(KEY_PREFIX_MAX_RATIO_BGUSER + mConfigIdentifier,
2528                     mMaxAllowedSlots.get(WORK_TYPE_BGUSER)).println();
2529         }
2530     }
2531 
2532     /** {@link WorkTypeConfig} for each memory trim level. */
2533     static class WorkConfigLimitsPerMemoryTrimLevel {
2534         public final WorkTypeConfig normal;
2535         public final WorkTypeConfig moderate;
2536         public final WorkTypeConfig low;
2537         public final WorkTypeConfig critical;
2538 
WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate, WorkTypeConfig low, WorkTypeConfig critical)2539         WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate,
2540                 WorkTypeConfig low, WorkTypeConfig critical) {
2541             this.normal = normal;
2542             this.moderate = moderate;
2543             this.low = low;
2544             this.critical = critical;
2545         }
2546     }
2547 
2548     /**
2549      * This class keeps the track of when a user's grace period expires.
2550      */
2551     @VisibleForTesting
2552     static class GracePeriodObserver extends UserSwitchObserver {
2553         // Key is UserId and Value is the time when grace period expires
2554         @VisibleForTesting
2555         final SparseLongArray mGracePeriodExpiration = new SparseLongArray();
2556         private int mCurrentUserId;
2557         @VisibleForTesting
2558         int mGracePeriod;
2559         private final UserManagerInternal mUserManagerInternal;
2560         final Object mLock = new Object();
2561 
2562 
GracePeriodObserver(Context context)2563         GracePeriodObserver(Context context) {
2564             mCurrentUserId = LocalServices.getService(ActivityManagerInternal.class)
2565                     .getCurrentUserId();
2566             mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
2567             mGracePeriod = Math.max(0, context.getResources().getInteger(
2568                     R.integer.config_jobSchedulerUserGracePeriod));
2569         }
2570 
2571         @Override
onUserSwitchComplete(int newUserId)2572         public void onUserSwitchComplete(int newUserId) {
2573             final long expiration = sElapsedRealtimeClock.millis() + mGracePeriod;
2574             synchronized (mLock) {
2575                 if (mCurrentUserId != UserHandle.USER_NULL
2576                         && mUserManagerInternal.exists(mCurrentUserId)) {
2577                     mGracePeriodExpiration.append(mCurrentUserId, expiration);
2578                 }
2579                 mGracePeriodExpiration.delete(newUserId);
2580                 mCurrentUserId = newUserId;
2581             }
2582         }
2583 
onUserRemoved(int userId)2584         void onUserRemoved(int userId) {
2585             synchronized (mLock) {
2586                 mGracePeriodExpiration.delete(userId);
2587             }
2588         }
2589 
2590         @VisibleForTesting
isWithinGracePeriodForUser(int userId)2591         public boolean isWithinGracePeriodForUser(int userId) {
2592             synchronized (mLock) {
2593                 return userId == mCurrentUserId
2594                         || sElapsedRealtimeClock.millis()
2595                         < mGracePeriodExpiration.get(userId, Long.MAX_VALUE);
2596             }
2597         }
2598     }
2599 
2600     /**
2601      * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
2602      * are running/pending, how many more job can start.
2603      *
2604      * Extracted for testing and logging.
2605      */
2606     @VisibleForTesting
2607     static class WorkCountTracker {
2608         private int mConfigMaxTotal;
2609         private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
2610         private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
2611         private final SparseIntArray mRecycledReserved = new SparseIntArray(NUM_WORK_TYPES);
2612 
2613         /**
2614          * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't
2615          * enough ready jobs of a type to take up all of the desired reserved slots.
2616          */
2617         private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
2618         private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES);
2619         private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES);
2620         private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES);
2621         private int mNumUnspecializedRemaining = 0;
2622 
setConfig(@onNull WorkTypeConfig workTypeConfig)2623         void setConfig(@NonNull WorkTypeConfig workTypeConfig) {
2624             mConfigMaxTotal = workTypeConfig.getMaxTotal();
2625             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2626                 mConfigNumReservedSlots.put(workType, workTypeConfig.getMinReserved(workType));
2627                 mConfigAbsoluteMaxSlots.put(workType, workTypeConfig.getMax(workType));
2628             }
2629 
2630             mNumUnspecializedRemaining = mConfigMaxTotal;
2631             for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
2632                 mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
2633                         mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
2634             }
2635         }
2636 
resetCounts()2637         void resetCounts() {
2638             mNumActuallyReservedSlots.clear();
2639             mNumPendingJobs.clear();
2640             mNumRunningJobs.clear();
2641             resetStagingCount();
2642         }
2643 
resetStagingCount()2644         void resetStagingCount() {
2645             mNumStartingJobs.clear();
2646         }
2647 
incrementRunningJobCount(@orkType int workType)2648         void incrementRunningJobCount(@WorkType int workType) {
2649             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
2650         }
2651 
incrementPendingJobCount(int workTypes)2652         void incrementPendingJobCount(int workTypes) {
2653             adjustPendingJobCount(workTypes, true);
2654         }
2655 
decrementPendingJobCount(int workTypes)2656         void decrementPendingJobCount(int workTypes) {
2657             if (adjustPendingJobCount(workTypes, false) > 1) {
2658                 // We don't need to adjust reservations if only one work type was modified
2659                 // because that work type is the one we're using.
2660 
2661                 for (int workType = 1; workType <= workTypes; workType <<= 1) {
2662                     if ((workType & workTypes) == workType) {
2663                         maybeAdjustReservations(workType);
2664                     }
2665                 }
2666             }
2667         }
2668 
2669         /** Returns the number of WorkTypes that were modified. */
adjustPendingJobCount(int workTypes, boolean add)2670         private int adjustPendingJobCount(int workTypes, boolean add) {
2671             final int adj = add ? 1 : -1;
2672 
2673             int numAdj = 0;
2674             // We don't know which type we'll classify the job as when we run it yet, so make sure
2675             // we have space in all applicable slots.
2676             for (int workType = 1; workType <= workTypes; workType <<= 1) {
2677                 if ((workTypes & workType) == workType) {
2678                     mNumPendingJobs.put(workType, mNumPendingJobs.get(workType) + adj);
2679                     numAdj++;
2680                 }
2681             }
2682 
2683             return numAdj;
2684         }
2685 
stageJob(@orkType int workType, int allWorkTypes)2686         void stageJob(@WorkType int workType, int allWorkTypes) {
2687             final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
2688             mNumStartingJobs.put(workType, newNumStartingJobs);
2689             decrementPendingJobCount(allWorkTypes);
2690             if (newNumStartingJobs + mNumRunningJobs.get(workType)
2691                     > mNumActuallyReservedSlots.get(workType)) {
2692                 mNumUnspecializedRemaining--;
2693             }
2694         }
2695 
onStagedJobFailed(@orkType int workType)2696         void onStagedJobFailed(@WorkType int workType) {
2697             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
2698             if (oldNumStartingJobs == 0) {
2699                 Slog.e(TAG, "# staged jobs for " + workType + " went negative.");
2700                 // We are in a bad state. We will eventually recover when the pending list is
2701                 // regenerated.
2702                 return;
2703             }
2704             mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
2705             maybeAdjustReservations(workType);
2706         }
2707 
maybeAdjustReservations(@orkType int workType)2708         private void maybeAdjustReservations(@WorkType int workType) {
2709             // Always make sure we reserve the minimum number of slots in case new jobs become ready
2710             // soon.
2711             final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType),
2712                     mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
2713                             + mNumPendingJobs.get(workType));
2714             if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) {
2715                 // We've run all jobs for this type. Let another type use it now.
2716                 mNumActuallyReservedSlots.put(workType, numRemainingForType);
2717                 int assignWorkType = WORK_TYPE_NONE;
2718                 for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
2719                     int wt = mNumActuallyReservedSlots.keyAt(i);
2720                     if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
2721                         // Try to give this slot to the highest bias one within its limits.
2722                         int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
2723                                 + mNumPendingJobs.get(wt);
2724                         if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
2725                                 && total > mNumActuallyReservedSlots.valueAt(i)) {
2726                             assignWorkType = wt;
2727                         }
2728                     }
2729                 }
2730                 if (assignWorkType != WORK_TYPE_NONE) {
2731                     mNumActuallyReservedSlots.put(assignWorkType,
2732                             mNumActuallyReservedSlots.get(assignWorkType) + 1);
2733                 } else {
2734                     mNumUnspecializedRemaining++;
2735                 }
2736             }
2737         }
2738 
onJobStarted(@orkType int workType)2739         void onJobStarted(@WorkType int workType) {
2740             mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
2741             final int oldNumStartingJobs = mNumStartingJobs.get(workType);
2742             if (oldNumStartingJobs == 0) {
2743                 Slog.e(TAG, "# stated jobs for " + workType + " went negative.");
2744                 // We are in a bad state. We will eventually recover when the pending list is
2745                 // regenerated. For now, only modify the running count.
2746             } else {
2747                 mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
2748             }
2749         }
2750 
onJobFinished(@orkType int workType)2751         void onJobFinished(@WorkType int workType) {
2752             final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1;
2753             if (newNumRunningJobs < 0) {
2754                 // We are in a bad state. We will eventually recover when the pending list is
2755                 // regenerated.
2756                 Slog.e(TAG, "# running jobs for " + workType + " went negative.");
2757                 return;
2758             }
2759             mNumRunningJobs.put(workType, newNumRunningJobs);
2760             maybeAdjustReservations(workType);
2761         }
2762 
onCountDone()2763         void onCountDone() {
2764             // Calculate how many slots to reserve for each work type. "Unspecialized" slots will
2765             // be reserved for higher importance types first (ie. top before ej before bg).
2766             // Steps:
2767             //   1. Account for slots for already running jobs
2768             //   2. Use remaining unaccounted slots to try and ensure minimum reserved slots
2769             //   3. Allocate remaining up to max, based on importance
2770 
2771             mNumUnspecializedRemaining = mConfigMaxTotal;
2772 
2773             // Step 1
2774             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2775                 int run = mNumRunningJobs.get(workType);
2776                 mRecycledReserved.put(workType, run);
2777                 mNumUnspecializedRemaining -= run;
2778             }
2779 
2780             // Step 2
2781             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2782                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
2783                 int res = mRecycledReserved.get(workType);
2784                 int fillUp = Math.max(0, Math.min(mNumUnspecializedRemaining,
2785                         Math.min(num, mConfigNumReservedSlots.get(workType) - res)));
2786                 res += fillUp;
2787                 mRecycledReserved.put(workType, res);
2788                 mNumUnspecializedRemaining -= fillUp;
2789             }
2790 
2791             // Step 3
2792             for (int workType = 1; workType < ALL_WORK_TYPES; workType <<= 1) {
2793                 int num = mNumRunningJobs.get(workType) + mNumPendingJobs.get(workType);
2794                 int res = mRecycledReserved.get(workType);
2795                 int unspecializedAssigned = Math.max(0,
2796                         Math.min(mNumUnspecializedRemaining,
2797                                 Math.min(mConfigAbsoluteMaxSlots.get(workType), num) - res));
2798                 mNumActuallyReservedSlots.put(workType, res + unspecializedAssigned);
2799                 mNumUnspecializedRemaining -= unspecializedAssigned;
2800             }
2801         }
2802 
canJobStart(int workTypes)2803         int canJobStart(int workTypes) {
2804             for (int workType = 1; workType <= workTypes; workType <<= 1) {
2805                 if ((workTypes & workType) == workType) {
2806                     final int maxAllowed = Math.min(
2807                             mConfigAbsoluteMaxSlots.get(workType),
2808                             mNumActuallyReservedSlots.get(workType) + mNumUnspecializedRemaining);
2809                     if (mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
2810                             < maxAllowed) {
2811                         return workType;
2812                     }
2813                 }
2814             }
2815             return WORK_TYPE_NONE;
2816         }
2817 
canJobStart(int workTypes, @WorkType int replacingWorkType)2818         int canJobStart(int workTypes, @WorkType int replacingWorkType) {
2819             final boolean changedNums;
2820             int oldNumRunning = mNumRunningJobs.get(replacingWorkType);
2821             if (replacingWorkType != WORK_TYPE_NONE && oldNumRunning > 0) {
2822                 mNumRunningJobs.put(replacingWorkType, oldNumRunning - 1);
2823                 // Lazy implementation to avoid lots of processing. Best way would be to go
2824                 // through the whole process of adjusting reservations, but the processing cost
2825                 // is likely not worth it.
2826                 mNumUnspecializedRemaining++;
2827                 changedNums = true;
2828             } else {
2829                 changedNums = false;
2830             }
2831 
2832             final int ret = canJobStart(workTypes);
2833             if (changedNums) {
2834                 mNumRunningJobs.put(replacingWorkType, oldNumRunning);
2835                 mNumUnspecializedRemaining--;
2836             }
2837             return ret;
2838         }
2839 
getPendingJobCount(@orkType final int workType)2840         int getPendingJobCount(@WorkType final int workType) {
2841             return mNumPendingJobs.get(workType, 0);
2842         }
2843 
getRunningJobCount(@orkType final int workType)2844         int getRunningJobCount(@WorkType final int workType) {
2845             return mNumRunningJobs.get(workType, 0);
2846         }
2847 
isOverTypeLimit(@orkType final int workType)2848         boolean isOverTypeLimit(@WorkType final int workType) {
2849             return getRunningJobCount(workType) > mConfigAbsoluteMaxSlots.get(workType);
2850         }
2851 
toString()2852         public String toString() {
2853             StringBuilder sb = new StringBuilder();
2854 
2855             sb.append("Config={");
2856             sb.append("tot=").append(mConfigMaxTotal);
2857             sb.append(" mins=");
2858             sb.append(mConfigNumReservedSlots);
2859             sb.append(" maxs=");
2860             sb.append(mConfigAbsoluteMaxSlots);
2861             sb.append("}");
2862 
2863             sb.append(", act res=").append(mNumActuallyReservedSlots);
2864             sb.append(", Pending=").append(mNumPendingJobs);
2865             sb.append(", Running=").append(mNumRunningJobs);
2866             sb.append(", Staged=").append(mNumStartingJobs);
2867             sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining);
2868 
2869             return sb.toString();
2870         }
2871     }
2872 
2873     @VisibleForTesting
2874     static class PackageStats {
2875         public int userId;
2876         public String packageName;
2877         public int numRunningEj;
2878         public int numRunningRegular;
2879         public int numStagedEj;
2880         public int numStagedRegular;
2881 
setPackage(int userId, @NonNull String packageName)2882         private void setPackage(int userId, @NonNull String packageName) {
2883             this.userId = userId;
2884             this.packageName = packageName;
2885             numRunningEj = numRunningRegular = 0;
2886             resetStagedCount();
2887         }
2888 
resetStagedCount()2889         private void resetStagedCount() {
2890             numStagedEj = numStagedRegular = 0;
2891         }
2892 
adjustRunningCount(boolean add, boolean forEj)2893         private void adjustRunningCount(boolean add, boolean forEj) {
2894             if (forEj) {
2895                 numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1));
2896             } else {
2897                 numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1));
2898             }
2899         }
2900 
adjustStagedCount(boolean add, boolean forEj)2901         private void adjustStagedCount(boolean add, boolean forEj) {
2902             if (forEj) {
2903                 numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1));
2904             } else {
2905                 numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1));
2906             }
2907         }
2908 
2909         @GuardedBy("mLock")
dumpLocked(IndentingPrintWriter pw)2910         private void dumpLocked(IndentingPrintWriter pw) {
2911             pw.print("PackageStats{");
2912             pw.print(userId);
2913             pw.print("-");
2914             pw.print(packageName);
2915             pw.print("#runEJ", numRunningEj);
2916             pw.print("#runReg", numRunningRegular);
2917             pw.print("#stagedEJ", numStagedEj);
2918             pw.print("#stagedReg", numStagedRegular);
2919             pw.println("}");
2920         }
2921     }
2922 
2923     @VisibleForTesting
2924     static final class ContextAssignment {
2925         public JobServiceContext context;
2926         public int preferredUid = JobServiceContext.NO_PREFERRED_UID;
2927         public int workType = WORK_TYPE_NONE;
2928         public String preemptReason;
2929         public int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
2930         public long timeUntilStoppableMs;
2931         public String shouldStopJobReason;
2932         public JobStatus newJob;
2933         public int newWorkType = WORK_TYPE_NONE;
2934 
clear()2935         void clear() {
2936             context = null;
2937             preferredUid = JobServiceContext.NO_PREFERRED_UID;
2938             workType = WORK_TYPE_NONE;
2939             preemptReason = null;
2940             preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
2941             timeUntilStoppableMs = 0;
2942             shouldStopJobReason = null;
2943             newJob = null;
2944             newWorkType = WORK_TYPE_NONE;
2945         }
2946     }
2947 
2948     @VisibleForTesting
2949     static final class AssignmentInfo {
2950         public long minPreferredUidOnlyWaitingTimeMs;
2951         public int numRunningImmediacyPrivileged;
2952         public int numRunningUi;
2953         public int numRunningEj;
2954         public int numRunningReg;
2955 
clear()2956         void clear() {
2957             minPreferredUidOnlyWaitingTimeMs = 0;
2958             numRunningImmediacyPrivileged = 0;
2959             numRunningUi = 0;
2960             numRunningEj = 0;
2961             numRunningReg = 0;
2962         }
2963     }
2964 
2965     // TESTING HELPERS
2966 
2967     @VisibleForTesting
addRunningJobForTesting(@onNull JobStatus job)2968     void addRunningJobForTesting(@NonNull JobStatus job) {
2969         mRunningJobs.add(job);
2970         final PackageStats packageStats =
2971                 getPackageStatsForTesting(job.getSourceUserId(), job.getSourcePackageName());
2972         packageStats.adjustRunningCount(true, job.shouldTreatAsExpeditedJob());
2973 
2974         final JobServiceContext context;
2975         if (mIdleContexts.size() > 0) {
2976             context = mIdleContexts.removeAt(mIdleContexts.size() - 1);
2977         } else {
2978             context = createNewJobServiceContext();
2979         }
2980         context.executeRunnableJob(job, mWorkCountTracker.canJobStart(getJobWorkTypes(job)));
2981         mActiveServices.add(context);
2982     }
2983 
2984     @VisibleForTesting
getPackageConcurrencyLimitEj()2985     int getPackageConcurrencyLimitEj() {
2986         return mPkgConcurrencyLimitEj;
2987     }
2988 
getPackageConcurrencyLimitRegular()2989     int getPackageConcurrencyLimitRegular() {
2990         return mPkgConcurrencyLimitRegular;
2991     }
2992 
2993     /** Gets the {@link PackageStats} object for the app and saves it for testing use. */
2994     @NonNull
2995     @VisibleForTesting
getPackageStatsForTesting(int userId, @NonNull String packageName)2996     PackageStats getPackageStatsForTesting(int userId, @NonNull String packageName) {
2997         final PackageStats packageStats = getPkgStatsLocked(userId, packageName);
2998         mActivePkgStats.add(userId, packageName, packageStats);
2999         return packageStats;
3000     }
3001 
3002     @VisibleForTesting
3003     static class Injector {
3004         @NonNull
createJobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager, JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper)3005         JobServiceContext createJobServiceContext(JobSchedulerService service,
3006                 JobConcurrencyManager concurrencyManager,
3007                 JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats,
3008                 JobPackageTracker tracker, Looper looper) {
3009             return new JobServiceContext(service, concurrencyManager, notificationCoordinator,
3010                     batteryStats, tracker, looper);
3011         }
3012     }
3013 }
3014