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