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