1 /* 2 * Copyright (C) 2014 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.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 20 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; 21 import static android.text.format.DateUtils.MINUTE_IN_MILLIS; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.UserIdInt; 26 import android.app.Activity; 27 import android.app.ActivityManager; 28 import android.app.ActivityManagerInternal; 29 import android.app.AppGlobals; 30 import android.app.IUidObserver; 31 import android.app.job.IJobScheduler; 32 import android.app.job.JobInfo; 33 import android.app.job.JobParameters; 34 import android.app.job.JobProtoEnums; 35 import android.app.job.JobScheduler; 36 import android.app.job.JobService; 37 import android.app.job.JobSnapshot; 38 import android.app.job.JobWorkItem; 39 import android.app.usage.UsageStatsManager; 40 import android.app.usage.UsageStatsManagerInternal; 41 import android.content.BroadcastReceiver; 42 import android.content.ComponentName; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.IntentFilter; 46 import android.content.pm.ApplicationInfo; 47 import android.content.pm.IPackageManager; 48 import android.content.pm.PackageManager; 49 import android.content.pm.PackageManager.NameNotFoundException; 50 import android.content.pm.PackageManagerInternal; 51 import android.content.pm.ParceledListSlice; 52 import android.content.pm.ServiceInfo; 53 import android.net.Uri; 54 import android.os.BatteryStats; 55 import android.os.BatteryStatsInternal; 56 import android.os.Binder; 57 import android.os.Handler; 58 import android.os.LimitExceededException; 59 import android.os.Looper; 60 import android.os.Message; 61 import android.os.ParcelFileDescriptor; 62 import android.os.Process; 63 import android.os.RemoteException; 64 import android.os.ServiceManager; 65 import android.os.SystemClock; 66 import android.os.UserHandle; 67 import android.os.WorkSource; 68 import android.provider.DeviceConfig; 69 import android.text.format.DateUtils; 70 import android.util.ArrayMap; 71 import android.util.ArraySet; 72 import android.util.IndentingPrintWriter; 73 import android.util.Log; 74 import android.util.Slog; 75 import android.util.SparseArray; 76 import android.util.SparseBooleanArray; 77 import android.util.SparseIntArray; 78 import android.util.SparseLongArray; 79 import android.util.SparseSetArray; 80 import android.util.TimeUtils; 81 import android.util.proto.ProtoOutputStream; 82 83 import com.android.internal.R; 84 import com.android.internal.annotations.GuardedBy; 85 import com.android.internal.annotations.VisibleForTesting; 86 import com.android.internal.app.IBatteryStats; 87 import com.android.internal.util.ArrayUtils; 88 import com.android.internal.util.DumpUtils; 89 import com.android.internal.util.FrameworkStatsLog; 90 import com.android.server.AppStateTracker; 91 import com.android.server.AppStateTrackerImpl; 92 import com.android.server.DeviceIdleInternal; 93 import com.android.server.JobSchedulerBackgroundThread; 94 import com.android.server.LocalServices; 95 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob; 96 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob; 97 import com.android.server.job.controllers.BackgroundJobsController; 98 import com.android.server.job.controllers.BatteryController; 99 import com.android.server.job.controllers.ComponentController; 100 import com.android.server.job.controllers.ConnectivityController; 101 import com.android.server.job.controllers.ContentObserverController; 102 import com.android.server.job.controllers.DeviceIdleJobsController; 103 import com.android.server.job.controllers.IdleController; 104 import com.android.server.job.controllers.JobStatus; 105 import com.android.server.job.controllers.QuotaController; 106 import com.android.server.job.controllers.RestrictingController; 107 import com.android.server.job.controllers.StateController; 108 import com.android.server.job.controllers.StorageController; 109 import com.android.server.job.controllers.TimeController; 110 import com.android.server.job.restrictions.JobRestriction; 111 import com.android.server.job.restrictions.ThermalStatusRestriction; 112 import com.android.server.pm.UserManagerInternal; 113 import com.android.server.usage.AppStandbyInternal; 114 import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; 115 import com.android.server.utils.quota.Categorizer; 116 import com.android.server.utils.quota.Category; 117 import com.android.server.utils.quota.CountQuotaTracker; 118 119 import libcore.util.EmptyArray; 120 121 import java.io.FileDescriptor; 122 import java.io.PrintWriter; 123 import java.time.Clock; 124 import java.time.Instant; 125 import java.time.ZoneId; 126 import java.time.ZoneOffset; 127 import java.util.ArrayList; 128 import java.util.Arrays; 129 import java.util.Collections; 130 import java.util.Comparator; 131 import java.util.List; 132 import java.util.Objects; 133 import java.util.function.Consumer; 134 import java.util.function.Predicate; 135 136 /** 137 * Responsible for taking jobs representing work to be performed by a client app, and determining 138 * based on the criteria specified when that job should be run against the client application's 139 * endpoint. 140 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing 141 * about constraints, or the state of active jobs. It receives callbacks from the various 142 * controllers and completed jobs and operates accordingly. 143 * 144 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object. 145 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}. 146 * @hide 147 */ 148 public class JobSchedulerService extends com.android.server.SystemService 149 implements StateChangedListener, JobCompletedListener { 150 public static final String TAG = "JobScheduler"; 151 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 152 public static final boolean DEBUG_STANDBY = DEBUG || false; 153 154 /** The maximum number of concurrent jobs we run at one time. */ 155 static final int MAX_JOB_CONTEXTS_COUNT = 16; 156 /** The maximum number of jobs that we allow an app to schedule */ 157 private static final int MAX_JOBS_PER_APP = 150; 158 /** The number of the most recently completed jobs to keep track of for debugging purposes. */ 159 private static final int NUM_COMPLETED_JOB_HISTORY = 20; 160 161 @VisibleForTesting 162 public static Clock sSystemClock = Clock.systemUTC(); 163 164 private abstract static class MySimpleClock extends Clock { 165 private final ZoneId mZoneId; 166 MySimpleClock(ZoneId zoneId)167 MySimpleClock(ZoneId zoneId) { 168 this.mZoneId = zoneId; 169 } 170 171 @Override getZone()172 public ZoneId getZone() { 173 return mZoneId; 174 } 175 176 @Override withZone(ZoneId zone)177 public Clock withZone(ZoneId zone) { 178 return new MySimpleClock(zone) { 179 @Override 180 public long millis() { 181 return MySimpleClock.this.millis(); 182 } 183 }; 184 } 185 186 @Override millis()187 public abstract long millis(); 188 189 @Override instant()190 public Instant instant() { 191 return Instant.ofEpochMilli(millis()); 192 } 193 } 194 195 @VisibleForTesting 196 public static Clock sUptimeMillisClock = new MySimpleClock(ZoneOffset.UTC) { 197 @Override 198 public long millis() { 199 return SystemClock.uptimeMillis(); 200 } 201 }; 202 203 @VisibleForTesting 204 public static Clock sElapsedRealtimeClock = new MySimpleClock(ZoneOffset.UTC) { 205 @Override 206 public long millis() { 207 return SystemClock.elapsedRealtime(); 208 } 209 }; 210 211 /** Global local for all job scheduler state. */ 212 final Object mLock = new Object(); 213 /** Master list of jobs. */ 214 final JobStore mJobs; 215 /** Tracking the standby bucket state of each app */ 216 final StandbyTracker mStandbyTracker; 217 /** Tracking amount of time each package runs for. */ 218 final JobPackageTracker mJobPackageTracker = new JobPackageTracker(); 219 final JobConcurrencyManager mConcurrencyManager; 220 221 static final int MSG_CHECK_INDIVIDUAL_JOB = 0; 222 static final int MSG_CHECK_JOB = 1; 223 static final int MSG_STOP_JOB = 2; 224 static final int MSG_CHECK_JOB_GREEDY = 3; 225 static final int MSG_UID_STATE_CHANGED = 4; 226 static final int MSG_UID_GONE = 5; 227 static final int MSG_UID_ACTIVE = 6; 228 static final int MSG_UID_IDLE = 7; 229 230 /** 231 * Track Services that have currently active or pending jobs. The index is provided by 232 * {@link JobStatus#getServiceToken()} 233 */ 234 final List<JobServiceContext> mActiveServices = new ArrayList<>(); 235 236 /** List of controllers that will notify this service of updates to jobs. */ 237 final List<StateController> mControllers; 238 /** 239 * List of controllers that will apply to all jobs in the RESTRICTED bucket. This is a subset of 240 * {@link #mControllers}. 241 */ 242 private final List<RestrictingController> mRestrictiveControllers; 243 /** Need direct access to this for testing. */ 244 private final BatteryController mBatteryController; 245 /** Need direct access to this for testing. */ 246 private final StorageController mStorageController; 247 /** Need directly for sending uid state changes */ 248 private final DeviceIdleJobsController mDeviceIdleJobsController; 249 /** Needed to get remaining quota time. */ 250 private final QuotaController mQuotaController; 251 /** 252 * List of restrictions. 253 * Note: do not add to or remove from this list at runtime except in the constructor, because we 254 * do not synchronize access to this list. 255 */ 256 private final List<JobRestriction> mJobRestrictions; 257 258 @NonNull 259 private final String mSystemGalleryPackage; 260 261 private final CountQuotaTracker mQuotaTracker; 262 private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; 263 private static final String QUOTA_TRACKER_SCHEDULE_LOGGED = 264 ".schedulePersisted out-of-quota logged"; 265 private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED = new Category( 266 ".schedulePersisted()"); 267 private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED = new Category( 268 ".schedulePersisted out-of-quota logged"); 269 private static final Categorizer QUOTA_CATEGORIZER = (userId, packageName, tag) -> { 270 if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) { 271 return QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED; 272 } 273 return QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED; 274 }; 275 276 /** 277 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list 278 * when ready to execute them. 279 */ 280 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>(); 281 282 int[] mStartedUsers = EmptyArray.INT; 283 284 final JobHandler mHandler; 285 final JobSchedulerStub mJobSchedulerStub; 286 287 PackageManagerInternal mLocalPM; 288 ActivityManagerInternal mActivityManagerInternal; 289 IBatteryStats mBatteryStats; 290 DeviceIdleInternal mLocalDeviceIdleController; 291 @VisibleForTesting 292 AppStateTrackerImpl mAppStateTracker; 293 final UsageStatsManagerInternal mUsageStats; 294 private final AppStandbyInternal mAppStandbyInternal; 295 296 /** 297 * Set to true once we are allowed to run third party apps. 298 */ 299 boolean mReadyToRock; 300 301 /** 302 * What we last reported to DeviceIdleController about whether we are active. 303 */ 304 boolean mReportedActive; 305 306 private int mLastCompletedJobIndex = 0; 307 private final JobStatus[] mLastCompletedJobs = new JobStatus[NUM_COMPLETED_JOB_HISTORY]; 308 private final long[] mLastCompletedJobTimeElapsed = new long[NUM_COMPLETED_JOB_HISTORY]; 309 310 /** 311 * A mapping of which uids are currently in the foreground to their effective priority. 312 */ 313 final SparseIntArray mUidPriorityOverride = new SparseIntArray(); 314 315 /** 316 * Which uids are currently performing backups, so we shouldn't allow their jobs to run. 317 */ 318 final SparseIntArray mBackingUpUids = new SparseIntArray(); 319 320 /** 321 * Cache of debuggable app status. 322 */ 323 final ArrayMap<String, Boolean> mDebuggableApps = new ArrayMap<>(); 324 325 /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */ 326 private final SparseSetArray<String> mUidToPackageCache = new SparseSetArray<>(); 327 328 /** 329 * Named indices into standby bucket arrays, for clarity in referring to 330 * specific buckets' bookkeeping. 331 */ 332 public static final int ACTIVE_INDEX = 0; 333 public static final int WORKING_INDEX = 1; 334 public static final int FREQUENT_INDEX = 2; 335 public static final int RARE_INDEX = 3; 336 public static final int NEVER_INDEX = 4; 337 // Putting RESTRICTED_INDEX after NEVER_INDEX to make it easier for proto dumping 338 // (ScheduledJobStateChanged and JobStatusDumpProto). 339 public static final int RESTRICTED_INDEX = 5; 340 341 private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener { 342 public void start() { 343 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 344 JobSchedulerBackgroundThread.getExecutor(), this); 345 // Load all the constants. 346 onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER)); 347 } 348 349 @Override 350 public void onPropertiesChanged(DeviceConfig.Properties properties) { 351 boolean apiQuotaScheduleUpdated = false; 352 boolean concurrencyUpdated = false; 353 boolean runtimeUpdated = false; 354 for (int controller = 0; controller < mControllers.size(); controller++) { 355 final StateController sc = mControllers.get(controller); 356 sc.prepareForUpdatedConstantsLocked(); 357 } 358 359 synchronized (mLock) { 360 for (String name : properties.getKeyset()) { 361 if (name == null) { 362 continue; 363 } 364 switch (name) { 365 case Constants.KEY_ENABLE_API_QUOTAS: 366 case Constants.KEY_API_QUOTA_SCHEDULE_COUNT: 367 case Constants.KEY_API_QUOTA_SCHEDULE_WINDOW_MS: 368 case Constants.KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT: 369 case Constants.KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION: 370 if (!apiQuotaScheduleUpdated) { 371 mConstants.updateApiQuotaConstantsLocked(); 372 updateQuotaTracker(); 373 apiQuotaScheduleUpdated = true; 374 } 375 break; 376 case Constants.KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT: 377 case Constants.KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS: 378 mConstants.updateBatchingConstantsLocked(); 379 break; 380 case Constants.KEY_HEAVY_USE_FACTOR: 381 case Constants.KEY_MODERATE_USE_FACTOR: 382 mConstants.updateUseFactorConstantsLocked(); 383 break; 384 case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS: 385 case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS: 386 mConstants.updateBackoffConstantsLocked(); 387 break; 388 case Constants.KEY_CONN_CONGESTION_DELAY_FRAC: 389 case Constants.KEY_CONN_PREFETCH_RELAX_FRAC: 390 mConstants.updateConnectivityConstantsLocked(); 391 break; 392 case Constants.KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS: 393 case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS: 394 case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS: 395 if (!runtimeUpdated) { 396 mConstants.updateRuntimeConstantsLocked(); 397 runtimeUpdated = true; 398 } 399 break; 400 default: 401 if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY) 402 && !concurrencyUpdated) { 403 mConcurrencyManager.updateConfigLocked(); 404 concurrencyUpdated = true; 405 } else { 406 for (int ctrlr = 0; ctrlr < mControllers.size(); ctrlr++) { 407 final StateController sc = mControllers.get(ctrlr); 408 sc.processConstantLocked(properties, name); 409 } 410 } 411 break; 412 } 413 } 414 for (int controller = 0; controller < mControllers.size(); controller++) { 415 final StateController sc = mControllers.get(controller); 416 sc.onConstantsUpdatedLocked(); 417 } 418 } 419 } 420 } 421 422 @VisibleForTesting 423 void updateQuotaTracker() { 424 mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS); 425 mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED, 426 mConstants.API_QUOTA_SCHEDULE_COUNT, 427 mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); 428 } 429 430 /** 431 * All times are in milliseconds. Any access to this class or its fields should be done while 432 * holding the JobSchedulerService.mLock lock. 433 */ 434 public static class Constants { 435 // Key names stored in the settings value. 436 private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT = 437 "min_ready_non_active_jobs_count"; 438 private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 439 "max_non_active_job_batch_delay_ms"; 440 private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor"; 441 private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor"; 442 443 private static final String KEY_MIN_LINEAR_BACKOFF_TIME_MS = "min_linear_backoff_time_ms"; 444 private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms"; 445 private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac"; 446 private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac"; 447 private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas"; 448 private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count"; 449 private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms"; 450 private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION = 451 "aq_schedule_throw_exception"; 452 private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = 453 "aq_schedule_return_failure"; 454 455 private static final String KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 456 "runtime_free_quota_max_limit_ms"; 457 private static final String KEY_RUNTIME_MIN_GUARANTEE_MS = "runtime_min_guarantee_ms"; 458 private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms"; 459 460 private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; 461 private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; 462 private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; 463 private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; 464 private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS; 465 private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS; 466 private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; 467 private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f; 468 private static final boolean DEFAULT_ENABLE_API_QUOTAS = true; 469 private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250; 470 private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS; 471 private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true; 472 private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false; 473 @VisibleForTesting 474 public static final long DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 30 * MINUTE_IN_MILLIS; 475 @VisibleForTesting 476 public static final long DEFAULT_RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS; 477 @VisibleForTesting 478 public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS; 479 480 /** 481 * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early. 482 */ 483 int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT; 484 485 /** 486 * Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for 487 * at least this amount of time. 488 */ 489 long MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS; 490 491 /** 492 * This is the job execution factor that is considered to be heavy use of the system. 493 */ 494 float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR; 495 /** 496 * This is the job execution factor that is considered to be moderate use of the system. 497 */ 498 float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR; 499 500 /** 501 * The minimum backoff time to allow for linear backoff. 502 */ 503 long MIN_LINEAR_BACKOFF_TIME_MS = DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS; 504 /** 505 * The minimum backoff time to allow for exponential backoff. 506 */ 507 long MIN_EXP_BACKOFF_TIME_MS = DEFAULT_MIN_EXP_BACKOFF_TIME_MS; 508 509 /** 510 * The fraction of a job's running window that must pass before we 511 * consider running it when the network is congested. 512 */ 513 public float CONN_CONGESTION_DELAY_FRAC = DEFAULT_CONN_CONGESTION_DELAY_FRAC; 514 /** 515 * The fraction of a prefetch job's running window that must pass before 516 * we consider matching it against a metered network. 517 */ 518 public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC; 519 520 /** 521 * Whether to enable quota limits on APIs. 522 */ 523 public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS; 524 /** 525 * The maximum number of schedule() calls an app can make in a set amount of time. 526 */ 527 public int API_QUOTA_SCHEDULE_COUNT = DEFAULT_API_QUOTA_SCHEDULE_COUNT; 528 /** 529 * The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over. 530 */ 531 public long API_QUOTA_SCHEDULE_WINDOW_MS = DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS; 532 /** 533 * Whether to throw an exception when an app hits its schedule quota limit. 534 */ 535 public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION = 536 DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION; 537 /** 538 * Whether or not to return a failure result when an app hits its schedule quota limit. 539 */ 540 public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = 541 DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT; 542 543 /** The maximum amount of time we will let a job run for when quota is "free". */ 544 public long RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; 545 546 /** 547 * The minimum amount of time we try to guarantee regular jobs will run for. 548 */ 549 public long RUNTIME_MIN_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_GUARANTEE_MS; 550 551 /** 552 * The minimum amount of time we try to guarantee EJs will run for. 553 */ 554 public long RUNTIME_MIN_EJ_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS; 555 556 private void updateBatchingConstantsLocked() { 557 MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt( 558 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 559 KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT, 560 DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT); 561 MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DeviceConfig.getLong( 562 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 563 KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, 564 DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS); 565 } 566 567 private void updateUseFactorConstantsLocked() { 568 HEAVY_USE_FACTOR = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 569 KEY_HEAVY_USE_FACTOR, 570 DEFAULT_HEAVY_USE_FACTOR); 571 MODERATE_USE_FACTOR = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 572 KEY_MODERATE_USE_FACTOR, 573 DEFAULT_MODERATE_USE_FACTOR); 574 } 575 576 private void updateBackoffConstantsLocked() { 577 MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 578 KEY_MIN_LINEAR_BACKOFF_TIME_MS, 579 DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS); 580 MIN_EXP_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 581 KEY_MIN_EXP_BACKOFF_TIME_MS, 582 DEFAULT_MIN_EXP_BACKOFF_TIME_MS); 583 } 584 585 private void updateConnectivityConstantsLocked() { 586 CONN_CONGESTION_DELAY_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 587 KEY_CONN_CONGESTION_DELAY_FRAC, 588 DEFAULT_CONN_CONGESTION_DELAY_FRAC); 589 CONN_PREFETCH_RELAX_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 590 KEY_CONN_PREFETCH_RELAX_FRAC, 591 DEFAULT_CONN_PREFETCH_RELAX_FRAC); 592 } 593 594 private void updateApiQuotaConstantsLocked() { 595 ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 596 KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS); 597 // Set a minimum value on the quota limit so it's not so low that it interferes with 598 // legitimate use cases. 599 API_QUOTA_SCHEDULE_COUNT = Math.max(250, 600 DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER, 601 KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT)); 602 API_QUOTA_SCHEDULE_WINDOW_MS = DeviceConfig.getLong( 603 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 604 KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS); 605 API_QUOTA_SCHEDULE_THROW_EXCEPTION = DeviceConfig.getBoolean( 606 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 607 KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, 608 DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION); 609 API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = DeviceConfig.getBoolean( 610 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 611 KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT, 612 DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT); 613 } 614 615 private void updateRuntimeConstantsLocked() { 616 DeviceConfig.Properties properties = DeviceConfig.getProperties( 617 DeviceConfig.NAMESPACE_JOB_SCHEDULER, 618 KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, 619 KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS); 620 621 // Make sure min runtime for regular jobs is at least 10 minutes. 622 RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS, 623 properties.getLong( 624 KEY_RUNTIME_MIN_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS)); 625 // Make sure min runtime for expedited jobs is at least one minute. 626 RUNTIME_MIN_EJ_GUARANTEE_MS = Math.max(MINUTE_IN_MILLIS, 627 properties.getLong( 628 KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS)); 629 RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS, 630 properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, 631 DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)); 632 } 633 634 void dump(IndentingPrintWriter pw) { 635 pw.println("Settings:"); 636 pw.increaseIndent(); 637 pw.print(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT, 638 MIN_READY_NON_ACTIVE_JOBS_COUNT).println(); 639 pw.print(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, 640 MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println(); 641 pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println(); 642 pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println(); 643 644 pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println(); 645 pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println(); 646 pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); 647 pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); 648 649 pw.print(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println(); 650 pw.print(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println(); 651 pw.print(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println(); 652 pw.print(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, 653 API_QUOTA_SCHEDULE_THROW_EXCEPTION).println(); 654 pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT, 655 API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println(); 656 657 pw.print(KEY_RUNTIME_MIN_GUARANTEE_MS, RUNTIME_MIN_GUARANTEE_MS).println(); 658 pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println(); 659 pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS) 660 .println(); 661 662 pw.decreaseIndent(); 663 } 664 665 void dump(ProtoOutputStream proto) { 666 proto.write(ConstantsProto.MIN_READY_NON_ACTIVE_JOBS_COUNT, 667 MIN_READY_NON_ACTIVE_JOBS_COUNT); 668 proto.write(ConstantsProto.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS, 669 MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS); 670 proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR); 671 proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR); 672 673 proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS); 674 proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS); 675 proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); 676 proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC); 677 678 proto.write(ConstantsProto.ENABLE_API_QUOTAS, ENABLE_API_QUOTAS); 679 proto.write(ConstantsProto.API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT); 680 proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS); 681 proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION, 682 API_QUOTA_SCHEDULE_THROW_EXCEPTION); 683 proto.write(ConstantsProto.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT, 684 API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT); 685 } 686 } 687 688 final Constants mConstants; 689 final ConstantsObserver mConstantsObserver; 690 691 @VisibleForTesting 692 class PendingJobComparator implements Comparator<JobStatus> { 693 private final SparseLongArray mEarliestRegEnqueueTimeCache = new SparseLongArray(); 694 695 /** 696 * Refresh sorting determinants based on the current state of {@link #mPendingJobs}. 697 */ 698 @GuardedBy("mLock") 699 @VisibleForTesting 700 void refreshLocked() { 701 mEarliestRegEnqueueTimeCache.clear(); 702 for (int i = 0; i < mPendingJobs.size(); ++i) { 703 final JobStatus job = mPendingJobs.get(i); 704 final int uid = job.getSourceUid(); 705 if (!job.isRequestedExpeditedJob()) { 706 final long earliestEnqueueTime = 707 mEarliestRegEnqueueTimeCache.get(uid, Long.MAX_VALUE); 708 mEarliestRegEnqueueTimeCache.put(uid, 709 Math.min(earliestEnqueueTime, job.enqueueTime)); 710 } 711 } 712 } 713 714 @Override 715 public int compare(JobStatus o1, JobStatus o2) { 716 if (o1 == o2) { 717 return 0; 718 } 719 // Jobs with an override state set (via adb) should be put first as tests/developers 720 // expect the jobs to run immediately. 721 if (o1.overrideState != o2.overrideState) { 722 // Higher override state (OVERRIDE_FULL) should be before lower state 723 // (OVERRIDE_SOFT) 724 return o2.overrideState - o1.overrideState; 725 } 726 final boolean o1EJ = o1.isRequestedExpeditedJob(); 727 final boolean o2EJ = o2.isRequestedExpeditedJob(); 728 if (o1.getSourceUid() == o2.getSourceUid()) { 729 if (o1EJ != o2EJ) { 730 // Attempt to run requested expedited jobs ahead of regular jobs, regardless of 731 // expedited job quota. 732 return o1EJ ? -1 : 1; 733 } 734 } 735 if (o1EJ || o2EJ) { 736 // We MUST prioritize EJs ahead of regular jobs within a single app. Since we do 737 // that, in order to satisfy the transitivity constraint of the comparator, if 738 // any UID has an EJ, we must ensure that the EJ is ordered ahead of the regular 739 // job of a different app IF the app with an EJ had another job that came before 740 // the differing app. For example, if app A has regJob1 at t1 and eJob3 at t3 and 741 // app B has regJob2 at t2, eJob3 must be ordered before regJob2 because it will be 742 // ordered before regJob1. 743 // Regular jobs don't need to jump the line. 744 745 final long uid1EarliestRegEnqueueTime = Math.min(o1.enqueueTime, 746 mEarliestRegEnqueueTimeCache.get(o1.getSourceUid(), Long.MAX_VALUE)); 747 final long uid2EarliestRegEnqueueTime = Math.min(o2.enqueueTime, 748 mEarliestRegEnqueueTimeCache.get(o2.getSourceUid(), Long.MAX_VALUE)); 749 750 if (o1EJ && o2EJ) { 751 if (uid1EarliestRegEnqueueTime < uid2EarliestRegEnqueueTime) { 752 return -1; 753 } else if (uid1EarliestRegEnqueueTime > uid2EarliestRegEnqueueTime) { 754 return 1; 755 } 756 } else if (o1EJ && uid1EarliestRegEnqueueTime <= o2.enqueueTime) { 757 // Include = to ensure that if we sorted an EJ ahead of a regular job at time X 758 // then we make sure to sort it ahead of all regular jobs at time X. 759 return -1; 760 } else if (o2EJ && uid2EarliestRegEnqueueTime <= o1.enqueueTime) { 761 // Include = to ensure that if we sorted an EJ ahead of a regular job at time X 762 // then we make sure to sort it ahead of all regular jobs at time X. 763 return 1; 764 } 765 } 766 if (o1.enqueueTime < o2.enqueueTime) { 767 return -1; 768 } 769 return o1.enqueueTime > o2.enqueueTime ? 1 : 0; 770 } 771 } 772 773 @VisibleForTesting 774 final PendingJobComparator mPendingJobComparator = new PendingJobComparator(); 775 776 static <T> void addOrderedItem(ArrayList<T> array, T newItem, Comparator<T> comparator) { 777 int where = Collections.binarySearch(array, newItem, comparator); 778 if (where < 0) { 779 where = ~where; 780 } 781 array.add(where, newItem); 782 } 783 784 /** 785 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we 786 * still clean up. On reinstall the package will have a new uid. 787 */ 788 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 789 @Override 790 public void onReceive(Context context, Intent intent) { 791 final String action = intent.getAction(); 792 if (DEBUG) { 793 Slog.d(TAG, "Receieved: " + action); 794 } 795 final String pkgName = getPackageName(intent); 796 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); 797 798 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { 799 // Purge the app's jobs if the whole package was just disabled. When this is 800 // the case the component name will be a bare package name. 801 if (pkgName != null && pkgUid != -1) { 802 final String[] changedComponents = intent.getStringArrayExtra( 803 Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); 804 if (changedComponents != null) { 805 for (String component : changedComponents) { 806 if (component.equals(pkgName)) { 807 if (DEBUG) { 808 Slog.d(TAG, "Package state change: " + pkgName); 809 } 810 try { 811 final int userId = UserHandle.getUserId(pkgUid); 812 IPackageManager pm = AppGlobals.getPackageManager(); 813 final int state = pm.getApplicationEnabledSetting(pkgName, userId); 814 if (state == COMPONENT_ENABLED_STATE_DISABLED 815 || state == COMPONENT_ENABLED_STATE_DISABLED_USER) { 816 if (DEBUG) { 817 Slog.d(TAG, "Removing jobs for package " + pkgName 818 + " in user " + userId); 819 } 820 synchronized (mLock) { 821 // There's no guarantee that the process has been 822 // stopped by the time we get here, but since this is 823 // a user-initiated action, it should be fine to just 824 // put USER instead of UNINSTALL or DISABLED. 825 cancelJobsForPackageAndUidLocked(pkgName, pkgUid, 826 JobParameters.STOP_REASON_USER, 827 JobParameters.INTERNAL_STOP_REASON_UNINSTALL, 828 "app disabled"); 829 } 830 } 831 } catch (RemoteException|IllegalArgumentException e) { 832 /* 833 * IllegalArgumentException means that the package doesn't exist. 834 * This arises when PACKAGE_CHANGED broadcast delivery has lagged 835 * behind outright uninstall, so by the time we try to act it's gone. 836 * We don't need to act on this PACKAGE_CHANGED when this happens; 837 * we'll get a PACKAGE_REMOVED later and clean up then. 838 * 839 * RemoteException can't actually happen; the package manager is 840 * running in this same process. 841 */ 842 } 843 break; 844 } 845 } 846 if (DEBUG) { 847 Slog.d(TAG, "Something in " + pkgName 848 + " changed. Reevaluating controller states."); 849 } 850 synchronized (mLock) { 851 for (int c = mControllers.size() - 1; c >= 0; --c) { 852 mControllers.get(c).reevaluateStateLocked(pkgUid); 853 } 854 } 855 } 856 } else { 857 Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid); 858 } 859 } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { 860 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 861 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 862 synchronized (mLock) { 863 mUidToPackageCache.remove(uid); 864 } 865 } 866 } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { 867 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); 868 if (DEBUG) { 869 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); 870 } 871 synchronized (mLock) { 872 mUidToPackageCache.remove(uidRemoved); 873 // There's no guarantee that the process has been stopped by the time we 874 // get here, but since this is generally a user-initiated action, it should 875 // be fine to just put USER instead of UNINSTALL or DISABLED. 876 cancelJobsForPackageAndUidLocked(pkgName, uidRemoved, 877 JobParameters.STOP_REASON_USER, 878 JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "app uninstalled"); 879 for (int c = 0; c < mControllers.size(); ++c) { 880 mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); 881 } 882 mDebuggableApps.remove(pkgName); 883 mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid); 884 } 885 } else if (Intent.ACTION_USER_ADDED.equals(action)) { 886 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); 887 synchronized (mLock) { 888 for (int c = 0; c < mControllers.size(); ++c) { 889 mControllers.get(c).onUserAddedLocked(userId); 890 } 891 } 892 } else if (Intent.ACTION_USER_REMOVED.equals(action)) { 893 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); 894 if (DEBUG) { 895 Slog.d(TAG, "Removing jobs for user: " + userId); 896 } 897 synchronized (mLock) { 898 mUidToPackageCache.clear(); 899 cancelJobsForUserLocked(userId); 900 for (int c = 0; c < mControllers.size(); ++c) { 901 mControllers.get(c).onUserRemovedLocked(userId); 902 } 903 } 904 mConcurrencyManager.onUserRemoved(userId); 905 } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { 906 // Has this package scheduled any jobs, such that we will take action 907 // if it were to be force-stopped? 908 if (pkgUid != -1) { 909 List<JobStatus> jobsForUid; 910 synchronized (mLock) { 911 jobsForUid = mJobs.getJobsByUid(pkgUid); 912 } 913 for (int i = jobsForUid.size() - 1; i >= 0; i--) { 914 if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) { 915 if (DEBUG) { 916 Slog.d(TAG, "Restart query: package " + pkgName + " at uid " 917 + pkgUid + " has jobs"); 918 } 919 setResultCode(Activity.RESULT_OK); 920 break; 921 } 922 } 923 } 924 } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) { 925 // possible force-stop 926 if (pkgUid != -1) { 927 if (DEBUG) { 928 Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid); 929 } 930 synchronized (mLock) { 931 cancelJobsForPackageAndUidLocked(pkgName, pkgUid, 932 JobParameters.STOP_REASON_USER, 933 JobParameters.INTERNAL_STOP_REASON_CANCELED, 934 "app force stopped"); 935 } 936 } 937 } 938 } 939 }; 940 941 private String getPackageName(Intent intent) { 942 Uri uri = intent.getData(); 943 String pkg = uri != null ? uri.getSchemeSpecificPart() : null; 944 return pkg; 945 } 946 947 final private IUidObserver mUidObserver = new IUidObserver.Stub() { 948 @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, 949 int capability) { 950 mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget(); 951 } 952 953 @Override public void onUidGone(int uid, boolean disabled) { 954 mHandler.obtainMessage(MSG_UID_GONE, uid, disabled ? 1 : 0).sendToTarget(); 955 } 956 957 @Override public void onUidActive(int uid) throws RemoteException { 958 mHandler.obtainMessage(MSG_UID_ACTIVE, uid, 0).sendToTarget(); 959 } 960 961 @Override public void onUidIdle(int uid, boolean disabled) { 962 mHandler.obtainMessage(MSG_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget(); 963 } 964 965 @Override public void onUidCachedChanged(int uid, boolean cached) { 966 } 967 }; 968 969 public Context getTestableContext() { 970 return getContext(); 971 } 972 973 public Object getLock() { 974 return mLock; 975 } 976 977 public JobStore getJobStore() { 978 return mJobs; 979 } 980 981 public Constants getConstants() { 982 return mConstants; 983 } 984 985 public boolean isChainedAttributionEnabled() { 986 return WorkSource.isChainedBatteryAttributionEnabled(getContext()); 987 } 988 989 @Nullable 990 @GuardedBy("mLock") 991 public ArraySet<String> getPackagesForUidLocked(final int uid) { 992 ArraySet<String> packages = mUidToPackageCache.get(uid); 993 if (packages == null) { 994 try { 995 String[] pkgs = AppGlobals.getPackageManager() 996 .getPackagesForUid(uid); 997 if (pkgs != null) { 998 for (String pkg : pkgs) { 999 mUidToPackageCache.add(uid, pkg); 1000 } 1001 packages = mUidToPackageCache.get(uid); 1002 } 1003 } catch (RemoteException e) { 1004 // Shouldn't happen. 1005 } 1006 } 1007 return packages; 1008 } 1009 1010 @Override 1011 public void onUserStarting(@NonNull TargetUser user) { 1012 synchronized (mLock) { 1013 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier()); 1014 } 1015 // The user is starting but credential encrypted storage is still locked. 1016 // Only direct-boot-aware jobs can safely run. 1017 // Let's kick off any eligible jobs for this user. 1018 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 1019 } 1020 1021 @Override 1022 public void onUserUnlocked(@NonNull TargetUser user) { 1023 // The user is fully unlocked and credential encrypted storage is now decrypted. 1024 // Direct-boot-UNaware jobs can now safely run. 1025 // Let's kick off any outstanding jobs for this user. 1026 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 1027 } 1028 1029 @Override 1030 public void onUserStopping(@NonNull TargetUser user) { 1031 synchronized (mLock) { 1032 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, user.getUserIdentifier()); 1033 } 1034 } 1035 1036 /** 1037 * Return whether an UID is active or idle. 1038 */ 1039 private boolean isUidActive(int uid) { 1040 return mAppStateTracker.isUidActiveSynced(uid); 1041 } 1042 1043 private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive; 1044 1045 public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, 1046 int userId, String tag) { 1047 // Rate limit excessive schedule() calls. 1048 final String servicePkg = job.getService().getPackageName(); 1049 if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) { 1050 // Only limit schedule calls for persisted jobs scheduled by the app itself. 1051 final String pkg = packageName == null ? servicePkg : packageName; 1052 if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) { 1053 if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) { 1054 // Don't log too frequently 1055 Slog.wtf(TAG, userId + "-" + pkg + " has called schedule() too many times"); 1056 mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED); 1057 } 1058 mAppStandbyInternal.restrictApp( 1059 pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY); 1060 if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) { 1061 final boolean isDebuggable; 1062 synchronized (mLock) { 1063 if (!mDebuggableApps.containsKey(packageName)) { 1064 try { 1065 final ApplicationInfo appInfo = AppGlobals.getPackageManager() 1066 .getApplicationInfo(pkg, 0, userId); 1067 if (appInfo != null) { 1068 mDebuggableApps.put(packageName, 1069 (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); 1070 } else { 1071 return JobScheduler.RESULT_FAILURE; 1072 } 1073 } catch (RemoteException e) { 1074 throw new RuntimeException(e); 1075 } 1076 } 1077 isDebuggable = mDebuggableApps.get(packageName); 1078 } 1079 if (isDebuggable) { 1080 // Only throw the exception for debuggable apps. 1081 throw new LimitExceededException( 1082 "schedule()/enqueue() called more than " 1083 + mQuotaTracker.getLimit( 1084 QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED) 1085 + " times in the past " 1086 + mQuotaTracker.getWindowSizeMs( 1087 QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED) 1088 + "ms. See the documentation for more information."); 1089 } 1090 } 1091 if (mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT) { 1092 return JobScheduler.RESULT_FAILURE; 1093 } 1094 } 1095 mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG); 1096 } 1097 1098 if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) { 1099 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString() 1100 + " -- package not allowed to start"); 1101 return JobScheduler.RESULT_FAILURE; 1102 } 1103 1104 synchronized (mLock) { 1105 final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); 1106 1107 if (work != null && toCancel != null) { 1108 // Fast path: we are adding work to an existing job, and the JobInfo is not 1109 // changing. We can just directly enqueue this work in to the job. 1110 if (toCancel.getJob().equals(job)) { 1111 1112 toCancel.enqueueWorkLocked(work); 1113 1114 // If any of work item is enqueued when the source is in the foreground, 1115 // exempt the entire job. 1116 toCancel.maybeAddForegroundExemption(mIsUidActivePredicate); 1117 1118 return JobScheduler.RESULT_SUCCESS; 1119 } 1120 } 1121 1122 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag); 1123 1124 // Return failure early if expedited job quota used up. 1125 if (jobStatus.isRequestedExpeditedJob() 1126 && !mQuotaController.isWithinEJQuotaLocked(jobStatus)) { 1127 return JobScheduler.RESULT_FAILURE; 1128 } 1129 1130 // Give exemption if the source is in the foreground just now. 1131 // Note if it's a sync job, this method is called on the handler so it's not exactly 1132 // the state when requestSync() was called, but that should be fine because of the 1133 // 1 minute foreground grace period. 1134 jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate); 1135 1136 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); 1137 // Jobs on behalf of others don't apply to the per-app job cap 1138 if (packageName == null) { 1139 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) { 1140 Slog.w(TAG, "Too many jobs for uid " + uId); 1141 throw new IllegalStateException("Apps may not schedule more than " 1142 + MAX_JOBS_PER_APP + " distinct jobs"); 1143 } 1144 } 1145 1146 // This may throw a SecurityException. 1147 jobStatus.prepareLocked(); 1148 1149 if (toCancel != null) { 1150 // Implicitly replaces the existing job record with the new instance 1151 cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP, 1152 JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app"); 1153 } else { 1154 startTrackingJobLocked(jobStatus, null); 1155 } 1156 1157 if (work != null) { 1158 // If work has been supplied, enqueue it into the new job. 1159 jobStatus.enqueueWorkLocked(work); 1160 } 1161 1162 FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, 1163 uId, null, jobStatus.getBatteryName(), 1164 FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED, 1165 JobProtoEnums.INTERNAL_STOP_REASON_UNKNOWN, jobStatus.getStandbyBucket(), 1166 jobStatus.getJobId(), 1167 jobStatus.hasChargingConstraint(), 1168 jobStatus.hasBatteryNotLowConstraint(), 1169 jobStatus.hasStorageNotLowConstraint(), 1170 jobStatus.hasTimingDelayConstraint(), 1171 jobStatus.hasDeadlineConstraint(), 1172 jobStatus.hasIdleConstraint(), 1173 jobStatus.hasConnectivityConstraint(), 1174 jobStatus.hasContentTriggerConstraint(), 1175 jobStatus.isRequestedExpeditedJob(), 1176 /* isRunningAsExpeditedJob */ false, 1177 JobProtoEnums.STOP_REASON_UNDEFINED); 1178 1179 // If the job is immediately ready to run, then we can just immediately 1180 // put it in the pending list and try to schedule it. This is especially 1181 // important for jobs with a 0 deadline constraint, since they will happen a fair 1182 // amount, we want to handle them as quickly as possible, and semantically we want to 1183 // make sure we have started holding the wake lock for the job before returning to 1184 // the caller. 1185 // If the job is not yet ready to run, there is nothing more to do -- we are 1186 // now just waiting for one of its controllers to change state and schedule 1187 // the job appropriately. 1188 if (isReadyToBeExecutedLocked(jobStatus)) { 1189 // This is a new job, we can just immediately put it on the pending 1190 // list and try to run it. 1191 mJobPackageTracker.notePending(jobStatus); 1192 addOrderedItem(mPendingJobs, jobStatus, mPendingJobComparator); 1193 maybeRunPendingJobsLocked(); 1194 } else { 1195 evaluateControllerStatesLocked(jobStatus); 1196 } 1197 } 1198 return JobScheduler.RESULT_SUCCESS; 1199 } 1200 1201 public List<JobInfo> getPendingJobs(int uid) { 1202 synchronized (mLock) { 1203 List<JobStatus> jobs = mJobs.getJobsByUid(uid); 1204 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size()); 1205 for (int i = jobs.size() - 1; i >= 0; i--) { 1206 JobStatus job = jobs.get(i); 1207 outList.add(job.getJob()); 1208 } 1209 return outList; 1210 } 1211 } 1212 1213 public JobInfo getPendingJob(int uid, int jobId) { 1214 synchronized (mLock) { 1215 List<JobStatus> jobs = mJobs.getJobsByUid(uid); 1216 for (int i = jobs.size() - 1; i >= 0; i--) { 1217 JobStatus job = jobs.get(i); 1218 if (job.getJobId() == jobId) { 1219 return job.getJob(); 1220 } 1221 } 1222 return null; 1223 } 1224 } 1225 1226 private void cancelJobsForUserLocked(int userHandle) { 1227 final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle); 1228 for (int i = 0; i < jobsForUser.size(); i++) { 1229 JobStatus toRemove = jobsForUser.get(i); 1230 // There's no guarantee that the process has been stopped by the time we get here, 1231 // but since this is a user-initiated action, it should be fine to just put USER 1232 // instead of UNINSTALL or DISABLED. 1233 cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER, 1234 JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "user removed"); 1235 } 1236 } 1237 1238 private void cancelJobsForNonExistentUsers() { 1239 UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); 1240 synchronized (mLock) { 1241 mJobs.removeJobsOfUnlistedUsers(umi.getUserIds()); 1242 } 1243 } 1244 1245 private void cancelJobsForPackageAndUidLocked(String pkgName, int uid, 1246 @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { 1247 if ("android".equals(pkgName)) { 1248 Slog.wtfStack(TAG, "Can't cancel all jobs for system package"); 1249 return; 1250 } 1251 final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid); 1252 for (int i = jobsForUid.size() - 1; i >= 0; i--) { 1253 final JobStatus job = jobsForUid.get(i); 1254 if (job.getSourcePackageName().equals(pkgName)) { 1255 cancelJobImplLocked(job, null, reason, internalReasonCode, debugReason); 1256 } 1257 } 1258 } 1259 1260 /** 1261 * Entry point from client to cancel all jobs originating from their uid. 1262 * This will remove the job from the master list, and cancel the job if it was staged for 1263 * execution or being executed. 1264 * 1265 * @param uid Uid to check against for removal of a job. 1266 */ 1267 public boolean cancelJobsForUid(int uid, @JobParameters.StopReason int reason, 1268 int internalReasonCode, String debugReason) { 1269 if (uid == Process.SYSTEM_UID) { 1270 Slog.wtfStack(TAG, "Can't cancel all jobs for system uid"); 1271 return false; 1272 } 1273 1274 boolean jobsCanceled = false; 1275 synchronized (mLock) { 1276 final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid); 1277 for (int i = 0; i < jobsForUid.size(); i++) { 1278 JobStatus toRemove = jobsForUid.get(i); 1279 cancelJobImplLocked(toRemove, null, reason, internalReasonCode, debugReason); 1280 jobsCanceled = true; 1281 } 1282 } 1283 return jobsCanceled; 1284 } 1285 1286 /** 1287 * Entry point from client to cancel the job corresponding to the jobId provided. 1288 * This will remove the job from the master list, and cancel the job if it was staged for 1289 * execution or being executed. 1290 * 1291 * @param uid Uid of the calling client. 1292 * @param jobId Id of the job, provided at schedule-time. 1293 */ 1294 private boolean cancelJob(int uid, int jobId, int callingUid, 1295 @JobParameters.StopReason int reason) { 1296 JobStatus toCancel; 1297 synchronized (mLock) { 1298 toCancel = mJobs.getJobByUidAndJobId(uid, jobId); 1299 if (toCancel != null) { 1300 cancelJobImplLocked(toCancel, null, reason, 1301 JobParameters.INTERNAL_STOP_REASON_CANCELED, 1302 "cancel() called by app, callingUid=" + callingUid 1303 + " uid=" + uid + " jobId=" + jobId); 1304 } 1305 return (toCancel != null); 1306 } 1307 } 1308 1309 /** 1310 * Cancel the given job, stopping it if it's currently executing. If {@code incomingJob} 1311 * is null, the cancelled job is removed outright from the system. If 1312 * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of 1313 * currently scheduled jobs. 1314 */ 1315 private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, 1316 @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { 1317 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); 1318 cancelled.unprepareLocked(); 1319 stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */); 1320 // Remove from pending queue. 1321 if (mPendingJobs.remove(cancelled)) { 1322 mJobPackageTracker.noteNonpending(cancelled); 1323 } 1324 // Cancel if running. 1325 stopJobOnServiceContextLocked(cancelled, reason, internalReasonCode, debugReason); 1326 // If this is a replacement, bring in the new version of the job 1327 if (incomingJob != null) { 1328 if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString()); 1329 startTrackingJobLocked(incomingJob, cancelled); 1330 } 1331 reportActiveLocked(); 1332 } 1333 1334 void updateUidState(int uid, int procState) { 1335 synchronized (mLock) { 1336 final int prevPriority = mUidPriorityOverride.get(uid, JobInfo.PRIORITY_DEFAULT); 1337 if (procState == ActivityManager.PROCESS_STATE_TOP) { 1338 // Only use this if we are exactly the top app. All others can live 1339 // with just the foreground priority. This means that persistent processes 1340 // can never be the top app priority... that is fine. 1341 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP); 1342 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 1343 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_SERVICE); 1344 } else if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { 1345 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE); 1346 } else { 1347 mUidPriorityOverride.delete(uid); 1348 } 1349 final int newPriority = mUidPriorityOverride.get(uid, JobInfo.PRIORITY_DEFAULT); 1350 if (prevPriority != newPriority) { 1351 if (DEBUG) { 1352 Slog.d(TAG, "UID " + uid + " priority changed from " + prevPriority 1353 + " to " + newPriority); 1354 } 1355 for (int c = 0; c < mControllers.size(); ++c) { 1356 mControllers.get(c).onUidPriorityChangedLocked(uid, newPriority); 1357 } 1358 } 1359 } 1360 } 1361 1362 @Override 1363 public void onDeviceIdleStateChanged(boolean deviceIdle) { 1364 synchronized (mLock) { 1365 if (DEBUG) { 1366 Slog.d(TAG, "Doze state changed: " + deviceIdle); 1367 } 1368 if (deviceIdle) { 1369 // When becoming idle, make sure no jobs are actively running, 1370 // except those using the idle exemption flag. 1371 for (int i=0; i<mActiveServices.size(); i++) { 1372 JobServiceContext jsc = mActiveServices.get(i); 1373 final JobStatus executing = jsc.getRunningJobLocked(); 1374 if (executing != null && !executing.canRunInDoze()) { 1375 jsc.cancelExecutingJobLocked(JobParameters.STOP_REASON_DEVICE_STATE, 1376 JobParameters.INTERNAL_STOP_REASON_DEVICE_IDLE, 1377 "cancelled due to doze"); 1378 } 1379 } 1380 } else { 1381 // When coming out of idle, allow thing to start back up. 1382 if (mReadyToRock) { 1383 if (mLocalDeviceIdleController != null) { 1384 if (!mReportedActive) { 1385 mReportedActive = true; 1386 mLocalDeviceIdleController.setJobsActive(true); 1387 } 1388 } 1389 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 1390 } 1391 } 1392 } 1393 } 1394 1395 @Override 1396 public void onRestrictedBucketChanged(List<JobStatus> jobs) { 1397 final int len = jobs.size(); 1398 if (len == 0) { 1399 Slog.wtf(TAG, "onRestrictedBucketChanged called with no jobs"); 1400 return; 1401 } 1402 synchronized (mLock) { 1403 for (int i = 0; i < len; ++i) { 1404 JobStatus js = jobs.get(i); 1405 for (int j = mRestrictiveControllers.size() - 1; j >= 0; --j) { 1406 // Effective standby bucket can change after this in some situations so use 1407 // the real bucket so that the job is tracked by the controllers. 1408 if (js.getStandbyBucket() == RESTRICTED_INDEX) { 1409 mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js); 1410 } else { 1411 mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js); 1412 } 1413 } 1414 } 1415 } 1416 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 1417 } 1418 1419 void reportActiveLocked() { 1420 // active is true if pending queue contains jobs OR some job is running. 1421 boolean active = mPendingJobs.size() > 0; 1422 if (mPendingJobs.size() <= 0) { 1423 for (int i=0; i<mActiveServices.size(); i++) { 1424 final JobServiceContext jsc = mActiveServices.get(i); 1425 final JobStatus job = jsc.getRunningJobLocked(); 1426 if (job != null 1427 && !job.canRunInDoze() 1428 && !job.dozeWhitelisted 1429 && !job.uidActive) { 1430 // We will report active if we have a job running and it is not an exception 1431 // due to being in the foreground or whitelisted. 1432 active = true; 1433 break; 1434 } 1435 } 1436 } 1437 1438 if (mReportedActive != active) { 1439 mReportedActive = active; 1440 if (mLocalDeviceIdleController != null) { 1441 mLocalDeviceIdleController.setJobsActive(active); 1442 } 1443 } 1444 } 1445 1446 void reportAppUsage(String packageName, int userId) { 1447 // This app just transitioned into interactive use or near equivalent, so we should 1448 // take a look at its job state for feedback purposes. 1449 } 1450 1451 /** 1452 * Initializes the system service. 1453 * <p> 1454 * Subclasses must define a single argument constructor that accepts the context 1455 * and passes it to super. 1456 * </p> 1457 * 1458 * @param context The system server context. 1459 */ 1460 public JobSchedulerService(Context context) { 1461 super(context); 1462 1463 mLocalPM = LocalServices.getService(PackageManagerInternal.class); 1464 mActivityManagerInternal = Objects.requireNonNull( 1465 LocalServices.getService(ActivityManagerInternal.class)); 1466 1467 mHandler = new JobHandler(context.getMainLooper()); 1468 mConstants = new Constants(); 1469 mConstantsObserver = new ConstantsObserver(); 1470 mJobSchedulerStub = new JobSchedulerStub(); 1471 1472 mConcurrencyManager = new JobConcurrencyManager(this); 1473 1474 // Set up the app standby bucketing tracker 1475 mStandbyTracker = new StandbyTracker(); 1476 mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); 1477 mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER); 1478 mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED, 1479 mConstants.API_QUOTA_SCHEDULE_COUNT, 1480 mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); 1481 // Log at most once per minute. 1482 mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000); 1483 1484 mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class); 1485 mAppStandbyInternal.addListener(mStandbyTracker); 1486 1487 // The job store needs to call back 1488 publishLocalService(JobSchedulerInternal.class, new LocalService()); 1489 1490 // Initialize the job store and set up any persisted jobs 1491 mJobs = JobStore.initAndGet(this); 1492 1493 // Create the controllers. 1494 mControllers = new ArrayList<StateController>(); 1495 final ConnectivityController connectivityController = new ConnectivityController(this); 1496 mControllers.add(connectivityController); 1497 mControllers.add(new TimeController(this)); 1498 final IdleController idleController = new IdleController(this); 1499 mControllers.add(idleController); 1500 mBatteryController = new BatteryController(this); 1501 mControllers.add(mBatteryController); 1502 mStorageController = new StorageController(this); 1503 mControllers.add(mStorageController); 1504 final BackgroundJobsController backgroundJobsController = 1505 new BackgroundJobsController(this); 1506 mControllers.add(backgroundJobsController); 1507 mControllers.add(new ContentObserverController(this)); 1508 mDeviceIdleJobsController = new DeviceIdleJobsController(this); 1509 mControllers.add(mDeviceIdleJobsController); 1510 mQuotaController = 1511 new QuotaController(this, backgroundJobsController, connectivityController); 1512 mControllers.add(mQuotaController); 1513 mControllers.add(new ComponentController(this)); 1514 1515 mRestrictiveControllers = new ArrayList<>(); 1516 mRestrictiveControllers.add(mBatteryController); 1517 mRestrictiveControllers.add(connectivityController); 1518 mRestrictiveControllers.add(idleController); 1519 1520 // Create restrictions 1521 mJobRestrictions = new ArrayList<>(); 1522 mJobRestrictions.add(new ThermalStatusRestriction(this)); 1523 1524 mSystemGalleryPackage = Objects.requireNonNull( 1525 context.getString(R.string.config_systemGallery)); 1526 1527 // If the job store determined that it can't yet reschedule persisted jobs, 1528 // we need to start watching the clock. 1529 if (!mJobs.jobTimesInflatedValid()) { 1530 Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling"); 1531 context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED)); 1532 } 1533 } 1534 1535 private final BroadcastReceiver mTimeSetReceiver = new BroadcastReceiver() { 1536 @Override 1537 public void onReceive(Context context, Intent intent) { 1538 if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) { 1539 // When we reach clock sanity, recalculate the temporal windows 1540 // of all affected jobs. 1541 if (mJobs.clockNowValidToInflate(sSystemClock.millis())) { 1542 Slog.i(TAG, "RTC now valid; recalculating persisted job windows"); 1543 1544 // We've done our job now, so stop watching the time. 1545 context.unregisterReceiver(this); 1546 1547 // And kick off the work to update the affected jobs, using a secondary 1548 // thread instead of chugging away here on the main looper thread. 1549 new Thread(mJobTimeUpdater, "JobSchedulerTimeSetReceiver").start(); 1550 } 1551 } 1552 } 1553 }; 1554 1555 private final Runnable mJobTimeUpdater = () -> { 1556 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); 1557 1558 final ArrayList<JobStatus> toRemove = new ArrayList<>(); 1559 final ArrayList<JobStatus> toAdd = new ArrayList<>(); 1560 synchronized (mLock) { 1561 // Note: we intentionally both look up the existing affected jobs and replace them 1562 // with recalculated ones inside the same lock lifetime. 1563 getJobStore().getRtcCorrectedJobsLocked(toAdd, toRemove); 1564 1565 // Now, at each position [i], we have both the existing JobStatus 1566 // and the one that replaces it. 1567 final int N = toAdd.size(); 1568 for (int i = 0; i < N; i++) { 1569 final JobStatus oldJob = toRemove.get(i); 1570 final JobStatus newJob = toAdd.get(i); 1571 if (DEBUG) { 1572 Slog.v(TAG, " replacing " + oldJob + " with " + newJob); 1573 } 1574 cancelJobImplLocked(oldJob, newJob, JobParameters.STOP_REASON_SYSTEM_PROCESSING, 1575 JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED, "deferred rtc calculation"); 1576 } 1577 } 1578 }; 1579 1580 @Override 1581 public void onStart() { 1582 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub); 1583 } 1584 1585 @Override 1586 public void onBootPhase(int phase) { 1587 if (PHASE_SYSTEM_SERVICES_READY == phase) { 1588 mConstantsObserver.start(); 1589 for (StateController controller : mControllers) { 1590 controller.onSystemServicesReady(); 1591 } 1592 1593 mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull( 1594 LocalServices.getService(AppStateTracker.class)); 1595 1596 // Register br for package removals and user removals. 1597 final IntentFilter filter = new IntentFilter(); 1598 filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); 1599 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 1600 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 1601 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); 1602 filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); 1603 filter.addDataScheme("package"); 1604 getContext().registerReceiverAsUser( 1605 mBroadcastReceiver, UserHandle.ALL, filter, null, null); 1606 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); 1607 userFilter.addAction(Intent.ACTION_USER_ADDED); 1608 getContext().registerReceiverAsUser( 1609 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); 1610 try { 1611 ActivityManager.getService().registerUidObserver(mUidObserver, 1612 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE 1613 | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE, 1614 ActivityManager.PROCESS_STATE_UNKNOWN, null); 1615 } catch (RemoteException e) { 1616 // ignored; both services live in system_server 1617 } 1618 1619 mConcurrencyManager.onSystemReady(); 1620 1621 // Remove any jobs that are not associated with any of the current users. 1622 cancelJobsForNonExistentUsers(); 1623 1624 for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { 1625 mJobRestrictions.get(i).onSystemServicesReady(); 1626 } 1627 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 1628 synchronized (mLock) { 1629 // Let's go! 1630 mReadyToRock = true; 1631 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( 1632 BatteryStats.SERVICE_NAME)); 1633 mLocalDeviceIdleController = 1634 LocalServices.getService(DeviceIdleInternal.class); 1635 // Create the "runners". 1636 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { 1637 mActiveServices.add( 1638 new JobServiceContext(this, mConcurrencyManager, mBatteryStats, 1639 mJobPackageTracker, getContext().getMainLooper())); 1640 } 1641 // Attach jobs to their controllers. 1642 mJobs.forEachJob((job) -> { 1643 for (int controller = 0; controller < mControllers.size(); controller++) { 1644 final StateController sc = mControllers.get(controller); 1645 sc.maybeStartTrackingJobLocked(job, null); 1646 } 1647 }); 1648 // GO GO GO! 1649 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 1650 } 1651 } 1652 } 1653 1654 /** 1655 * Called when we have a job status object that we need to insert in our 1656 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know 1657 * about. 1658 */ 1659 private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { 1660 if (!jobStatus.isPreparedLocked()) { 1661 Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus); 1662 } 1663 jobStatus.enqueueTime = sElapsedRealtimeClock.millis(); 1664 final boolean update = mJobs.add(jobStatus); 1665 if (mReadyToRock) { 1666 for (int i = 0; i < mControllers.size(); i++) { 1667 StateController controller = mControllers.get(i); 1668 if (update) { 1669 controller.maybeStopTrackingJobLocked(jobStatus, null, true); 1670 } 1671 controller.maybeStartTrackingJobLocked(jobStatus, lastJob); 1672 } 1673 } 1674 } 1675 1676 /** 1677 * Called when we want to remove a JobStatus object that we've finished executing. 1678 * @return true if the job was removed. 1679 */ 1680 private boolean stopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, 1681 boolean removeFromPersisted) { 1682 // Deal with any remaining work items in the old job. 1683 jobStatus.stopTrackingJobLocked(incomingJob); 1684 1685 // Remove from store as well as controllers. 1686 final boolean removed = mJobs.remove(jobStatus, removeFromPersisted); 1687 if (removed && mReadyToRock) { 1688 for (int i=0; i<mControllers.size(); i++) { 1689 StateController controller = mControllers.get(i); 1690 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false); 1691 } 1692 } 1693 return removed; 1694 } 1695 1696 private boolean stopJobOnServiceContextLocked(JobStatus job, 1697 @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { 1698 for (int i = 0; i < mActiveServices.size(); i++) { 1699 JobServiceContext jsc = mActiveServices.get(i); 1700 final JobStatus executing = jsc.getRunningJobLocked(); 1701 if (executing != null && executing.matches(job.getUid(), job.getJobId())) { 1702 jsc.cancelExecutingJobLocked(reason, internalReasonCode, debugReason); 1703 return true; 1704 } 1705 } 1706 return false; 1707 } 1708 1709 void noteJobsPending(List<JobStatus> jobs) { 1710 for (int i = jobs.size() - 1; i >= 0; i--) { 1711 JobStatus job = jobs.get(i); 1712 mJobPackageTracker.notePending(job); 1713 } 1714 } 1715 1716 void noteJobsNonpending(List<JobStatus> jobs) { 1717 for (int i = jobs.size() - 1; i >= 0; i--) { 1718 JobStatus job = jobs.get(i); 1719 mJobPackageTracker.noteNonpending(job); 1720 } 1721 } 1722 1723 /** 1724 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to 1725 * specify an override deadline on a failed job (the failed job will run even though it's not 1726 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any 1727 * ready job with {@link JobStatus#getNumFailures()} > 0 will be executed. 1728 * 1729 * @param failureToReschedule Provided job status that we will reschedule. 1730 * @return A newly instantiated JobStatus with the same constraints as the last job except 1731 * with adjusted timing constraints. 1732 * 1733 * @see #maybeQueueReadyJobsForExecutionLocked 1734 */ 1735 @VisibleForTesting 1736 JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) { 1737 final long elapsedNowMillis = sElapsedRealtimeClock.millis(); 1738 final JobInfo job = failureToReschedule.getJob(); 1739 1740 final long initialBackoffMillis = job.getInitialBackoffMillis(); 1741 final int backoffAttempts = failureToReschedule.getNumFailures() + 1; 1742 long delayMillis; 1743 1744 switch (job.getBackoffPolicy()) { 1745 case JobInfo.BACKOFF_POLICY_LINEAR: { 1746 long backoff = initialBackoffMillis; 1747 if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) { 1748 backoff = mConstants.MIN_LINEAR_BACKOFF_TIME_MS; 1749 } 1750 delayMillis = backoff * backoffAttempts; 1751 } break; 1752 default: 1753 if (DEBUG) { 1754 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential."); 1755 } 1756 case JobInfo.BACKOFF_POLICY_EXPONENTIAL: { 1757 long backoff = initialBackoffMillis; 1758 if (backoff < mConstants.MIN_EXP_BACKOFF_TIME_MS) { 1759 backoff = mConstants.MIN_EXP_BACKOFF_TIME_MS; 1760 } 1761 delayMillis = (long) Math.scalb(backoff, backoffAttempts - 1); 1762 } break; 1763 } 1764 delayMillis = 1765 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); 1766 JobStatus newJob = new JobStatus(failureToReschedule, 1767 elapsedNowMillis + delayMillis, 1768 JobStatus.NO_LATEST_RUNTIME, backoffAttempts, 1769 failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis()); 1770 if (job.isPeriodic()) { 1771 newJob.setOriginalLatestRunTimeElapsed( 1772 failureToReschedule.getOriginalLatestRunTimeElapsed()); 1773 } 1774 for (int ic=0; ic<mControllers.size(); ic++) { 1775 StateController controller = mControllers.get(ic); 1776 controller.rescheduleForFailureLocked(newJob, failureToReschedule); 1777 } 1778 return newJob; 1779 } 1780 1781 /** 1782 * Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This 1783 * does not cause a job's period to be larger than requested (eg: if the requested period is 1784 * shorter than this buffer). This is used to put a limit on when JobScheduler will intervene 1785 * and try to optimize scheduling if the current job finished less than this amount of time to 1786 * the start of the next period 1787 */ 1788 private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS; 1789 1790 /** The maximum period a periodic job can have. Anything higher will be clamped down to this. */ 1791 public static final long MAX_ALLOWED_PERIOD_MS = 365 * 24 * 60 * 60 * 1000L; 1792 1793 /** 1794 * Called after a periodic has executed so we can reschedule it. We take the last execution 1795 * time of the job to be the time of completion (i.e. the time at which this function is 1796 * called). 1797 * <p>This could be inaccurate b/c the job can run for as long as 1798 * {@link Constants#DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS}, but 1799 * will lead to underscheduling at least, rather than if we had taken the last execution time 1800 * to be the start of the execution. 1801 * 1802 * @return A new job representing the execution criteria for this instantiation of the 1803 * recurring job. 1804 */ 1805 @VisibleForTesting 1806 JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) { 1807 final long elapsedNow = sElapsedRealtimeClock.millis(); 1808 final long newLatestRuntimeElapsed; 1809 // Make sure period is in the interval [min_possible_period, max_possible_period]. 1810 final long period = Math.max(JobInfo.getMinPeriodMillis(), 1811 Math.min(MAX_ALLOWED_PERIOD_MS, periodicToReschedule.getJob().getIntervalMillis())); 1812 // Make sure flex is in the interval [min_possible_flex, period]. 1813 final long flex = Math.max(JobInfo.getMinFlexMillis(), 1814 Math.min(period, periodicToReschedule.getJob().getFlexMillis())); 1815 long rescheduleBuffer = 0; 1816 1817 long olrte = periodicToReschedule.getOriginalLatestRunTimeElapsed(); 1818 if (olrte < 0 || olrte == JobStatus.NO_LATEST_RUNTIME) { 1819 Slog.wtf(TAG, "Invalid periodic job original latest run time: " + olrte); 1820 olrte = elapsedNow; 1821 } 1822 final long latestRunTimeElapsed = olrte; 1823 1824 final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed); 1825 if (elapsedNow > latestRunTimeElapsed) { 1826 // The job ran past its expected run window. Have it count towards the current window 1827 // and schedule a new job for the next window. 1828 if (DEBUG) { 1829 Slog.i(TAG, "Periodic job ran after its intended window."); 1830 } 1831 long numSkippedWindows = (diffMs / period) + 1; // +1 to include original window 1832 if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER, 1833 (period - flex) / 2)) { 1834 if (DEBUG) { 1835 Slog.d(TAG, "Custom flex job ran too close to next window."); 1836 } 1837 // For custom flex periods, if the job was run too close to the next window, 1838 // skip the next window and schedule for the following one. 1839 numSkippedWindows += 1; 1840 } 1841 newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows); 1842 } else { 1843 newLatestRuntimeElapsed = latestRunTimeElapsed + period; 1844 if (diffMs < PERIODIC_JOB_WINDOW_BUFFER && diffMs < period / 6) { 1845 // Add a little buffer to the start of the next window so the job doesn't run 1846 // too soon after this completed one. 1847 rescheduleBuffer = Math.min(PERIODIC_JOB_WINDOW_BUFFER, period / 6 - diffMs); 1848 } 1849 } 1850 1851 if (newLatestRuntimeElapsed < elapsedNow) { 1852 Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: " 1853 + newLatestRuntimeElapsed); 1854 return new JobStatus(periodicToReschedule, 1855 elapsedNow + period - flex, elapsedNow + period, 1856 0 /* backoffAttempt */, 1857 sSystemClock.millis() /* lastSuccessfulRunTime */, 1858 periodicToReschedule.getLastFailedRunTime()); 1859 } 1860 1861 final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed 1862 - Math.min(flex, period - rescheduleBuffer); 1863 1864 if (DEBUG) { 1865 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" + 1866 newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000 1867 + "]s"); 1868 } 1869 return new JobStatus(periodicToReschedule, 1870 newEarliestRunTimeElapsed, newLatestRuntimeElapsed, 1871 0 /* backoffAttempt */, 1872 sSystemClock.millis() /* lastSuccessfulRunTime */, 1873 periodicToReschedule.getLastFailedRunTime()); 1874 } 1875 1876 // JobCompletedListener implementations. 1877 1878 /** 1879 * A job just finished executing. We fetch the 1880 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on 1881 * whether we want to reschedule we re-add it to the controllers. 1882 * 1883 * @param jobStatus Completed job. 1884 * @param needsReschedule Whether the implementing class should reschedule this job. 1885 */ 1886 @Override 1887 public void onJobCompletedLocked(JobStatus jobStatus, int debugStopReason, 1888 boolean needsReschedule) { 1889 if (DEBUG) { 1890 Slog.d(TAG, "Completed " + jobStatus + ", reason=" + debugStopReason 1891 + ", reschedule=" + needsReschedule); 1892 } 1893 1894 mLastCompletedJobs[mLastCompletedJobIndex] = jobStatus; 1895 mLastCompletedJobTimeElapsed[mLastCompletedJobIndex] = sElapsedRealtimeClock.millis(); 1896 mLastCompletedJobIndex = (mLastCompletedJobIndex + 1) % NUM_COMPLETED_JOB_HISTORY; 1897 1898 if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_UNINSTALL 1899 || debugStopReason == JobParameters.INTERNAL_STOP_REASON_DATA_CLEARED) { 1900 // The job should have already been cleared from the rest of the JS tracking. No need 1901 // to go through all that flow again. 1902 jobStatus.unprepareLocked(); 1903 reportActiveLocked(); 1904 return; 1905 } 1906 1907 // Intentionally not checking expedited job quota here. An app can't find out if it's run 1908 // out of quota when it asks JS to reschedule an expedited job. Instead, the rescheduled 1909 // EJ will just be demoted to a regular job if the app has no EJ quota left. 1910 1911 // If the job wants to be rescheduled, we first need to make the next upcoming 1912 // job so we can transfer any appropriate state over from the previous job when 1913 // we stop it. 1914 final JobStatus rescheduledJob = needsReschedule 1915 ? getRescheduleJobForFailureLocked(jobStatus) : null; 1916 if (rescheduledJob != null 1917 && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT 1918 || debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) { 1919 rescheduledJob.disallowRunInBatterySaverAndDoze(); 1920 } 1921 1922 // Do not write back immediately if this is a periodic job. The job may get lost if system 1923 // shuts down before it is added back. 1924 if (!stopTrackingJobLocked(jobStatus, rescheduledJob, !jobStatus.getJob().isPeriodic())) { 1925 if (DEBUG) { 1926 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); 1927 } 1928 JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId()); 1929 if (newJs != null) { 1930 // This job was stopped because the app scheduled a new job with the same job ID. 1931 // Check if the new job is ready to run. 1932 mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, newJs).sendToTarget(); 1933 } 1934 return; 1935 } 1936 1937 if (rescheduledJob != null) { 1938 try { 1939 rescheduledJob.prepareLocked(); 1940 } catch (SecurityException e) { 1941 Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledJob); 1942 } 1943 startTrackingJobLocked(rescheduledJob, jobStatus); 1944 } else if (jobStatus.getJob().isPeriodic()) { 1945 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus); 1946 try { 1947 rescheduledPeriodic.prepareLocked(); 1948 } catch (SecurityException e) { 1949 Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledPeriodic); 1950 } 1951 startTrackingJobLocked(rescheduledPeriodic, jobStatus); 1952 } 1953 jobStatus.unprepareLocked(); 1954 reportActiveLocked(); 1955 } 1956 1957 // StateChangedListener implementations. 1958 1959 /** 1960 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that 1961 * some controller's state has changed, so as to run through the list of jobs and start/stop 1962 * any that are eligible. 1963 */ 1964 @Override 1965 public void onControllerStateChanged() { 1966 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 1967 } 1968 1969 @Override 1970 public void onRunJobNow(JobStatus jobStatus) { 1971 if (jobStatus == null) { 1972 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); 1973 } else { 1974 mHandler.obtainMessage(MSG_CHECK_INDIVIDUAL_JOB, jobStatus).sendToTarget(); 1975 } 1976 } 1977 1978 final private class JobHandler extends Handler { 1979 1980 public JobHandler(Looper looper) { 1981 super(looper); 1982 } 1983 1984 @Override 1985 public void handleMessage(Message message) { 1986 synchronized (mLock) { 1987 if (!mReadyToRock) { 1988 return; 1989 } 1990 switch (message.what) { 1991 case MSG_CHECK_INDIVIDUAL_JOB: { 1992 JobStatus js = (JobStatus) message.obj; 1993 if (js != null) { 1994 if (isReadyToBeExecutedLocked(js)) { 1995 mJobPackageTracker.notePending(js); 1996 addOrderedItem(mPendingJobs, js, mPendingJobComparator); 1997 } 1998 } else { 1999 Slog.e(TAG, "Given null job to check individually"); 2000 } 2001 } break; 2002 case MSG_CHECK_JOB: 2003 if (DEBUG) { 2004 Slog.d(TAG, "MSG_CHECK_JOB"); 2005 } 2006 removeMessages(MSG_CHECK_JOB); 2007 if (mReportedActive) { 2008 // if jobs are currently being run, queue all ready jobs for execution. 2009 queueReadyJobsForExecutionLocked(); 2010 } else { 2011 // Check the list of jobs and run some of them if we feel inclined. 2012 maybeQueueReadyJobsForExecutionLocked(); 2013 } 2014 break; 2015 case MSG_CHECK_JOB_GREEDY: 2016 if (DEBUG) { 2017 Slog.d(TAG, "MSG_CHECK_JOB_GREEDY"); 2018 } 2019 queueReadyJobsForExecutionLocked(); 2020 break; 2021 case MSG_STOP_JOB: 2022 cancelJobImplLocked((JobStatus) message.obj, null, message.arg1, 2023 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, 2024 "app no longer allowed to run"); 2025 break; 2026 2027 case MSG_UID_STATE_CHANGED: { 2028 final int uid = message.arg1; 2029 final int procState = message.arg2; 2030 updateUidState(uid, procState); 2031 break; 2032 } 2033 case MSG_UID_GONE: { 2034 final int uid = message.arg1; 2035 final boolean disabled = message.arg2 != 0; 2036 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY); 2037 if (disabled) { 2038 cancelJobsForUid(uid, 2039 JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 2040 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, 2041 "uid gone"); 2042 } 2043 synchronized (mLock) { 2044 mDeviceIdleJobsController.setUidActiveLocked(uid, false); 2045 } 2046 break; 2047 } 2048 case MSG_UID_ACTIVE: { 2049 final int uid = message.arg1; 2050 synchronized (mLock) { 2051 mDeviceIdleJobsController.setUidActiveLocked(uid, true); 2052 } 2053 break; 2054 } 2055 case MSG_UID_IDLE: { 2056 final int uid = message.arg1; 2057 final boolean disabled = message.arg2 != 0; 2058 if (disabled) { 2059 cancelJobsForUid(uid, 2060 JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 2061 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, 2062 "app uid idle"); 2063 } 2064 synchronized (mLock) { 2065 mDeviceIdleJobsController.setUidActiveLocked(uid, false); 2066 } 2067 break; 2068 } 2069 2070 } 2071 maybeRunPendingJobsLocked(); 2072 } 2073 } 2074 } 2075 2076 /** 2077 * Check if a job is restricted by any of the declared {@link JobRestriction}s. 2078 * Note, that the jobs with {@link JobInfo#PRIORITY_FOREGROUND_APP} priority or higher may not 2079 * be restricted, thus we won't even perform the check, but simply return null early. 2080 * 2081 * @param job to be checked 2082 * @return the first {@link JobRestriction} restricting the given job that has been found; null 2083 * - if passes all the restrictions or has priority {@link JobInfo#PRIORITY_FOREGROUND_APP} 2084 * or higher. 2085 */ 2086 private JobRestriction checkIfRestricted(JobStatus job) { 2087 if (evaluateJobPriorityLocked(job) >= JobInfo.PRIORITY_FOREGROUND_APP) { 2088 // Jobs with PRIORITY_FOREGROUND_APP or higher should not be restricted 2089 return null; 2090 } 2091 for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { 2092 final JobRestriction restriction = mJobRestrictions.get(i); 2093 if (restriction.isJobRestricted(job)) { 2094 return restriction; 2095 } 2096 } 2097 return null; 2098 } 2099 2100 private void stopNonReadyActiveJobsLocked() { 2101 for (int i=0; i<mActiveServices.size(); i++) { 2102 JobServiceContext serviceContext = mActiveServices.get(i); 2103 final JobStatus running = serviceContext.getRunningJobLocked(); 2104 if (running == null) { 2105 continue; 2106 } 2107 if (!running.isReady()) { 2108 // If a restricted job doesn't have dynamic constraints satisfied, assume that's 2109 // the reason the job is being stopped, instead of because of other constraints 2110 // not being satisfied. 2111 if (running.getEffectiveStandbyBucket() == RESTRICTED_INDEX 2112 && !running.areDynamicConstraintsSatisfied()) { 2113 serviceContext.cancelExecutingJobLocked( 2114 running.getStopReason(), 2115 JobParameters.INTERNAL_STOP_REASON_RESTRICTED_BUCKET, 2116 "cancelled due to restricted bucket"); 2117 } else { 2118 serviceContext.cancelExecutingJobLocked( 2119 running.getStopReason(), 2120 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED, 2121 "cancelled due to unsatisfied constraints"); 2122 } 2123 } else { 2124 final JobRestriction restriction = checkIfRestricted(running); 2125 if (restriction != null) { 2126 final int internalReasonCode = restriction.getInternalReason(); 2127 serviceContext.cancelExecutingJobLocked(restriction.getReason(), 2128 internalReasonCode, 2129 "restricted due to " 2130 + JobParameters.getInternalReasonCodeDescription( 2131 internalReasonCode)); 2132 } 2133 } 2134 } 2135 } 2136 2137 /** 2138 * Run through list of jobs and execute all possible - at least one is expired so we do 2139 * as many as we can. 2140 */ 2141 @GuardedBy("mLock") 2142 private void queueReadyJobsForExecutionLocked() { 2143 // This method will check and capture all ready jobs, so we don't need to keep any messages 2144 // in the queue. 2145 mHandler.removeMessages(MSG_CHECK_JOB_GREEDY); 2146 mHandler.removeMessages(MSG_CHECK_INDIVIDUAL_JOB); 2147 // MSG_CHECK_JOB is a weaker form of _GREEDY. Since we're checking and queueing all ready 2148 // jobs, we don't need to keep any MSG_CHECK_JOB messages in the queue. 2149 mHandler.removeMessages(MSG_CHECK_JOB); 2150 if (DEBUG) { 2151 Slog.d(TAG, "queuing all ready jobs for execution:"); 2152 } 2153 noteJobsNonpending(mPendingJobs); 2154 mPendingJobs.clear(); 2155 stopNonReadyActiveJobsLocked(); 2156 mJobs.forEachJob(mReadyQueueFunctor); 2157 mReadyQueueFunctor.postProcessLocked(); 2158 2159 if (DEBUG) { 2160 final int queuedJobs = mPendingJobs.size(); 2161 if (queuedJobs == 0) { 2162 Slog.d(TAG, "No jobs pending."); 2163 } else { 2164 Slog.d(TAG, queuedJobs + " jobs queued."); 2165 } 2166 } 2167 } 2168 2169 final class ReadyJobQueueFunctor implements Consumer<JobStatus> { 2170 final ArrayList<JobStatus> newReadyJobs = new ArrayList<>(); 2171 2172 @Override 2173 public void accept(JobStatus job) { 2174 if (isReadyToBeExecutedLocked(job)) { 2175 if (DEBUG) { 2176 Slog.d(TAG, " queued " + job.toShortString()); 2177 } 2178 newReadyJobs.add(job); 2179 } else { 2180 evaluateControllerStatesLocked(job); 2181 } 2182 } 2183 2184 @GuardedBy("mLock") 2185 private void postProcessLocked() { 2186 noteJobsPending(newReadyJobs); 2187 mPendingJobs.addAll(newReadyJobs); 2188 if (mPendingJobs.size() > 1) { 2189 mPendingJobComparator.refreshLocked(); 2190 mPendingJobs.sort(mPendingJobComparator); 2191 } 2192 2193 newReadyJobs.clear(); 2194 } 2195 } 2196 2197 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor(); 2198 2199 /** 2200 * The state of at least one job has changed. Here is where we could enforce various 2201 * policies on when we want to execute jobs. 2202 */ 2203 final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> { 2204 int forceBatchedCount; 2205 int unbatchedCount; 2206 final List<JobStatus> runnableJobs = new ArrayList<>(); 2207 2208 public MaybeReadyJobQueueFunctor() { 2209 reset(); 2210 } 2211 2212 // Functor method invoked for each job via JobStore.forEachJob() 2213 @Override 2214 public void accept(JobStatus job) { 2215 if (isReadyToBeExecutedLocked(job)) { 2216 if (mActivityManagerInternal.isAppStartModeDisabled(job.getUid(), 2217 job.getJob().getService().getPackageName())) { 2218 Slog.w(TAG, "Aborting job " + job.getUid() + ":" 2219 + job.getJob().toString() + " -- package not allowed to start"); 2220 mHandler.obtainMessage(MSG_STOP_JOB, 2221 JobParameters.STOP_REASON_BACKGROUND_RESTRICTION, 0, job) 2222 .sendToTarget(); 2223 return; 2224 } 2225 2226 final boolean shouldForceBatchJob; 2227 if (job.shouldTreatAsExpeditedJob()) { 2228 // Never batch expedited jobs, even for RESTRICTED apps. 2229 shouldForceBatchJob = false; 2230 } else if (job.getEffectiveStandbyBucket() == RESTRICTED_INDEX) { 2231 // Restricted jobs must always be batched 2232 shouldForceBatchJob = true; 2233 } else if (job.getNumFailures() > 0) { 2234 shouldForceBatchJob = false; 2235 } else { 2236 final long nowElapsed = sElapsedRealtimeClock.millis(); 2237 final boolean batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0 2238 && nowElapsed - job.getFirstForceBatchedTimeElapsed() 2239 >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS; 2240 shouldForceBatchJob = 2241 mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1 2242 && job.getEffectiveStandbyBucket() != ACTIVE_INDEX 2243 && !batchDelayExpired; 2244 } 2245 2246 if (shouldForceBatchJob) { 2247 // Force batching non-ACTIVE jobs. Don't include them in the other counts. 2248 forceBatchedCount++; 2249 if (job.getFirstForceBatchedTimeElapsed() == 0) { 2250 job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis()); 2251 } 2252 } else { 2253 unbatchedCount++; 2254 } 2255 runnableJobs.add(job); 2256 } else { 2257 evaluateControllerStatesLocked(job); 2258 } 2259 } 2260 2261 @GuardedBy("mLock") 2262 @VisibleForTesting 2263 void postProcessLocked() { 2264 if (unbatchedCount > 0 2265 || forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT) { 2266 if (DEBUG) { 2267 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs."); 2268 } 2269 noteJobsPending(runnableJobs); 2270 mPendingJobs.addAll(runnableJobs); 2271 if (mPendingJobs.size() > 1) { 2272 mPendingJobComparator.refreshLocked(); 2273 mPendingJobs.sort(mPendingJobComparator); 2274 } 2275 } else { 2276 if (DEBUG) { 2277 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything."); 2278 } 2279 } 2280 2281 // Be ready for next time 2282 reset(); 2283 } 2284 2285 @VisibleForTesting 2286 void reset() { 2287 forceBatchedCount = 0; 2288 unbatchedCount = 0; 2289 runnableJobs.clear(); 2290 } 2291 } 2292 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor(); 2293 2294 @GuardedBy("mLock") 2295 private void maybeQueueReadyJobsForExecutionLocked() { 2296 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs..."); 2297 2298 noteJobsNonpending(mPendingJobs); 2299 mPendingJobs.clear(); 2300 stopNonReadyActiveJobsLocked(); 2301 mJobs.forEachJob(mMaybeQueueFunctor); 2302 mMaybeQueueFunctor.postProcessLocked(); 2303 } 2304 2305 /** Returns true if both the calling and source users for the job are started. */ 2306 @GuardedBy("mLock") 2307 public boolean areUsersStartedLocked(final JobStatus job) { 2308 boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId()); 2309 if (job.getUserId() == job.getSourceUserId()) { 2310 return sourceStarted; 2311 } 2312 return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId()); 2313 } 2314 2315 /** 2316 * Criteria for moving a job into the pending queue: 2317 * - It's ready. 2318 * - It's not pending. 2319 * - It's not already running on a JSC. 2320 * - The user that requested the job is running. 2321 * - The job's standby bucket has come due to be runnable. 2322 * - The component is enabled and runnable. 2323 */ 2324 @VisibleForTesting 2325 @GuardedBy("mLock") 2326 boolean isReadyToBeExecutedLocked(JobStatus job) { 2327 return isReadyToBeExecutedLocked(job, true); 2328 } 2329 2330 @GuardedBy("mLock") 2331 boolean isReadyToBeExecutedLocked(JobStatus job, boolean rejectActive) { 2332 final boolean jobReady = job.isReady(); 2333 2334 if (DEBUG) { 2335 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() 2336 + " ready=" + jobReady); 2337 } 2338 2339 // This is a condition that is very likely to be false (most jobs that are 2340 // scheduled are sitting there, not ready yet) and very cheap to check (just 2341 // a few conditions on data in JobStatus). 2342 if (!jobReady) { 2343 if (job.getSourcePackageName().equals("android.jobscheduler.cts.jobtestapp")) { 2344 Slog.v(TAG, " NOT READY: " + job); 2345 } 2346 return false; 2347 } 2348 2349 final boolean jobExists = mJobs.containsJob(job); 2350 final boolean userStarted = areUsersStartedLocked(job); 2351 final boolean backingUp = mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0; 2352 2353 if (DEBUG) { 2354 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() 2355 + " exists=" + jobExists + " userStarted=" + userStarted 2356 + " backingUp=" + backingUp); 2357 } 2358 2359 // These are also fairly cheap to check, though they typically will not 2360 // be conditions we fail. 2361 if (!jobExists || !userStarted || backingUp) { 2362 return false; 2363 } 2364 2365 if (checkIfRestricted(job) != null) { 2366 return false; 2367 } 2368 2369 final boolean jobPending = mPendingJobs.contains(job); 2370 final boolean jobActive = rejectActive && mConcurrencyManager.isJobRunningLocked(job); 2371 2372 if (DEBUG) { 2373 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() 2374 + " pending=" + jobPending + " active=" + jobActive); 2375 } 2376 2377 // These can be a little more expensive (especially jobActive, since we need to 2378 // go through the array of all potentially active jobs), so we are doing them 2379 // later... but still before checking with the package manager! 2380 if (jobPending || jobActive) { 2381 return false; 2382 } 2383 2384 // Validate that the defined package+service is still present & viable. 2385 return isComponentUsable(job); 2386 } 2387 2388 private boolean isComponentUsable(@NonNull JobStatus job) { 2389 final ServiceInfo service = job.serviceInfo; 2390 2391 if (service == null) { 2392 if (DEBUG) { 2393 Slog.v(TAG, "isComponentUsable: " + job.toShortString() 2394 + " component not present"); 2395 } 2396 return false; 2397 } 2398 2399 // Everything else checked out so far, so this is the final yes/no check 2400 final boolean appIsBad = mActivityManagerInternal.isAppBad( 2401 service.processName, service.applicationInfo.uid); 2402 if (DEBUG && appIsBad) { 2403 Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable"); 2404 } 2405 return !appIsBad; 2406 } 2407 2408 @VisibleForTesting 2409 void evaluateControllerStatesLocked(final JobStatus job) { 2410 for (int c = mControllers.size() - 1; c >= 0; --c) { 2411 final StateController sc = mControllers.get(c); 2412 sc.evaluateStateLocked(job); 2413 } 2414 } 2415 2416 /** 2417 * Returns true if non-job constraint components are in place -- if job.isReady() returns true 2418 * and this method returns true, then the job is ready to be executed. 2419 */ 2420 public boolean areComponentsInPlaceLocked(JobStatus job) { 2421 // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same 2422 // conditions. 2423 2424 final boolean jobExists = mJobs.containsJob(job); 2425 final boolean userStarted = areUsersStartedLocked(job); 2426 final boolean backingUp = mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0; 2427 2428 if (DEBUG) { 2429 Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString() 2430 + " exists=" + jobExists + " userStarted=" + userStarted 2431 + " backingUp=" + backingUp); 2432 } 2433 2434 // These are also fairly cheap to check, though they typically will not 2435 // be conditions we fail. 2436 if (!jobExists || !userStarted || backingUp) { 2437 return false; 2438 } 2439 2440 final JobRestriction restriction = checkIfRestricted(job); 2441 if (restriction != null) { 2442 if (DEBUG) { 2443 Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString() 2444 + " restricted due to " + restriction.getInternalReason()); 2445 } 2446 return false; 2447 } 2448 2449 // Job pending/active doesn't affect the readiness of a job. 2450 2451 // The expensive check: validate that the defined package+service is 2452 // still present & viable. 2453 return isComponentUsable(job); 2454 } 2455 2456 /** Returns the minimum amount of time we should let this job run before timing out. */ 2457 public long getMinJobExecutionGuaranteeMs(JobStatus job) { 2458 synchronized (mLock) { 2459 if (job.shouldTreatAsExpeditedJob()) { 2460 // Don't guarantee RESTRICTED jobs more than 5 minutes. 2461 return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX 2462 ? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS 2463 : Math.min(mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, 5 * MINUTE_IN_MILLIS); 2464 } else { 2465 return mConstants.RUNTIME_MIN_GUARANTEE_MS; 2466 } 2467 } 2468 } 2469 2470 /** Returns the maximum amount of time this job could run for. */ 2471 public long getMaxJobExecutionTimeMs(JobStatus job) { 2472 synchronized (mLock) { 2473 return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, 2474 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 2475 } 2476 } 2477 2478 /** 2479 * Reconcile jobs in the pending queue against available execution contexts. 2480 * A controller can force a job into the pending queue even if it's already running, but 2481 * here is where we decide whether to actually execute it. 2482 */ 2483 void maybeRunPendingJobsLocked() { 2484 if (DEBUG) { 2485 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); 2486 } 2487 mConcurrencyManager.assignJobsToContextsLocked(); 2488 reportActiveLocked(); 2489 } 2490 2491 private int adjustJobPriority(int curPriority, JobStatus job) { 2492 if (curPriority < JobInfo.PRIORITY_TOP_APP) { 2493 float factor = mJobPackageTracker.getLoadFactor(job); 2494 if (factor >= mConstants.HEAVY_USE_FACTOR) { 2495 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING; 2496 } else if (factor >= mConstants.MODERATE_USE_FACTOR) { 2497 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING; 2498 } 2499 } 2500 return curPriority; 2501 } 2502 2503 int evaluateJobPriorityLocked(JobStatus job) { 2504 int priority = job.getPriority(); 2505 if (priority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE) { 2506 return adjustJobPriority(priority, job); 2507 } 2508 int override = mUidPriorityOverride.get(job.getSourceUid(), 0); 2509 if (override != 0) { 2510 return adjustJobPriority(override, job); 2511 } 2512 return adjustJobPriority(priority, job); 2513 } 2514 2515 final class LocalService implements JobSchedulerInternal { 2516 2517 /** 2518 * Returns a list of all pending jobs. A running job is not considered pending. Periodic 2519 * jobs are always considered pending. 2520 */ 2521 @Override 2522 public List<JobInfo> getSystemScheduledPendingJobs() { 2523 synchronized (mLock) { 2524 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>(); 2525 mJobs.forEachJob(Process.SYSTEM_UID, (job) -> { 2526 if (job.getJob().isPeriodic() || !mConcurrencyManager.isJobRunningLocked(job)) { 2527 pendingJobs.add(job.getJob()); 2528 } 2529 }); 2530 return pendingJobs; 2531 } 2532 } 2533 2534 @Override 2535 public void cancelJobsForUid(int uid, @JobParameters.StopReason int reason, 2536 int internalReasonCode, String debugReason) { 2537 JobSchedulerService.this.cancelJobsForUid(uid, reason, internalReasonCode, debugReason); 2538 } 2539 2540 @Override 2541 public void addBackingUpUid(int uid) { 2542 synchronized (mLock) { 2543 // No need to actually do anything here, since for a full backup the 2544 // activity manager will kill the process which will kill the job (and 2545 // cause it to restart, but now it can't run). 2546 mBackingUpUids.put(uid, uid); 2547 } 2548 } 2549 2550 @Override 2551 public void removeBackingUpUid(int uid) { 2552 synchronized (mLock) { 2553 mBackingUpUids.delete(uid); 2554 // If there are any jobs for this uid, we need to rebuild the pending list 2555 // in case they are now ready to run. 2556 if (mJobs.countJobsForUid(uid) > 0) { 2557 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 2558 } 2559 } 2560 } 2561 2562 @Override 2563 public void clearAllBackingUpUids() { 2564 synchronized (mLock) { 2565 if (mBackingUpUids.size() > 0) { 2566 mBackingUpUids.clear(); 2567 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 2568 } 2569 } 2570 } 2571 2572 @Override 2573 public String getMediaBackupPackage() { 2574 return mSystemGalleryPackage; 2575 } 2576 2577 @Override 2578 public void reportAppUsage(String packageName, int userId) { 2579 JobSchedulerService.this.reportAppUsage(packageName, userId); 2580 } 2581 2582 @Override 2583 public JobStorePersistStats getPersistStats() { 2584 synchronized (mLock) { 2585 return new JobStorePersistStats(mJobs.getPersistStats()); 2586 } 2587 } 2588 } 2589 2590 /** 2591 * Tracking of app assignments to standby buckets 2592 */ 2593 final class StandbyTracker extends AppIdleStateChangeListener { 2594 2595 // AppIdleStateChangeListener interface for live updates 2596 2597 @Override 2598 public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, 2599 boolean idle, int bucket, int reason) { 2600 // QuotaController handles this now. 2601 } 2602 2603 @Override 2604 public void onUserInteractionStarted(String packageName, int userId) { 2605 final int uid = mLocalPM.getPackageUid(packageName, 2606 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 2607 if (uid < 0) { 2608 // Quietly ignore; the case is already logged elsewhere 2609 return; 2610 } 2611 2612 long sinceLast = mUsageStats.getTimeSinceLastJobRun(packageName, userId); 2613 if (sinceLast > 2 * DateUtils.DAY_IN_MILLIS) { 2614 // Too long ago, not worth logging 2615 sinceLast = 0L; 2616 } 2617 final DeferredJobCounter counter = new DeferredJobCounter(); 2618 synchronized (mLock) { 2619 mJobs.forEachJobForSourceUid(uid, counter); 2620 } 2621 if (counter.numDeferred() > 0 || sinceLast > 0) { 2622 BatteryStatsInternal mBatteryStatsInternal = LocalServices.getService 2623 (BatteryStatsInternal.class); 2624 mBatteryStatsInternal.noteJobsDeferred(uid, counter.numDeferred(), sinceLast); 2625 FrameworkStatsLog.write_non_chained( 2626 FrameworkStatsLog.DEFERRED_JOB_STATS_REPORTED, uid, null, 2627 counter.numDeferred(), sinceLast); 2628 } 2629 } 2630 } 2631 2632 static class DeferredJobCounter implements Consumer<JobStatus> { 2633 private int mDeferred = 0; 2634 2635 public int numDeferred() { 2636 return mDeferred; 2637 } 2638 2639 @Override 2640 public void accept(JobStatus job) { 2641 if (job.getWhenStandbyDeferred() > 0) { 2642 mDeferred++; 2643 } 2644 } 2645 } 2646 2647 public static int standbyBucketToBucketIndex(int bucket) { 2648 // Normalize AppStandby constants to indices into our bookkeeping 2649 if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) { 2650 return NEVER_INDEX; 2651 } else if (bucket > UsageStatsManager.STANDBY_BUCKET_RARE) { 2652 return RESTRICTED_INDEX; 2653 } else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) { 2654 return RARE_INDEX; 2655 } else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) { 2656 return FREQUENT_INDEX; 2657 } else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) { 2658 return WORKING_INDEX; 2659 } else { 2660 return ACTIVE_INDEX; 2661 } 2662 } 2663 2664 // Static to support external callers 2665 public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) { 2666 UsageStatsManagerInternal usageStats = LocalServices.getService( 2667 UsageStatsManagerInternal.class); 2668 int bucket = usageStats != null 2669 ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow) 2670 : 0; 2671 2672 bucket = standbyBucketToBucketIndex(bucket); 2673 2674 if (DEBUG_STANDBY) { 2675 Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket); 2676 } 2677 return bucket; 2678 } 2679 2680 /** 2681 * Binder stub trampoline implementation 2682 */ 2683 final class JobSchedulerStub extends IJobScheduler.Stub { 2684 /** Cache determination of whether a given app can persist jobs 2685 * key is uid of the calling app; value is undetermined/true/false 2686 */ 2687 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); 2688 2689 // Enforce that only the app itself (or shared uid participant) can schedule a 2690 // job that runs one of the app's services, as well as verifying that the 2691 // named service properly requires the BIND_JOB_SERVICE permission 2692 private void enforceValidJobRequest(int uid, JobInfo job) { 2693 final PackageManager pm = getContext() 2694 .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0) 2695 .getPackageManager(); 2696 final ComponentName service = job.getService(); 2697 try { 2698 ServiceInfo si = pm.getServiceInfo(service, 2699 PackageManager.MATCH_DIRECT_BOOT_AWARE 2700 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); 2701 if (si == null) { 2702 throw new IllegalArgumentException("No such service " + service); 2703 } 2704 if (si.applicationInfo.uid != uid) { 2705 throw new IllegalArgumentException("uid " + uid + 2706 " cannot schedule job in " + service.getPackageName()); 2707 } 2708 if (!JobService.PERMISSION_BIND.equals(si.permission)) { 2709 throw new IllegalArgumentException("Scheduled service " + service 2710 + " does not require android.permission.BIND_JOB_SERVICE permission"); 2711 } 2712 } catch (NameNotFoundException e) { 2713 throw new IllegalArgumentException( 2714 "Tried to schedule job for non-existent component: " + service); 2715 } 2716 } 2717 2718 private boolean canPersistJobs(int pid, int uid) { 2719 // If we get this far we're good to go; all we need to do now is check 2720 // whether the app is allowed to persist its scheduled work. 2721 final boolean canPersist; 2722 synchronized (mPersistCache) { 2723 Boolean cached = mPersistCache.get(uid); 2724 if (cached != null) { 2725 canPersist = cached.booleanValue(); 2726 } else { 2727 // Persisting jobs is tantamount to running at boot, so we permit 2728 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED 2729 // permission 2730 int result = getContext().checkPermission( 2731 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid); 2732 canPersist = (result == PackageManager.PERMISSION_GRANTED); 2733 mPersistCache.put(uid, canPersist); 2734 } 2735 } 2736 return canPersist; 2737 } 2738 2739 private void validateJobFlags(JobInfo job, int callingUid) { 2740 job.enforceValidity(); 2741 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) { 2742 getContext().enforceCallingOrSelfPermission( 2743 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG); 2744 } 2745 if ((job.getFlags() & JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY) != 0) { 2746 if (callingUid != Process.SYSTEM_UID) { 2747 throw new SecurityException("Job has invalid flags"); 2748 } 2749 if (job.isPeriodic()) { 2750 Slog.wtf(TAG, "Periodic jobs mustn't have" 2751 + " FLAG_EXEMPT_FROM_APP_STANDBY. Job=" + job); 2752 } 2753 } 2754 } 2755 2756 // IJobScheduler implementation 2757 @Override 2758 public int schedule(JobInfo job) throws RemoteException { 2759 if (DEBUG) { 2760 Slog.d(TAG, "Scheduling job: " + job.toString()); 2761 } 2762 final int pid = Binder.getCallingPid(); 2763 final int uid = Binder.getCallingUid(); 2764 final int userId = UserHandle.getUserId(uid); 2765 2766 enforceValidJobRequest(uid, job); 2767 if (job.isPersisted()) { 2768 if (!canPersistJobs(pid, uid)) { 2769 throw new IllegalArgumentException("Error: requested job be persisted without" 2770 + " holding RECEIVE_BOOT_COMPLETED permission."); 2771 } 2772 } 2773 2774 validateJobFlags(job, uid); 2775 2776 final long ident = Binder.clearCallingIdentity(); 2777 try { 2778 return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId, 2779 null); 2780 } finally { 2781 Binder.restoreCallingIdentity(ident); 2782 } 2783 } 2784 2785 // IJobScheduler implementation 2786 @Override 2787 public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException { 2788 if (DEBUG) { 2789 Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work); 2790 } 2791 final int uid = Binder.getCallingUid(); 2792 final int userId = UserHandle.getUserId(uid); 2793 2794 enforceValidJobRequest(uid, job); 2795 if (job.isPersisted()) { 2796 throw new IllegalArgumentException("Can't enqueue work for persisted jobs"); 2797 } 2798 if (work == null) { 2799 throw new NullPointerException("work is null"); 2800 } 2801 2802 validateJobFlags(job, uid); 2803 2804 final long ident = Binder.clearCallingIdentity(); 2805 try { 2806 return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId, 2807 null); 2808 } finally { 2809 Binder.restoreCallingIdentity(ident); 2810 } 2811 } 2812 2813 @Override 2814 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag) 2815 throws RemoteException { 2816 final int callerUid = Binder.getCallingUid(); 2817 if (DEBUG) { 2818 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString() 2819 + " on behalf of " + packageName + "/"); 2820 } 2821 2822 if (packageName == null) { 2823 throw new NullPointerException("Must specify a package for scheduleAsPackage()"); 2824 } 2825 2826 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission( 2827 android.Manifest.permission.UPDATE_DEVICE_STATS); 2828 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) { 2829 throw new SecurityException("Caller uid " + callerUid 2830 + " not permitted to schedule jobs for other apps"); 2831 } 2832 2833 validateJobFlags(job, callerUid); 2834 2835 final long ident = Binder.clearCallingIdentity(); 2836 try { 2837 return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid, 2838 packageName, userId, tag); 2839 } finally { 2840 Binder.restoreCallingIdentity(ident); 2841 } 2842 } 2843 2844 @Override 2845 public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException { 2846 final int uid = Binder.getCallingUid(); 2847 2848 final long ident = Binder.clearCallingIdentity(); 2849 try { 2850 return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid)); 2851 } finally { 2852 Binder.restoreCallingIdentity(ident); 2853 } 2854 } 2855 2856 @Override 2857 public JobInfo getPendingJob(int jobId) throws RemoteException { 2858 final int uid = Binder.getCallingUid(); 2859 2860 final long ident = Binder.clearCallingIdentity(); 2861 try { 2862 return JobSchedulerService.this.getPendingJob(uid, jobId); 2863 } finally { 2864 Binder.restoreCallingIdentity(ident); 2865 } 2866 } 2867 2868 @Override 2869 public void cancelAll() throws RemoteException { 2870 final int uid = Binder.getCallingUid(); 2871 final long ident = Binder.clearCallingIdentity(); 2872 try { 2873 JobSchedulerService.this.cancelJobsForUid(uid, 2874 JobParameters.STOP_REASON_CANCELLED_BY_APP, 2875 JobParameters.INTERNAL_STOP_REASON_CANCELED, 2876 "cancelAll() called by app, callingUid=" + uid); 2877 } finally { 2878 Binder.restoreCallingIdentity(ident); 2879 } 2880 } 2881 2882 @Override 2883 public void cancel(int jobId) throws RemoteException { 2884 final int uid = Binder.getCallingUid(); 2885 2886 final long ident = Binder.clearCallingIdentity(); 2887 try { 2888 JobSchedulerService.this.cancelJob(uid, jobId, uid, 2889 JobParameters.STOP_REASON_CANCELLED_BY_APP); 2890 } finally { 2891 Binder.restoreCallingIdentity(ident); 2892 } 2893 } 2894 2895 /** 2896 * "dumpsys" infrastructure 2897 */ 2898 @Override 2899 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2900 if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return; 2901 2902 int filterUid = -1; 2903 boolean proto = false; 2904 if (!ArrayUtils.isEmpty(args)) { 2905 int opti = 0; 2906 while (opti < args.length) { 2907 String arg = args[opti]; 2908 if ("-h".equals(arg)) { 2909 dumpHelp(pw); 2910 return; 2911 } else if ("-a".equals(arg)) { 2912 // Ignore, we always dump all. 2913 } else if ("--proto".equals(arg)) { 2914 proto = true; 2915 } else if (arg.length() > 0 && arg.charAt(0) == '-') { 2916 pw.println("Unknown option: " + arg); 2917 return; 2918 } else { 2919 break; 2920 } 2921 opti++; 2922 } 2923 if (opti < args.length) { 2924 String pkg = args[opti]; 2925 try { 2926 filterUid = getContext().getPackageManager().getPackageUid(pkg, 2927 PackageManager.MATCH_ANY_USER); 2928 } catch (NameNotFoundException ignored) { 2929 pw.println("Invalid package: " + pkg); 2930 return; 2931 } 2932 } 2933 } 2934 2935 final long identityToken = Binder.clearCallingIdentity(); 2936 try { 2937 if (proto) { 2938 JobSchedulerService.this.dumpInternalProto(fd, filterUid); 2939 } else { 2940 JobSchedulerService.this.dumpInternal(new IndentingPrintWriter(pw, " "), 2941 filterUid); 2942 } 2943 } finally { 2944 Binder.restoreCallingIdentity(identityToken); 2945 } 2946 } 2947 2948 @Override 2949 public int handleShellCommand(@NonNull ParcelFileDescriptor in, 2950 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, 2951 @NonNull String[] args) { 2952 return (new JobSchedulerShellCommand(JobSchedulerService.this)).exec( 2953 this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), 2954 args); 2955 } 2956 2957 /** 2958 * <b>For internal system user only!</b> 2959 * Returns a list of all currently-executing jobs. 2960 */ 2961 @Override 2962 public List<JobInfo> getStartedJobs() { 2963 final int uid = Binder.getCallingUid(); 2964 if (uid != Process.SYSTEM_UID) { 2965 throw new SecurityException( 2966 "getStartedJobs() is system internal use only."); 2967 } 2968 2969 final ArrayList<JobInfo> runningJobs; 2970 2971 synchronized (mLock) { 2972 runningJobs = new ArrayList<>(mActiveServices.size()); 2973 for (JobServiceContext jsc : mActiveServices) { 2974 final JobStatus job = jsc.getRunningJobLocked(); 2975 if (job != null) { 2976 runningJobs.add(job.getJob()); 2977 } 2978 } 2979 } 2980 2981 return runningJobs; 2982 } 2983 2984 /** 2985 * <b>For internal system user only!</b> 2986 * Returns a snapshot of the state of all jobs known to the system. 2987 * 2988 * <p class="note">This is a slow operation, so it should be called sparingly. 2989 */ 2990 @Override 2991 public ParceledListSlice<JobSnapshot> getAllJobSnapshots() { 2992 final int uid = Binder.getCallingUid(); 2993 if (uid != Process.SYSTEM_UID) { 2994 throw new SecurityException( 2995 "getAllJobSnapshots() is system internal use only."); 2996 } 2997 synchronized (mLock) { 2998 final ArrayList<JobSnapshot> snapshots = new ArrayList<>(mJobs.size()); 2999 mJobs.forEachJob((job) -> snapshots.add( 3000 new JobSnapshot(job.getJob(), job.getSatisfiedConstraintFlags(), 3001 isReadyToBeExecutedLocked(job)))); 3002 return new ParceledListSlice<>(snapshots); 3003 } 3004 } 3005 } 3006 3007 // Shell command infrastructure: run the given job immediately 3008 int executeRunCommand(String pkgName, int userId, int jobId, boolean satisfied, boolean force) { 3009 Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + userId 3010 + " " + jobId + " s=" + satisfied + " f=" + force); 3011 3012 try { 3013 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, 3014 userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM); 3015 if (uid < 0) { 3016 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE; 3017 } 3018 3019 synchronized (mLock) { 3020 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId); 3021 if (js == null) { 3022 return JobSchedulerShellCommand.CMD_ERR_NO_JOB; 3023 } 3024 3025 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL 3026 : (satisfied ? JobStatus.OVERRIDE_SORTING : JobStatus.OVERRIDE_SOFT); 3027 3028 // Re-evaluate constraints after the override is set in case one of the overridden 3029 // constraints was preventing another constraint from thinking it needed to update. 3030 for (int c = mControllers.size() - 1; c >= 0; --c) { 3031 mControllers.get(c).reevaluateStateLocked(uid); 3032 } 3033 3034 if (!js.isConstraintsSatisfied()) { 3035 js.overrideState = JobStatus.OVERRIDE_NONE; 3036 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS; 3037 } 3038 3039 queueReadyJobsForExecutionLocked(); 3040 maybeRunPendingJobsLocked(); 3041 } 3042 } catch (RemoteException e) { 3043 // can't happen 3044 } 3045 return 0; 3046 } 3047 3048 // Shell command infrastructure: immediately timeout currently executing jobs 3049 int executeTimeoutCommand(PrintWriter pw, String pkgName, int userId, 3050 boolean hasJobId, int jobId) { 3051 if (DEBUG) { 3052 Slog.v(TAG, "executeTimeoutCommand(): " + pkgName + "/" + userId + " " + jobId); 3053 } 3054 3055 synchronized (mLock) { 3056 boolean foundSome = false; 3057 for (int i=0; i<mActiveServices.size(); i++) { 3058 final JobServiceContext jc = mActiveServices.get(i); 3059 final JobStatus js = jc.getRunningJobLocked(); 3060 if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) { 3061 foundSome = true; 3062 pw.print("Timing out: "); 3063 js.printUniqueId(pw); 3064 pw.print(" "); 3065 pw.println(js.getServiceComponent().flattenToShortString()); 3066 } 3067 } 3068 if (!foundSome) { 3069 pw.println("No matching executing jobs found."); 3070 } 3071 } 3072 return 0; 3073 } 3074 3075 // Shell command infrastructure: cancel a scheduled job 3076 int executeCancelCommand(PrintWriter pw, String pkgName, int userId, 3077 boolean hasJobId, int jobId) { 3078 if (DEBUG) { 3079 Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId); 3080 } 3081 3082 int pkgUid = -1; 3083 try { 3084 IPackageManager pm = AppGlobals.getPackageManager(); 3085 pkgUid = pm.getPackageUid(pkgName, 0, userId); 3086 } catch (RemoteException e) { /* can't happen */ } 3087 3088 if (pkgUid < 0) { 3089 pw.println("Package " + pkgName + " not found."); 3090 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE; 3091 } 3092 3093 if (!hasJobId) { 3094 pw.println("Canceling all jobs for " + pkgName + " in user " + userId); 3095 if (!cancelJobsForUid(pkgUid, JobParameters.STOP_REASON_USER, 3096 JobParameters.INTERNAL_STOP_REASON_CANCELED, 3097 "cancel shell command for package")) { 3098 pw.println("No matching jobs found."); 3099 } 3100 } else { 3101 pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId); 3102 if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) { 3103 pw.println("No matching job found."); 3104 } 3105 } 3106 3107 return 0; 3108 } 3109 3110 void setMonitorBattery(boolean enabled) { 3111 synchronized (mLock) { 3112 if (mBatteryController != null) { 3113 mBatteryController.getTracker().setMonitorBatteryLocked(enabled); 3114 } 3115 } 3116 } 3117 3118 int getBatterySeq() { 3119 synchronized (mLock) { 3120 return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1; 3121 } 3122 } 3123 3124 boolean getBatteryCharging() { 3125 synchronized (mLock) { 3126 return mBatteryController != null 3127 ? mBatteryController.getTracker().isOnStablePower() : false; 3128 } 3129 } 3130 3131 boolean getBatteryNotLow() { 3132 synchronized (mLock) { 3133 return mBatteryController != null 3134 ? mBatteryController.getTracker().isBatteryNotLow() : false; 3135 } 3136 } 3137 3138 int getStorageSeq() { 3139 synchronized (mLock) { 3140 return mStorageController != null ? mStorageController.getTracker().getSeq() : -1; 3141 } 3142 } 3143 3144 boolean getStorageNotLow() { 3145 synchronized (mLock) { 3146 return mStorageController != null 3147 ? mStorageController.getTracker().isStorageNotLow() : false; 3148 } 3149 } 3150 3151 // Shell command infrastructure 3152 int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) { 3153 try { 3154 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, 3155 userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM); 3156 if (uid < 0) { 3157 pw.print("unknown("); pw.print(pkgName); pw.println(")"); 3158 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE; 3159 } 3160 3161 synchronized (mLock) { 3162 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId); 3163 if (DEBUG) Slog.d(TAG, "get-job-state " + uid + "/" + jobId + ": " + js); 3164 if (js == null) { 3165 pw.print("unknown("); UserHandle.formatUid(pw, uid); 3166 pw.print("/jid"); pw.print(jobId); pw.println(")"); 3167 return JobSchedulerShellCommand.CMD_ERR_NO_JOB; 3168 } 3169 3170 boolean printed = false; 3171 if (mPendingJobs.contains(js)) { 3172 pw.print("pending"); 3173 printed = true; 3174 } 3175 if (mConcurrencyManager.isJobRunningLocked(js)) { 3176 if (printed) { 3177 pw.print(" "); 3178 } 3179 printed = true; 3180 pw.println("active"); 3181 } 3182 if (!ArrayUtils.contains(mStartedUsers, js.getUserId())) { 3183 if (printed) { 3184 pw.print(" "); 3185 } 3186 printed = true; 3187 pw.println("user-stopped"); 3188 } 3189 if (!ArrayUtils.contains(mStartedUsers, js.getSourceUserId())) { 3190 if (printed) { 3191 pw.print(" "); 3192 } 3193 printed = true; 3194 pw.println("source-user-stopped"); 3195 } 3196 if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) { 3197 if (printed) { 3198 pw.print(" "); 3199 } 3200 printed = true; 3201 pw.println("backing-up"); 3202 } 3203 boolean componentPresent = false; 3204 try { 3205 componentPresent = (AppGlobals.getPackageManager().getServiceInfo( 3206 js.getServiceComponent(), 3207 PackageManager.MATCH_DIRECT_BOOT_AUTO, 3208 js.getUserId()) != null); 3209 } catch (RemoteException e) { 3210 } 3211 if (!componentPresent) { 3212 if (printed) { 3213 pw.print(" "); 3214 } 3215 printed = true; 3216 pw.println("no-component"); 3217 } 3218 if (js.isReady()) { 3219 if (printed) { 3220 pw.print(" "); 3221 } 3222 printed = true; 3223 pw.println("ready"); 3224 } 3225 if (!printed) { 3226 pw.print("waiting"); 3227 } 3228 pw.println(); 3229 } 3230 } catch (RemoteException e) { 3231 // can't happen 3232 } 3233 return 0; 3234 } 3235 3236 void resetExecutionQuota(@NonNull String pkgName, int userId) { 3237 synchronized (mLock) { 3238 mQuotaController.clearAppStatsLocked(userId, pkgName); 3239 } 3240 } 3241 3242 void resetScheduleQuota() { 3243 mQuotaTracker.clear(); 3244 } 3245 3246 void triggerDockState(boolean idleState) { 3247 final Intent dockIntent; 3248 if (idleState) { 3249 dockIntent = new Intent(Intent.ACTION_DOCK_IDLE); 3250 } else { 3251 dockIntent = new Intent(Intent.ACTION_DOCK_ACTIVE); 3252 } 3253 dockIntent.setPackage("android"); 3254 dockIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); 3255 getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL); 3256 } 3257 3258 static void dumpHelp(PrintWriter pw) { 3259 pw.println("Job Scheduler (jobscheduler) dump options:"); 3260 pw.println(" [-h] [package] ..."); 3261 pw.println(" -h: print this help"); 3262 pw.println(" [package] is an optional package name to limit the output to."); 3263 } 3264 3265 /** Sort jobs by caller UID, then by Job ID. */ 3266 private static void sortJobs(List<JobStatus> jobs) { 3267 Collections.sort(jobs, new Comparator<JobStatus>() { 3268 @Override 3269 public int compare(JobStatus o1, JobStatus o2) { 3270 int uid1 = o1.getUid(); 3271 int uid2 = o2.getUid(); 3272 int id1 = o1.getJobId(); 3273 int id2 = o2.getJobId(); 3274 if (uid1 != uid2) { 3275 return uid1 < uid2 ? -1 : 1; 3276 } 3277 return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0); 3278 } 3279 }); 3280 } 3281 3282 void dumpInternal(final IndentingPrintWriter pw, int filterUid) { 3283 final int filterAppId = UserHandle.getAppId(filterUid); 3284 final long now = sSystemClock.millis(); 3285 final long nowElapsed = sElapsedRealtimeClock.millis(); 3286 final long nowUptime = sUptimeMillisClock.millis(); 3287 3288 final Predicate<JobStatus> predicate = (js) -> { 3289 return filterAppId == -1 || UserHandle.getAppId(js.getUid()) == filterAppId 3290 || UserHandle.getAppId(js.getSourceUid()) == filterAppId; 3291 }; 3292 synchronized (mLock) { 3293 mConstants.dump(pw); 3294 for (StateController controller : mControllers) { 3295 pw.increaseIndent(); 3296 controller.dumpConstants(pw); 3297 pw.decreaseIndent(); 3298 } 3299 pw.println(); 3300 3301 for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { 3302 mJobRestrictions.get(i).dumpConstants(pw); 3303 } 3304 pw.println(); 3305 3306 mQuotaTracker.dump(pw); 3307 pw.println(); 3308 3309 pw.println("Started users: " + Arrays.toString(mStartedUsers)); 3310 pw.print("Registered "); 3311 pw.print(mJobs.size()); 3312 pw.println(" jobs:"); 3313 pw.increaseIndent(); 3314 boolean jobPrinted = false; 3315 if (mJobs.size() > 0) { 3316 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs(); 3317 sortJobs(jobs); 3318 for (JobStatus job : jobs) { 3319 // Skip printing details if the caller requested a filter 3320 if (!predicate.test(job)) { 3321 continue; 3322 } 3323 jobPrinted = true; 3324 3325 pw.print("JOB #"); job.printUniqueId(pw); pw.print(": "); 3326 pw.println(job.toShortStringExceptUniqueId()); 3327 3328 pw.increaseIndent(); 3329 job.dump(pw, true, nowElapsed); 3330 3331 pw.print("Restricted due to:"); 3332 final boolean isRestricted = checkIfRestricted(job) != null; 3333 if (isRestricted) { 3334 for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { 3335 final JobRestriction restriction = mJobRestrictions.get(i); 3336 if (restriction.isJobRestricted(job)) { 3337 final int reason = restriction.getInternalReason(); 3338 pw.print(" "); 3339 pw.print(JobParameters.getInternalReasonCodeDescription(reason)); 3340 } 3341 } 3342 } else { 3343 pw.print(" none"); 3344 } 3345 pw.println("."); 3346 3347 pw.print("Ready: "); 3348 pw.print(isReadyToBeExecutedLocked(job)); 3349 pw.print(" (job="); 3350 pw.print(job.isReady()); 3351 pw.print(" user="); 3352 pw.print(areUsersStartedLocked(job)); 3353 pw.print(" !restricted="); 3354 pw.print(!isRestricted); 3355 pw.print(" !pending="); 3356 pw.print(!mPendingJobs.contains(job)); 3357 pw.print(" !active="); 3358 pw.print(!mConcurrencyManager.isJobRunningLocked(job)); 3359 pw.print(" !backingup="); 3360 pw.print(!(mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0)); 3361 pw.print(" comp="); 3362 pw.print(isComponentUsable(job)); 3363 pw.println(")"); 3364 3365 pw.decreaseIndent(); 3366 } 3367 } 3368 if (!jobPrinted) { 3369 pw.println("None."); 3370 } 3371 pw.decreaseIndent(); 3372 3373 for (int i=0; i<mControllers.size(); i++) { 3374 pw.println(); 3375 pw.println(mControllers.get(i).getClass().getSimpleName() + ":"); 3376 pw.increaseIndent(); 3377 mControllers.get(i).dumpControllerStateLocked(pw, predicate); 3378 pw.decreaseIndent(); 3379 } 3380 3381 boolean overridePrinted = false; 3382 for (int i=0; i< mUidPriorityOverride.size(); i++) { 3383 int uid = mUidPriorityOverride.keyAt(i); 3384 if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) { 3385 if (!overridePrinted) { 3386 overridePrinted = true; 3387 pw.println(); 3388 pw.println("Uid priority overrides:"); 3389 pw.increaseIndent(); 3390 } 3391 pw.print(UserHandle.formatUid(uid)); 3392 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i)); 3393 } 3394 } 3395 if (overridePrinted) { 3396 pw.decreaseIndent(); 3397 } 3398 3399 boolean uidMapPrinted = false; 3400 for (int i = 0; i < mUidToPackageCache.size(); ++i) { 3401 final int uid = mUidToPackageCache.keyAt(i); 3402 if (filterUid != -1 && filterUid != uid) { 3403 continue; 3404 } 3405 if (!uidMapPrinted) { 3406 uidMapPrinted = true; 3407 pw.println(); 3408 pw.println("Cached UID->package map:"); 3409 pw.increaseIndent(); 3410 } 3411 pw.print(uid); 3412 pw.print(": "); 3413 pw.println(mUidToPackageCache.get(uid)); 3414 } 3415 if (uidMapPrinted) { 3416 pw.decreaseIndent(); 3417 } 3418 3419 boolean backingPrinted = false; 3420 for (int i = 0; i < mBackingUpUids.size(); i++) { 3421 int uid = mBackingUpUids.keyAt(i); 3422 if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) { 3423 if (!backingPrinted) { 3424 pw.println(); 3425 pw.println("Backing up uids:"); 3426 pw.increaseIndent(); 3427 backingPrinted = true; 3428 } else { 3429 pw.print(", "); 3430 } 3431 pw.print(UserHandle.formatUid(uid)); 3432 } 3433 } 3434 if (backingPrinted) { 3435 pw.decreaseIndent(); 3436 pw.println(); 3437 } 3438 3439 pw.println(); 3440 mJobPackageTracker.dump(pw, filterAppId); 3441 pw.println(); 3442 if (mJobPackageTracker.dumpHistory(pw, filterAppId)) { 3443 pw.println(); 3444 } 3445 3446 boolean pendingPrinted = false; 3447 pw.println("Pending queue:"); 3448 pw.increaseIndent(); 3449 for (int i=0; i<mPendingJobs.size(); i++) { 3450 JobStatus job = mPendingJobs.get(i); 3451 if (!predicate.test(job)) { 3452 continue; 3453 } 3454 if (!pendingPrinted) { 3455 pendingPrinted = true; 3456 } 3457 3458 pw.print("Pending #"); pw.print(i); pw.print(": "); 3459 pw.println(job.toShortString()); 3460 3461 pw.increaseIndent(); 3462 job.dump(pw, false, nowElapsed); 3463 int priority = evaluateJobPriorityLocked(job); 3464 pw.print("Evaluated priority: "); 3465 pw.println(JobInfo.getPriorityString(priority)); 3466 3467 pw.print("Tag: "); pw.println(job.getTag()); 3468 pw.print("Enq: "); 3469 TimeUtils.formatDuration(job.madePending - nowUptime, pw); 3470 pw.decreaseIndent(); 3471 pw.println(); 3472 } 3473 if (!pendingPrinted) { 3474 pw.println("None"); 3475 } 3476 pw.decreaseIndent(); 3477 3478 pw.println(); 3479 pw.println("Active jobs:"); 3480 pw.increaseIndent(); 3481 for (int i=0; i<mActiveServices.size(); i++) { 3482 JobServiceContext jsc = mActiveServices.get(i); 3483 final JobStatus job = jsc.getRunningJobLocked(); 3484 3485 if (job != null && !predicate.test(job)) { 3486 continue; 3487 } 3488 3489 pw.print("Slot #"); pw.print(i); pw.print(": "); 3490 jsc.dumpLocked(pw, nowElapsed); 3491 3492 if (job != null) { 3493 pw.increaseIndent(); 3494 3495 pw.increaseIndent(); 3496 job.dump(pw, false, nowElapsed); 3497 pw.decreaseIndent(); 3498 3499 pw.print("Evaluated priority: "); 3500 pw.println(JobInfo.getPriorityString(job.lastEvaluatedPriority)); 3501 3502 pw.print("Active at "); 3503 TimeUtils.formatDuration(job.madeActive - nowUptime, pw); 3504 pw.print(", pending for "); 3505 TimeUtils.formatDuration(job.madeActive - job.madePending, pw); 3506 pw.decreaseIndent(); 3507 pw.println(); 3508 } 3509 } 3510 pw.decreaseIndent(); 3511 3512 pw.println(); 3513 boolean recentPrinted = false; 3514 pw.println("Recently completed jobs:"); 3515 pw.increaseIndent(); 3516 for (int r = 1; r <= NUM_COMPLETED_JOB_HISTORY; ++r) { 3517 // Print most recent first 3518 final int idx = (mLastCompletedJobIndex + NUM_COMPLETED_JOB_HISTORY - r) 3519 % NUM_COMPLETED_JOB_HISTORY; 3520 final JobStatus job = mLastCompletedJobs[idx]; 3521 if (job != null) { 3522 if (!predicate.test(job)) { 3523 continue; 3524 } 3525 recentPrinted = true; 3526 TimeUtils.formatDuration(mLastCompletedJobTimeElapsed[idx], nowElapsed, pw); 3527 pw.println(); 3528 // Double indent for readability 3529 pw.increaseIndent(); 3530 pw.increaseIndent(); 3531 job.dump(pw, true, nowElapsed); 3532 pw.decreaseIndent(); 3533 pw.decreaseIndent(); 3534 } 3535 } 3536 if (!recentPrinted) { 3537 pw.println("None"); 3538 } 3539 pw.decreaseIndent(); 3540 pw.println(); 3541 3542 if (filterUid == -1) { 3543 pw.println(); 3544 pw.print("mReadyToRock="); pw.println(mReadyToRock); 3545 pw.print("mReportedActive="); pw.println(mReportedActive); 3546 } 3547 pw.println(); 3548 3549 mConcurrencyManager.dumpLocked(pw, now, nowElapsed); 3550 3551 pw.println(); 3552 pw.print("PersistStats: "); 3553 pw.println(mJobs.getPersistStats()); 3554 } 3555 pw.println(); 3556 } 3557 3558 void dumpInternalProto(final FileDescriptor fd, int filterUid) { 3559 ProtoOutputStream proto = new ProtoOutputStream(fd); 3560 final int filterAppId = UserHandle.getAppId(filterUid); 3561 final long now = sSystemClock.millis(); 3562 final long nowElapsed = sElapsedRealtimeClock.millis(); 3563 final long nowUptime = sUptimeMillisClock.millis(); 3564 final Predicate<JobStatus> predicate = (js) -> { 3565 return filterAppId == -1 || UserHandle.getAppId(js.getUid()) == filterAppId 3566 || UserHandle.getAppId(js.getSourceUid()) == filterAppId; 3567 }; 3568 3569 synchronized (mLock) { 3570 final long settingsToken = proto.start(JobSchedulerServiceDumpProto.SETTINGS); 3571 mConstants.dump(proto); 3572 for (StateController controller : mControllers) { 3573 controller.dumpConstants(proto); 3574 } 3575 proto.end(settingsToken); 3576 3577 for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { 3578 mJobRestrictions.get(i).dumpConstants(proto); 3579 } 3580 3581 for (int u : mStartedUsers) { 3582 proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u); 3583 } 3584 3585 mQuotaTracker.dump(proto, JobSchedulerServiceDumpProto.QUOTA_TRACKER); 3586 3587 if (mJobs.size() > 0) { 3588 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs(); 3589 sortJobs(jobs); 3590 for (JobStatus job : jobs) { 3591 final long rjToken = proto.start(JobSchedulerServiceDumpProto.REGISTERED_JOBS); 3592 job.writeToShortProto(proto, JobSchedulerServiceDumpProto.RegisteredJob.INFO); 3593 3594 // Skip printing details if the caller requested a filter 3595 if (!predicate.test(job)) { 3596 continue; 3597 } 3598 3599 job.dump(proto, JobSchedulerServiceDumpProto.RegisteredJob.DUMP, true, nowElapsed); 3600 3601 proto.write( 3602 JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY_TO_BE_EXECUTED, 3603 isReadyToBeExecutedLocked(job)); 3604 proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY, 3605 job.isReady()); 3606 proto.write(JobSchedulerServiceDumpProto.RegisteredJob.ARE_USERS_STARTED, 3607 areUsersStartedLocked(job)); 3608 proto.write( 3609 JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_RESTRICTED, 3610 checkIfRestricted(job) != null); 3611 proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING, 3612 mPendingJobs.contains(job)); 3613 proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE, 3614 mConcurrencyManager.isJobRunningLocked(job)); 3615 proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_UID_BACKING_UP, 3616 mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0); 3617 proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_USABLE, 3618 isComponentUsable(job)); 3619 3620 for (JobRestriction restriction : mJobRestrictions) { 3621 final long restrictionsToken = proto.start( 3622 JobSchedulerServiceDumpProto.RegisteredJob.RESTRICTIONS); 3623 proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON, 3624 restriction.getInternalReason()); 3625 proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING, 3626 restriction.isJobRestricted(job)); 3627 proto.end(restrictionsToken); 3628 } 3629 3630 proto.end(rjToken); 3631 } 3632 } 3633 for (StateController controller : mControllers) { 3634 controller.dumpControllerStateLocked( 3635 proto, JobSchedulerServiceDumpProto.CONTROLLERS, predicate); 3636 } 3637 for (int i=0; i< mUidPriorityOverride.size(); i++) { 3638 int uid = mUidPriorityOverride.keyAt(i); 3639 if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) { 3640 long pToken = proto.start(JobSchedulerServiceDumpProto.PRIORITY_OVERRIDES); 3641 proto.write(JobSchedulerServiceDumpProto.PriorityOverride.UID, uid); 3642 proto.write(JobSchedulerServiceDumpProto.PriorityOverride.OVERRIDE_VALUE, 3643 mUidPriorityOverride.valueAt(i)); 3644 proto.end(pToken); 3645 } 3646 } 3647 for (int i = 0; i < mBackingUpUids.size(); i++) { 3648 int uid = mBackingUpUids.keyAt(i); 3649 if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) { 3650 proto.write(JobSchedulerServiceDumpProto.BACKING_UP_UIDS, uid); 3651 } 3652 } 3653 3654 mJobPackageTracker.dump(proto, JobSchedulerServiceDumpProto.PACKAGE_TRACKER, 3655 filterAppId); 3656 mJobPackageTracker.dumpHistory(proto, JobSchedulerServiceDumpProto.HISTORY, 3657 filterAppId); 3658 3659 for (JobStatus job : mPendingJobs) { 3660 final long pjToken = proto.start(JobSchedulerServiceDumpProto.PENDING_JOBS); 3661 3662 job.writeToShortProto(proto, PendingJob.INFO); 3663 job.dump(proto, PendingJob.DUMP, false, nowElapsed); 3664 proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobPriorityLocked(job)); 3665 proto.write(PendingJob.PENDING_DURATION_MS, nowUptime - job.madePending); 3666 3667 proto.end(pjToken); 3668 } 3669 for (JobServiceContext jsc : mActiveServices) { 3670 final long ajToken = proto.start(JobSchedulerServiceDumpProto.ACTIVE_JOBS); 3671 final JobStatus job = jsc.getRunningJobLocked(); 3672 3673 if (job == null) { 3674 final long ijToken = proto.start(ActiveJob.INACTIVE); 3675 3676 proto.write(ActiveJob.InactiveJob.TIME_SINCE_STOPPED_MS, 3677 nowElapsed - jsc.mStoppedTime); 3678 if (jsc.mStoppedReason != null) { 3679 proto.write(ActiveJob.InactiveJob.STOPPED_REASON, 3680 jsc.mStoppedReason); 3681 } 3682 3683 proto.end(ijToken); 3684 } else { 3685 final long rjToken = proto.start(ActiveJob.RUNNING); 3686 3687 job.writeToShortProto(proto, ActiveJob.RunningJob.INFO); 3688 3689 proto.write(ActiveJob.RunningJob.RUNNING_DURATION_MS, 3690 nowElapsed - jsc.getExecutionStartTimeElapsed()); 3691 proto.write(ActiveJob.RunningJob.TIME_UNTIL_TIMEOUT_MS, 3692 jsc.getTimeoutElapsed() - nowElapsed); 3693 3694 job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed); 3695 3696 proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY, 3697 evaluateJobPriorityLocked(job)); 3698 3699 proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS, 3700 nowUptime - job.madeActive); 3701 proto.write(ActiveJob.RunningJob.PENDING_DURATION_MS, 3702 job.madeActive - job.madePending); 3703 3704 proto.end(rjToken); 3705 } 3706 proto.end(ajToken); 3707 } 3708 if (filterUid == -1) { 3709 proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock); 3710 proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive); 3711 } 3712 mConcurrencyManager.dumpProtoLocked(proto, 3713 JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed); 3714 3715 mJobs.getPersistStats().dumpDebug(proto, JobSchedulerServiceDumpProto.PERSIST_STATS); 3716 } 3717 3718 proto.flush(); 3719 } 3720 } 3721