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