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