JobSchedulerService.java revision 5db09084c8e4efc6311754243c39962fc8e7a766
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 newEarliestRunTimeElapsed = elapsedNow + runEarly; 597 long period = periodicToReschedule.getJob().getIntervalMillis(); 598 long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period; 599 600 if (DEBUG) { 601 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" + 602 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s"); 603 } 604 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, 605 newLatestRuntimeElapsed, 0 /* backoffAttempt */); 606 } 607 608 // JobCompletedListener implementations. 609 610 /** 611 * A job just finished executing. We fetch the 612 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on 613 * whether we want to reschedule we readd it to the controllers. 614 * @param jobStatus Completed job. 615 * @param needsReschedule Whether the implementing class should reschedule this job. 616 */ 617 @Override 618 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) { 619 if (DEBUG) { 620 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule); 621 } 622 if (!stopTrackingJob(jobStatus)) { 623 if (DEBUG) { 624 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?"); 625 } 626 return; 627 } 628 if (needsReschedule) { 629 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus); 630 startTrackingJob(rescheduled); 631 } else if (jobStatus.getJob().isPeriodic()) { 632 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus); 633 startTrackingJob(rescheduledPeriodic); 634 } 635 reportActive(); 636 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget(); 637 } 638 639 // StateChangedListener implementations. 640 641 /** 642 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that 643 * some controller's state has changed, so as to run through the list of jobs and start/stop 644 * any that are eligible. 645 */ 646 @Override 647 public void onControllerStateChanged() { 648 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); 649 } 650 651 @Override 652 public void onRunJobNow(JobStatus jobStatus) { 653 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget(); 654 } 655 656 private class JobHandler extends Handler { 657 658 public JobHandler(Looper looper) { 659 super(looper); 660 } 661 662 @Override 663 public void handleMessage(Message message) { 664 synchronized (mJobs) { 665 if (!mReadyToRock) { 666 return; 667 } 668 } 669 switch (message.what) { 670 case MSG_JOB_EXPIRED: 671 synchronized (mJobs) { 672 JobStatus runNow = (JobStatus) message.obj; 673 // runNow can be null, which is a controller's way of indicating that its 674 // state is such that all ready jobs should be run immediately. 675 if (runNow != null && !mPendingJobs.contains(runNow) 676 && mJobs.containsJob(runNow)) { 677 mPendingJobs.add(runNow); 678 } 679 queueReadyJobsForExecutionLockedH(); 680 } 681 break; 682 case MSG_CHECK_JOB: 683 synchronized (mJobs) { 684 if (mReportedActive) { 685 // if jobs are currently being run, queue all ready jobs for execution. 686 queueReadyJobsForExecutionLockedH(); 687 } else { 688 // Check the list of jobs and run some of them if we feel inclined. 689 maybeQueueReadyJobsForExecutionLockedH(); 690 } 691 } 692 break; 693 case MSG_CHECK_JOB_GREEDY: 694 synchronized (mJobs) { 695 queueReadyJobsForExecutionLockedH(); 696 } 697 break; 698 case MSG_STOP_JOB: 699 cancelJobImpl((JobStatus)message.obj); 700 break; 701 } 702 maybeRunPendingJobsH(); 703 // Don't remove JOB_EXPIRED in case one came along while processing the queue. 704 removeMessages(MSG_CHECK_JOB); 705 } 706 707 /** 708 * Run through list of jobs and execute all possible - at least one is expired so we do 709 * as many as we can. 710 */ 711 private void queueReadyJobsForExecutionLockedH() { 712 ArraySet<JobStatus> jobs = mJobs.getJobs(); 713 mPendingJobs.clear(); 714 if (DEBUG) { 715 Slog.d(TAG, "queuing all ready jobs for execution:"); 716 } 717 for (int i=0; i<jobs.size(); i++) { 718 JobStatus job = jobs.valueAt(i); 719 if (isReadyToBeExecutedLocked(job)) { 720 if (DEBUG) { 721 Slog.d(TAG, " queued " + job.toShortString()); 722 } 723 mPendingJobs.add(job); 724 } else if (areJobConstraintsNotSatisfied(job)) { 725 stopJobOnServiceContextLocked(job, 726 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED); 727 } 728 } 729 if (DEBUG) { 730 final int queuedJobs = mPendingJobs.size(); 731 if (queuedJobs == 0) { 732 Slog.d(TAG, "No jobs pending."); 733 } else { 734 Slog.d(TAG, queuedJobs + " jobs queued."); 735 } 736 } 737 } 738 739 /** 740 * The state of at least one job has changed. Here is where we could enforce various 741 * policies on when we want to execute jobs. 742 * Right now the policy is such: 743 * If >1 of the ready jobs is idle mode we send all of them off 744 * if more than 2 network connectivity jobs are ready we send them all off. 745 * If more than 4 jobs total are ready we send them all off. 746 * TODO: It would be nice to consolidate these sort of high-level policies somewhere. 747 */ 748 private void maybeQueueReadyJobsForExecutionLockedH() { 749 mPendingJobs.clear(); 750 int chargingCount = 0; 751 int idleCount = 0; 752 int backoffCount = 0; 753 int connectivityCount = 0; 754 List<JobStatus> runnableJobs = null; 755 ArraySet<JobStatus> jobs = mJobs.getJobs(); 756 for (int i=0; i<jobs.size(); i++) { 757 JobStatus job = jobs.valueAt(i); 758 if (isReadyToBeExecutedLocked(job)) { 759 try { 760 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(), 761 job.getJob().getService().getPackageName()) 762 == ActivityManager.APP_START_MODE_DISABLED) { 763 Slog.w(TAG, "Aborting job " + job.getUid() + ":" 764 + job.getJob().toString() + " -- package not allowed to start"); 765 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget(); 766 continue; 767 } 768 } catch (RemoteException e) { 769 } 770 if (job.getNumFailures() > 0) { 771 backoffCount++; 772 } 773 if (job.hasIdleConstraint()) { 774 idleCount++; 775 } 776 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) { 777 connectivityCount++; 778 } 779 if (job.hasChargingConstraint()) { 780 chargingCount++; 781 } 782 if (runnableJobs == null) { 783 runnableJobs = new ArrayList<>(); 784 } 785 runnableJobs.add(job); 786 } else if (areJobConstraintsNotSatisfied(job)) { 787 stopJobOnServiceContextLocked(job, 788 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED); 789 } 790 } 791 if (backoffCount > 0 || 792 idleCount >= MIN_IDLE_COUNT || 793 connectivityCount >= MIN_CONNECTIVITY_COUNT || 794 chargingCount >= MIN_CHARGING_COUNT || 795 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) { 796 if (DEBUG) { 797 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs."); 798 } 799 for (int i=0; i<runnableJobs.size(); i++) { 800 mPendingJobs.add(runnableJobs.get(i)); 801 } 802 } else { 803 if (DEBUG) { 804 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything."); 805 } 806 } 807 } 808 809 /** 810 * Criteria for moving a job into the pending queue: 811 * - It's ready. 812 * - It's not pending. 813 * - It's not already running on a JSC. 814 * - The user that requested the job is running. 815 */ 816 private boolean isReadyToBeExecutedLocked(JobStatus job) { 817 final boolean jobReady = job.isReady(); 818 final boolean jobPending = mPendingJobs.contains(job); 819 final boolean jobActive = isCurrentlyActiveLocked(job); 820 final boolean userRunning = mStartedUsers.contains(job.getUserId()); 821 if (DEBUG) { 822 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() 823 + " ready=" + jobReady + " pending=" + jobPending 824 + " active=" + jobActive + " userRunning=" + userRunning); 825 } 826 return userRunning && jobReady && !jobPending && !jobActive; 827 } 828 829 /** 830 * Criteria for cancelling an active job: 831 * - It's not ready 832 * - It's running on a JSC. 833 */ 834 private boolean areJobConstraintsNotSatisfied(JobStatus job) { 835 return !job.isReady() && isCurrentlyActiveLocked(job); 836 } 837 838 /** 839 * Reconcile jobs in the pending queue against available execution contexts. 840 * A controller can force a job into the pending queue even if it's already running, but 841 * here is where we decide whether to actually execute it. 842 */ 843 private void maybeRunPendingJobsH() { 844 synchronized (mJobs) { 845 if (mDeviceIdleMode) { 846 // If device is idle, we will not schedule jobs to run. 847 return; 848 } 849 if (DEBUG) { 850 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); 851 } 852 assignJobsToContextsH(); 853 reportActive(); 854 } 855 } 856 } 857 858 /** 859 * Takes jobs from pending queue and runs them on available contexts. 860 * If no contexts are available, preempts lower priority jobs to 861 * run higher priority ones. 862 * Lock on mJobs before calling this function. 863 */ 864 private void assignJobsToContextsH() { 865 if (DEBUG) { 866 Slog.d(TAG, printPendingQueue()); 867 } 868 869 // This array essentially stores the state of mActiveServices array. 870 // ith index stores the job present on the ith JobServiceContext. 871 // We manipulate this array until we arrive at what jobs should be running on 872 // what JobServiceContext. 873 JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT]; 874 // Indicates whether we need to act on this jobContext id 875 boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT]; 876 int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT]; 877 for (int i=0; i<mActiveServices.size(); i++) { 878 contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob(); 879 preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid(); 880 } 881 if (DEBUG) { 882 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial")); 883 } 884 Iterator<JobStatus> it = mPendingJobs.iterator(); 885 while (it.hasNext()) { 886 JobStatus nextPending = it.next(); 887 888 // If job is already running, go to next job. 889 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap); 890 if (jobRunningContext != -1) { 891 continue; 892 } 893 894 // Find a context for nextPending. The context should be available OR 895 // it should have lowest priority among all running jobs 896 // (sharing the same Uid as nextPending) 897 int minPriority = Integer.MAX_VALUE; 898 int minPriorityContextId = -1; 899 for (int i=0; i<mActiveServices.size(); i++) { 900 JobStatus job = contextIdToJobMap[i]; 901 int preferredUid = preferredUidForContext[i]; 902 if (job == null && (preferredUid == nextPending.getUid() || 903 preferredUid == JobServiceContext.NO_PREFERRED_UID) ) { 904 minPriorityContextId = i; 905 break; 906 } 907 if (job.getUid() != nextPending.getUid()) { 908 continue; 909 } 910 if (job.getPriority() >= nextPending.getPriority()) { 911 continue; 912 } 913 if (minPriority > nextPending.getPriority()) { 914 minPriority = nextPending.getPriority(); 915 minPriorityContextId = i; 916 } 917 } 918 if (minPriorityContextId != -1) { 919 contextIdToJobMap[minPriorityContextId] = nextPending; 920 act[minPriorityContextId] = true; 921 } 922 } 923 if (DEBUG) { 924 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); 925 } 926 for (int i=0; i<mActiveServices.size(); i++) { 927 boolean preservePreferredUid = false; 928 if (act[i]) { 929 JobStatus js = mActiveServices.get(i).getRunningJob(); 930 if (js != null) { 931 if (DEBUG) { 932 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob()); 933 } 934 // preferredUid will be set to uid of currently running job. 935 mActiveServices.get(i).preemptExecutingJob(); 936 preservePreferredUid = true; 937 } else { 938 if (DEBUG) { 939 Slog.d(TAG, "About to run job on context " 940 + String.valueOf(i) + ", job: " + contextIdToJobMap[i]); 941 } 942 if (!mActiveServices.get(i).executeRunnableJob(contextIdToJobMap[i])) { 943 Slog.d(TAG, "Error executing " + contextIdToJobMap[i]); 944 } 945 mPendingJobs.remove(contextIdToJobMap[i]); 946 } 947 } 948 if (!preservePreferredUid) { 949 mActiveServices.get(i).clearPreferredUid(); 950 } 951 } 952 } 953 954 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) { 955 for (int i=0; i<map.length; i++) { 956 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) { 957 return i; 958 } 959 } 960 return -1; 961 } 962 963 /** 964 * Binder stub trampoline implementation 965 */ 966 final class JobSchedulerStub extends IJobScheduler.Stub { 967 /** Cache determination of whether a given app can persist jobs 968 * key is uid of the calling app; value is undetermined/true/false 969 */ 970 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); 971 972 // Enforce that only the app itself (or shared uid participant) can schedule a 973 // job that runs one of the app's services, as well as verifying that the 974 // named service properly requires the BIND_JOB_SERVICE permission 975 private void enforceValidJobRequest(int uid, JobInfo job) { 976 final IPackageManager pm = AppGlobals.getPackageManager(); 977 final ComponentName service = job.getService(); 978 try { 979 ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid)); 980 if (si == null) { 981 throw new IllegalArgumentException("No such service " + service); 982 } 983 if (si.applicationInfo.uid != uid) { 984 throw new IllegalArgumentException("uid " + uid + 985 " cannot schedule job in " + service.getPackageName()); 986 } 987 if (!JobService.PERMISSION_BIND.equals(si.permission)) { 988 throw new IllegalArgumentException("Scheduled service " + service 989 + " does not require android.permission.BIND_JOB_SERVICE permission"); 990 } 991 } catch (RemoteException e) { 992 // Can't happen; the Package Manager is in this same process 993 } 994 } 995 996 private boolean canPersistJobs(int pid, int uid) { 997 // If we get this far we're good to go; all we need to do now is check 998 // whether the app is allowed to persist its scheduled work. 999 final boolean canPersist; 1000 synchronized (mPersistCache) { 1001 Boolean cached = mPersistCache.get(uid); 1002 if (cached != null) { 1003 canPersist = cached.booleanValue(); 1004 } else { 1005 // Persisting jobs is tantamount to running at boot, so we permit 1006 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED 1007 // permission 1008 int result = getContext().checkPermission( 1009 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid); 1010 canPersist = (result == PackageManager.PERMISSION_GRANTED); 1011 mPersistCache.put(uid, canPersist); 1012 } 1013 } 1014 return canPersist; 1015 } 1016 1017 // IJobScheduler implementation 1018 @Override 1019 public int schedule(JobInfo job) throws RemoteException { 1020 if (DEBUG) { 1021 Slog.d(TAG, "Scheduling job: " + job.toString()); 1022 } 1023 final int pid = Binder.getCallingPid(); 1024 final int uid = Binder.getCallingUid(); 1025 1026 enforceValidJobRequest(uid, job); 1027 if (job.isPersisted()) { 1028 if (!canPersistJobs(pid, uid)) { 1029 throw new IllegalArgumentException("Error: requested job be persisted without" 1030 + " holding RECEIVE_BOOT_COMPLETED permission."); 1031 } 1032 } 1033 1034 long ident = Binder.clearCallingIdentity(); 1035 try { 1036 return JobSchedulerService.this.schedule(job, uid); 1037 } finally { 1038 Binder.restoreCallingIdentity(ident); 1039 } 1040 } 1041 1042 @Override 1043 public List<JobInfo> getAllPendingJobs() throws RemoteException { 1044 final int uid = Binder.getCallingUid(); 1045 1046 long ident = Binder.clearCallingIdentity(); 1047 try { 1048 return JobSchedulerService.this.getPendingJobs(uid); 1049 } finally { 1050 Binder.restoreCallingIdentity(ident); 1051 } 1052 } 1053 1054 @Override 1055 public void cancelAll() throws RemoteException { 1056 final int uid = Binder.getCallingUid(); 1057 1058 long ident = Binder.clearCallingIdentity(); 1059 try { 1060 JobSchedulerService.this.cancelJobsForUid(uid, true); 1061 } finally { 1062 Binder.restoreCallingIdentity(ident); 1063 } 1064 } 1065 1066 @Override 1067 public void cancel(int jobId) throws RemoteException { 1068 final int uid = Binder.getCallingUid(); 1069 1070 long ident = Binder.clearCallingIdentity(); 1071 try { 1072 JobSchedulerService.this.cancelJob(uid, jobId); 1073 } finally { 1074 Binder.restoreCallingIdentity(ident); 1075 } 1076 } 1077 1078 /** 1079 * "dumpsys" infrastructure 1080 */ 1081 @Override 1082 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1083 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1084 1085 long identityToken = Binder.clearCallingIdentity(); 1086 try { 1087 JobSchedulerService.this.dumpInternal(pw); 1088 } finally { 1089 Binder.restoreCallingIdentity(identityToken); 1090 } 1091 } 1092 }; 1093 1094 private String printContextIdToJobMap(JobStatus[] map, String initial) { 1095 StringBuilder s = new StringBuilder(initial + ": "); 1096 for (int i=0; i<map.length; i++) { 1097 s.append("(") 1098 .append(map[i] == null? -1: map[i].getJobId()) 1099 .append(map[i] == null? -1: map[i].getUid()) 1100 .append(")" ); 1101 } 1102 return s.toString(); 1103 } 1104 1105 private String printPendingQueue() { 1106 StringBuilder s = new StringBuilder("Pending queue: "); 1107 Iterator<JobStatus> it = mPendingJobs.iterator(); 1108 while (it.hasNext()) { 1109 JobStatus js = it.next(); 1110 s.append("(") 1111 .append(js.getJob().getId()) 1112 .append(", ") 1113 .append(js.getUid()) 1114 .append(") "); 1115 } 1116 return s.toString(); 1117 } 1118 1119 void dumpInternal(PrintWriter pw) { 1120 final long now = SystemClock.elapsedRealtime(); 1121 synchronized (mJobs) { 1122 pw.print("Started users: "); 1123 for (int i=0; i<mStartedUsers.size(); i++) { 1124 pw.print("u" + mStartedUsers.get(i) + " "); 1125 } 1126 pw.println(); 1127 pw.println("Registered jobs:"); 1128 if (mJobs.size() > 0) { 1129 ArraySet<JobStatus> jobs = mJobs.getJobs(); 1130 for (int i=0; i<jobs.size(); i++) { 1131 JobStatus job = jobs.valueAt(i); 1132 job.dump(pw, " "); 1133 } 1134 } else { 1135 pw.println(" None."); 1136 } 1137 for (int i=0; i<mControllers.size(); i++) { 1138 pw.println(); 1139 mControllers.get(i).dumpControllerState(pw); 1140 } 1141 pw.println(); 1142 pw.println(printPendingQueue()); 1143 pw.println(); 1144 pw.println("Active jobs:"); 1145 for (int i=0; i<mActiveServices.size(); i++) { 1146 JobServiceContext jsc = mActiveServices.get(i); 1147 if (jsc.getRunningJob() == null) { 1148 continue; 1149 } else { 1150 final long timeout = jsc.getTimeoutElapsed(); 1151 pw.print("Running for: "); 1152 pw.print((now - jsc.getExecutionStartTimeElapsed())/1000); 1153 pw.print("s timeout="); 1154 pw.print(timeout); 1155 pw.print(" fromnow="); 1156 pw.println(timeout-now); 1157 jsc.getRunningJob().dump(pw, " "); 1158 } 1159 } 1160 pw.println(); 1161 pw.print("mReadyToRock="); pw.println(mReadyToRock); 1162 pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode); 1163 pw.print("mReportedActive="); pw.println(mReportedActive); 1164 } 1165 pw.println(); 1166 } 1167} 1168