1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.job; 18 19 import java.io.FileDescriptor; 20 import java.io.PrintWriter; 21 import java.util.ArrayList; 22 import java.util.Iterator; 23 import java.util.List; 24 25 import android.app.AppGlobals; 26 import android.app.job.JobInfo; 27 import android.app.job.JobScheduler; 28 import android.app.job.JobService; 29 import android.app.job.IJobScheduler; 30 import android.content.BroadcastReceiver; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.IntentFilter; 35 import android.content.pm.IPackageManager; 36 import android.content.pm.PackageManager; 37 import android.content.pm.ServiceInfo; 38 import android.os.BatteryStats; 39 import android.os.Binder; 40 import android.os.Handler; 41 import android.os.Looper; 42 import android.os.Message; 43 import android.os.RemoteException; 44 import android.os.ServiceManager; 45 import android.os.SystemClock; 46 import android.os.UserHandle; 47 import android.util.ArraySet; 48 import android.util.Slog; 49 import android.util.SparseArray; 50 51 import com.android.internal.app.IBatteryStats; 52 import com.android.server.job.controllers.BatteryController; 53 import com.android.server.job.controllers.ConnectivityController; 54 import com.android.server.job.controllers.IdleController; 55 import com.android.server.job.controllers.JobStatus; 56 import com.android.server.job.controllers.StateController; 57 import com.android.server.job.controllers.TimeController; 58 59 /** 60 * Responsible for taking jobs representing work to be performed by a client app, and determining 61 * based on the criteria specified when that job should be run against the client application's 62 * endpoint. 63 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing 64 * about constraints, or the state of active jobs. It receives callbacks from the various 65 * controllers and completed jobs and operates accordingly. 66 * 67 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object. 68 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}. 69 * @hide 70 */ 71 public class JobSchedulerService extends com.android.server.SystemService 72 implements StateChangedListener, JobCompletedListener { 73 static final boolean DEBUG = false; 74 /** The number of concurrent jobs we run at one time. */ 75 private static final int MAX_JOB_CONTEXTS_COUNT = 3; 76 static final String TAG = "JobSchedulerService"; 77 /** Master list of jobs. */ 78 final JobStore mJobs; 79 80 static final int MSG_JOB_EXPIRED = 0; 81 static final int MSG_CHECK_JOB = 1; 82 83 // Policy constants 84 /** 85 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things 86 * early. 87 */ 88 static final int MIN_IDLE_COUNT = 1; 89 /** 90 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things 91 * early. 92 */ 93 static final int MIN_CHARGING_COUNT = 1; 94 /** 95 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule 96 * things early. 97 */ 98 static final int MIN_CONNECTIVITY_COUNT = 2; 99 /** 100 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running 101 * some work early. 102 * This is correlated with the amount of batching we'll be able to do. 103 */ 104 static final int MIN_READY_JOBS_COUNT = 2; 105 106 /** 107 * Track Services that have currently active or pending jobs. The index is provided by 108 * {@link JobStatus#getServiceToken()} 109 */ 110 final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>(); 111 /** List of controllers that will notify this service of updates to jobs. */ 112 List<StateController> mControllers; 113 /** 114 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list 115 * when ready to execute them. 116 */ 117 final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>(); 118 119 final ArrayList<Integer> mStartedUsers = new ArrayList(); 120 121 final JobHandler mHandler; 122 final JobSchedulerStub mJobSchedulerStub; 123 124 IBatteryStats mBatteryStats; 125 126 /** 127 * Set to true once we are allowed to run third party apps. 128 */ 129 boolean mReadyToRock; 130 131 /** 132 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we 133 * still clean up. On reinstall the package will have a new uid. 134 */ 135 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 136 @Override 137 public void onReceive(Context context, Intent intent) { 138 Slog.d(TAG, "Receieved: " + intent.getAction()); 139 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 140 // If this is an outright uninstall rather than the first half of an 141 // app update sequence, cancel the jobs associated with the app. 142 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 143 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); 144 if (DEBUG) { 145 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); 146 } 147 cancelJobsForUid(uidRemoved); 148 } 149 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { 150 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); 151 if (DEBUG) { 152 Slog.d(TAG, "Removing jobs for user: " + userId); 153 } 154 cancelJobsForUser(userId); 155 } 156 } 157 }; 158 159 @Override 160 public void onStartUser(int userHandle) { 161 mStartedUsers.add(userHandle); 162 // Let's kick any outstanding jobs for this user. 163 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 164 } 165 166 @Override 167 public void onStopUser(int userHandle) { 168 mStartedUsers.remove(Integer.valueOf(userHandle)); 169 } 170 171 /** 172 * Entry point from client to schedule the provided job. 173 * This cancels the job if it's already been scheduled, and replaces it with the one provided. 174 * @param job JobInfo object containing execution parameters 175 * @param uId The package identifier of the application this job is for. 176 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes. 177 */ 178 public int schedule(JobInfo job, int uId) { 179 JobStatus jobStatus = new JobStatus(job, uId); 180 cancelJob(uId, job.getId()); 181 startTrackingJob(jobStatus); 182 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 183 return JobScheduler.RESULT_SUCCESS; 184 } 185 186 public List<JobInfo> getPendingJobs(int uid) { 187 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(); 188 synchronized (mJobs) { 189 ArraySet<JobStatus> jobs = mJobs.getJobs(); 190 for (int i=0; i<jobs.size(); i++) { 191 JobStatus job = jobs.valueAt(i); 192 if (job.getUid() == uid) { 193 outList.add(job.getJob()); 194 } 195 } 196 } 197 return outList; 198 } 199 200 private void cancelJobsForUser(int userHandle) { 201 List<JobStatus> jobsForUser; 202 synchronized (mJobs) { 203 jobsForUser = mJobs.getJobsByUser(userHandle); 204 } 205 for (int i=0; i<jobsForUser.size(); i++) { 206 JobStatus toRemove = jobsForUser.get(i); 207 cancelJobImpl(toRemove); 208 } 209 } 210 211 /** 212 * Entry point from client to cancel all jobs originating from their uid. 213 * This will remove the job from the master list, and cancel the job if it was staged for 214 * execution or being executed. 215 * @param uid Uid to check against for removal of a job. 216 */ 217 public void cancelJobsForUid(int uid) { 218 List<JobStatus> jobsForUid; 219 synchronized (mJobs) { 220 jobsForUid = mJobs.getJobsByUid(uid); 221 } 222 for (int i=0; i<jobsForUid.size(); i++) { 223 JobStatus toRemove = jobsForUid.get(i); 224 cancelJobImpl(toRemove); 225 } 226 } 227 228 /** 229 * Entry point from client to cancel the job corresponding to the jobId provided. 230 * This will remove the job from the master list, and cancel the job if it was staged for 231 * execution or being executed. 232 * @param uid Uid of the calling client. 233 * @param jobId Id of the job, provided at schedule-time. 234 */ 235 public void cancelJob(int uid, int jobId) { 236 JobStatus toCancel; 237 synchronized (mJobs) { 238 toCancel = mJobs.getJobByUidAndJobId(uid, jobId); 239 } 240 if (toCancel != null) { 241 cancelJobImpl(toCancel); 242 } 243 } 244 245 private void cancelJobImpl(JobStatus cancelled) { 246 if (DEBUG) { 247 Slog.d(TAG, "Cancelling: " + cancelled); 248 } 249 stopTrackingJob(cancelled); 250 synchronized (mJobs) { 251 // Remove from pending queue. 252 mPendingJobs.remove(cancelled); 253 // Cancel if running. 254 stopJobOnServiceContextLocked(cancelled); 255 } 256 } 257 258 /** 259 * Initializes the system service. 260 * <p> 261 * Subclasses must define a single argument constructor that accepts the context 262 * and passes it to super. 263 * </p> 264 * 265 * @param context The system server context. 266 */ 267 public JobSchedulerService(Context context) { 268 super(context); 269 // Create the controllers. 270 mControllers = new ArrayList<StateController>(); 271 mControllers.add(ConnectivityController.get(this)); 272 mControllers.add(TimeController.get(this)); 273 mControllers.add(IdleController.get(this)); 274 mControllers.add(BatteryController.get(this)); 275 276 mHandler = new JobHandler(context.getMainLooper()); 277 mJobSchedulerStub = new JobSchedulerStub(); 278 mJobs = JobStore.initAndGet(this); 279 } 280 281 @Override 282 public void onStart() { 283 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub); 284 } 285 286 @Override 287 public void onBootPhase(int phase) { 288 if (PHASE_SYSTEM_SERVICES_READY == phase) { 289 // Register br for package removals and user removals. 290 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); 291 filter.addDataScheme("package"); 292 getContext().registerReceiverAsUser( 293 mBroadcastReceiver, UserHandle.ALL, filter, null, null); 294 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); 295 getContext().registerReceiverAsUser( 296 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); 297 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 298 synchronized (mJobs) { 299 // Let's go! 300 mReadyToRock = true; 301 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( 302 BatteryStats.SERVICE_NAME)); 303 // Create the "runners". 304 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { 305 mActiveServices.add( 306 new JobServiceContext(this, mBatteryStats, 307 getContext().getMainLooper())); 308 } 309 // Attach jobs to their controllers. 310 ArraySet<JobStatus> jobs = mJobs.getJobs(); 311 for (int i=0; i<jobs.size(); i++) { 312 JobStatus job = jobs.valueAt(i); 313 for (int controller=0; controller<mControllers.size(); controller++) { 314 mControllers.get(controller).maybeStartTrackingJob(job); 315 } 316 } 317 // GO GO GO! 318 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 319 } 320 } 321 } 322 323 /** 324 * Called when we have a job status object that we need to insert in our 325 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know 326 * about. 327 */ 328 private void startTrackingJob(JobStatus jobStatus) { 329 boolean update; 330 boolean rocking; 331 synchronized (mJobs) { 332 update = mJobs.add(jobStatus); 333 rocking = mReadyToRock; 334 } 335 if (rocking) { 336 for (int i=0; i<mControllers.size(); i++) { 337 StateController controller = mControllers.get(i); 338 if (update) { 339 controller.maybeStopTrackingJob(jobStatus); 340 } 341 controller.maybeStartTrackingJob(jobStatus); 342 } 343 } 344 } 345 346 /** 347 * Called when we want to remove a JobStatus object that we've finished executing. Returns the 348 * object removed. 349 */ 350 private boolean stopTrackingJob(JobStatus jobStatus) { 351 boolean removed; 352 boolean rocking; 353 synchronized (mJobs) { 354 // Remove from store as well as controllers. 355 removed = mJobs.remove(jobStatus); 356 rocking = mReadyToRock; 357 } 358 if (removed && rocking) { 359 for (int i=0; i<mControllers.size(); i++) { 360 StateController controller = mControllers.get(i); 361 controller.maybeStopTrackingJob(jobStatus); 362 } 363 } 364 return removed; 365 } 366 367 private boolean stopJobOnServiceContextLocked(JobStatus job) { 368 for (int i=0; i<mActiveServices.size(); i++) { 369 JobServiceContext jsc = mActiveServices.get(i); 370 final JobStatus executing = jsc.getRunningJob(); 371 if (executing != null && executing.matches(job.getUid(), job.getJobId())) { 372 jsc.cancelExecutingJob(); 373 return true; 374 } 375 } 376 return false; 377 } 378 379 /** 380 * @param job JobStatus we are querying against. 381 * @return Whether or not the job represented by the status object is currently being run or 382 * is pending. 383 */ 384 private boolean isCurrentlyActiveLocked(JobStatus job) { 385 for (int i=0; i<mActiveServices.size(); i++) { 386 JobServiceContext serviceContext = mActiveServices.get(i); 387 final JobStatus running = serviceContext.getRunningJob(); 388 if (running != null && running.matches(job.getUid(), job.getJobId())) { 389 return true; 390 } 391 } 392 return false; 393 } 394 395 /** 396 * A job is rescheduled with exponential back-off if the client requests this from their 397 * execution logic. 398 * A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the 399 * timeliness of the reschedule. For an idle-mode job, no deadline is given. 400 * @param failureToReschedule Provided job status that we will reschedule. 401 * @return A newly instantiated JobStatus with the same constraints as the last job except 402 * with adjusted timing constraints. 403 */ 404 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) { 405 final long elapsedNowMillis = SystemClock.elapsedRealtime(); 406 final JobInfo job = failureToReschedule.getJob(); 407 408 final long initialBackoffMillis = job.getInitialBackoffMillis(); 409 final int backoffAttempts = failureToReschedule.getNumFailures() + 1; 410 long delayMillis; 411 412 switch (job.getBackoffPolicy()) { 413 case JobInfo.BACKOFF_POLICY_LINEAR: 414 delayMillis = initialBackoffMillis * backoffAttempts; 415 break; 416 default: 417 if (DEBUG) { 418 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential."); 419 } 420 case JobInfo.BACKOFF_POLICY_EXPONENTIAL: 421 delayMillis = 422 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1); 423 break; 424 } 425 delayMillis = 426 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); 427 return new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, 428 JobStatus.NO_LATEST_RUNTIME, backoffAttempts); 429 } 430 431 /** 432 * Called after a periodic has executed so we can to re-add it. We take the last execution time 433 * of the job to be the time of completion (i.e. the time at which this function is called). 434 * This could be inaccurate b/c the job can run for as long as 435 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead 436 * to underscheduling at least, rather than if we had taken the last execution time to be the 437 * start of the execution. 438 * @return A new job representing the execution criteria for this instantiation of the 439 * recurring job. 440 */ 441 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) { 442 final long elapsedNow = SystemClock.elapsedRealtime(); 443 // Compute how much of the period is remaining. 444 long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0); 445 long newEarliestRunTimeElapsed = elapsedNow + runEarly; 446 long period = periodicToReschedule.getJob().getIntervalMillis(); 447 long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period; 448 449 if (DEBUG) { 450 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" + 451 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); 452 } 453 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, 454 newLatestRuntimeElapsed, 0 /* backoffAttempt */); 455 } 456 457 // JobCompletedListener implementations. 458 459 /** 460 * A job just finished executing. We fetch the 461 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on 462 * whether we want to reschedule we readd it to the controllers. 463 * @param jobStatus Completed job. 464 * @param needsReschedule Whether the implementing class should reschedule this job. 465 */ 466 @Override 467 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) { 468 if (DEBUG) { 469 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); 470 } 471 if (!stopTrackingJob(jobStatus)) { 472 if (DEBUG) { 473 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); 474 } 475 return; 476 } 477 if (needsReschedule) { 478 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus); 479 startTrackingJob(rescheduled); 480 } else if (jobStatus.getJob().isPeriodic()) { 481 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus); 482 startTrackingJob(rescheduledPeriodic); 483 } 484 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 485 } 486 487 // StateChangedListener implementations. 488 489 /** 490 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that 491 * some controller's state has changed, so as to run through the list of jobs and start/stop 492 * any that are eligible. 493 */ 494 @Override 495 public void onControllerStateChanged() { 496 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 497 } 498 499 @Override 500 public void onRunJobNow(JobStatus jobStatus) { 501 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget(); 502 } 503 504 private class JobHandler extends Handler { 505 506 public JobHandler(Looper looper) { 507 super(looper); 508 } 509 510 @Override 511 public void handleMessage(Message message) { 512 synchronized (mJobs) { 513 if (!mReadyToRock) { 514 return; 515 } 516 } 517 switch (message.what) { 518 case MSG_JOB_EXPIRED: 519 synchronized (mJobs) { 520 JobStatus runNow = (JobStatus) message.obj; 521 // runNow can be null, which is a controller's way of indicating that its 522 // state is such that all ready jobs should be run immediately. 523 if (runNow != null && !mPendingJobs.contains(runNow) 524 && mJobs.containsJob(runNow)) { 525 mPendingJobs.add(runNow); 526 } 527 queueReadyJobsForExecutionLockedH(); 528 } 529 break; 530 case MSG_CHECK_JOB: 531 synchronized (mJobs) { 532 // Check the list of jobs and run some of them if we feel inclined. 533 maybeQueueReadyJobsForExecutionLockedH(); 534 } 535 break; 536 } 537 maybeRunPendingJobsH(); 538 // Don't remove JOB_EXPIRED in case one came along while processing the queue. 539 removeMessages(MSG_CHECK_JOB); 540 } 541 542 /** 543 * Run through list of jobs and execute all possible - at least one is expired so we do 544 * as many as we can. 545 */ 546 private void queueReadyJobsForExecutionLockedH() { 547 ArraySet<JobStatus> jobs = mJobs.getJobs(); 548 if (DEBUG) { 549 Slog.d(TAG, "queuing all ready jobs for execution:"); 550 } 551 for (int i=0; i<jobs.size(); i++) { 552 JobStatus job = jobs.valueAt(i); 553 if (isReadyToBeExecutedLocked(job)) { 554 if (DEBUG) { 555 Slog.d(TAG, " queued " + job.toShortString()); 556 } 557 mPendingJobs.add(job); 558 } else if (isReadyToBeCancelledLocked(job)) { 559 stopJobOnServiceContextLocked(job); 560 } 561 } 562 if (DEBUG) { 563 final int queuedJobs = mPendingJobs.size(); 564 if (queuedJobs == 0) { 565 Slog.d(TAG, "No jobs pending."); 566 } else { 567 Slog.d(TAG, queuedJobs + " jobs queued."); 568 } 569 } 570 } 571 572 /** 573 * The state of at least one job has changed. Here is where we could enforce various 574 * policies on when we want to execute jobs. 575 * Right now the policy is such: 576 * If >1 of the ready jobs is idle mode we send all of them off 577 * if more than 2 network connectivity jobs are ready we send them all off. 578 * If more than 4 jobs total are ready we send them all off. 579 * TODO: It would be nice to consolidate these sort of high-level policies somewhere. 580 */ 581 private void maybeQueueReadyJobsForExecutionLockedH() { 582 int chargingCount = 0; 583 int idleCount = 0; 584 int backoffCount = 0; 585 int connectivityCount = 0; 586 List<JobStatus> runnableJobs = new ArrayList<JobStatus>(); 587 ArraySet<JobStatus> jobs = mJobs.getJobs(); 588 for (int i=0; i<jobs.size(); i++) { 589 JobStatus job = jobs.valueAt(i); 590 if (isReadyToBeExecutedLocked(job)) { 591 if (job.getNumFailures() > 0) { 592 backoffCount++; 593 } 594 if (job.hasIdleConstraint()) { 595 idleCount++; 596 } 597 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) { 598 connectivityCount++; 599 } 600 if (job.hasChargingConstraint()) { 601 chargingCount++; 602 } 603 runnableJobs.add(job); 604 } else if (isReadyToBeCancelledLocked(job)) { 605 stopJobOnServiceContextLocked(job); 606 } 607 } 608 if (backoffCount > 0 || 609 idleCount >= MIN_IDLE_COUNT || 610 connectivityCount >= MIN_CONNECTIVITY_COUNT || 611 chargingCount >= MIN_CHARGING_COUNT || 612 runnableJobs.size() >= MIN_READY_JOBS_COUNT) { 613 if (DEBUG) { 614 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); 615 } 616 for (int i=0; i<runnableJobs.size(); i++) { 617 mPendingJobs.add(runnableJobs.get(i)); 618 } 619 } else { 620 if (DEBUG) { 621 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything."); 622 } 623 } 624 if (DEBUG) { 625 Slog.d(TAG, "idle=" + idleCount + " connectivity=" + 626 connectivityCount + " charging=" + chargingCount + " tot=" + 627 runnableJobs.size()); 628 } 629 } 630 631 /** 632 * Criteria for moving a job into the pending queue: 633 * - It's ready. 634 * - It's not pending. 635 * - It's not already running on a JSC. 636 * - The user that requested the job is running. 637 */ 638 private boolean isReadyToBeExecutedLocked(JobStatus job) { 639 final boolean jobReady = job.isReady(); 640 final boolean jobPending = mPendingJobs.contains(job); 641 final boolean jobActive = isCurrentlyActiveLocked(job); 642 final boolean userRunning = mStartedUsers.contains(job.getUserId()); 643 644 if (DEBUG) { 645 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() 646 + " ready=" + jobReady + " pending=" + jobPending 647 + " active=" + jobActive + " userRunning=" + userRunning); 648 } 649 return userRunning && jobReady && !jobPending && !jobActive; 650 } 651 652 /** 653 * Criteria for cancelling an active job: 654 * - It's not ready 655 * - It's running on a JSC. 656 */ 657 private boolean isReadyToBeCancelledLocked(JobStatus job) { 658 return !job.isReady() && isCurrentlyActiveLocked(job); 659 } 660 661 /** 662 * Reconcile jobs in the pending queue against available execution contexts. 663 * A controller can force a job into the pending queue even if it's already running, but 664 * here is where we decide whether to actually execute it. 665 */ 666 private void maybeRunPendingJobsH() { 667 synchronized (mJobs) { 668 Iterator<JobStatus> it = mPendingJobs.iterator(); 669 if (DEBUG) { 670 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); 671 } 672 while (it.hasNext()) { 673 JobStatus nextPending = it.next(); 674 JobServiceContext availableContext = null; 675 for (int i=0; i<mActiveServices.size(); i++) { 676 JobServiceContext jsc = mActiveServices.get(i); 677 final JobStatus running = jsc.getRunningJob(); 678 if (running != null && running.matches(nextPending.getUid(), 679 nextPending.getJobId())) { 680 // Already running this job for this uId, skip. 681 availableContext = null; 682 break; 683 } 684 if (jsc.isAvailable()) { 685 availableContext = jsc; 686 } 687 } 688 if (availableContext != null) { 689 if (!availableContext.executeRunnableJob(nextPending)) { 690 if (DEBUG) { 691 Slog.d(TAG, "Error executing " + nextPending); 692 } 693 mJobs.remove(nextPending); 694 } 695 it.remove(); 696 } 697 } 698 } 699 } 700 } 701 702 /** 703 * Binder stub trampoline implementation 704 */ 705 final class JobSchedulerStub extends IJobScheduler.Stub { 706 /** Cache determination of whether a given app can persist jobs 707 * key is uid of the calling app; value is undetermined/true/false 708 */ 709 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); 710 711 // Enforce that only the app itself (or shared uid participant) can schedule a 712 // job that runs one of the app's services, as well as verifying that the 713 // named service properly requires the BIND_JOB_SERVICE permission 714 private void enforceValidJobRequest(int uid, JobInfo job) { 715 final IPackageManager pm = AppGlobals.getPackageManager(); 716 final ComponentName service = job.getService(); 717 try { 718 ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid)); 719 if (si == null) { 720 throw new IllegalArgumentException("No such service " + service); 721 } 722 if (si.applicationInfo.uid != uid) { 723 throw new IllegalArgumentException("uid " + uid + 724 " cannot schedule job in " + service.getPackageName()); 725 } 726 if (!JobService.PERMISSION_BIND.equals(si.permission)) { 727 throw new IllegalArgumentException("Scheduled service " + service 728 + " does not require android.permission.BIND_JOB_SERVICE permission"); 729 } 730 } catch (RemoteException e) { 731 // Can't happen; the Package Manager is in this same process 732 } 733 } 734 735 private boolean canPersistJobs(int pid, int uid) { 736 // If we get this far we're good to go; all we need to do now is check 737 // whether the app is allowed to persist its scheduled work. 738 final boolean canPersist; 739 synchronized (mPersistCache) { 740 Boolean cached = mPersistCache.get(uid); 741 if (cached != null) { 742 canPersist = cached.booleanValue(); 743 } else { 744 // Persisting jobs is tantamount to running at boot, so we permit 745 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED 746 // permission 747 int result = getContext().checkPermission( 748 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid); 749 canPersist = (result == PackageManager.PERMISSION_GRANTED); 750 mPersistCache.put(uid, canPersist); 751 } 752 } 753 return canPersist; 754 } 755 756 // IJobScheduler implementation 757 @Override 758 public int schedule(JobInfo job) throws RemoteException { 759 if (DEBUG) { 760 Slog.d(TAG, "Scheduling job: " + job.toString()); 761 } 762 final int pid = Binder.getCallingPid(); 763 final int uid = Binder.getCallingUid(); 764 765 enforceValidJobRequest(uid, job); 766 if (job.isPersisted()) { 767 if (!canPersistJobs(pid, uid)) { 768 throw new IllegalArgumentException("Error: requested job be persisted without" 769 + " holding RECEIVE_BOOT_COMPLETED permission."); 770 } 771 } 772 773 long ident = Binder.clearCallingIdentity(); 774 try { 775 return JobSchedulerService.this.schedule(job, uid); 776 } finally { 777 Binder.restoreCallingIdentity(ident); 778 } 779 } 780 781 @Override 782 public List<JobInfo> getAllPendingJobs() throws RemoteException { 783 final int uid = Binder.getCallingUid(); 784 785 long ident = Binder.clearCallingIdentity(); 786 try { 787 return JobSchedulerService.this.getPendingJobs(uid); 788 } finally { 789 Binder.restoreCallingIdentity(ident); 790 } 791 } 792 793 @Override 794 public void cancelAll() throws RemoteException { 795 final int uid = Binder.getCallingUid(); 796 797 long ident = Binder.clearCallingIdentity(); 798 try { 799 JobSchedulerService.this.cancelJobsForUid(uid); 800 } finally { 801 Binder.restoreCallingIdentity(ident); 802 } 803 } 804 805 @Override 806 public void cancel(int jobId) throws RemoteException { 807 final int uid = Binder.getCallingUid(); 808 809 long ident = Binder.clearCallingIdentity(); 810 try { 811 JobSchedulerService.this.cancelJob(uid, jobId); 812 } finally { 813 Binder.restoreCallingIdentity(ident); 814 } 815 } 816 817 /** 818 * "dumpsys" infrastructure 819 */ 820 @Override 821 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 822 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 823 824 long identityToken = Binder.clearCallingIdentity(); 825 try { 826 JobSchedulerService.this.dumpInternal(pw); 827 } finally { 828 Binder.restoreCallingIdentity(identityToken); 829 } 830 } 831 }; 832 833 void dumpInternal(PrintWriter pw) { 834 final long now = SystemClock.elapsedRealtime(); 835 synchronized (mJobs) { 836 pw.print("Started users: "); 837 for (int i=0; i<mStartedUsers.size(); i++) { 838 pw.print("u" + mStartedUsers.get(i) + " "); 839 } 840 pw.println(); 841 pw.println("Registered jobs:"); 842 if (mJobs.size() > 0) { 843 ArraySet<JobStatus> jobs = mJobs.getJobs(); 844 for (int i=0; i<jobs.size(); i++) { 845 JobStatus job = jobs.valueAt(i); 846 job.dump(pw, " "); 847 } 848 } else { 849 pw.println(" None."); 850 } 851 for (int i=0; i<mControllers.size(); i++) { 852 pw.println(); 853 mControllers.get(i).dumpControllerState(pw); 854 } 855 pw.println(); 856 pw.println("Pending:"); 857 for (int i=0; i<mPendingJobs.size(); i++) { 858 pw.println(mPendingJobs.get(i).hashCode()); 859 } 860 pw.println(); 861 pw.println("Active jobs:"); 862 for (int i=0; i<mActiveServices.size(); i++) { 863 JobServiceContext jsc = mActiveServices.get(i); 864 if (jsc.isAvailable()) { 865 continue; 866 } else { 867 final long timeout = jsc.getTimeoutElapsed(); 868 pw.print("Running for: "); 869 pw.print((now - jsc.getExecutionStartTimeElapsed())/1000); 870 pw.print("s timeout="); 871 pw.print(timeout); 872 pw.print(" fromnow="); 873 pw.println(timeout-now); 874 jsc.getRunningJob().dump(pw, " "); 875 } 876 } 877 pw.println(); 878 pw.print("mReadyToRock="); pw.println(mReadyToRock); 879 } 880 pw.println(); 881 } 882 } 883