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