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