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