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 com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 20 21 import android.app.job.IJobCallback; 22 import android.app.job.IJobService; 23 import android.app.job.JobInfo; 24 import android.app.job.JobParameters; 25 import android.app.job.JobProtoEnums; 26 import android.app.job.JobWorkItem; 27 import android.app.usage.UsageStatsManagerInternal; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.ServiceConnection; 32 import android.net.Uri; 33 import android.os.Binder; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.Looper; 37 import android.os.Message; 38 import android.os.PowerManager; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.os.WorkSource; 42 import android.util.EventLog; 43 import android.util.Slog; 44 import android.util.TimeUtils; 45 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.app.IBatteryStats; 49 import com.android.internal.util.FrameworkStatsLog; 50 import com.android.server.EventLogTags; 51 import com.android.server.LocalServices; 52 import com.android.server.job.controllers.JobStatus; 53 54 /** 55 * Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this 56 * class. 57 * 58 * There are two important interactions into this class from the 59 * {@link com.android.server.job.JobSchedulerService}. To execute a job and to cancel a job. 60 * - Execution of a new job is handled by the {@link #mAvailable}. This bit is flipped once when a 61 * job lands, and again when it is complete. 62 * - Cancelling is trickier, because there are also interactions from the client. It's possible 63 * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a 64 * {@link #doCancelLocked} after the client has already finished. This is handled by having 65 * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether 66 * the context is still valid. 67 * To mitigate this, we avoid sending duplicate onStopJob() 68 * calls to the client after they've specified jobFinished(). 69 */ 70 public final class JobServiceContext implements ServiceConnection { 71 private static final boolean DEBUG = JobSchedulerService.DEBUG; 72 private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY; 73 74 private static final String TAG = "JobServiceContext"; 75 /** Amount of time a job is allowed to execute for before being considered timed-out. */ 76 public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins. 77 /** Amount of time the JobScheduler waits for the initial service launch+bind. */ 78 private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000; 79 /** Amount of time the JobScheduler will wait for a response from an app for a message. */ 80 private static final long OP_TIMEOUT_MILLIS = 8 * 1000; 81 82 private static final String[] VERB_STRINGS = { 83 "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED" 84 }; 85 86 // States that a job occupies while interacting with the client. 87 static final int VERB_BINDING = 0; 88 static final int VERB_STARTING = 1; 89 static final int VERB_EXECUTING = 2; 90 static final int VERB_STOPPING = 3; 91 static final int VERB_FINISHED = 4; 92 93 // Messages that result from interactions with the client service. 94 /** System timed out waiting for a response. */ 95 private static final int MSG_TIMEOUT = 0; 96 97 public static final int NO_PREFERRED_UID = -1; 98 99 private final Handler mCallbackHandler; 100 /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */ 101 private final JobCompletedListener mCompletedListener; 102 /** Used for service binding, etc. */ 103 private final Context mContext; 104 private final Object mLock; 105 private final IBatteryStats mBatteryStats; 106 private final JobPackageTracker mJobPackageTracker; 107 private PowerManager.WakeLock mWakeLock; 108 109 // Execution state. 110 private JobParameters mParams; 111 @VisibleForTesting 112 int mVerb; 113 private boolean mCancelled; 114 115 /** 116 * All the information maintained about the job currently being executed. 117 * 118 * Any reads (dereferences) not done from the handler thread must be synchronized on 119 * {@link #mLock}. 120 * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}. 121 */ 122 private JobStatus mRunningJob; 123 private JobCallback mRunningCallback; 124 /** Used to store next job to run when current job is to be preempted. */ 125 private int mPreferredUid; 126 IJobService service; 127 128 /** 129 * Whether this context is free. This is set to false at the start of execution, and reset to 130 * true when execution is complete. 131 */ 132 @GuardedBy("mLock") 133 private boolean mAvailable; 134 /** Track start time. */ 135 private long mExecutionStartTimeElapsed; 136 /** Track when job will timeout. */ 137 private long mTimeoutElapsed; 138 139 // Debugging: reason this job was last stopped. 140 public String mStoppedReason; 141 142 // Debugging: time this job was last stopped. 143 public long mStoppedTime; 144 145 final class JobCallback extends IJobCallback.Stub { 146 public String mStoppedReason; 147 public long mStoppedTime; 148 149 @Override acknowledgeStartMessage(int jobId, boolean ongoing)150 public void acknowledgeStartMessage(int jobId, boolean ongoing) { 151 doAcknowledgeStartMessage(this, jobId, ongoing); 152 } 153 154 @Override acknowledgeStopMessage(int jobId, boolean reschedule)155 public void acknowledgeStopMessage(int jobId, boolean reschedule) { 156 doAcknowledgeStopMessage(this, jobId, reschedule); 157 } 158 159 @Override dequeueWork(int jobId)160 public JobWorkItem dequeueWork(int jobId) { 161 return doDequeueWork(this, jobId); 162 } 163 164 @Override completeWork(int jobId, int workId)165 public boolean completeWork(int jobId, int workId) { 166 return doCompleteWork(this, jobId, workId); 167 } 168 169 @Override jobFinished(int jobId, boolean reschedule)170 public void jobFinished(int jobId, boolean reschedule) { 171 doJobFinished(this, jobId, reschedule); 172 } 173 } 174 JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper)175 JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, 176 JobPackageTracker tracker, Looper looper) { 177 this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper); 178 } 179 180 @VisibleForTesting JobServiceContext(Context context, Object lock, IBatteryStats batteryStats, JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper)181 JobServiceContext(Context context, Object lock, IBatteryStats batteryStats, 182 JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) { 183 mContext = context; 184 mLock = lock; 185 mBatteryStats = batteryStats; 186 mJobPackageTracker = tracker; 187 mCallbackHandler = new JobServiceHandler(looper); 188 mCompletedListener = completedListener; 189 mAvailable = true; 190 mVerb = VERB_FINISHED; 191 mPreferredUid = NO_PREFERRED_UID; 192 } 193 194 /** 195 * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()} 196 * and ensure it is null to make sure this is a valid context. 197 * @param job The status of the job that we are going to run. 198 * @return True if the job is valid and is running. False if the job cannot be executed. 199 */ executeRunnableJob(JobStatus job)200 boolean executeRunnableJob(JobStatus job) { 201 synchronized (mLock) { 202 if (!mAvailable) { 203 Slog.e(TAG, "Starting new runnable but context is unavailable > Error."); 204 return false; 205 } 206 207 mPreferredUid = NO_PREFERRED_UID; 208 209 mRunningJob = job; 210 mRunningCallback = new JobCallback(); 211 final boolean isDeadlineExpired = 212 job.hasDeadlineConstraint() && 213 (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis()); 214 Uri[] triggeredUris = null; 215 if (job.changedUris != null) { 216 triggeredUris = new Uri[job.changedUris.size()]; 217 job.changedUris.toArray(triggeredUris); 218 } 219 String[] triggeredAuthorities = null; 220 if (job.changedAuthorities != null) { 221 triggeredAuthorities = new String[job.changedAuthorities.size()]; 222 job.changedAuthorities.toArray(triggeredAuthorities); 223 } 224 final JobInfo ji = job.getJob(); 225 mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(), 226 ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(), 227 isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network); 228 mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis(); 229 230 final long whenDeferred = job.getWhenStandbyDeferred(); 231 if (whenDeferred > 0) { 232 final long deferral = mExecutionStartTimeElapsed - whenDeferred; 233 EventLog.writeEvent(EventLogTags.JOB_DEFERRED_EXECUTION, deferral); 234 if (DEBUG_STANDBY) { 235 StringBuilder sb = new StringBuilder(128); 236 sb.append("Starting job deferred for standby by "); 237 TimeUtils.formatDuration(deferral, sb); 238 sb.append(" ms : "); 239 sb.append(job.toShortString()); 240 Slog.v(TAG, sb.toString()); 241 } 242 } 243 244 // Once we'e begun executing a job, we by definition no longer care whether 245 // it was inflated from disk with not-yet-coherent delay/deadline bounds. 246 job.clearPersistedUtcTimes(); 247 248 mVerb = VERB_BINDING; 249 scheduleOpTimeOutLocked(); 250 final Intent intent = new Intent().setComponent(job.getServiceComponent()); 251 boolean binding = false; 252 try { 253 binding = mContext.bindServiceAsUser(intent, this, 254 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND 255 | Context.BIND_NOT_PERCEPTIBLE, 256 UserHandle.of(job.getUserId())); 257 } catch (SecurityException e) { 258 // Some permission policy, for example INTERACT_ACROSS_USERS and 259 // android:singleUser, can result in a SecurityException being thrown from 260 // bindServiceAsUser(). If this happens, catch it and fail gracefully. 261 Slog.w(TAG, "Job service " + job.getServiceComponent().getShortClassName() 262 + " cannot be executed: " + e.getMessage()); 263 binding = false; 264 } 265 if (!binding) { 266 if (DEBUG) { 267 Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable."); 268 } 269 mRunningJob = null; 270 mRunningCallback = null; 271 mParams = null; 272 mExecutionStartTimeElapsed = 0L; 273 mVerb = VERB_FINISHED; 274 removeOpTimeOutLocked(); 275 return false; 276 } 277 mJobPackageTracker.noteActive(job); 278 FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, 279 job.getSourceUid(), null, job.getBatteryName(), 280 FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__STARTED, 281 JobProtoEnums.STOP_REASON_UNKNOWN, job.getStandbyBucket(), job.getJobId(), 282 job.hasChargingConstraint(), 283 job.hasBatteryNotLowConstraint(), 284 job.hasStorageNotLowConstraint(), 285 job.hasTimingDelayConstraint(), 286 job.hasDeadlineConstraint(), 287 job.hasIdleConstraint(), 288 job.hasConnectivityConstraint(), 289 job.hasContentTriggerConstraint()); 290 try { 291 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); 292 } catch (RemoteException e) { 293 // Whatever. 294 } 295 final String jobPackage = job.getSourcePackageName(); 296 final int jobUserId = job.getSourceUserId(); 297 UsageStatsManagerInternal usageStats = 298 LocalServices.getService(UsageStatsManagerInternal.class); 299 usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed); 300 mAvailable = false; 301 mStoppedReason = null; 302 mStoppedTime = 0; 303 return true; 304 } 305 } 306 307 /** 308 * Used externally to query the running job. Will return null if there is no job running. 309 */ getRunningJobLocked()310 JobStatus getRunningJobLocked() { 311 return mRunningJob; 312 } 313 314 /** 315 * Used only for debugging. Will return <code>"<null>"</code> if there is no job running. 316 */ getRunningJobNameLocked()317 private String getRunningJobNameLocked() { 318 return mRunningJob != null ? mRunningJob.toShortString() : "<null>"; 319 } 320 321 /** Called externally when a job that was scheduled for execution should be cancelled. */ 322 @GuardedBy("mLock") cancelExecutingJobLocked(int reason, String debugReason)323 void cancelExecutingJobLocked(int reason, String debugReason) { 324 doCancelLocked(reason, debugReason); 325 } 326 327 @GuardedBy("mLock") preemptExecutingJobLocked()328 void preemptExecutingJobLocked() { 329 doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption"); 330 } 331 getPreferredUid()332 int getPreferredUid() { 333 return mPreferredUid; 334 } 335 clearPreferredUid()336 void clearPreferredUid() { 337 mPreferredUid = NO_PREFERRED_UID; 338 } 339 getExecutionStartTimeElapsed()340 long getExecutionStartTimeElapsed() { 341 return mExecutionStartTimeElapsed; 342 } 343 getTimeoutElapsed()344 long getTimeoutElapsed() { 345 return mTimeoutElapsed; 346 } 347 348 @GuardedBy("mLock") timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId, String reason)349 boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId, 350 String reason) { 351 final JobStatus executing = getRunningJobLocked(); 352 if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId()) 353 && (pkgName == null || pkgName.equals(executing.getSourcePackageName())) 354 && (!matchJobId || jobId == executing.getJobId())) { 355 if (mVerb == VERB_EXECUTING) { 356 mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason); 357 sendStopMessageLocked("force timeout from shell"); 358 return true; 359 } 360 } 361 return false; 362 } 363 doJobFinished(JobCallback cb, int jobId, boolean reschedule)364 void doJobFinished(JobCallback cb, int jobId, boolean reschedule) { 365 doCallback(cb, reschedule, "app called jobFinished"); 366 } 367 doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule)368 void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) { 369 doCallback(cb, reschedule, null); 370 } 371 doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing)372 void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) { 373 doCallback(cb, ongoing, "finished start"); 374 } 375 doDequeueWork(JobCallback cb, int jobId)376 JobWorkItem doDequeueWork(JobCallback cb, int jobId) { 377 final long ident = Binder.clearCallingIdentity(); 378 try { 379 synchronized (mLock) { 380 assertCallerLocked(cb); 381 if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) { 382 // This job is either all done, or on its way out. Either way, it 383 // should not dispatch any more work. We will pick up any remaining 384 // work the next time we start the job again. 385 return null; 386 } 387 final JobWorkItem work = mRunningJob.dequeueWorkLocked(); 388 if (work == null && !mRunningJob.hasExecutingWorkLocked()) { 389 // This will finish the job. 390 doCallbackLocked(false, "last work dequeued"); 391 } 392 return work; 393 } 394 } finally { 395 Binder.restoreCallingIdentity(ident); 396 } 397 } 398 doCompleteWork(JobCallback cb, int jobId, int workId)399 boolean doCompleteWork(JobCallback cb, int jobId, int workId) { 400 final long ident = Binder.clearCallingIdentity(); 401 try { 402 synchronized (mLock) { 403 assertCallerLocked(cb); 404 return mRunningJob.completeWorkLocked(workId); 405 } 406 } finally { 407 Binder.restoreCallingIdentity(ident); 408 } 409 } 410 411 /** 412 * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work 413 * we intend to send to the client - we stop sending work when the service is unbound so until 414 * then we keep the wakelock. 415 * @param name The concrete component name of the service that has been connected. 416 * @param service The IBinder of the Service's communication channel, 417 */ 418 @Override onServiceConnected(ComponentName name, IBinder service)419 public void onServiceConnected(ComponentName name, IBinder service) { 420 JobStatus runningJob; 421 synchronized (mLock) { 422 // This isn't strictly necessary b/c the JobServiceHandler is running on the main 423 // looper and at this point we can't get any binder callbacks from the client. Better 424 // safe than sorry. 425 runningJob = mRunningJob; 426 427 if (runningJob == null || !name.equals(runningJob.getServiceComponent())) { 428 closeAndCleanupJobLocked(true /* needsReschedule */, 429 "connected for different component"); 430 return; 431 } 432 this.service = IJobService.Stub.asInterface(service); 433 final PowerManager pm = 434 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 435 PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 436 runningJob.getTag()); 437 wl.setWorkSource(deriveWorkSource(runningJob)); 438 wl.setReferenceCounted(false); 439 wl.acquire(); 440 441 // We use a new wakelock instance per job. In rare cases there is a race between 442 // teardown following job completion/cancellation and new job service spin-up 443 // such that if we simply assign mWakeLock to be the new instance, we orphan 444 // the currently-live lock instead of cleanly replacing it. Watch for this and 445 // explicitly fast-forward the release if we're in that situation. 446 if (mWakeLock != null) { 447 Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock 448 + " tag=" + mWakeLock.getTag()); 449 mWakeLock.release(); 450 } 451 mWakeLock = wl; 452 doServiceBoundLocked(); 453 } 454 } 455 deriveWorkSource(JobStatus runningJob)456 private WorkSource deriveWorkSource(JobStatus runningJob) { 457 final int jobUid = runningJob.getSourceUid(); 458 if (WorkSource.isChainedBatteryAttributionEnabled(mContext)) { 459 WorkSource workSource = new WorkSource(); 460 workSource.createWorkChain() 461 .addNode(jobUid, null) 462 .addNode(android.os.Process.SYSTEM_UID, "JobScheduler"); 463 return workSource; 464 } else { 465 return new WorkSource(jobUid); 466 } 467 } 468 469 /** If the client service crashes we reschedule this job and clean up. */ 470 @Override onServiceDisconnected(ComponentName name)471 public void onServiceDisconnected(ComponentName name) { 472 synchronized (mLock) { 473 closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected"); 474 } 475 } 476 477 /** 478 * This class is reused across different clients, and passes itself in as a callback. Check 479 * whether the client exercising the callback is the client we expect. 480 * @return True if the binder calling is coming from the client we expect. 481 */ verifyCallerLocked(JobCallback cb)482 private boolean verifyCallerLocked(JobCallback cb) { 483 if (mRunningCallback != cb) { 484 if (DEBUG) { 485 Slog.d(TAG, "Stale callback received, ignoring."); 486 } 487 return false; 488 } 489 return true; 490 } 491 assertCallerLocked(JobCallback cb)492 private void assertCallerLocked(JobCallback cb) { 493 if (!verifyCallerLocked(cb)) { 494 StringBuilder sb = new StringBuilder(128); 495 sb.append("Caller no longer running"); 496 if (cb.mStoppedReason != null) { 497 sb.append(", last stopped "); 498 TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb); 499 sb.append(" because: "); 500 sb.append(cb.mStoppedReason); 501 } 502 throw new SecurityException(sb.toString()); 503 } 504 } 505 506 /** 507 * Scheduling of async messages (basically timeouts at this point). 508 */ 509 private class JobServiceHandler extends Handler { JobServiceHandler(Looper looper)510 JobServiceHandler(Looper looper) { 511 super(looper); 512 } 513 514 @Override handleMessage(Message message)515 public void handleMessage(Message message) { 516 switch (message.what) { 517 case MSG_TIMEOUT: 518 synchronized (mLock) { 519 if (message.obj == mRunningCallback) { 520 handleOpTimeoutLocked(); 521 } else { 522 JobCallback jc = (JobCallback)message.obj; 523 StringBuilder sb = new StringBuilder(128); 524 sb.append("Ignoring timeout of no longer active job"); 525 if (jc.mStoppedReason != null) { 526 sb.append(", stopped "); 527 TimeUtils.formatDuration(sElapsedRealtimeClock.millis() 528 - jc.mStoppedTime, sb); 529 sb.append(" because: "); 530 sb.append(jc.mStoppedReason); 531 } 532 Slog.w(TAG, sb.toString()); 533 } 534 } 535 break; 536 default: 537 Slog.e(TAG, "Unrecognised message: " + message); 538 } 539 } 540 } 541 542 @GuardedBy("mLock") doServiceBoundLocked()543 void doServiceBoundLocked() { 544 removeOpTimeOutLocked(); 545 handleServiceBoundLocked(); 546 } 547 doCallback(JobCallback cb, boolean reschedule, String reason)548 void doCallback(JobCallback cb, boolean reschedule, String reason) { 549 final long ident = Binder.clearCallingIdentity(); 550 try { 551 synchronized (mLock) { 552 if (!verifyCallerLocked(cb)) { 553 return; 554 } 555 doCallbackLocked(reschedule, reason); 556 } 557 } finally { 558 Binder.restoreCallingIdentity(ident); 559 } 560 } 561 562 @GuardedBy("mLock") doCallbackLocked(boolean reschedule, String reason)563 void doCallbackLocked(boolean reschedule, String reason) { 564 if (DEBUG) { 565 Slog.d(TAG, "doCallback of : " + mRunningJob 566 + " v:" + VERB_STRINGS[mVerb]); 567 } 568 removeOpTimeOutLocked(); 569 570 if (mVerb == VERB_STARTING) { 571 handleStartedLocked(reschedule); 572 } else if (mVerb == VERB_EXECUTING || 573 mVerb == VERB_STOPPING) { 574 handleFinishedLocked(reschedule, reason); 575 } else { 576 if (DEBUG) { 577 Slog.d(TAG, "Unrecognised callback: " + mRunningJob); 578 } 579 } 580 } 581 582 @GuardedBy("mLock") doCancelLocked(int arg1, String debugReason)583 void doCancelLocked(int arg1, String debugReason) { 584 if (mVerb == VERB_FINISHED) { 585 if (DEBUG) { 586 Slog.d(TAG, 587 "Trying to process cancel for torn-down context, ignoring."); 588 } 589 return; 590 } 591 mParams.setStopReason(arg1, debugReason); 592 if (arg1 == JobParameters.REASON_PREEMPT) { 593 mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : 594 NO_PREFERRED_UID; 595 } 596 handleCancelLocked(debugReason); 597 } 598 599 /** Start the job on the service. */ 600 @GuardedBy("mLock") handleServiceBoundLocked()601 private void handleServiceBoundLocked() { 602 if (DEBUG) { 603 Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked()); 604 } 605 if (mVerb != VERB_BINDING) { 606 Slog.e(TAG, "Sending onStartJob for a job that isn't pending. " 607 + VERB_STRINGS[mVerb]); 608 closeAndCleanupJobLocked(false /* reschedule */, "started job not pending"); 609 return; 610 } 611 if (mCancelled) { 612 if (DEBUG) { 613 Slog.d(TAG, "Job cancelled while waiting for bind to complete. " 614 + mRunningJob); 615 } 616 closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind"); 617 return; 618 } 619 try { 620 mVerb = VERB_STARTING; 621 scheduleOpTimeOutLocked(); 622 service.startJob(mParams); 623 } catch (Exception e) { 624 // We catch 'Exception' because client-app malice or bugs might induce a wide 625 // range of possible exception-throw outcomes from startJob() and its handling 626 // of the client's ParcelableBundle extras. 627 Slog.e(TAG, "Error sending onStart message to '" + 628 mRunningJob.getServiceComponent().getShortClassName() + "' ", e); 629 } 630 } 631 632 /** 633 * State behaviours. 634 * VERB_STARTING -> Successful start, change job to VERB_EXECUTING and post timeout. 635 * _PENDING -> Error 636 * _EXECUTING -> Error 637 * _STOPPING -> Error 638 */ 639 @GuardedBy("mLock") handleStartedLocked(boolean workOngoing)640 private void handleStartedLocked(boolean workOngoing) { 641 switch (mVerb) { 642 case VERB_STARTING: 643 mVerb = VERB_EXECUTING; 644 if (!workOngoing) { 645 // Job is finished already so fast-forward to handleFinished. 646 handleFinishedLocked(false, "onStartJob returned false"); 647 return; 648 } 649 if (mCancelled) { 650 if (DEBUG) { 651 Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete."); 652 } 653 // Cancelled *while* waiting for acknowledgeStartMessage from client. 654 handleCancelLocked(null); 655 return; 656 } 657 scheduleOpTimeOutLocked(); 658 break; 659 default: 660 Slog.e(TAG, "Handling started job but job wasn't starting! Was " 661 + VERB_STRINGS[mVerb] + "."); 662 return; 663 } 664 } 665 666 /** 667 * VERB_EXECUTING -> Client called jobFinished(), clean up and notify done. 668 * _STOPPING -> Successful finish, clean up and notify done. 669 * _STARTING -> Error 670 * _PENDING -> Error 671 */ 672 @GuardedBy("mLock") handleFinishedLocked(boolean reschedule, String reason)673 private void handleFinishedLocked(boolean reschedule, String reason) { 674 switch (mVerb) { 675 case VERB_EXECUTING: 676 case VERB_STOPPING: 677 closeAndCleanupJobLocked(reschedule, reason); 678 break; 679 default: 680 Slog.e(TAG, "Got an execution complete message for a job that wasn't being" + 681 "executed. Was " + VERB_STRINGS[mVerb] + "."); 682 } 683 } 684 685 /** 686 * A job can be in various states when a cancel request comes in: 687 * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for 688 * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)} 689 * _STARTING -> Mark as cancelled and wait for 690 * {@link JobServiceContext#doAcknowledgeStartMessage} 691 * _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks 692 * in the message queue. 693 * _ENDING -> No point in doing anything here, so we ignore. 694 */ 695 @GuardedBy("mLock") handleCancelLocked(String reason)696 private void handleCancelLocked(String reason) { 697 if (JobSchedulerService.DEBUG) { 698 Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " " 699 + VERB_STRINGS[mVerb]); 700 } 701 switch (mVerb) { 702 case VERB_BINDING: 703 case VERB_STARTING: 704 mCancelled = true; 705 applyStoppedReasonLocked(reason); 706 break; 707 case VERB_EXECUTING: 708 sendStopMessageLocked(reason); 709 break; 710 case VERB_STOPPING: 711 // Nada. 712 break; 713 default: 714 Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb); 715 break; 716 } 717 } 718 719 /** Process MSG_TIMEOUT here. */ 720 @GuardedBy("mLock") handleOpTimeoutLocked()721 private void handleOpTimeoutLocked() { 722 switch (mVerb) { 723 case VERB_BINDING: 724 Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked() 725 + ", dropping."); 726 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding"); 727 break; 728 case VERB_STARTING: 729 // Client unresponsive - wedged or failed to respond in time. We don't really 730 // know what happened so let's log it and notify the JobScheduler 731 // FINISHED/NO-RETRY. 732 Slog.w(TAG, "No response from client for onStartJob " 733 + getRunningJobNameLocked()); 734 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting"); 735 break; 736 case VERB_STOPPING: 737 // At least we got somewhere, so fail but ask the JobScheduler to reschedule. 738 Slog.w(TAG, "No response from client for onStopJob " 739 + getRunningJobNameLocked()); 740 closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping"); 741 break; 742 case VERB_EXECUTING: 743 // Not an error - client ran out of time. 744 Slog.i(TAG, "Client timed out while executing (no jobFinished received), " + 745 "sending onStop: " + getRunningJobNameLocked()); 746 mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out"); 747 sendStopMessageLocked("timeout while executing"); 748 break; 749 default: 750 Slog.e(TAG, "Handling timeout for an invalid job state: " 751 + getRunningJobNameLocked() + ", dropping."); 752 closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout"); 753 } 754 } 755 756 /** 757 * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING -> 758 * VERB_STOPPING. 759 */ 760 @GuardedBy("mLock") sendStopMessageLocked(String reason)761 private void sendStopMessageLocked(String reason) { 762 removeOpTimeOutLocked(); 763 if (mVerb != VERB_EXECUTING) { 764 Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob); 765 closeAndCleanupJobLocked(false /* reschedule */, reason); 766 return; 767 } 768 try { 769 applyStoppedReasonLocked(reason); 770 mVerb = VERB_STOPPING; 771 scheduleOpTimeOutLocked(); 772 service.stopJob(mParams); 773 } catch (RemoteException e) { 774 Slog.e(TAG, "Error sending onStopJob to client.", e); 775 // The job's host app apparently crashed during the job, so we should reschedule. 776 closeAndCleanupJobLocked(true /* reschedule */, "host crashed when trying to stop"); 777 } 778 } 779 780 /** 781 * The provided job has finished, either by calling 782 * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)} 783 * or from acknowledging the stop message we sent. Either way, we're done tracking it and 784 * we want to clean up internally. 785 */ 786 @GuardedBy("mLock") closeAndCleanupJobLocked(boolean reschedule, String reason)787 private void closeAndCleanupJobLocked(boolean reschedule, String reason) { 788 final JobStatus completedJob; 789 if (mVerb == VERB_FINISHED) { 790 return; 791 } 792 applyStoppedReasonLocked(reason); 793 completedJob = mRunningJob; 794 mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason); 795 FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED, 796 completedJob.getSourceUid(), null, completedJob.getBatteryName(), 797 FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED, 798 mParams.getStopReason(), completedJob.getStandbyBucket(), completedJob.getJobId(), 799 completedJob.hasChargingConstraint(), 800 completedJob.hasBatteryNotLowConstraint(), 801 completedJob.hasStorageNotLowConstraint(), 802 completedJob.hasTimingDelayConstraint(), 803 completedJob.hasDeadlineConstraint(), 804 completedJob.hasIdleConstraint(), 805 completedJob.hasConnectivityConstraint(), 806 completedJob.hasContentTriggerConstraint()); 807 try { 808 mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), 809 mParams.getStopReason()); 810 } catch (RemoteException e) { 811 // Whatever. 812 } 813 if (mWakeLock != null) { 814 mWakeLock.release(); 815 } 816 mContext.unbindService(JobServiceContext.this); 817 mWakeLock = null; 818 mRunningJob = null; 819 mRunningCallback = null; 820 mParams = null; 821 mVerb = VERB_FINISHED; 822 mCancelled = false; 823 service = null; 824 mAvailable = true; 825 removeOpTimeOutLocked(); 826 mCompletedListener.onJobCompletedLocked(completedJob, reschedule); 827 } 828 applyStoppedReasonLocked(String reason)829 private void applyStoppedReasonLocked(String reason) { 830 if (reason != null && mStoppedReason == null) { 831 mStoppedReason = reason; 832 mStoppedTime = sElapsedRealtimeClock.millis(); 833 if (mRunningCallback != null) { 834 mRunningCallback.mStoppedReason = mStoppedReason; 835 mRunningCallback.mStoppedTime = mStoppedTime; 836 } 837 } 838 } 839 840 /** 841 * Called when sending a message to the client, over whose execution we have no control. If 842 * we haven't received a response in a certain amount of time, we want to give up and carry 843 * on with life. 844 */ scheduleOpTimeOutLocked()845 private void scheduleOpTimeOutLocked() { 846 removeOpTimeOutLocked(); 847 848 final long timeoutMillis; 849 switch (mVerb) { 850 case VERB_EXECUTING: 851 timeoutMillis = EXECUTING_TIMESLICE_MILLIS; 852 break; 853 854 case VERB_BINDING: 855 timeoutMillis = OP_BIND_TIMEOUT_MILLIS; 856 break; 857 858 default: 859 timeoutMillis = OP_TIMEOUT_MILLIS; 860 break; 861 } 862 if (DEBUG) { 863 Slog.d(TAG, "Scheduling time out for '" + 864 mRunningJob.getServiceComponent().getShortClassName() + "' jId: " + 865 mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s"); 866 } 867 Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback); 868 mCallbackHandler.sendMessageDelayed(m, timeoutMillis); 869 mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis; 870 } 871 872 removeOpTimeOutLocked()873 private void removeOpTimeOutLocked() { 874 mCallbackHandler.removeMessages(MSG_TIMEOUT); 875 } 876 } 877