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