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