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