JobSchedulerService.java revision b5c0788d3686351cfe594c6b680d2857f89e4a63
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 17package com.android.server.job; 18 19import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 20import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; 21 22import java.io.FileDescriptor; 23import java.io.PrintWriter; 24import java.util.ArrayList; 25import java.util.Arrays; 26import java.util.Iterator; 27import java.util.List; 28 29import android.app.ActivityManager; 30import android.app.ActivityManagerNative; 31import android.app.AppGlobals; 32import android.app.IUidObserver; 33import android.app.job.JobInfo; 34import android.app.job.JobParameters; 35import android.app.job.JobScheduler; 36import android.app.job.JobService; 37import android.app.job.IJobScheduler; 38import android.content.BroadcastReceiver; 39import android.content.ComponentName; 40import android.content.Context; 41import android.content.Intent; 42import android.content.IntentFilter; 43import android.content.pm.IPackageManager; 44import android.content.pm.PackageManager; 45import android.content.pm.ServiceInfo; 46import android.content.pm.PackageManager.NameNotFoundException; 47import android.net.Uri; 48import android.os.BatteryStats; 49import android.os.Binder; 50import android.os.Handler; 51import android.os.Looper; 52import android.os.Message; 53import android.os.Process; 54import android.os.PowerManager; 55import android.os.RemoteException; 56import android.os.ResultReceiver; 57import android.os.ServiceManager; 58import android.os.SystemClock; 59import android.os.UserHandle; 60import android.util.Slog; 61import android.util.SparseArray; 62import android.util.SparseIntArray; 63import android.util.TimeUtils; 64 65import com.android.internal.app.IBatteryStats; 66import com.android.internal.app.procstats.ProcessStats; 67import com.android.internal.util.ArrayUtils; 68import com.android.server.DeviceIdleController; 69import com.android.server.LocalServices; 70import com.android.server.job.JobStore.JobStatusFunctor; 71import com.android.server.job.controllers.AppIdleController; 72import com.android.server.job.controllers.BatteryController; 73import com.android.server.job.controllers.ConnectivityController; 74import com.android.server.job.controllers.ContentObserverController; 75import com.android.server.job.controllers.DeviceIdleJobsController; 76import com.android.server.job.controllers.IdleController; 77import com.android.server.job.controllers.JobStatus; 78import com.android.server.job.controllers.StateController; 79import com.android.server.job.controllers.TimeController; 80 81import libcore.util.EmptyArray; 82 83/** 84 * Responsible for taking jobs representing work to be performed by a client app, and determining 85 * based on the criteria specified when that job should be run against the client application's 86 * endpoint. 87 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing 88 * about constraints, or the state of active jobs. It receives callbacks from the various 89 * controllers and completed jobs and operates accordingly. 90 * 91 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object. 92 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}. 93 * @hide 94 */ 95public final class JobSchedulerService extends com.android.server.SystemService 96 implements StateChangedListener, JobCompletedListener { 97 static final String TAG = "JobSchedulerService"; 98 public static final boolean DEBUG = false; 99 100 /** The maximum number of concurrent jobs we run at one time. */ 101 private static final int MAX_JOB_CONTEXTS_COUNT = 12; 102 /** The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. */ 103 private static final int FG_JOB_CONTEXTS_COUNT = 4; 104 /** Enforce a per-app limit on scheduled jobs? */ 105 private static final boolean ENFORCE_MAX_JOBS = true; 106 /** The maximum number of jobs that we allow an unprivileged app to schedule */ 107 private static final int MAX_JOBS_PER_APP = 100; 108 /** This is the job execution factor that is considered to be heavy use of the system. */ 109 private static final float HEAVY_USE_FACTOR = .9f; 110 /** This is the job execution factor that is considered to be moderate use of the system. */ 111 private static final float MODERATE_USE_FACTOR = .5f; 112 113 /** Global local for all job scheduler state. */ 114 final Object mLock = new Object(); 115 /** Master list of jobs. */ 116 final JobStore mJobs; 117 /** Tracking amount of time each package runs for. */ 118 final JobPackageTracker mJobPackageTracker = new JobPackageTracker(); 119 120 static final int MSG_JOB_EXPIRED = 0; 121 static final int MSG_CHECK_JOB = 1; 122 static final int MSG_STOP_JOB = 2; 123 static final int MSG_CHECK_JOB_GREEDY = 3; 124 125 // Policy constants 126 /** 127 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things 128 * early. 129 */ 130 static final int MIN_IDLE_COUNT = 1; 131 /** 132 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things 133 * early. 134 */ 135 static final int MIN_CHARGING_COUNT = 1; 136 /** 137 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule 138 * things early. 139 */ 140 static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready. 141 /** 142 * Minimum # of content trigger jobs that must be ready in order to force the JMS to schedule 143 * things early. 144 */ 145 static final int MIN_CONTENT_COUNT = 1; 146 /** 147 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running 148 * some work early. 149 * This is correlated with the amount of batching we'll be able to do. 150 */ 151 static final int MIN_READY_JOBS_COUNT = 2; 152 153 /** 154 * Track Services that have currently active or pending jobs. The index is provided by 155 * {@link JobStatus#getServiceToken()} 156 */ 157 final List<JobServiceContext> mActiveServices = new ArrayList<>(); 158 /** List of controllers that will notify this service of updates to jobs. */ 159 List<StateController> mControllers; 160 /** 161 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list 162 * when ready to execute them. 163 */ 164 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>(); 165 166 int[] mStartedUsers = EmptyArray.INT; 167 168 final JobHandler mHandler; 169 final JobSchedulerStub mJobSchedulerStub; 170 171 IBatteryStats mBatteryStats; 172 PowerManager mPowerManager; 173 DeviceIdleController.LocalService mLocalDeviceIdleController; 174 175 /** 176 * Set to true once we are allowed to run third party apps. 177 */ 178 boolean mReadyToRock; 179 180 /** 181 * What we last reported to DeviceIdleController about whether we are active. 182 */ 183 boolean mReportedActive; 184 185 /** 186 * Current limit on the number of concurrent JobServiceContext entries we want to 187 * keep actively running a job. 188 */ 189 int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; 190 191 /** 192 * Which uids are currently in the foreground. 193 */ 194 final SparseIntArray mUidPriorityOverride = new SparseIntArray(); 195 196 // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked -- 197 198 /** 199 * This array essentially stores the state of mActiveServices array. 200 * The ith index stores the job present on the ith JobServiceContext. 201 * We manipulate this array until we arrive at what jobs should be running on 202 * what JobServiceContext. 203 */ 204 JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; 205 /** 206 * Indicates whether we need to act on this jobContext id 207 */ 208 boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT]; 209 /** 210 * The uid whose jobs we would like to assign to a context. 211 */ 212 int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; 213 214 /** 215 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we 216 * still clean up. On reinstall the package will have a new uid. 217 */ 218 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 219 @Override 220 public void onReceive(Context context, Intent intent) { 221 if (DEBUG) { 222 Slog.d(TAG, "Receieved: " + intent.getAction()); 223 } 224 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) { 225 // Purge the app's jobs if the whole package was just disabled. When this is 226 // the case the component name will be a bare package name. 227 final String pkgName = getPackageName(intent); 228 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); 229 if (pkgName != null && pkgUid != -1) { 230 final String[] changedComponents = intent.getStringArrayExtra( 231 Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); 232 if (changedComponents != null) { 233 for (String component : changedComponents) { 234 if (component.equals(pkgName)) { 235 if (DEBUG) { 236 Slog.d(TAG, "Package state change: " + pkgName); 237 } 238 try { 239 final int userId = UserHandle.getUserId(pkgUid); 240 IPackageManager pm = AppGlobals.getPackageManager(); 241 final int state = pm.getApplicationEnabledSetting(pkgName, userId); 242 if (state == COMPONENT_ENABLED_STATE_DISABLED 243 || state == COMPONENT_ENABLED_STATE_DISABLED_USER) { 244 if (DEBUG) { 245 Slog.d(TAG, "Removing jobs for package " + pkgName 246 + " in user " + userId); 247 } 248 cancelJobsForUid(pkgUid, true); 249 } 250 } catch (RemoteException e) { /* cannot happen */ } 251 break; 252 } 253 } 254 } 255 } else { 256 Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid); 257 } 258 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 259 // If this is an outright uninstall rather than the first half of an 260 // app update sequence, cancel the jobs associated with the app. 261 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 262 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); 263 if (DEBUG) { 264 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); 265 } 266 cancelJobsForUid(uidRemoved, true); 267 } 268 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { 269 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); 270 if (DEBUG) { 271 Slog.d(TAG, "Removing jobs for user: " + userId); 272 } 273 cancelJobsForUser(userId); 274 } 275 } 276 }; 277 278 private String getPackageName(Intent intent) { 279 Uri uri = intent.getData(); 280 String pkg = uri != null ? uri.getSchemeSpecificPart() : null; 281 return pkg; 282 } 283 284 final private IUidObserver mUidObserver = new IUidObserver.Stub() { 285 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException { 286 updateUidState(uid, procState); 287 } 288 289 @Override public void onUidGone(int uid) throws RemoteException { 290 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY); 291 } 292 293 @Override public void onUidActive(int uid) throws RemoteException { 294 } 295 296 @Override public void onUidIdle(int uid) throws RemoteException { 297 cancelJobsForUid(uid, false); 298 } 299 }; 300 301 public Object getLock() { 302 return mLock; 303 } 304 305 public JobStore getJobStore() { 306 return mJobs; 307 } 308 309 @Override 310 public void onStartUser(int userHandle) { 311 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle); 312 // Let's kick any outstanding jobs for this user. 313 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 314 } 315 316 @Override 317 public void onUnlockUser(int userHandle) { 318 // Let's kick any outstanding jobs for this user. 319 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 320 } 321 322 @Override 323 public void onStopUser(int userHandle) { 324 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle); 325 } 326 327 /** 328 * Entry point from client to schedule the provided job. 329 * This cancels the job if it's already been scheduled, and replaces it with the one provided. 330 * @param job JobInfo object containing execution parameters 331 * @param uId The package identifier of the application this job is for. 332 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes. 333 */ 334 public int schedule(JobInfo job, int uId) { 335 return scheduleAsPackage(job, uId, null, -1, null); 336 } 337 338 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId, 339 String tag) { 340 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag); 341 try { 342 if (ActivityManagerNative.getDefault().getAppStartMode(uId, 343 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) { 344 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString() 345 + " -- package not allowed to start"); 346 return JobScheduler.RESULT_FAILURE; 347 } 348 } catch (RemoteException e) { 349 } 350 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); 351 JobStatus toCancel; 352 synchronized (mLock) { 353 // Jobs on behalf of others don't apply to the per-app job cap 354 if (ENFORCE_MAX_JOBS && packageName == null) { 355 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) { 356 Slog.w(TAG, "Too many jobs for uid " + uId); 357 throw new IllegalStateException("Apps may not schedule more than " 358 + MAX_JOBS_PER_APP + " distinct jobs"); 359 } 360 } 361 362 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId()); 363 if (toCancel != null) { 364 cancelJobImpl(toCancel, jobStatus); 365 } 366 startTrackingJob(jobStatus, toCancel); 367 } 368 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 369 return JobScheduler.RESULT_SUCCESS; 370 } 371 372 public List<JobInfo> getPendingJobs(int uid) { 373 synchronized (mLock) { 374 List<JobStatus> jobs = mJobs.getJobsByUid(uid); 375 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size()); 376 for (int i = jobs.size() - 1; i >= 0; i--) { 377 JobStatus job = jobs.get(i); 378 outList.add(job.getJob()); 379 } 380 return outList; 381 } 382 } 383 384 public JobInfo getPendingJob(int uid, int jobId) { 385 synchronized (mLock) { 386 List<JobStatus> jobs = mJobs.getJobsByUid(uid); 387 for (int i = jobs.size() - 1; i >= 0; i--) { 388 JobStatus job = jobs.get(i); 389 if (job.getJobId() == jobId) { 390 return job.getJob(); 391 } 392 } 393 return null; 394 } 395 } 396 397 void cancelJobsForUser(int userHandle) { 398 List<JobStatus> jobsForUser; 399 synchronized (mLock) { 400 jobsForUser = mJobs.getJobsByUser(userHandle); 401 } 402 for (int i=0; i<jobsForUser.size(); i++) { 403 JobStatus toRemove = jobsForUser.get(i); 404 cancelJobImpl(toRemove, null); 405 } 406 } 407 408 /** 409 * Entry point from client to cancel all jobs originating from their uid. 410 * This will remove the job from the master list, and cancel the job if it was staged for 411 * execution or being executed. 412 * @param uid Uid to check against for removal of a job. 413 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those 414 * whose apps are stopped. 415 */ 416 public void cancelJobsForUid(int uid, boolean forceAll) { 417 List<JobStatus> jobsForUid; 418 synchronized (mLock) { 419 jobsForUid = mJobs.getJobsByUid(uid); 420 } 421 for (int i=0; i<jobsForUid.size(); i++) { 422 JobStatus toRemove = jobsForUid.get(i); 423 if (!forceAll) { 424 String packageName = toRemove.getServiceComponent().getPackageName(); 425 try { 426 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName) 427 != ActivityManager.APP_START_MODE_DISABLED) { 428 continue; 429 } 430 } catch (RemoteException e) { 431 } 432 } 433 cancelJobImpl(toRemove, null); 434 } 435 } 436 437 /** 438 * Entry point from client to cancel the job corresponding to the jobId provided. 439 * This will remove the job from the master list, and cancel the job if it was staged for 440 * execution or being executed. 441 * @param uid Uid of the calling client. 442 * @param jobId Id of the job, provided at schedule-time. 443 */ 444 public void cancelJob(int uid, int jobId) { 445 JobStatus toCancel; 446 synchronized (mLock) { 447 toCancel = mJobs.getJobByUidAndJobId(uid, jobId); 448 } 449 if (toCancel != null) { 450 cancelJobImpl(toCancel, null); 451 } 452 } 453 454 private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) { 455 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); 456 stopTrackingJob(cancelled, incomingJob, true /* writeBack */); 457 synchronized (mLock) { 458 // Remove from pending queue. 459 if (mPendingJobs.remove(cancelled)) { 460 mJobPackageTracker.noteNonpending(cancelled); 461 } 462 // Cancel if running. 463 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED); 464 reportActive(); 465 } 466 } 467 468 void updateUidState(int uid, int procState) { 469 synchronized (mLock) { 470 if (procState == ActivityManager.PROCESS_STATE_TOP) { 471 // Only use this if we are exactly the top app. All others can live 472 // with just the foreground priority. This means that persistent processes 473 // can never be the top app priority... that is fine. 474 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP); 475 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 476 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP); 477 } else { 478 mUidPriorityOverride.delete(uid); 479 } 480 } 481 } 482 483 @Override 484 public void onDeviceIdleStateChanged(boolean deviceIdle) { 485 synchronized (mLock) { 486 if (deviceIdle) { 487 // When becoming idle, make sure no jobs are actively running. 488 for (int i=0; i<mActiveServices.size(); i++) { 489 JobServiceContext jsc = mActiveServices.get(i); 490 final JobStatus executing = jsc.getRunningJob(); 491 if (executing != null) { 492 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE); 493 } 494 } 495 } else { 496 // When coming out of idle, allow thing to start back up. 497 if (mReadyToRock) { 498 if (mLocalDeviceIdleController != null) { 499 if (!mReportedActive) { 500 mReportedActive = true; 501 mLocalDeviceIdleController.setJobsActive(true); 502 } 503 } 504 } 505 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 506 } 507 } 508 } 509 510 void reportActive() { 511 // active is true if pending queue contains jobs OR some job is running. 512 boolean active = mPendingJobs.size() > 0; 513 if (mPendingJobs.size() <= 0) { 514 for (int i=0; i<mActiveServices.size(); i++) { 515 JobServiceContext jsc = mActiveServices.get(i); 516 if (jsc.getRunningJob() != null) { 517 active = true; 518 break; 519 } 520 } 521 } 522 523 if (mReportedActive != active) { 524 mReportedActive = active; 525 if (mLocalDeviceIdleController != null) { 526 mLocalDeviceIdleController.setJobsActive(active); 527 } 528 } 529 } 530 531 /** 532 * Initializes the system service. 533 * <p> 534 * Subclasses must define a single argument constructor that accepts the context 535 * and passes it to super. 536 * </p> 537 * 538 * @param context The system server context. 539 */ 540 public JobSchedulerService(Context context) { 541 super(context); 542 // Create the controllers. 543 mControllers = new ArrayList<StateController>(); 544 mControllers.add(ConnectivityController.get(this)); 545 mControllers.add(TimeController.get(this)); 546 mControllers.add(IdleController.get(this)); 547 mControllers.add(BatteryController.get(this)); 548 mControllers.add(AppIdleController.get(this)); 549 mControllers.add(ContentObserverController.get(this)); 550 mControllers.add(DeviceIdleJobsController.get(this)); 551 552 mHandler = new JobHandler(context.getMainLooper()); 553 mJobSchedulerStub = new JobSchedulerStub(); 554 mJobs = JobStore.initAndGet(this); 555 } 556 557 @Override 558 public void onStart() { 559 publishLocalService(JobSchedulerInternal.class, new LocalService()); 560 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub); 561 } 562 563 @Override 564 public void onBootPhase(int phase) { 565 if (PHASE_SYSTEM_SERVICES_READY == phase) { 566 // Register br for package removals and user removals. 567 final IntentFilter filter = new IntentFilter(); 568 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 569 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 570 filter.addDataScheme("package"); 571 getContext().registerReceiverAsUser( 572 mBroadcastReceiver, UserHandle.ALL, filter, null, null); 573 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); 574 getContext().registerReceiverAsUser( 575 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); 576 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE); 577 try { 578 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver, 579 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE 580 | ActivityManager.UID_OBSERVER_IDLE); 581 } catch (RemoteException e) { 582 // ignored; both services live in system_server 583 } 584 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 585 synchronized (mLock) { 586 // Let's go! 587 mReadyToRock = true; 588 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( 589 BatteryStats.SERVICE_NAME)); 590 mLocalDeviceIdleController 591 = LocalServices.getService(DeviceIdleController.LocalService.class); 592 // Create the "runners". 593 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { 594 mActiveServices.add( 595 new JobServiceContext(this, mBatteryStats, mJobPackageTracker, 596 getContext().getMainLooper())); 597 } 598 // Attach jobs to their controllers. 599 mJobs.forEachJob(new JobStatusFunctor() { 600 @Override 601 public void process(JobStatus job) { 602 for (int controller = 0; controller < mControllers.size(); controller++) { 603 final StateController sc = mControllers.get(controller); 604 sc.maybeStartTrackingJobLocked(job, null); 605 } 606 } 607 }); 608 // GO GO GO! 609 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 610 } 611 } 612 } 613 614 /** 615 * Called when we have a job status object that we need to insert in our 616 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know 617 * about. 618 */ 619 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) { 620 synchronized (mLock) { 621 final boolean update = mJobs.add(jobStatus); 622 if (mReadyToRock) { 623 for (int i = 0; i < mControllers.size(); i++) { 624 StateController controller = mControllers.get(i); 625 if (update) { 626 controller.maybeStopTrackingJobLocked(jobStatus, null, true); 627 } 628 controller.maybeStartTrackingJobLocked(jobStatus, lastJob); 629 } 630 } 631 } 632 } 633 634 /** 635 * Called when we want to remove a JobStatus object that we've finished executing. Returns the 636 * object removed. 637 */ 638 private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob, 639 boolean writeBack) { 640 synchronized (mLock) { 641 // Remove from store as well as controllers. 642 final boolean removed = mJobs.remove(jobStatus, writeBack); 643 if (removed && mReadyToRock) { 644 for (int i=0; i<mControllers.size(); i++) { 645 StateController controller = mControllers.get(i); 646 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false); 647 } 648 } 649 return removed; 650 } 651 } 652 653 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) { 654 for (int i=0; i<mActiveServices.size(); i++) { 655 JobServiceContext jsc = mActiveServices.get(i); 656 final JobStatus executing = jsc.getRunningJob(); 657 if (executing != null && executing.matches(job.getUid(), job.getJobId())) { 658 jsc.cancelExecutingJob(reason); 659 return true; 660 } 661 } 662 return false; 663 } 664 665 /** 666 * @param job JobStatus we are querying against. 667 * @return Whether or not the job represented by the status object is currently being run or 668 * is pending. 669 */ 670 private boolean isCurrentlyActiveLocked(JobStatus job) { 671 for (int i=0; i<mActiveServices.size(); i++) { 672 JobServiceContext serviceContext = mActiveServices.get(i); 673 final JobStatus running = serviceContext.getRunningJob(); 674 if (running != null && running.matches(job.getUid(), job.getJobId())) { 675 return true; 676 } 677 } 678 return false; 679 } 680 681 void noteJobsPending(List<JobStatus> jobs) { 682 for (int i = jobs.size() - 1; i >= 0; i--) { 683 JobStatus job = jobs.get(i); 684 mJobPackageTracker.notePending(job); 685 } 686 } 687 688 void noteJobsNonpending(List<JobStatus> jobs) { 689 for (int i = jobs.size() - 1; i >= 0; i--) { 690 JobStatus job = jobs.get(i); 691 mJobPackageTracker.noteNonpending(job); 692 } 693 } 694 695 /** 696 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to 697 * specify an override deadline on a failed job (the failed job will run even though it's not 698 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any 699 * ready job with {@link JobStatus#numFailures} > 0 will be executed. 700 * 701 * @param failureToReschedule Provided job status that we will reschedule. 702 * @return A newly instantiated JobStatus with the same constraints as the last job except 703 * with adjusted timing constraints. 704 * 705 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH 706 */ 707 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) { 708 final long elapsedNowMillis = SystemClock.elapsedRealtime(); 709 final JobInfo job = failureToReschedule.getJob(); 710 711 final long initialBackoffMillis = job.getInitialBackoffMillis(); 712 final int backoffAttempts = failureToReschedule.getNumFailures() + 1; 713 long delayMillis; 714 715 switch (job.getBackoffPolicy()) { 716 case JobInfo.BACKOFF_POLICY_LINEAR: 717 delayMillis = initialBackoffMillis * backoffAttempts; 718 break; 719 default: 720 if (DEBUG) { 721 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential."); 722 } 723 case JobInfo.BACKOFF_POLICY_EXPONENTIAL: 724 delayMillis = 725 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1); 726 break; 727 } 728 delayMillis = 729 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); 730 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, 731 JobStatus.NO_LATEST_RUNTIME, backoffAttempts); 732 for (int ic=0; ic<mControllers.size(); ic++) { 733 StateController controller = mControllers.get(ic); 734 controller.rescheduleForFailure(newJob, failureToReschedule); 735 } 736 return newJob; 737 } 738 739 /** 740 * Called after a periodic has executed so we can reschedule it. We take the last execution 741 * time of the job to be the time of completion (i.e. the time at which this function is 742 * called). 743 * This could be inaccurate b/c the job can run for as long as 744 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead 745 * to underscheduling at least, rather than if we had taken the last execution time to be the 746 * start of the execution. 747 * @return A new job representing the execution criteria for this instantiation of the 748 * recurring job. 749 */ 750 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) { 751 final long elapsedNow = SystemClock.elapsedRealtime(); 752 // Compute how much of the period is remaining. 753 long runEarly = 0L; 754 755 // If this periodic was rescheduled it won't have a deadline. 756 if (periodicToReschedule.hasDeadlineConstraint()) { 757 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L); 758 } 759 long flex = periodicToReschedule.getJob().getFlexMillis(); 760 long period = periodicToReschedule.getJob().getIntervalMillis(); 761 long newLatestRuntimeElapsed = elapsedNow + runEarly + period; 762 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex; 763 764 if (DEBUG) { 765 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" + 766 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); 767 } 768 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, 769 newLatestRuntimeElapsed, 0 /* backoffAttempt */); 770 } 771 772 // JobCompletedListener implementations. 773 774 /** 775 * A job just finished executing. We fetch the 776 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on 777 * whether we want to reschedule we readd it to the controllers. 778 * @param jobStatus Completed job. 779 * @param needsReschedule Whether the implementing class should reschedule this job. 780 */ 781 @Override 782 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) { 783 if (DEBUG) { 784 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); 785 } 786 // Do not write back immediately if this is a periodic job. The job may get lost if system 787 // shuts down before it is added back. 788 if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) { 789 if (DEBUG) { 790 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); 791 } 792 // We still want to check for jobs to execute, because this job may have 793 // scheduled a new job under the same job id, and now we can run it. 794 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); 795 return; 796 } 797 // Note: there is a small window of time in here where, when rescheduling a job, 798 // we will stop monitoring its content providers. This should be fixed by stopping 799 // the old job after scheduling the new one, but since we have no lock held here 800 // that may cause ordering problems if the app removes jobStatus while in here. 801 if (needsReschedule) { 802 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus); 803 startTrackingJob(rescheduled, jobStatus); 804 } else if (jobStatus.getJob().isPeriodic()) { 805 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus); 806 startTrackingJob(rescheduledPeriodic, jobStatus); 807 } 808 reportActive(); 809 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); 810 } 811 812 // StateChangedListener implementations. 813 814 /** 815 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that 816 * some controller's state has changed, so as to run through the list of jobs and start/stop 817 * any that are eligible. 818 */ 819 @Override 820 public void onControllerStateChanged() { 821 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 822 } 823 824 @Override 825 public void onRunJobNow(JobStatus jobStatus) { 826 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget(); 827 } 828 829 private class JobHandler extends Handler { 830 831 public JobHandler(Looper looper) { 832 super(looper); 833 } 834 835 @Override 836 public void handleMessage(Message message) { 837 synchronized (mLock) { 838 if (!mReadyToRock) { 839 return; 840 } 841 } 842 switch (message.what) { 843 case MSG_JOB_EXPIRED: 844 synchronized (mLock) { 845 JobStatus runNow = (JobStatus) message.obj; 846 // runNow can be null, which is a controller's way of indicating that its 847 // state is such that all ready jobs should be run immediately. 848 if (runNow != null && !mPendingJobs.contains(runNow) 849 && mJobs.containsJob(runNow)) { 850 mJobPackageTracker.notePending(runNow); 851 mPendingJobs.add(runNow); 852 } 853 queueReadyJobsForExecutionLockedH(); 854 } 855 break; 856 case MSG_CHECK_JOB: 857 synchronized (mLock) { 858 if (mReportedActive) { 859 // if jobs are currently being run, queue all ready jobs for execution. 860 queueReadyJobsForExecutionLockedH(); 861 } else { 862 // Check the list of jobs and run some of them if we feel inclined. 863 maybeQueueReadyJobsForExecutionLockedH(); 864 } 865 } 866 break; 867 case MSG_CHECK_JOB_GREEDY: 868 synchronized (mLock) { 869 queueReadyJobsForExecutionLockedH(); 870 } 871 break; 872 case MSG_STOP_JOB: 873 cancelJobImpl((JobStatus)message.obj, null); 874 break; 875 } 876 maybeRunPendingJobsH(); 877 // Don't remove JOB_EXPIRED in case one came along while processing the queue. 878 removeMessages(MSG_CHECK_JOB); 879 } 880 881 /** 882 * Run through list of jobs and execute all possible - at least one is expired so we do 883 * as many as we can. 884 */ 885 private void queueReadyJobsForExecutionLockedH() { 886 if (DEBUG) { 887 Slog.d(TAG, "queuing all ready jobs for execution:"); 888 } 889 noteJobsNonpending(mPendingJobs); 890 mPendingJobs.clear(); 891 mJobs.forEachJob(mReadyQueueFunctor); 892 mReadyQueueFunctor.postProcess(); 893 894 if (DEBUG) { 895 final int queuedJobs = mPendingJobs.size(); 896 if (queuedJobs == 0) { 897 Slog.d(TAG, "No jobs pending."); 898 } else { 899 Slog.d(TAG, queuedJobs + " jobs queued."); 900 } 901 } 902 } 903 904 class ReadyJobQueueFunctor implements JobStatusFunctor { 905 ArrayList<JobStatus> newReadyJobs; 906 907 @Override 908 public void process(JobStatus job) { 909 if (isReadyToBeExecutedLocked(job)) { 910 if (DEBUG) { 911 Slog.d(TAG, " queued " + job.toShortString()); 912 } 913 if (newReadyJobs == null) { 914 newReadyJobs = new ArrayList<JobStatus>(); 915 } 916 newReadyJobs.add(job); 917 } else if (areJobConstraintsNotSatisfiedLocked(job)) { 918 stopJobOnServiceContextLocked(job, 919 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED); 920 } 921 } 922 923 public void postProcess() { 924 if (newReadyJobs != null) { 925 noteJobsPending(newReadyJobs); 926 mPendingJobs.addAll(newReadyJobs); 927 } 928 newReadyJobs = null; 929 } 930 } 931 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor(); 932 933 /** 934 * The state of at least one job has changed. Here is where we could enforce various 935 * policies on when we want to execute jobs. 936 * Right now the policy is such: 937 * If >1 of the ready jobs is idle mode we send all of them off 938 * if more than 2 network connectivity jobs are ready we send them all off. 939 * If more than 4 jobs total are ready we send them all off. 940 * TODO: It would be nice to consolidate these sort of high-level policies somewhere. 941 */ 942 class MaybeReadyJobQueueFunctor implements JobStatusFunctor { 943 int chargingCount; 944 int idleCount; 945 int backoffCount; 946 int connectivityCount; 947 int contentCount; 948 List<JobStatus> runnableJobs; 949 950 public MaybeReadyJobQueueFunctor() { 951 reset(); 952 } 953 954 // Functor method invoked for each job via JobStore.forEachJob() 955 @Override 956 public void process(JobStatus job) { 957 if (isReadyToBeExecutedLocked(job)) { 958 try { 959 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(), 960 job.getJob().getService().getPackageName()) 961 == ActivityManager.APP_START_MODE_DISABLED) { 962 Slog.w(TAG, "Aborting job " + job.getUid() + ":" 963 + job.getJob().toString() + " -- package not allowed to start"); 964 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget(); 965 return; 966 } 967 } catch (RemoteException e) { 968 } 969 if (job.getNumFailures() > 0) { 970 backoffCount++; 971 } 972 if (job.hasIdleConstraint()) { 973 idleCount++; 974 } 975 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint() 976 || job.hasNotRoamingConstraint()) { 977 connectivityCount++; 978 } 979 if (job.hasChargingConstraint()) { 980 chargingCount++; 981 } 982 if (job.hasContentTriggerConstraint()) { 983 contentCount++; 984 } 985 if (runnableJobs == null) { 986 runnableJobs = new ArrayList<>(); 987 } 988 runnableJobs.add(job); 989 } else if (areJobConstraintsNotSatisfiedLocked(job)) { 990 stopJobOnServiceContextLocked(job, 991 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED); 992 } 993 } 994 995 public void postProcess() { 996 if (backoffCount > 0 || 997 idleCount >= MIN_IDLE_COUNT || 998 connectivityCount >= MIN_CONNECTIVITY_COUNT || 999 chargingCount >= MIN_CHARGING_COUNT || 1000 contentCount >= MIN_CONTENT_COUNT || 1001 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) { 1002 if (DEBUG) { 1003 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); 1004 } 1005 noteJobsPending(runnableJobs); 1006 mPendingJobs.addAll(runnableJobs); 1007 } else { 1008 if (DEBUG) { 1009 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything."); 1010 } 1011 } 1012 1013 // Be ready for next time 1014 reset(); 1015 } 1016 1017 private void reset() { 1018 chargingCount = 0; 1019 idleCount = 0; 1020 backoffCount = 0; 1021 connectivityCount = 0; 1022 contentCount = 0; 1023 runnableJobs = null; 1024 } 1025 } 1026 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor(); 1027 1028 private void maybeQueueReadyJobsForExecutionLockedH() { 1029 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs..."); 1030 1031 noteJobsNonpending(mPendingJobs); 1032 mPendingJobs.clear(); 1033 mJobs.forEachJob(mMaybeQueueFunctor); 1034 mMaybeQueueFunctor.postProcess(); 1035 } 1036 1037 /** 1038 * Criteria for moving a job into the pending queue: 1039 * - It's ready. 1040 * - It's not pending. 1041 * - It's not already running on a JSC. 1042 * - The user that requested the job is running. 1043 * - The component is enabled and runnable. 1044 */ 1045 private boolean isReadyToBeExecutedLocked(JobStatus job) { 1046 final boolean jobReady = job.isReady(); 1047 final boolean jobPending = mPendingJobs.contains(job); 1048 final boolean jobActive = isCurrentlyActiveLocked(job); 1049 1050 final int userId = job.getUserId(); 1051 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId); 1052 final boolean componentPresent; 1053 try { 1054 componentPresent = (AppGlobals.getPackageManager().getServiceInfo( 1055 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING, 1056 userId) != null); 1057 } catch (RemoteException e) { 1058 throw e.rethrowAsRuntimeException(); 1059 } 1060 1061 if (DEBUG) { 1062 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() 1063 + " ready=" + jobReady + " pending=" + jobPending 1064 + " active=" + jobActive + " userStarted=" + userStarted 1065 + " componentPresent=" + componentPresent); 1066 } 1067 return userStarted && componentPresent && jobReady && !jobPending && !jobActive; 1068 } 1069 1070 /** 1071 * Criteria for cancelling an active job: 1072 * - It's not ready 1073 * - It's running on a JSC. 1074 */ 1075 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) { 1076 return !job.isReady() && isCurrentlyActiveLocked(job); 1077 } 1078 1079 /** 1080 * Reconcile jobs in the pending queue against available execution contexts. 1081 * A controller can force a job into the pending queue even if it's already running, but 1082 * here is where we decide whether to actually execute it. 1083 */ 1084 private void maybeRunPendingJobsH() { 1085 synchronized (mLock) { 1086 if (DEBUG) { 1087 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); 1088 } 1089 assignJobsToContextsLocked(); 1090 reportActive(); 1091 } 1092 } 1093 } 1094 1095 private int adjustJobPriority(int curPriority, JobStatus job) { 1096 if (curPriority < JobInfo.PRIORITY_TOP_APP) { 1097 float factor = mJobPackageTracker.getLoadFactor(job); 1098 if (factor >= HEAVY_USE_FACTOR) { 1099 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING; 1100 } else if (factor >= MODERATE_USE_FACTOR) { 1101 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING; 1102 } 1103 } 1104 return curPriority; 1105 } 1106 1107 private int evaluateJobPriorityLocked(JobStatus job) { 1108 int priority = job.getPriority(); 1109 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) { 1110 return adjustJobPriority(priority, job); 1111 } 1112 int override = mUidPriorityOverride.get(job.getSourceUid(), 0); 1113 if (override != 0) { 1114 return adjustJobPriority(override, job); 1115 } 1116 return adjustJobPriority(priority, job); 1117 } 1118 1119 /** 1120 * Takes jobs from pending queue and runs them on available contexts. 1121 * If no contexts are available, preempts lower priority jobs to 1122 * run higher priority ones. 1123 * Lock on mJobs before calling this function. 1124 */ 1125 private void assignJobsToContextsLocked() { 1126 if (DEBUG) { 1127 Slog.d(TAG, printPendingQueue()); 1128 } 1129 1130 int memLevel; 1131 try { 1132 memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel(); 1133 } catch (RemoteException e) { 1134 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; 1135 } 1136 switch (memLevel) { 1137 case ProcessStats.ADJ_MEM_FACTOR_MODERATE: 1138 mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) * 2) / 3; 1139 break; 1140 case ProcessStats.ADJ_MEM_FACTOR_LOW: 1141 mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) / 3; 1142 break; 1143 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: 1144 mMaxActiveJobs = 1; 1145 break; 1146 default: 1147 mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT; 1148 break; 1149 } 1150 1151 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap; 1152 boolean[] act = mTmpAssignAct; 1153 int[] preferredUidForContext = mTmpAssignPreferredUidForContext; 1154 int numActive = 0; 1155 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { 1156 final JobServiceContext js = mActiveServices.get(i); 1157 if ((contextIdToJobMap[i] = js.getRunningJob()) != null) { 1158 numActive++; 1159 } 1160 act[i] = false; 1161 preferredUidForContext[i] = js.getPreferredUid(); 1162 } 1163 if (DEBUG) { 1164 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); 1165 } 1166 for (int i=0; i<mPendingJobs.size(); i++) { 1167 JobStatus nextPending = mPendingJobs.get(i); 1168 1169 // If job is already running, go to next job. 1170 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); 1171 if (jobRunningContext != -1) { 1172 continue; 1173 } 1174 1175 final int priority = evaluateJobPriorityLocked(nextPending); 1176 nextPending.lastEvaluatedPriority = priority; 1177 1178 // Find a context for nextPending. The context should be available OR 1179 // it should have lowest priority among all running jobs 1180 // (sharing the same Uid as nextPending) 1181 int minPriority = Integer.MAX_VALUE; 1182 int minPriorityContextId = -1; 1183 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) { 1184 JobStatus job = contextIdToJobMap[j]; 1185 int preferredUid = preferredUidForContext[j]; 1186 if (job == null) { 1187 if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) && 1188 (preferredUid == nextPending.getUid() || 1189 preferredUid == JobServiceContext.NO_PREFERRED_UID)) { 1190 // This slot is free, and we haven't yet hit the limit on 1191 // concurrent jobs... we can just throw the job in to here. 1192 minPriorityContextId = j; 1193 numActive++; 1194 break; 1195 } 1196 // No job on this context, but nextPending can't run here because 1197 // the context has a preferred Uid or we have reached the limit on 1198 // concurrent jobs. 1199 continue; 1200 } 1201 if (job.getUid() != nextPending.getUid()) { 1202 continue; 1203 } 1204 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) { 1205 continue; 1206 } 1207 if (minPriority > nextPending.lastEvaluatedPriority) { 1208 minPriority = nextPending.lastEvaluatedPriority; 1209 minPriorityContextId = j; 1210 } 1211 } 1212 if (minPriorityContextId != -1) { 1213 contextIdToJobMap[minPriorityContextId] = nextPending; 1214 act[minPriorityContextId] = true; 1215 } 1216 } 1217 if (DEBUG) { 1218 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); 1219 } 1220 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) { 1221 boolean preservePreferredUid = false; 1222 if (act[i]) { 1223 JobStatus js = mActiveServices.get(i).getRunningJob(); 1224 if (js != null) { 1225 if (DEBUG) { 1226 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob()); 1227 } 1228 // preferredUid will be set to uid of currently running job. 1229 mActiveServices.get(i).preemptExecutingJob(); 1230 preservePreferredUid = true; 1231 } else { 1232 final JobStatus pendingJob = contextIdToJobMap[i]; 1233 if (DEBUG) { 1234 Slog.d(TAG, "About to run job on context " 1235 + String.valueOf(i) + ", job: " + pendingJob); 1236 } 1237 for (int ic=0; ic<mControllers.size(); ic++) { 1238 mControllers.get(ic).prepareForExecutionLocked(pendingJob); 1239 } 1240 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) { 1241 Slog.d(TAG, "Error executing " + pendingJob); 1242 } 1243 if (mPendingJobs.remove(pendingJob)) { 1244 mJobPackageTracker.noteNonpending(pendingJob); 1245 } 1246 } 1247 } 1248 if (!preservePreferredUid) { 1249 mActiveServices.get(i).clearPreferredUid(); 1250 } 1251 } 1252 } 1253 1254 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { 1255 for (int i=0; i<map.length; i++) { 1256 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { 1257 return i; 1258 } 1259 } 1260 return -1; 1261 } 1262 1263 final class LocalService implements JobSchedulerInternal { 1264 1265 /** 1266 * Returns a list of all pending jobs. A running job is not considered pending. Periodic 1267 * jobs are always considered pending. 1268 */ 1269 @Override 1270 public List<JobInfo> getSystemScheduledPendingJobs() { 1271 synchronized (mLock) { 1272 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>(); 1273 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() { 1274 @Override 1275 public void process(JobStatus job) { 1276 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) { 1277 pendingJobs.add(job.getJob()); 1278 } 1279 } 1280 }); 1281 return pendingJobs; 1282 } 1283 } 1284 } 1285 1286 /** 1287 * Binder stub trampoline implementation 1288 */ 1289 final class JobSchedulerStub extends IJobScheduler.Stub { 1290 /** Cache determination of whether a given app can persist jobs 1291 * key is uid of the calling app; value is undetermined/true/false 1292 */ 1293 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); 1294 1295 // Enforce that only the app itself (or shared uid participant) can schedule a 1296 // job that runs one of the app's services, as well as verifying that the 1297 // named service properly requires the BIND_JOB_SERVICE permission 1298 private void enforceValidJobRequest(int uid, JobInfo job) { 1299 final IPackageManager pm = AppGlobals.getPackageManager(); 1300 final ComponentName service = job.getService(); 1301 try { 1302 ServiceInfo si = pm.getServiceInfo(service, 1303 PackageManager.MATCH_DIRECT_BOOT_AWARE 1304 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 1305 UserHandle.getUserId(uid)); 1306 if (si == null) { 1307 throw new IllegalArgumentException("No such service " + service); 1308 } 1309 if (si.applicationInfo.uid != uid) { 1310 throw new IllegalArgumentException("uid " + uid + 1311 " cannot schedule job in " + service.getPackageName()); 1312 } 1313 if (!JobService.PERMISSION_BIND.equals(si.permission)) { 1314 throw new IllegalArgumentException("Scheduled service " + service 1315 + " does not require android.permission.BIND_JOB_SERVICE permission"); 1316 } 1317 } catch (RemoteException e) { 1318 // Can't happen; the Package Manager is in this same process 1319 } 1320 } 1321 1322 private boolean canPersistJobs(int pid, int uid) { 1323 // If we get this far we're good to go; all we need to do now is check 1324 // whether the app is allowed to persist its scheduled work. 1325 final boolean canPersist; 1326 synchronized (mPersistCache) { 1327 Boolean cached = mPersistCache.get(uid); 1328 if (cached != null) { 1329 canPersist = cached.booleanValue(); 1330 } else { 1331 // Persisting jobs is tantamount to running at boot, so we permit 1332 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED 1333 // permission 1334 int result = getContext().checkPermission( 1335 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid); 1336 canPersist = (result == PackageManager.PERMISSION_GRANTED); 1337 mPersistCache.put(uid, canPersist); 1338 } 1339 } 1340 return canPersist; 1341 } 1342 1343 // IJobScheduler implementation 1344 @Override 1345 public int schedule(JobInfo job) throws RemoteException { 1346 if (DEBUG) { 1347 Slog.d(TAG, "Scheduling job: " + job.toString()); 1348 } 1349 final int pid = Binder.getCallingPid(); 1350 final int uid = Binder.getCallingUid(); 1351 1352 enforceValidJobRequest(uid, job); 1353 if (job.isPersisted()) { 1354 if (!canPersistJobs(pid, uid)) { 1355 throw new IllegalArgumentException("Error: requested job be persisted without" 1356 + " holding RECEIVE_BOOT_COMPLETED permission."); 1357 } 1358 } 1359 1360 long ident = Binder.clearCallingIdentity(); 1361 try { 1362 return JobSchedulerService.this.schedule(job, uid); 1363 } finally { 1364 Binder.restoreCallingIdentity(ident); 1365 } 1366 } 1367 1368 @Override 1369 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag) 1370 throws RemoteException { 1371 final int callerUid = Binder.getCallingUid(); 1372 if (DEBUG) { 1373 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString() 1374 + " on behalf of " + packageName); 1375 } 1376 1377 if (packageName == null) { 1378 throw new NullPointerException("Must specify a package for scheduleAsPackage()"); 1379 } 1380 1381 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission( 1382 android.Manifest.permission.UPDATE_DEVICE_STATS); 1383 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) { 1384 throw new SecurityException("Caller uid " + callerUid 1385 + " not permitted to schedule jobs for other apps"); 1386 } 1387 1388 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) { 1389 getContext().enforceCallingOrSelfPermission( 1390 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG); 1391 } 1392 1393 long ident = Binder.clearCallingIdentity(); 1394 try { 1395 return JobSchedulerService.this.scheduleAsPackage(job, callerUid, 1396 packageName, userId, tag); 1397 } finally { 1398 Binder.restoreCallingIdentity(ident); 1399 } 1400 } 1401 1402 @Override 1403 public List<JobInfo> getAllPendingJobs() throws RemoteException { 1404 final int uid = Binder.getCallingUid(); 1405 1406 long ident = Binder.clearCallingIdentity(); 1407 try { 1408 return JobSchedulerService.this.getPendingJobs(uid); 1409 } finally { 1410 Binder.restoreCallingIdentity(ident); 1411 } 1412 } 1413 1414 @Override 1415 public JobInfo getPendingJob(int jobId) throws RemoteException { 1416 final int uid = Binder.getCallingUid(); 1417 1418 long ident = Binder.clearCallingIdentity(); 1419 try { 1420 return JobSchedulerService.this.getPendingJob(uid, jobId); 1421 } finally { 1422 Binder.restoreCallingIdentity(ident); 1423 } 1424 } 1425 1426 @Override 1427 public void cancelAll() throws RemoteException { 1428 final int uid = Binder.getCallingUid(); 1429 1430 long ident = Binder.clearCallingIdentity(); 1431 try { 1432 JobSchedulerService.this.cancelJobsForUid(uid, true); 1433 } finally { 1434 Binder.restoreCallingIdentity(ident); 1435 } 1436 } 1437 1438 @Override 1439 public void cancel(int jobId) throws RemoteException { 1440 final int uid = Binder.getCallingUid(); 1441 1442 long ident = Binder.clearCallingIdentity(); 1443 try { 1444 JobSchedulerService.this.cancelJob(uid, jobId); 1445 } finally { 1446 Binder.restoreCallingIdentity(ident); 1447 } 1448 } 1449 1450 /** 1451 * "dumpsys" infrastructure 1452 */ 1453 @Override 1454 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1455 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1456 1457 long identityToken = Binder.clearCallingIdentity(); 1458 try { 1459 JobSchedulerService.this.dumpInternal(pw, args); 1460 } finally { 1461 Binder.restoreCallingIdentity(identityToken); 1462 } 1463 } 1464 1465 @Override 1466 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, 1467 String[] args, ResultReceiver resultReceiver) throws RemoteException { 1468 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec( 1469 this, in, out, err, args, resultReceiver); 1470 } 1471 }; 1472 1473 // Shell command infrastructure: run the given job immediately 1474 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) { 1475 if (DEBUG) { 1476 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId 1477 + " " + jobId + " f=" + force); 1478 } 1479 1480 try { 1481 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId); 1482 if (uid < 0) { 1483 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE; 1484 } 1485 1486 synchronized (mLock) { 1487 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId); 1488 if (js == null) { 1489 return JobSchedulerShellCommand.CMD_ERR_NO_JOB; 1490 } 1491 1492 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT; 1493 if (!js.isConstraintsSatisfied()) { 1494 js.overrideState = 0; 1495 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS; 1496 } 1497 1498 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); 1499 } 1500 } catch (RemoteException e) { 1501 // can't happen 1502 } 1503 return 0; 1504 } 1505 1506 private String printContextIdToJobMap(JobStatus[] map, String initial) { 1507 StringBuilder s = new StringBuilder(initial + ": "); 1508 for (int i=0; i<map.length; i++) { 1509 s.append("(") 1510 .append(map[i] == null? -1: map[i].getJobId()) 1511 .append(map[i] == null? -1: map[i].getUid()) 1512 .append(")" ); 1513 } 1514 return s.toString(); 1515 } 1516 1517 private String printPendingQueue() { 1518 StringBuilder s = new StringBuilder("Pending queue: "); 1519 Iterator<JobStatus> it = mPendingJobs.iterator(); 1520 while (it.hasNext()) { 1521 JobStatus js = it.next(); 1522 s.append("(") 1523 .append(js.getJob().getId()) 1524 .append(", ") 1525 .append(js.getUid()) 1526 .append(") "); 1527 } 1528 return s.toString(); 1529 } 1530 1531 static void dumpHelp(PrintWriter pw) { 1532 pw.println("Job Scheduler (jobscheduler) dump options:"); 1533 pw.println(" [-h] [package] ..."); 1534 pw.println(" -h: print this help"); 1535 pw.println(" [package] is an optional package name to limit the output to."); 1536 } 1537 1538 void dumpInternal(final PrintWriter pw, String[] args) { 1539 int filterUid = -1; 1540 if (!ArrayUtils.isEmpty(args)) { 1541 int opti = 0; 1542 while (opti < args.length) { 1543 String arg = args[opti]; 1544 if ("-h".equals(arg)) { 1545 dumpHelp(pw); 1546 return; 1547 } else if ("-a".equals(arg)) { 1548 // Ignore, we always dump all. 1549 } else if (arg.length() > 0 && arg.charAt(0) == '-') { 1550 pw.println("Unknown option: " + arg); 1551 return; 1552 } else { 1553 break; 1554 } 1555 opti++; 1556 } 1557 if (opti < args.length) { 1558 String pkg = args[opti]; 1559 try { 1560 filterUid = getContext().getPackageManager().getPackageUid(pkg, 1561 PackageManager.MATCH_UNINSTALLED_PACKAGES); 1562 } catch (NameNotFoundException ignored) { 1563 pw.println("Invalid package: " + pkg); 1564 return; 1565 } 1566 } 1567 } 1568 1569 final int filterUidFinal = UserHandle.getAppId(filterUid); 1570 final long now = SystemClock.elapsedRealtime(); 1571 synchronized (mLock) { 1572 pw.println("Started users: " + Arrays.toString(mStartedUsers)); 1573 pw.println("Registered jobs:"); 1574 if (mJobs.size() > 0) { 1575 mJobs.forEachJob(new JobStatusFunctor() { 1576 private int index = 0; 1577 1578 @Override 1579 public void process(JobStatus job) { 1580 pw.print(" Job #"); pw.print(index++); pw.print(": "); 1581 pw.println(job.toShortString()); 1582 1583 // Skip printing details if the caller requested a filter 1584 if (!job.shouldDump(filterUidFinal)) { 1585 return; 1586 } 1587 1588 job.dump(pw, " ", true); 1589 pw.print(" Ready: "); 1590 pw.print(mHandler.isReadyToBeExecutedLocked(job)); 1591 pw.print(" (job="); 1592 pw.print(job.isReady()); 1593 pw.print(" pending="); 1594 pw.print(mPendingJobs.contains(job)); 1595 pw.print(" active="); 1596 pw.print(isCurrentlyActiveLocked(job)); 1597 pw.print(" user="); 1598 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId())); 1599 pw.println(")"); 1600 } 1601 }); 1602 } else { 1603 pw.println(" None."); 1604 } 1605 for (int i=0; i<mControllers.size(); i++) { 1606 pw.println(); 1607 mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal); 1608 } 1609 pw.println(); 1610 pw.println("Uid priority overrides:"); 1611 for (int i=0; i< mUidPriorityOverride.size(); i++) { 1612 int uid = mUidPriorityOverride.keyAt(i); 1613 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) { 1614 pw.print(" "); pw.print(UserHandle.formatUid(uid)); 1615 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i)); 1616 } 1617 } 1618 pw.println(); 1619 mJobPackageTracker.dump(pw, "", filterUidFinal); 1620 pw.println(); 1621 if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) { 1622 pw.println(); 1623 } 1624 pw.println("Pending queue:"); 1625 for (int i=0; i<mPendingJobs.size(); i++) { 1626 JobStatus job = mPendingJobs.get(i); 1627 pw.print(" Pending #"); pw.print(i); pw.print(": "); 1628 pw.println(job.toShortString()); 1629 job.dump(pw, " ", false); 1630 int priority = evaluateJobPriorityLocked(job); 1631 if (priority != JobInfo.PRIORITY_DEFAULT) { 1632 pw.print(" Evaluated priority: "); pw.println(priority); 1633 } 1634 pw.print(" Tag: "); pw.println(job.getTag()); 1635 } 1636 pw.println(); 1637 pw.println("Active jobs:"); 1638 for (int i=0; i<mActiveServices.size(); i++) { 1639 JobServiceContext jsc = mActiveServices.get(i); 1640 pw.print(" Slot #"); pw.print(i); pw.print(": "); 1641 if (jsc.getRunningJob() == null) { 1642 pw.println("inactive"); 1643 continue; 1644 } else { 1645 pw.println(jsc.getRunningJob().toShortString()); 1646 pw.print(" Running for: "); 1647 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw); 1648 pw.print(", timeout at: "); 1649 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw); 1650 pw.println(); 1651 jsc.getRunningJob().dump(pw, " ", false); 1652 int priority = evaluateJobPriorityLocked(jsc.getRunningJob()); 1653 if (priority != JobInfo.PRIORITY_DEFAULT) { 1654 pw.print(" Evaluated priority: "); pw.println(priority); 1655 } 1656 } 1657 } 1658 if (filterUid == -1) { 1659 pw.println(); 1660 pw.print("mReadyToRock="); pw.println(mReadyToRock); 1661 pw.print("mReportedActive="); pw.println(mReportedActive); 1662 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs); 1663 } 1664 } 1665 pw.println(); 1666 } 1667} 1668