1 /* 2 * Copyright (C) 2021 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.controllers; 18 19 import static android.app.job.JobInfo.getPriorityString; 20 21 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 22 23 import android.annotation.NonNull; 24 import android.app.job.JobInfo; 25 import android.util.ArrayMap; 26 import android.util.ArraySet; 27 import android.util.IndentingPrintWriter; 28 import android.util.Log; 29 import android.util.Slog; 30 import android.util.SparseArrayMap; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.server.AppSchedulingModuleThread; 34 import com.android.server.LocalServices; 35 import com.android.server.job.JobSchedulerService; 36 import com.android.server.tare.EconomicPolicy; 37 import com.android.server.tare.EconomyManagerInternal; 38 import com.android.server.tare.EconomyManagerInternal.ActionBill; 39 import com.android.server.tare.JobSchedulerEconomicPolicy; 40 41 import java.util.List; 42 import java.util.function.Predicate; 43 44 /** 45 * Controller that interfaces with Tare ({@link EconomyManagerInternal} and manages each job's 46 * ability to run per TARE policies. 47 * 48 * @see JobSchedulerEconomicPolicy 49 */ 50 public class TareController extends StateController { 51 private static final String TAG = "JobScheduler.TARE"; 52 private static final boolean DEBUG = JobSchedulerService.DEBUG 53 || Log.isLoggable(TAG, Log.DEBUG); 54 55 /** 56 * Bill to use while we're waiting to start a min priority job. If a job isn't running yet, 57 * don't consider it eligible to run unless it can pay for a job start and at least some 58 * period of execution time. We don't want min priority jobs to use up all available credits, 59 * so we make sure to only run them while there are enough credits to run higher priority jobs. 60 */ 61 private static final ActionBill BILL_JOB_START_MIN = 62 new ActionBill(List.of( 63 new EconomyManagerInternal.AnticipatedAction( 64 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START, 1, 0), 65 new EconomyManagerInternal.AnticipatedAction( 66 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 120_000L) 67 )); 68 69 /** 70 * Bill to use when a min priority job is currently running. We don't want min priority jobs 71 * to use up remaining credits, so we make sure to only run them while there are enough 72 * credits to run higher priority jobs. We stop the job when the app's credits get too low. 73 */ 74 private static final ActionBill BILL_JOB_RUNNING_MIN = 75 new ActionBill(List.of( 76 new EconomyManagerInternal.AnticipatedAction( 77 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 60_000L) 78 )); 79 80 /** 81 * Bill to use while we're waiting to start a low priority job. If a job isn't running yet, 82 * don't consider it eligible to run unless it can pay for a job start and at least some 83 * period of execution time. We don't want low priority jobs to use up all available credits, 84 * so we make sure to only run them while there are enough credits to run higher priority jobs. 85 */ 86 private static final ActionBill BILL_JOB_START_LOW = 87 new ActionBill(List.of( 88 new EconomyManagerInternal.AnticipatedAction( 89 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START, 1, 0), 90 new EconomyManagerInternal.AnticipatedAction( 91 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 60_000L) 92 )); 93 94 /** 95 * Bill to use when a low priority job is currently running. We don't want low priority jobs 96 * to use up all available credits, so we make sure to only run them while there are enough 97 * credits to run higher priority jobs. We stop the job when the app's credits get too low. 98 */ 99 private static final ActionBill BILL_JOB_RUNNING_LOW = 100 new ActionBill(List.of( 101 new EconomyManagerInternal.AnticipatedAction( 102 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 30_000L) 103 )); 104 105 /** 106 * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it 107 * eligible to run unless it can pay for a job start and at least some period of execution time. 108 */ 109 private static final ActionBill BILL_JOB_START_DEFAULT = 110 new ActionBill(List.of( 111 new EconomyManagerInternal.AnticipatedAction( 112 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START, 1, 0), 113 new EconomyManagerInternal.AnticipatedAction( 114 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 30_000L) 115 )); 116 117 /** 118 * Bill to use when a default priority job is currently running. We want to track and make 119 * sure the app can continue to pay for 1 more second of execution time. We stop the job when 120 * the app can no longer pay for that time. 121 */ 122 private static final ActionBill BILL_JOB_RUNNING_DEFAULT = 123 new ActionBill(List.of( 124 new EconomyManagerInternal.AnticipatedAction( 125 JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 1_000L) 126 )); 127 128 /** 129 * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it 130 * eligible to run unless it can pay for a job start and at least some period of execution time. 131 */ 132 private static final ActionBill BILL_JOB_START_HIGH = 133 new ActionBill(List.of( 134 new EconomyManagerInternal.AnticipatedAction( 135 JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0), 136 new EconomyManagerInternal.AnticipatedAction( 137 JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 30_000L) 138 )); 139 140 /** 141 * Bill to use when a high priority job is currently running. We want to track and make sure 142 * the app can continue to pay for 1 more second of execution time. We stop the job when the 143 * app can no longer pay for that time. 144 */ 145 private static final ActionBill BILL_JOB_RUNNING_HIGH = 146 new ActionBill(List.of( 147 new EconomyManagerInternal.AnticipatedAction( 148 JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 1_000L) 149 )); 150 151 152 /** 153 * Bill to use while we're waiting to start a max priority job. This should only be used for 154 * requested-EJs that aren't allowed to run as EJs. If a job isn't running yet, don't consider 155 * it eligible to run unless it can pay for a job start and at least some period of execution 156 * time. 157 */ 158 private static final ActionBill BILL_JOB_START_MAX = 159 new ActionBill(List.of( 160 new EconomyManagerInternal.AnticipatedAction( 161 JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0), 162 new EconomyManagerInternal.AnticipatedAction( 163 JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 30_000L) 164 )); 165 166 /** 167 * Bill to use when a max priority job is currently running. This should only be used for 168 * requested-EJs that aren't allowed to run as EJs. We want to track and make sure 169 * the app can continue to pay for 1 more second of execution time. We stop the job when the 170 * app can no longer pay for that time. 171 */ 172 private static final ActionBill BILL_JOB_RUNNING_MAX = 173 new ActionBill(List.of( 174 new EconomyManagerInternal.AnticipatedAction( 175 JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1_000L) 176 )); 177 178 /** 179 * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it 180 * eligible to run unless it can pay for a job start and at least some period of execution time. 181 */ 182 private static final ActionBill BILL_JOB_START_MAX_EXPEDITED = 183 new ActionBill(List.of( 184 new EconomyManagerInternal.AnticipatedAction( 185 JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0), 186 new EconomyManagerInternal.AnticipatedAction( 187 JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 30_000L) 188 )); 189 190 /** 191 * Bill to use when a max priority EJ is currently running (as an EJ). We want to track and 192 * make sure the app can continue to pay for 1 more second of execution time. We stop the job 193 * when the app can no longer pay for that time. 194 */ 195 private static final ActionBill BILL_JOB_RUNNING_MAX_EXPEDITED = 196 new ActionBill(List.of( 197 new EconomyManagerInternal.AnticipatedAction( 198 JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1_000L) 199 )); 200 201 /** 202 * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it 203 * eligible to run unless it can pay for a job start and at least some period of execution time. 204 */ 205 private static final ActionBill BILL_JOB_START_HIGH_EXPEDITED = 206 new ActionBill(List.of( 207 new EconomyManagerInternal.AnticipatedAction( 208 JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0), 209 new EconomyManagerInternal.AnticipatedAction( 210 JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 30_000L) 211 )); 212 213 /** 214 * Bill to use when a high priority EJ is currently running (as an EJ). We want to track and 215 * make sure the app can continue to pay for 1 more second of execution time. We stop the job 216 * when the app can no longer pay for that time. 217 */ 218 private static final ActionBill BILL_JOB_RUNNING_HIGH_EXPEDITED = 219 new ActionBill(List.of( 220 new EconomyManagerInternal.AnticipatedAction( 221 JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 1_000L) 222 )); 223 224 private final EconomyManagerInternal mEconomyManagerInternal; 225 226 private final BackgroundJobsController mBackgroundJobsController; 227 private final ConnectivityController mConnectivityController; 228 229 /** 230 * Local cache of the ability of each userId-pkg to afford the various bills we're tracking for 231 * them. 232 */ 233 @GuardedBy("mLock") 234 private final SparseArrayMap<String, ArrayMap<ActionBill, Boolean>> mAffordabilityCache = 235 new SparseArrayMap<>(); 236 237 /** 238 * List of all tracked jobs. Out SparseArrayMap is userId-sourcePkg. The inner mapping is the 239 * anticipated actions and all the jobs that are applicable to them. 240 */ 241 @GuardedBy("mLock") 242 private final SparseArrayMap<String, ArrayMap<ActionBill, ArraySet<JobStatus>>> 243 mRegisteredBillsAndJobs = new SparseArrayMap<>(); 244 245 private final EconomyManagerInternal.AffordabilityChangeListener mAffordabilityChangeListener = 246 (userId, pkgName, bill, canAfford) -> { 247 final long nowElapsed = sElapsedRealtimeClock.millis(); 248 if (DEBUG) { 249 Slog.d(TAG, 250 userId + ":" + pkgName + " affordability for " + getBillName(bill) 251 + " changed to " + canAfford); 252 } 253 synchronized (mLock) { 254 ArrayMap<ActionBill, Boolean> actionAffordability = 255 mAffordabilityCache.get(userId, pkgName); 256 if (actionAffordability == null) { 257 actionAffordability = new ArrayMap<>(); 258 mAffordabilityCache.add(userId, pkgName, actionAffordability); 259 } 260 actionAffordability.put(bill, canAfford); 261 262 final ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = 263 mRegisteredBillsAndJobs.get(userId, pkgName); 264 if (billToJobMap != null) { 265 final ArraySet<JobStatus> jobs = billToJobMap.get(bill); 266 if (jobs != null) { 267 final ArraySet<JobStatus> changedJobs = new ArraySet<>(); 268 for (int i = 0; i < jobs.size(); ++i) { 269 final JobStatus job = jobs.valueAt(i); 270 // Use hasEnoughWealth if canAfford is false in case the job has 271 // other bills it can depend on (eg. EJs being demoted to 272 // regular jobs). 273 if (job.setTareWealthConstraintSatisfied(nowElapsed, 274 canAfford || hasEnoughWealthLocked(job))) { 275 changedJobs.add(job); 276 } 277 if (job.isRequestedExpeditedJob() 278 && setExpeditedTareApproved(job, nowElapsed, 279 canAffordExpeditedBillLocked(job))) { 280 changedJobs.add(job); 281 } 282 } 283 if (changedJobs.size() > 0) { 284 mStateChangedListener.onControllerStateChanged(changedJobs); 285 } 286 } 287 } 288 } 289 }; 290 291 /** 292 * List of jobs that started while the UID was in the TOP state. There will usually be no more 293 * than {@value JobConcurrencyManager#MAX_STANDARD_JOB_CONCURRENCY} running at once, so an 294 * ArraySet is fine. 295 */ 296 @GuardedBy("mLock") 297 private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>(); 298 299 @GuardedBy("mLock") 300 private boolean mIsEnabled; 301 TareController(JobSchedulerService service, @NonNull BackgroundJobsController backgroundJobsController, @NonNull ConnectivityController connectivityController)302 public TareController(JobSchedulerService service, 303 @NonNull BackgroundJobsController backgroundJobsController, 304 @NonNull ConnectivityController connectivityController) { 305 super(service); 306 mBackgroundJobsController = backgroundJobsController; 307 mConnectivityController = connectivityController; 308 mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class); 309 mIsEnabled = mConstants.USE_TARE_POLICY; 310 } 311 312 @Override 313 @GuardedBy("mLock") maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)314 public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 315 final long nowElapsed = sElapsedRealtimeClock.millis(); 316 if (jobStatus.shouldTreatAsUserInitiatedJob()) { 317 // User-initiated jobs should always be allowed to run. 318 jobStatus.setTareWealthConstraintSatisfied(nowElapsed, true); 319 return; 320 } 321 jobStatus.setTareWealthConstraintSatisfied(nowElapsed, hasEnoughWealthLocked(jobStatus)); 322 setExpeditedTareApproved(jobStatus, nowElapsed, 323 jobStatus.isRequestedExpeditedJob() && canAffordExpeditedBillLocked(jobStatus)); 324 325 final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus); 326 for (int i = 0; i < bills.size(); ++i) { 327 addJobToBillList(jobStatus, bills.valueAt(i)); 328 } 329 } 330 331 @Override 332 @GuardedBy("mLock") prepareForExecutionLocked(JobStatus jobStatus)333 public void prepareForExecutionLocked(JobStatus jobStatus) { 334 if (jobStatus.shouldTreatAsUserInitiatedJob()) { 335 // TODO(202954395): consider noting execution with the EconomyManager even though it 336 // won't affect this job 337 return; 338 } 339 final int userId = jobStatus.getSourceUserId(); 340 final String pkgName = jobStatus.getSourcePackageName(); 341 ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = 342 mRegisteredBillsAndJobs.get(userId, pkgName); 343 if (billToJobMap == null) { 344 Slog.e(TAG, "Job is being prepared but doesn't have a pre-existing billToJobMap"); 345 } else { 346 for (int i = 0; i < billToJobMap.size(); ++i) { 347 removeJobFromBillList(jobStatus, billToJobMap.keyAt(i)); 348 } 349 } 350 351 final int uid = jobStatus.getSourceUid(); 352 if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) { 353 if (DEBUG) { 354 Slog.d(TAG, jobStatus.toShortString() + " is top started job"); 355 } 356 mTopStartedJobs.add(jobStatus); 357 // Top jobs won't count towards quota so there's no need to involve the EconomyManager. 358 } else { 359 addJobToBillList(jobStatus, getRunningBill(jobStatus)); 360 mEconomyManagerInternal.noteOngoingEventStarted(userId, pkgName, 361 getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); 362 } 363 } 364 365 @Override 366 @GuardedBy("mLock") unprepareFromExecutionLocked(JobStatus jobStatus)367 public void unprepareFromExecutionLocked(JobStatus jobStatus) { 368 if (jobStatus.shouldTreatAsUserInitiatedJob()) { 369 return; 370 } 371 final int userId = jobStatus.getSourceUserId(); 372 final String pkgName = jobStatus.getSourcePackageName(); 373 // If this method is called, then jobStatus.madeActive was never updated, so don't use it 374 // to determine if the EconomyManager was notified. 375 if (!mTopStartedJobs.remove(jobStatus)) { 376 // If the job was started while the app was top, then the EconomyManager wasn't notified 377 // of the job start. 378 mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName, 379 getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); 380 } 381 382 final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus); 383 ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = 384 mRegisteredBillsAndJobs.get(userId, pkgName); 385 if (billToJobMap == null) { 386 Slog.e(TAG, "Job was just unprepared but didn't have a pre-existing billToJobMap"); 387 } else { 388 for (int i = 0; i < billToJobMap.size(); ++i) { 389 removeJobFromBillList(jobStatus, billToJobMap.keyAt(i)); 390 } 391 } 392 for (int i = 0; i < bills.size(); ++i) { 393 addJobToBillList(jobStatus, bills.valueAt(i)); 394 } 395 } 396 397 @Override 398 @GuardedBy("mLock") maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob)399 public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { 400 if (jobStatus.shouldTreatAsUserInitiatedJob()) { 401 return; 402 } 403 final int userId = jobStatus.getSourceUserId(); 404 final String pkgName = jobStatus.getSourcePackageName(); 405 if (!mTopStartedJobs.remove(jobStatus) && jobStatus.madeActive > 0) { 406 // Only note the job stop if we previously told the EconomyManager that the job started. 407 // If the job was started while the app was top, then the EconomyManager wasn't notified 408 // of the job start. 409 mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName, 410 getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId())); 411 } 412 ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = 413 mRegisteredBillsAndJobs.get(userId, pkgName); 414 if (billToJobMap != null) { 415 for (int i = 0; i < billToJobMap.size(); ++i) { 416 removeJobFromBillList(jobStatus, billToJobMap.keyAt(i)); 417 } 418 } 419 } 420 421 @Override 422 @GuardedBy("mLock") onConstantsUpdatedLocked()423 public void onConstantsUpdatedLocked() { 424 if (mIsEnabled != mConstants.USE_TARE_POLICY) { 425 mIsEnabled = mConstants.USE_TARE_POLICY; 426 // Update job bookkeeping out of band. 427 AppSchedulingModuleThread.getHandler().post(() -> { 428 synchronized (mLock) { 429 final long nowElapsed = sElapsedRealtimeClock.millis(); 430 mService.getJobStore().forEachJob((jobStatus) -> { 431 if (!mIsEnabled) { 432 jobStatus.setTareWealthConstraintSatisfied(nowElapsed, true); 433 setExpeditedTareApproved(jobStatus, nowElapsed, true); 434 } else { 435 jobStatus.setTareWealthConstraintSatisfied( 436 nowElapsed, hasEnoughWealthLocked(jobStatus)); 437 setExpeditedTareApproved(jobStatus, nowElapsed, 438 jobStatus.isRequestedExpeditedJob() 439 && canAffordExpeditedBillLocked(jobStatus)); 440 } 441 }); 442 } 443 }); 444 } 445 } 446 447 @GuardedBy("mLock") canScheduleEJ(@onNull JobStatus jobStatus)448 public boolean canScheduleEJ(@NonNull JobStatus jobStatus) { 449 if (!mIsEnabled) { 450 return true; 451 } 452 if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) { 453 return canAffordBillLocked(jobStatus, BILL_JOB_START_MAX_EXPEDITED); 454 } 455 return canAffordBillLocked(jobStatus, BILL_JOB_START_HIGH_EXPEDITED); 456 } 457 458 /** @return true if the job was started while the app was in the TOP state. */ 459 @GuardedBy("mLock") isTopStartedJobLocked(@onNull final JobStatus jobStatus)460 private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) { 461 return mTopStartedJobs.contains(jobStatus); 462 } 463 464 @GuardedBy("mLock") getMaxJobExecutionTimeMsLocked(@onNull JobStatus jobStatus)465 public long getMaxJobExecutionTimeMsLocked(@NonNull JobStatus jobStatus) { 466 if (!mIsEnabled) { 467 return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; 468 } 469 return mEconomyManagerInternal.getMaxDurationMs( 470 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), 471 getRunningBill(jobStatus)); 472 } 473 474 @GuardedBy("mLock") addJobToBillList(@onNull JobStatus jobStatus, @NonNull ActionBill bill)475 private void addJobToBillList(@NonNull JobStatus jobStatus, @NonNull ActionBill bill) { 476 final int userId = jobStatus.getSourceUserId(); 477 final String pkgName = jobStatus.getSourcePackageName(); 478 ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = 479 mRegisteredBillsAndJobs.get(userId, pkgName); 480 if (billToJobMap == null) { 481 billToJobMap = new ArrayMap<>(); 482 mRegisteredBillsAndJobs.add(userId, pkgName, billToJobMap); 483 } 484 ArraySet<JobStatus> jobs = billToJobMap.get(bill); 485 if (jobs == null) { 486 jobs = new ArraySet<>(); 487 billToJobMap.put(bill, jobs); 488 } 489 if (jobs.add(jobStatus)) { 490 mEconomyManagerInternal.registerAffordabilityChangeListener(userId, pkgName, 491 mAffordabilityChangeListener, bill); 492 } 493 } 494 495 @GuardedBy("mLock") removeJobFromBillList(@onNull JobStatus jobStatus, @NonNull ActionBill bill)496 private void removeJobFromBillList(@NonNull JobStatus jobStatus, @NonNull ActionBill bill) { 497 final int userId = jobStatus.getSourceUserId(); 498 final String pkgName = jobStatus.getSourcePackageName(); 499 final ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap = 500 mRegisteredBillsAndJobs.get(userId, pkgName); 501 if (billToJobMap != null) { 502 final ArraySet<JobStatus> jobs = billToJobMap.get(bill); 503 if (jobs == null || (jobs.remove(jobStatus) && jobs.size() == 0)) { 504 mEconomyManagerInternal.unregisterAffordabilityChangeListener( 505 userId, pkgName, mAffordabilityChangeListener, bill); 506 // Remove the cached value so we don't accidentally use it when the app 507 // schedules a new job. 508 final ArrayMap<ActionBill, Boolean> actionAffordability = 509 mAffordabilityCache.get(userId, pkgName); 510 if (actionAffordability != null) { 511 actionAffordability.remove(bill); 512 } 513 } 514 } 515 } 516 517 @NonNull getPossibleStartBills(JobStatus jobStatus)518 private ArraySet<ActionBill> getPossibleStartBills(JobStatus jobStatus) { 519 // TODO: factor in network cost when available 520 final ArraySet<ActionBill> bills = new ArraySet<>(); 521 if (jobStatus.isRequestedExpeditedJob()) { 522 if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) { 523 bills.add(BILL_JOB_START_MAX_EXPEDITED); 524 } else { 525 bills.add(BILL_JOB_START_HIGH_EXPEDITED); 526 } 527 } 528 switch (jobStatus.getEffectivePriority()) { 529 case JobInfo.PRIORITY_MAX: 530 bills.add(BILL_JOB_START_MAX); 531 break; 532 case JobInfo.PRIORITY_HIGH: 533 bills.add(BILL_JOB_START_HIGH); 534 break; 535 case JobInfo.PRIORITY_DEFAULT: 536 bills.add(BILL_JOB_START_DEFAULT); 537 break; 538 case JobInfo.PRIORITY_LOW: 539 bills.add(BILL_JOB_START_LOW); 540 break; 541 case JobInfo.PRIORITY_MIN: 542 bills.add(BILL_JOB_START_MIN); 543 break; 544 default: 545 Slog.wtf(TAG, "Unexpected priority: " 546 + JobInfo.getPriorityString(jobStatus.getEffectivePriority())); 547 break; 548 } 549 return bills; 550 } 551 552 @NonNull getRunningBill(JobStatus jobStatus)553 private ActionBill getRunningBill(JobStatus jobStatus) { 554 // TODO: factor in network cost when available 555 if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.startedAsExpeditedJob) { 556 if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) { 557 return BILL_JOB_RUNNING_MAX_EXPEDITED; 558 } else { 559 return BILL_JOB_RUNNING_HIGH_EXPEDITED; 560 } 561 } 562 switch (jobStatus.getEffectivePriority()) { 563 case JobInfo.PRIORITY_MAX: 564 return BILL_JOB_RUNNING_MAX; 565 case JobInfo.PRIORITY_HIGH: 566 return BILL_JOB_RUNNING_HIGH; 567 case JobInfo.PRIORITY_LOW: 568 return BILL_JOB_RUNNING_LOW; 569 case JobInfo.PRIORITY_MIN: 570 return BILL_JOB_RUNNING_MIN; 571 default: 572 Slog.wtf(TAG, "Got unexpected priority: " + jobStatus.getEffectivePriority()); 573 // Intentional fallthrough 574 case JobInfo.PRIORITY_DEFAULT: 575 return BILL_JOB_RUNNING_DEFAULT; 576 } 577 } 578 579 @EconomicPolicy.AppAction getRunningActionId(@onNull JobStatus job)580 private static int getRunningActionId(@NonNull JobStatus job) { 581 switch (job.getEffectivePriority()) { 582 case JobInfo.PRIORITY_MAX: 583 return JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING; 584 case JobInfo.PRIORITY_HIGH: 585 return JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING; 586 case JobInfo.PRIORITY_LOW: 587 return JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING; 588 case JobInfo.PRIORITY_MIN: 589 return JobSchedulerEconomicPolicy.ACTION_JOB_MIN_RUNNING; 590 default: 591 Slog.wtf(TAG, "Unknown priority: " + getPriorityString(job.getEffectivePriority())); 592 // Intentional fallthrough 593 case JobInfo.PRIORITY_DEFAULT: 594 return JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING; 595 } 596 } 597 598 @GuardedBy("mLock") canAffordBillLocked(@onNull JobStatus jobStatus, @NonNull ActionBill bill)599 private boolean canAffordBillLocked(@NonNull JobStatus jobStatus, @NonNull ActionBill bill) { 600 if (!mIsEnabled) { 601 return true; 602 } 603 if (mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP 604 || isTopStartedJobLocked(jobStatus)) { 605 // Jobs for the top app should always be allowed to run, and any jobs started while 606 // the app is on top shouldn't consume any credits. 607 return true; 608 } 609 final int userId = jobStatus.getSourceUserId(); 610 final String pkgName = jobStatus.getSourcePackageName(); 611 ArrayMap<ActionBill, Boolean> actionAffordability = 612 mAffordabilityCache.get(userId, pkgName); 613 if (actionAffordability == null) { 614 actionAffordability = new ArrayMap<>(); 615 mAffordabilityCache.add(userId, pkgName, actionAffordability); 616 } 617 618 if (actionAffordability.containsKey(bill)) { 619 return actionAffordability.get(bill); 620 } 621 622 final boolean canAfford = mEconomyManagerInternal.canPayFor(userId, pkgName, bill); 623 actionAffordability.put(bill, canAfford); 624 return canAfford; 625 } 626 627 @GuardedBy("mLock") canAffordExpeditedBillLocked(@onNull JobStatus jobStatus)628 private boolean canAffordExpeditedBillLocked(@NonNull JobStatus jobStatus) { 629 if (!mIsEnabled) { 630 return true; 631 } 632 if (!jobStatus.isRequestedExpeditedJob()) { 633 return false; 634 } 635 if (mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP 636 || isTopStartedJobLocked(jobStatus)) { 637 // Jobs for the top app should always be allowed to run, and any jobs started while 638 // the app is on top shouldn't consume any credits. 639 return true; 640 } 641 if (mService.isCurrentlyRunningLocked(jobStatus)) { 642 return canAffordBillLocked(jobStatus, getRunningBill(jobStatus)); 643 } 644 645 if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) { 646 return canAffordBillLocked(jobStatus, BILL_JOB_START_MAX_EXPEDITED); 647 } 648 return canAffordBillLocked(jobStatus, BILL_JOB_START_HIGH_EXPEDITED); 649 } 650 651 @GuardedBy("mLock") hasEnoughWealthLocked(@onNull JobStatus jobStatus)652 private boolean hasEnoughWealthLocked(@NonNull JobStatus jobStatus) { 653 if (!mIsEnabled) { 654 return true; 655 } 656 if (jobStatus.shouldTreatAsUserInitiatedJob()) { 657 // Always allow user-initiated jobs. 658 return true; 659 } 660 if (mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP 661 || isTopStartedJobLocked(jobStatus)) { 662 // Jobs for the top app should always be allowed to run, and any jobs started while 663 // the app is on top shouldn't consume any credits. 664 return true; 665 } 666 if (mService.isCurrentlyRunningLocked(jobStatus)) { 667 return canAffordBillLocked(jobStatus, getRunningBill(jobStatus)); 668 } 669 670 final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus); 671 for (int i = 0; i < bills.size(); ++i) { 672 ActionBill bill = bills.valueAt(i); 673 if (canAffordBillLocked(jobStatus, bill)) { 674 return true; 675 } 676 } 677 return false; 678 } 679 680 /** 681 * If the satisfaction changes, this will tell connectivity & background jobs controller to 682 * also re-evaluate their state. 683 */ setExpeditedTareApproved(@onNull JobStatus jobStatus, long nowElapsed, boolean isApproved)684 private boolean setExpeditedTareApproved(@NonNull JobStatus jobStatus, long nowElapsed, 685 boolean isApproved) { 686 if (jobStatus.setExpeditedJobTareApproved(nowElapsed, isApproved)) { 687 mBackgroundJobsController.evaluateStateLocked(jobStatus); 688 mConnectivityController.evaluateStateLocked(jobStatus); 689 if (isApproved && jobStatus.isReady()) { 690 mStateChangedListener.onRunJobNow(jobStatus); 691 } 692 return true; 693 } 694 return false; 695 } 696 697 @NonNull getBillName(@onNull ActionBill bill)698 private String getBillName(@NonNull ActionBill bill) { 699 if (bill.equals(BILL_JOB_START_MAX_EXPEDITED)) { 700 return "EJ_MAX_START_BILL"; 701 } 702 if (bill.equals(BILL_JOB_RUNNING_MAX_EXPEDITED)) { 703 return "EJ_MAX_RUNNING_BILL"; 704 } 705 if (bill.equals(BILL_JOB_START_HIGH_EXPEDITED)) { 706 return "EJ_HIGH_START_BILL"; 707 } 708 if (bill.equals(BILL_JOB_RUNNING_HIGH_EXPEDITED)) { 709 return "EJ_HIGH_RUNNING_BILL"; 710 } 711 if (bill.equals(BILL_JOB_START_HIGH)) { 712 return "HIGH_START_BILL"; 713 } 714 if (bill.equals(BILL_JOB_RUNNING_HIGH)) { 715 return "HIGH_RUNNING_BILL"; 716 } 717 if (bill.equals(BILL_JOB_START_DEFAULT)) { 718 return "DEFAULT_START_BILL"; 719 } 720 if (bill.equals(BILL_JOB_RUNNING_DEFAULT)) { 721 return "DEFAULT_RUNNING_BILL"; 722 } 723 if (bill.equals(BILL_JOB_START_LOW)) { 724 return "LOW_START_BILL"; 725 } 726 if (bill.equals(BILL_JOB_RUNNING_LOW)) { 727 return "LOW_RUNNING_BILL"; 728 } 729 if (bill.equals(BILL_JOB_START_MIN)) { 730 return "MIN_START_BILL"; 731 } 732 if (bill.equals(BILL_JOB_RUNNING_MIN)) { 733 return "MIN_RUNNING_BILL"; 734 } 735 return "UNKNOWN_BILL (" + bill + ")"; 736 } 737 738 @Override dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)739 public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { 740 pw.print("Is enabled: "); 741 pw.println(mIsEnabled); 742 743 pw.println("Affordability cache:"); 744 pw.increaseIndent(); 745 mAffordabilityCache.forEach((userId, pkgName, billMap) -> { 746 final int numBills = billMap.size(); 747 if (numBills > 0) { 748 pw.print(userId); 749 pw.print(":"); 750 pw.print(pkgName); 751 pw.println(":"); 752 753 pw.increaseIndent(); 754 for (int i = 0; i < numBills; ++i) { 755 pw.print(getBillName(billMap.keyAt(i))); 756 pw.print(": "); 757 pw.println(billMap.valueAt(i)); 758 } 759 pw.decreaseIndent(); 760 } 761 }); 762 pw.decreaseIndent(); 763 } 764 } 765