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