JobSchedulerService.java revision 347c2780aab9dbacb7e0336d3585890647187bf4
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.ActivityManager; 26import android.app.ActivityManagerNative; 27import android.app.AppGlobals; 28import android.app.IUidObserver; 29import android.app.job.JobInfo; 30import android.app.job.JobParameters; 31import android.app.job.JobScheduler; 32import android.app.job.JobService; 33import android.app.job.IJobScheduler; 34import android.content.BroadcastReceiver; 35import android.content.ComponentName; 36import android.content.Context; 37import android.content.Intent; 38import android.content.IntentFilter; 39import android.content.pm.IPackageManager; 40import android.content.pm.PackageManager; 41import android.content.pm.ServiceInfo; 42import android.os.BatteryStats; 43import android.os.Binder; 44import android.os.Handler; 45import android.os.Looper; 46import android.os.Message; 47import android.os.PowerManager; 48import android.os.RemoteException; 49import android.os.ServiceManager; 50import android.os.SystemClock; 51import android.os.UserHandle; 52import android.util.ArraySet; 53import android.util.Slog; 54import android.util.SparseArray; 55 56import com.android.internal.app.IBatteryStats; 57import com.android.server.DeviceIdleController; 58import com.android.server.LocalServices; 59import com.android.server.job.controllers.AppIdleController; 60import com.android.server.job.controllers.BatteryController; 61import com.android.server.job.controllers.ConnectivityController; 62import com.android.server.job.controllers.IdleController; 63import com.android.server.job.controllers.JobStatus; 64import com.android.server.job.controllers.StateController; 65import com.android.server.job.controllers.TimeController; 66 67/** 68 * Responsible for taking jobs representing work to be performed by a client app, and determining 69 * based on the criteria specified when that job should be run against the client application's 70 * endpoint. 71 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing 72 * about constraints, or the state of active jobs. It receives callbacks from the various 73 * controllers and completed jobs and operates accordingly. 74 * 75 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object. 76 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}. 77 * @hide 78 */ 79public class JobSchedulerService extends com.android.server.SystemService 80 implements StateChangedListener, JobCompletedListener { 81 public static final boolean DEBUG = false; 82 /** The number of concurrent jobs we run at one time. */ 83 private static final int MAX_JOB_CONTEXTS_COUNT 84 = ActivityManager.isLowRamDeviceStatic() ? 1 : 3; 85 static final String TAG = "JobSchedulerService"; 86 /** Master list of jobs. */ 87 final JobStore mJobs; 88 89 static final int MSG_JOB_EXPIRED = 0; 90 static final int MSG_CHECK_JOB = 1; 91 static final int MSG_STOP_JOB = 2; 92 static final int MSG_CHECK_JOB_GREEDY = 3; 93 94 // Policy constants 95 /** 96 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things 97 * early. 98 */ 99 static final int MIN_IDLE_COUNT = 1; 100 /** 101 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things 102 * early. 103 */ 104 static final int MIN_CHARGING_COUNT = 1; 105 /** 106 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule 107 * things early. 108 */ 109 static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready. 110 /** 111 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running 112 * some work early. 113 * This is correlated with the amount of batching we'll be able to do. 114 */ 115 static final int MIN_READY_JOBS_COUNT = 2; 116 117 /** 118 * Track Services that have currently active or pending jobs. The index is provided by 119 * {@link JobStatus#getServiceToken()} 120 */ 121 final List<JobServiceContext> mActiveServices = new ArrayList<>(); 122 /** List of controllers that will notify this service of updates to jobs. */ 123 List<StateController> mControllers; 124 /** 125 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list 126 * when ready to execute them. 127 */ 128 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>(); 129 130 final ArrayList<Integer> mStartedUsers = new ArrayList<>(); 131 132 final JobHandler mHandler; 133 final JobSchedulerStub mJobSchedulerStub; 134 135 IBatteryStats mBatteryStats; 136 PowerManager mPowerManager; 137 DeviceIdleController.LocalService mLocalDeviceIdleController; 138 139 /** 140 * Set to true once we are allowed to run third party apps. 141 */ 142 boolean mReadyToRock; 143 144 /** 145 * True when in device idle mode, so we don't want to schedule any jobs. 146 */ 147 boolean mDeviceIdleMode; 148 149 /** 150 * What we last reported to DeviceIdleController about wheter we are active. 151 */ 152 boolean mReportedActive; 153 154 /** 155 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we 156 * still clean up. On reinstall the package will have a new uid. 157 */ 158 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 159 @Override 160 public void onReceive(Context context, Intent intent) { 161 Slog.d(TAG, "Receieved: " + intent.getAction()); 162 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { 163 // If this is an outright uninstall rather than the first half of an 164 // app update sequence, cancel the jobs associated with the app. 165 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { 166 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1); 167 if (DEBUG) { 168 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); 169 } 170 cancelJobsForUid(uidRemoved, true); 171 } 172 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { 173 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); 174 if (DEBUG) { 175 Slog.d(TAG, "Removing jobs for user: " + userId); 176 } 177 cancelJobsForUser(userId); 178 } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction()) 179 || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) { 180 updateIdleMode(mPowerManager != null 181 ? (mPowerManager.isDeviceIdleMode() 182 || mPowerManager.isLightDeviceIdleMode()) 183 : false); 184 } 185 } 186 }; 187 188 final private IUidObserver mUidObserver = new IUidObserver.Stub() { 189 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException { 190 } 191 192 @Override public void onUidGone(int uid) throws RemoteException { 193 } 194 195 @Override public void onUidActive(int uid) throws RemoteException { 196 } 197 198 @Override public void onUidIdle(int uid) throws RemoteException { 199 cancelJobsForUid(uid, false); 200 } 201 }; 202 203 @Override 204 public void onStartUser(int userHandle) { 205 mStartedUsers.add(userHandle); 206 // Let's kick any outstanding jobs for this user. 207 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 208 } 209 210 @Override 211 public void onStopUser(int userHandle) { 212 mStartedUsers.remove(Integer.valueOf(userHandle)); 213 } 214 215 /** 216 * Entry point from client to schedule the provided job. 217 * This cancels the job if it's already been scheduled, and replaces it with the one provided. 218 * @param job JobInfo object containing execution parameters 219 * @param uId The package identifier of the application this job is for. 220 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes. 221 */ 222 public int schedule(JobInfo job, int uId) { 223 JobStatus jobStatus = new JobStatus(job, uId); 224 cancelJob(uId, job.getId()); 225 try { 226 if (ActivityManagerNative.getDefault().getAppStartMode(uId, 227 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) { 228 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString() 229 + " -- package not allowed to start"); 230 return JobScheduler.RESULT_FAILURE; 231 } 232 } catch (RemoteException e) { 233 } 234 startTrackingJob(jobStatus); 235 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 236 return JobScheduler.RESULT_SUCCESS; 237 } 238 239 public List<JobInfo> getPendingJobs(int uid) { 240 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(); 241 synchronized (mJobs) { 242 ArraySet<JobStatus> jobs = mJobs.getJobs(); 243 for (int i=0; i<jobs.size(); i++) { 244 JobStatus job = jobs.valueAt(i); 245 if (job.getUid() == uid) { 246 outList.add(job.getJob()); 247 } 248 } 249 } 250 return outList; 251 } 252 253 void cancelJobsForUser(int userHandle) { 254 List<JobStatus> jobsForUser; 255 synchronized (mJobs) { 256 jobsForUser = mJobs.getJobsByUser(userHandle); 257 } 258 for (int i=0; i<jobsForUser.size(); i++) { 259 JobStatus toRemove = jobsForUser.get(i); 260 cancelJobImpl(toRemove); 261 } 262 } 263 264 /** 265 * Entry point from client to cancel all jobs originating from their uid. 266 * This will remove the job from the master list, and cancel the job if it was staged for 267 * execution or being executed. 268 * @param uid Uid to check against for removal of a job. 269 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those 270 * whose apps are stopped. 271 */ 272 public void cancelJobsForUid(int uid, boolean forceAll) { 273 List<JobStatus> jobsForUid; 274 synchronized (mJobs) { 275 jobsForUid = mJobs.getJobsByUid(uid); 276 } 277 for (int i=0; i<jobsForUid.size(); i++) { 278 JobStatus toRemove = jobsForUid.get(i); 279 if (!forceAll) { 280 String packageName = toRemove.getServiceComponent().getPackageName(); 281 try { 282 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName) 283 != ActivityManager.APP_START_MODE_DISABLED) { 284 continue; 285 } 286 } catch (RemoteException e) { 287 } 288 } 289 cancelJobImpl(toRemove); 290 } 291 } 292 293 /** 294 * Entry point from client to cancel the job corresponding to the jobId provided. 295 * This will remove the job from the master list, and cancel the job if it was staged for 296 * execution or being executed. 297 * @param uid Uid of the calling client. 298 * @param jobId Id of the job, provided at schedule-time. 299 */ 300 public void cancelJob(int uid, int jobId) { 301 JobStatus toCancel; 302 synchronized (mJobs) { 303 toCancel = mJobs.getJobByUidAndJobId(uid, jobId); 304 } 305 if (toCancel != null) { 306 cancelJobImpl(toCancel); 307 } 308 } 309 310 private void cancelJobImpl(JobStatus cancelled) { 311 if (DEBUG) { 312 Slog.d(TAG, "Cancelling: " + cancelled); 313 } 314 stopTrackingJob(cancelled); 315 synchronized (mJobs) { 316 // Remove from pending queue. 317 mPendingJobs.remove(cancelled); 318 // Cancel if running. 319 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED); 320 reportActive(); 321 } 322 } 323 324 void updateIdleMode(boolean enabled) { 325 boolean changed = false; 326 boolean rocking; 327 synchronized (mJobs) { 328 if (mDeviceIdleMode != enabled) { 329 changed = true; 330 } 331 rocking = mReadyToRock; 332 } 333 if (changed) { 334 if (rocking) { 335 for (int i=0; i<mControllers.size(); i++) { 336 mControllers.get(i).deviceIdleModeChanged(enabled); 337 } 338 } 339 synchronized (mJobs) { 340 mDeviceIdleMode = enabled; 341 if (enabled) { 342 // When becoming idle, make sure no jobs are actively running. 343 for (int i=0; i<mActiveServices.size(); i++) { 344 JobServiceContext jsc = mActiveServices.get(i); 345 final JobStatus executing = jsc.getRunningJob(); 346 if (executing != null) { 347 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE); 348 } 349 } 350 } else { 351 // When coming out of idle, allow thing to start back up. 352 if (rocking) { 353 if (mLocalDeviceIdleController != null) { 354 if (!mReportedActive) { 355 mReportedActive = true; 356 mLocalDeviceIdleController.setJobsActive(true); 357 } 358 } 359 } 360 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 361 } 362 } 363 } 364 } 365 366 void reportActive() { 367 // active is true if pending queue contains jobs OR some job is running. 368 boolean active = mPendingJobs.size() > 0; 369 if (mPendingJobs.size() <= 0) { 370 for (int i=0; i<mActiveServices.size(); i++) { 371 JobServiceContext jsc = mActiveServices.get(i); 372 if (jsc.getRunningJob() != null) { 373 active = true; 374 break; 375 } 376 } 377 } 378 379 if (mReportedActive != active) { 380 mReportedActive = active; 381 if (mLocalDeviceIdleController != null) { 382 mLocalDeviceIdleController.setJobsActive(active); 383 } 384 } 385 } 386 387 /** 388 * Initializes the system service. 389 * <p> 390 * Subclasses must define a single argument constructor that accepts the context 391 * and passes it to super. 392 * </p> 393 * 394 * @param context The system server context. 395 */ 396 public JobSchedulerService(Context context) { 397 super(context); 398 // Create the controllers. 399 mControllers = new ArrayList<StateController>(); 400 mControllers.add(ConnectivityController.get(this)); 401 mControllers.add(TimeController.get(this)); 402 mControllers.add(IdleController.get(this)); 403 mControllers.add(BatteryController.get(this)); 404 mControllers.add(AppIdleController.get(this)); 405 406 mHandler = new JobHandler(context.getMainLooper()); 407 mJobSchedulerStub = new JobSchedulerStub(); 408 mJobs = JobStore.initAndGet(this); 409 } 410 411 @Override 412 public void onStart() { 413 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub); 414 } 415 416 @Override 417 public void onBootPhase(int phase) { 418 if (PHASE_SYSTEM_SERVICES_READY == phase) { 419 // Register br for package removals and user removals. 420 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); 421 filter.addDataScheme("package"); 422 getContext().registerReceiverAsUser( 423 mBroadcastReceiver, UserHandle.ALL, filter, null, null); 424 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); 425 userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); 426 userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); 427 getContext().registerReceiverAsUser( 428 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); 429 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE); 430 try { 431 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver, 432 ActivityManager.UID_OBSERVER_IDLE); 433 } catch (RemoteException e) { 434 // ignored; both services live in system_server 435 } 436 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 437 synchronized (mJobs) { 438 // Let's go! 439 mReadyToRock = true; 440 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( 441 BatteryStats.SERVICE_NAME)); 442 mLocalDeviceIdleController 443 = LocalServices.getService(DeviceIdleController.LocalService.class); 444 // Create the "runners". 445 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) { 446 mActiveServices.add( 447 new JobServiceContext(this, mBatteryStats, 448 getContext().getMainLooper())); 449 } 450 // Attach jobs to their controllers. 451 ArraySet<JobStatus> jobs = mJobs.getJobs(); 452 for (int i=0; i<jobs.size(); i++) { 453 JobStatus job = jobs.valueAt(i); 454 for (int controller=0; controller<mControllers.size(); controller++) { 455 mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode); 456 mControllers.get(controller).maybeStartTrackingJob(job); 457 } 458 } 459 // GO GO GO! 460 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 461 } 462 } 463 } 464 465 /** 466 * Called when we have a job status object that we need to insert in our 467 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know 468 * about. 469 */ 470 private void startTrackingJob(JobStatus jobStatus) { 471 boolean update; 472 boolean rocking; 473 synchronized (mJobs) { 474 update = mJobs.add(jobStatus); 475 rocking = mReadyToRock; 476 } 477 if (rocking) { 478 for (int i=0; i<mControllers.size(); i++) { 479 StateController controller = mControllers.get(i); 480 if (update) { 481 controller.maybeStopTrackingJob(jobStatus); 482 } 483 controller.maybeStartTrackingJob(jobStatus); 484 } 485 } 486 } 487 488 /** 489 * Called when we want to remove a JobStatus object that we've finished executing. Returns the 490 * object removed. 491 */ 492 private boolean stopTrackingJob(JobStatus jobStatus) { 493 boolean removed; 494 boolean rocking; 495 synchronized (mJobs) { 496 // Remove from store as well as controllers. 497 removed = mJobs.remove(jobStatus); 498 rocking = mReadyToRock; 499 } 500 if (removed && rocking) { 501 for (int i=0; i<mControllers.size(); i++) { 502 StateController controller = mControllers.get(i); 503 controller.maybeStopTrackingJob(jobStatus); 504 } 505 } 506 return removed; 507 } 508 509 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) { 510 for (int i=0; i<mActiveServices.size(); i++) { 511 JobServiceContext jsc = mActiveServices.get(i); 512 final JobStatus executing = jsc.getRunningJob(); 513 if (executing != null && executing.matches(job.getUid(), job.getJobId())) { 514 jsc.cancelExecutingJob(reason); 515 return true; 516 } 517 } 518 return false; 519 } 520 521 /** 522 * @param job JobStatus we are querying against. 523 * @return Whether or not the job represented by the status object is currently being run or 524 * is pending. 525 */ 526 private boolean isCurrentlyActiveLocked(JobStatus job) { 527 for (int i=0; i<mActiveServices.size(); i++) { 528 JobServiceContext serviceContext = mActiveServices.get(i); 529 final JobStatus running = serviceContext.getRunningJob(); 530 if (running != null && running.matches(job.getUid(), job.getJobId())) { 531 return true; 532 } 533 } 534 return false; 535 } 536 537 /** 538 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to 539 * specify an override deadline on a failed job (the failed job will run even though it's not 540 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any 541 * ready job with {@link JobStatus#numFailures} > 0 will be executed. 542 * 543 * @param failureToReschedule Provided job status that we will reschedule. 544 * @return A newly instantiated JobStatus with the same constraints as the last job except 545 * with adjusted timing constraints. 546 * 547 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH 548 */ 549 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) { 550 final long elapsedNowMillis = SystemClock.elapsedRealtime(); 551 final JobInfo job = failureToReschedule.getJob(); 552 553 final long initialBackoffMillis = job.getInitialBackoffMillis(); 554 final int backoffAttempts = failureToReschedule.getNumFailures() + 1; 555 long delayMillis; 556 557 switch (job.getBackoffPolicy()) { 558 case JobInfo.BACKOFF_POLICY_LINEAR: 559 delayMillis = initialBackoffMillis * backoffAttempts; 560 break; 561 default: 562 if (DEBUG) { 563 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential."); 564 } 565 case JobInfo.BACKOFF_POLICY_EXPONENTIAL: 566 delayMillis = 567 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1); 568 break; 569 } 570 delayMillis = 571 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); 572 return new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, 573 JobStatus.NO_LATEST_RUNTIME, backoffAttempts); 574 } 575 576 /** 577 * Called after a periodic has executed so we can reschedule it. We take the last execution 578 * time of the job to be the time of completion (i.e. the time at which this function is 579 * called). 580 * This could be inaccurate b/c the job can run for as long as 581 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead 582 * to underscheduling at least, rather than if we had taken the last execution time to be the 583 * start of the execution. 584 * @return A new job representing the execution criteria for this instantiation of the 585 * recurring job. 586 */ 587 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) { 588 final long elapsedNow = SystemClock.elapsedRealtime(); 589 // Compute how much of the period is remaining. 590 long runEarly = 0L; 591 592 // If this periodic was rescheduled it won't have a deadline. 593 if (periodicToReschedule.hasDeadlineConstraint()) { 594 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L); 595 } 596 long flex = periodicToReschedule.getJob().getFlexMillis(); 597 long period = periodicToReschedule.getJob().getIntervalMillis(); 598 long newLatestRuntimeElapsed = elapsedNow + runEarly + period; 599 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex; 600 601 if (DEBUG) { 602 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" + 603 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); 604 } 605 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, 606 newLatestRuntimeElapsed, 0 /* backoffAttempt */); 607 } 608 609 // JobCompletedListener implementations. 610 611 /** 612 * A job just finished executing. We fetch the 613 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on 614 * whether we want to reschedule we readd it to the controllers. 615 * @param jobStatus Completed job. 616 * @param needsReschedule Whether the implementing class should reschedule this job. 617 */ 618 @Override 619 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) { 620 if (DEBUG) { 621 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); 622 } 623 if (!stopTrackingJob(jobStatus)) { 624 if (DEBUG) { 625 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); 626 } 627 return; 628 } 629 if (needsReschedule) { 630 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus); 631 startTrackingJob(rescheduled); 632 } else if (jobStatus.getJob().isPeriodic()) { 633 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus); 634 startTrackingJob(rescheduledPeriodic); 635 } 636 reportActive(); 637 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); 638 } 639 640 // StateChangedListener implementations. 641 642 /** 643 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that 644 * some controller's state has changed, so as to run through the list of jobs and start/stop 645 * any that are eligible. 646 */ 647 @Override 648 public void onControllerStateChanged() { 649 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 650 } 651 652 @Override 653 public void onRunJobNow(JobStatus jobStatus) { 654 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget(); 655 } 656 657 private class JobHandler extends Handler { 658 659 public JobHandler(Looper looper) { 660 super(looper); 661 } 662 663 @Override 664 public void handleMessage(Message message) { 665 synchronized (mJobs) { 666 if (!mReadyToRock) { 667 return; 668 } 669 } 670 switch (message.what) { 671 case MSG_JOB_EXPIRED: 672 synchronized (mJobs) { 673 JobStatus runNow = (JobStatus) message.obj; 674 // runNow can be null, which is a controller's way of indicating that its 675 // state is such that all ready jobs should be run immediately. 676 if (runNow != null && !mPendingJobs.contains(runNow) 677 && mJobs.containsJob(runNow)) { 678 mPendingJobs.add(runNow); 679 } 680 queueReadyJobsForExecutionLockedH(); 681 } 682 break; 683 case MSG_CHECK_JOB: 684 synchronized (mJobs) { 685 if (mReportedActive) { 686 // if jobs are currently being run, queue all ready jobs for execution. 687 queueReadyJobsForExecutionLockedH(); 688 } else { 689 // Check the list of jobs and run some of them if we feel inclined. 690 maybeQueueReadyJobsForExecutionLockedH(); 691 } 692 } 693 break; 694 case MSG_CHECK_JOB_GREEDY: 695 synchronized (mJobs) { 696 queueReadyJobsForExecutionLockedH(); 697 } 698 break; 699 case MSG_STOP_JOB: 700 cancelJobImpl((JobStatus)message.obj); 701 break; 702 } 703 maybeRunPendingJobsH(); 704 // Don't remove JOB_EXPIRED in case one came along while processing the queue. 705 removeMessages(MSG_CHECK_JOB); 706 } 707 708 /** 709 * Run through list of jobs and execute all possible - at least one is expired so we do 710 * as many as we can. 711 */ 712 private void queueReadyJobsForExecutionLockedH() { 713 ArraySet<JobStatus> jobs = mJobs.getJobs(); 714 mPendingJobs.clear(); 715 if (DEBUG) { 716 Slog.d(TAG, "queuing all ready jobs for execution:"); 717 } 718 for (int i=0; i<jobs.size(); i++) { 719 JobStatus job = jobs.valueAt(i); 720 if (isReadyToBeExecutedLocked(job)) { 721 if (DEBUG) { 722 Slog.d(TAG, " queued " + job.toShortString()); 723 } 724 mPendingJobs.add(job); 725 } else if (areJobConstraintsNotSatisfied(job)) { 726 stopJobOnServiceContextLocked(job, 727 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED); 728 } 729 } 730 if (DEBUG) { 731 final int queuedJobs = mPendingJobs.size(); 732 if (queuedJobs == 0) { 733 Slog.d(TAG, "No jobs pending."); 734 } else { 735 Slog.d(TAG, queuedJobs + " jobs queued."); 736 } 737 } 738 } 739 740 /** 741 * The state of at least one job has changed. Here is where we could enforce various 742 * policies on when we want to execute jobs. 743 * Right now the policy is such: 744 * If >1 of the ready jobs is idle mode we send all of them off 745 * if more than 2 network connectivity jobs are ready we send them all off. 746 * If more than 4 jobs total are ready we send them all off. 747 * TODO: It would be nice to consolidate these sort of high-level policies somewhere. 748 */ 749 private void maybeQueueReadyJobsForExecutionLockedH() { 750 mPendingJobs.clear(); 751 int chargingCount = 0; 752 int idleCount = 0; 753 int backoffCount = 0; 754 int connectivityCount = 0; 755 List<JobStatus> runnableJobs = null; 756 ArraySet<JobStatus> jobs = mJobs.getJobs(); 757 for (int i=0; i<jobs.size(); i++) { 758 JobStatus job = jobs.valueAt(i); 759 if (isReadyToBeExecutedLocked(job)) { 760 try { 761 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(), 762 job.getJob().getService().getPackageName()) 763 == ActivityManager.APP_START_MODE_DISABLED) { 764 Slog.w(TAG, "Aborting job " + job.getUid() + ":" 765 + job.getJob().toString() + " -- package not allowed to start"); 766 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget(); 767 continue; 768 } 769 } catch (RemoteException e) { 770 } 771 if (job.getNumFailures() > 0) { 772 backoffCount++; 773 } 774 if (job.hasIdleConstraint()) { 775 idleCount++; 776 } 777 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) { 778 connectivityCount++; 779 } 780 if (job.hasChargingConstraint()) { 781 chargingCount++; 782 } 783 if (runnableJobs == null) { 784 runnableJobs = new ArrayList<>(); 785 } 786 runnableJobs.add(job); 787 } else if (areJobConstraintsNotSatisfied(job)) { 788 stopJobOnServiceContextLocked(job, 789 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED); 790 } 791 } 792 if (backoffCount > 0 || 793 idleCount >= MIN_IDLE_COUNT || 794 connectivityCount >= MIN_CONNECTIVITY_COUNT || 795 chargingCount >= MIN_CHARGING_COUNT || 796 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) { 797 if (DEBUG) { 798 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); 799 } 800 for (int i=0; i<runnableJobs.size(); i++) { 801 mPendingJobs.add(runnableJobs.get(i)); 802 } 803 } else { 804 if (DEBUG) { 805 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything."); 806 } 807 } 808 } 809 810 /** 811 * Criteria for moving a job into the pending queue: 812 * - It's ready. 813 * - It's not pending. 814 * - It's not already running on a JSC. 815 * - The user that requested the job is running. 816 */ 817 private boolean isReadyToBeExecutedLocked(JobStatus job) { 818 final boolean jobReady = job.isReady(); 819 final boolean jobPending = mPendingJobs.contains(job); 820 final boolean jobActive = isCurrentlyActiveLocked(job); 821 final boolean userRunning = mStartedUsers.contains(job.getUserId()); 822 if (DEBUG) { 823 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() 824 + " ready=" + jobReady + " pending=" + jobPending 825 + " active=" + jobActive + " userRunning=" + userRunning); 826 } 827 return userRunning && jobReady && !jobPending && !jobActive; 828 } 829 830 /** 831 * Criteria for cancelling an active job: 832 * - It's not ready 833 * - It's running on a JSC. 834 */ 835 private boolean areJobConstraintsNotSatisfied(JobStatus job) { 836 return !job.isReady() && isCurrentlyActiveLocked(job); 837 } 838 839 /** 840 * Reconcile jobs in the pending queue against available execution contexts. 841 * A controller can force a job into the pending queue even if it's already running, but 842 * here is where we decide whether to actually execute it. 843 */ 844 private void maybeRunPendingJobsH() { 845 synchronized (mJobs) { 846 if (mDeviceIdleMode) { 847 // If device is idle, we will not schedule jobs to run. 848 return; 849 } 850 if (DEBUG) { 851 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); 852 } 853 assignJobsToContextsH(); 854 reportActive(); 855 } 856 } 857 } 858 859 /** 860 * Takes jobs from pending queue and runs them on available contexts. 861 * If no contexts are available, preempts lower priority jobs to 862 * run higher priority ones. 863 * Lock on mJobs before calling this function. 864 */ 865 private void assignJobsToContextsH() { 866 if (DEBUG) { 867 Slog.d(TAG, printPendingQueue()); 868 } 869 870 // This array essentially stores the state of mActiveServices array. 871 // ith index stores the job present on the ith JobServiceContext. 872 // We manipulate this array until we arrive at what jobs should be running on 873 // what JobServiceContext. 874 JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; 875 // Indicates whether we need to act on this jobContext id 876 boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT]; 877 int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; 878 for (int i=0; i<mActiveServices.size(); i++) { 879 contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob(); 880 preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid(); 881 } 882 if (DEBUG) { 883 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); 884 } 885 Iterator<JobStatus> it = mPendingJobs.iterator(); 886 while (it.hasNext()) { 887 JobStatus nextPending = it.next(); 888 889 // If job is already running, go to next job. 890 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); 891 if (jobRunningContext != -1) { 892 continue; 893 } 894 895 // Find a context for nextPending. The context should be available OR 896 // it should have lowest priority among all running jobs 897 // (sharing the same Uid as nextPending) 898 int minPriority = Integer.MAX_VALUE; 899 int minPriorityContextId = -1; 900 for (int i=0; i<mActiveServices.size(); i++) { 901 JobStatus job = contextIdToJobMap[i]; 902 int preferredUid = preferredUidForContext[i]; 903 if (job == null && (preferredUid == nextPending.getUid() || 904 preferredUid == JobServiceContext.NO_PREFERRED_UID) ) { 905 minPriorityContextId = i; 906 break; 907 } 908 if (job == null) { 909 // No job on this context, but nextPending can't run here because 910 // the context has a preferred Uid. 911 continue; 912 } 913 if (job.getUid() != nextPending.getUid()) { 914 continue; 915 } 916 if (job.getPriority() >= nextPending.getPriority()) { 917 continue; 918 } 919 if (minPriority > nextPending.getPriority()) { 920 minPriority = nextPending.getPriority(); 921 minPriorityContextId = i; 922 } 923 } 924 if (minPriorityContextId != -1) { 925 contextIdToJobMap[minPriorityContextId] = nextPending; 926 act[minPriorityContextId] = true; 927 } 928 } 929 if (DEBUG) { 930 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); 931 } 932 for (int i=0; i<mActiveServices.size(); i++) { 933 boolean preservePreferredUid = false; 934 if (act[i]) { 935 JobStatus js = mActiveServices.get(i).getRunningJob(); 936 if (js != null) { 937 if (DEBUG) { 938 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob()); 939 } 940 // preferredUid will be set to uid of currently running job. 941 mActiveServices.get(i).preemptExecutingJob(); 942 preservePreferredUid = true; 943 } else { 944 if (DEBUG) { 945 Slog.d(TAG, "About to run job on context " 946 + String.valueOf(i) + ", job: " + contextIdToJobMap[i]); 947 } 948 if (!mActiveServices.get(i).executeRunnableJob(contextIdToJobMap[i])) { 949 Slog.d(TAG, "Error executing " + contextIdToJobMap[i]); 950 } 951 mPendingJobs.remove(contextIdToJobMap[i]); 952 } 953 } 954 if (!preservePreferredUid) { 955 mActiveServices.get(i).clearPreferredUid(); 956 } 957 } 958 } 959 960 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { 961 for (int i=0; i<map.length; i++) { 962 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { 963 return i; 964 } 965 } 966 return -1; 967 } 968 969 /** 970 * Binder stub trampoline implementation 971 */ 972 final class JobSchedulerStub extends IJobScheduler.Stub { 973 /** Cache determination of whether a given app can persist jobs 974 * key is uid of the calling app; value is undetermined/true/false 975 */ 976 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); 977 978 // Enforce that only the app itself (or shared uid participant) can schedule a 979 // job that runs one of the app's services, as well as verifying that the 980 // named service properly requires the BIND_JOB_SERVICE permission 981 private void enforceValidJobRequest(int uid, JobInfo job) { 982 final IPackageManager pm = AppGlobals.getPackageManager(); 983 final ComponentName service = job.getService(); 984 try { 985 ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid)); 986 if (si == null) { 987 throw new IllegalArgumentException("No such service " + service); 988 } 989 if (si.applicationInfo.uid != uid) { 990 throw new IllegalArgumentException("uid " + uid + 991 " cannot schedule job in " + service.getPackageName()); 992 } 993 if (!JobService.PERMISSION_BIND.equals(si.permission)) { 994 throw new IllegalArgumentException("Scheduled service " + service 995 + " does not require android.permission.BIND_JOB_SERVICE permission"); 996 } 997 } catch (RemoteException e) { 998 // Can't happen; the Package Manager is in this same process 999 } 1000 } 1001 1002 private boolean canPersistJobs(int pid, int uid) { 1003 // If we get this far we're good to go; all we need to do now is check 1004 // whether the app is allowed to persist its scheduled work. 1005 final boolean canPersist; 1006 synchronized (mPersistCache) { 1007 Boolean cached = mPersistCache.get(uid); 1008 if (cached != null) { 1009 canPersist = cached.booleanValue(); 1010 } else { 1011 // Persisting jobs is tantamount to running at boot, so we permit 1012 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED 1013 // permission 1014 int result = getContext().checkPermission( 1015 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid); 1016 canPersist = (result == PackageManager.PERMISSION_GRANTED); 1017 mPersistCache.put(uid, canPersist); 1018 } 1019 } 1020 return canPersist; 1021 } 1022 1023 // IJobScheduler implementation 1024 @Override 1025 public int schedule(JobInfo job) throws RemoteException { 1026 if (DEBUG) { 1027 Slog.d(TAG, "Scheduling job: " + job.toString()); 1028 } 1029 final int pid = Binder.getCallingPid(); 1030 final int uid = Binder.getCallingUid(); 1031 1032 enforceValidJobRequest(uid, job); 1033 if (job.isPersisted()) { 1034 if (!canPersistJobs(pid, uid)) { 1035 throw new IllegalArgumentException("Error: requested job be persisted without" 1036 + " holding RECEIVE_BOOT_COMPLETED permission."); 1037 } 1038 } 1039 1040 long ident = Binder.clearCallingIdentity(); 1041 try { 1042 return JobSchedulerService.this.schedule(job, uid); 1043 } finally { 1044 Binder.restoreCallingIdentity(ident); 1045 } 1046 } 1047 1048 @Override 1049 public List<JobInfo> getAllPendingJobs() throws RemoteException { 1050 final int uid = Binder.getCallingUid(); 1051 1052 long ident = Binder.clearCallingIdentity(); 1053 try { 1054 return JobSchedulerService.this.getPendingJobs(uid); 1055 } finally { 1056 Binder.restoreCallingIdentity(ident); 1057 } 1058 } 1059 1060 @Override 1061 public void cancelAll() throws RemoteException { 1062 final int uid = Binder.getCallingUid(); 1063 1064 long ident = Binder.clearCallingIdentity(); 1065 try { 1066 JobSchedulerService.this.cancelJobsForUid(uid, true); 1067 } finally { 1068 Binder.restoreCallingIdentity(ident); 1069 } 1070 } 1071 1072 @Override 1073 public void cancel(int jobId) throws RemoteException { 1074 final int uid = Binder.getCallingUid(); 1075 1076 long ident = Binder.clearCallingIdentity(); 1077 try { 1078 JobSchedulerService.this.cancelJob(uid, jobId); 1079 } finally { 1080 Binder.restoreCallingIdentity(ident); 1081 } 1082 } 1083 1084 /** 1085 * "dumpsys" infrastructure 1086 */ 1087 @Override 1088 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1089 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1090 1091 long identityToken = Binder.clearCallingIdentity(); 1092 try { 1093 JobSchedulerService.this.dumpInternal(pw); 1094 } finally { 1095 Binder.restoreCallingIdentity(identityToken); 1096 } 1097 } 1098 }; 1099 1100 private String printContextIdToJobMap(JobStatus[] map, String initial) { 1101 StringBuilder s = new StringBuilder(initial + ": "); 1102 for (int i=0; i<map.length; i++) { 1103 s.append("(") 1104 .append(map[i] == null? -1: map[i].getJobId()) 1105 .append(map[i] == null? -1: map[i].getUid()) 1106 .append(")" ); 1107 } 1108 return s.toString(); 1109 } 1110 1111 private String printPendingQueue() { 1112 StringBuilder s = new StringBuilder("Pending queue: "); 1113 Iterator<JobStatus> it = mPendingJobs.iterator(); 1114 while (it.hasNext()) { 1115 JobStatus js = it.next(); 1116 s.append("(") 1117 .append(js.getJob().getId()) 1118 .append(", ") 1119 .append(js.getUid()) 1120 .append(") "); 1121 } 1122 return s.toString(); 1123 } 1124 1125 void dumpInternal(PrintWriter pw) { 1126 final long now = SystemClock.elapsedRealtime(); 1127 synchronized (mJobs) { 1128 pw.print("Started users: "); 1129 for (int i=0; i<mStartedUsers.size(); i++) { 1130 pw.print("u" + mStartedUsers.get(i) + " "); 1131 } 1132 pw.println(); 1133 pw.println("Registered jobs:"); 1134 if (mJobs.size() > 0) { 1135 ArraySet<JobStatus> jobs = mJobs.getJobs(); 1136 for (int i=0; i<jobs.size(); i++) { 1137 JobStatus job = jobs.valueAt(i); 1138 job.dump(pw, " "); 1139 } 1140 } else { 1141 pw.println(" None."); 1142 } 1143 for (int i=0; i<mControllers.size(); i++) { 1144 pw.println(); 1145 mControllers.get(i).dumpControllerState(pw); 1146 } 1147 pw.println(); 1148 pw.println(printPendingQueue()); 1149 pw.println(); 1150 pw.println("Active jobs:"); 1151 for (int i=0; i<mActiveServices.size(); i++) { 1152 JobServiceContext jsc = mActiveServices.get(i); 1153 if (jsc.getRunningJob() == null) { 1154 continue; 1155 } else { 1156 final long timeout = jsc.getTimeoutElapsed(); 1157 pw.print("Running for: "); 1158 pw.print((now - jsc.getExecutionStartTimeElapsed())/1000); 1159 pw.print("s timeout="); 1160 pw.print(timeout); 1161 pw.print(" fromnow="); 1162 pw.println(timeout-now); 1163 jsc.getRunningJob().dump(pw, " "); 1164 } 1165 } 1166 pw.println(); 1167 pw.print("mReadyToRock="); pw.println(mReadyToRock); 1168 pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode); 1169 pw.print("mReportedActive="); pw.println(mReportedActive); 1170 } 1171 pw.println(); 1172 } 1173} 1174