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 acknowledgeStartMessage(int jobId, boolean ongoing)142 public void acknowledgeStartMessage(int jobId, boolean ongoing) { 143 doAcknowledgeStartMessage(this, jobId, ongoing); 144 } 145 146 @Override acknowledgeStopMessage(int jobId, boolean reschedule)147 public void acknowledgeStopMessage(int jobId, boolean reschedule) { 148 doAcknowledgeStopMessage(this, jobId, reschedule); 149 } 150 151 @Override dequeueWork(int jobId)152 public JobWorkItem dequeueWork(int jobId) { 153 return doDequeueWork(this, jobId); 154 } 155 156 @Override completeWork(int jobId, int workId)157 public boolean completeWork(int jobId, int workId) { 158 return doCompleteWork(this, jobId, workId); 159 } 160 161 @Override jobFinished(int jobId, boolean reschedule)162 public void jobFinished(int jobId, boolean reschedule) { 163 doJobFinished(this, jobId, reschedule); 164 } 165 } 166 JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper)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 JobServiceContext(Context context, Object lock, IBatteryStats batteryStats, JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper)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 */ executeRunnableJob(JobStatus job)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 mVerb = VERB_BINDING; 223 scheduleOpTimeOutLocked(); 224 final Intent intent = new Intent().setComponent(job.getServiceComponent()); 225 boolean binding = mContext.bindServiceAsUser(intent, this, 226 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND, 227 new UserHandle(job.getUserId())); 228 if (!binding) { 229 if (DEBUG) { 230 Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable."); 231 } 232 mRunningJob = null; 233 mRunningCallback = null; 234 mParams = null; 235 mExecutionStartTimeElapsed = 0L; 236 mVerb = VERB_FINISHED; 237 removeOpTimeOutLocked(); 238 return false; 239 } 240 try { 241 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); 242 } catch (RemoteException e) { 243 // Whatever. 244 } 245 mJobPackageTracker.noteActive(job); 246 mAvailable = false; 247 mStoppedReason = null; 248 mStoppedTime = 0; 249 return true; 250 } 251 } 252 253 /** 254 * Used externally to query the running job. Will return null if there is no job running. 255 */ getRunningJobLocked()256 JobStatus getRunningJobLocked() { 257 return mRunningJob; 258 } 259 260 /** Called externally when a job that was scheduled for execution should be cancelled. */ cancelExecutingJobLocked(int reason, String debugReason)261 void cancelExecutingJobLocked(int reason, String debugReason) { 262 doCancelLocked(reason, debugReason); 263 } 264 preemptExecutingJobLocked()265 void preemptExecutingJobLocked() { 266 doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption"); 267 } 268 getPreferredUid()269 int getPreferredUid() { 270 return mPreferredUid; 271 } 272 clearPreferredUid()273 void clearPreferredUid() { 274 mPreferredUid = NO_PREFERRED_UID; 275 } 276 getExecutionStartTimeElapsed()277 long getExecutionStartTimeElapsed() { 278 return mExecutionStartTimeElapsed; 279 } 280 getTimeoutElapsed()281 long getTimeoutElapsed() { 282 return mTimeoutElapsed; 283 } 284 timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId)285 boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId) { 286 final JobStatus executing = getRunningJobLocked(); 287 if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId()) 288 && (pkgName == null || pkgName.equals(executing.getSourcePackageName())) 289 && (!matchJobId || jobId == executing.getJobId())) { 290 if (mVerb == VERB_EXECUTING) { 291 mParams.setStopReason(JobParameters.REASON_TIMEOUT); 292 sendStopMessageLocked("force timeout from shell"); 293 return true; 294 } 295 } 296 return false; 297 } 298 doJobFinished(JobCallback cb, int jobId, boolean reschedule)299 void doJobFinished(JobCallback cb, int jobId, boolean reschedule) { 300 doCallback(cb, reschedule, "app called jobFinished"); 301 } 302 doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule)303 void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) { 304 doCallback(cb, reschedule, null); 305 } 306 doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing)307 void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) { 308 doCallback(cb, ongoing, "finished start"); 309 } 310 doDequeueWork(JobCallback cb, int jobId)311 JobWorkItem doDequeueWork(JobCallback cb, int jobId) { 312 final long ident = Binder.clearCallingIdentity(); 313 try { 314 synchronized (mLock) { 315 assertCallerLocked(cb); 316 if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) { 317 // This job is either all done, or on its way out. Either way, it 318 // should not dispatch any more work. We will pick up any remaining 319 // work the next time we start the job again. 320 return null; 321 } 322 final JobWorkItem work = mRunningJob.dequeueWorkLocked(); 323 if (work == null && !mRunningJob.hasExecutingWorkLocked()) { 324 // This will finish the job. 325 doCallbackLocked(false, "last work dequeued"); 326 } 327 return work; 328 } 329 } finally { 330 Binder.restoreCallingIdentity(ident); 331 } 332 } 333 doCompleteWork(JobCallback cb, int jobId, int workId)334 boolean doCompleteWork(JobCallback cb, int jobId, int workId) { 335 final long ident = Binder.clearCallingIdentity(); 336 try { 337 synchronized (mLock) { 338 assertCallerLocked(cb); 339 return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId); 340 } 341 } finally { 342 Binder.restoreCallingIdentity(ident); 343 } 344 } 345 346 /** 347 * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work 348 * we intend to send to the client - we stop sending work when the service is unbound so until 349 * then we keep the wakelock. 350 * @param name The concrete component name of the service that has been connected. 351 * @param service The IBinder of the Service's communication channel, 352 */ 353 @Override onServiceConnected(ComponentName name, IBinder service)354 public void onServiceConnected(ComponentName name, IBinder service) { 355 JobStatus runningJob; 356 synchronized (mLock) { 357 // This isn't strictly necessary b/c the JobServiceHandler is running on the main 358 // looper and at this point we can't get any binder callbacks from the client. Better 359 // safe than sorry. 360 runningJob = mRunningJob; 361 362 if (runningJob == null || !name.equals(runningJob.getServiceComponent())) { 363 closeAndCleanupJobLocked(true /* needsReschedule */, 364 "connected for different component"); 365 return; 366 } 367 this.service = IJobService.Stub.asInterface(service); 368 final PowerManager pm = 369 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 370 PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 371 runningJob.getTag()); 372 wl.setWorkSource(new WorkSource(runningJob.getSourceUid())); 373 wl.setReferenceCounted(false); 374 wl.acquire(); 375 376 // We use a new wakelock instance per job. In rare cases there is a race between 377 // teardown following job completion/cancellation and new job service spin-up 378 // such that if we simply assign mWakeLock to be the new instance, we orphan 379 // the currently-live lock instead of cleanly replacing it. Watch for this and 380 // explicitly fast-forward the release if we're in that situation. 381 if (mWakeLock != null) { 382 Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock 383 + " tag=" + mWakeLock.getTag()); 384 mWakeLock.release(); 385 } 386 mWakeLock = wl; 387 doServiceBoundLocked(); 388 } 389 } 390 391 /** If the client service crashes we reschedule this job and clean up. */ 392 @Override onServiceDisconnected(ComponentName name)393 public void onServiceDisconnected(ComponentName name) { 394 synchronized (mLock) { 395 closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected"); 396 } 397 } 398 399 /** 400 * This class is reused across different clients, and passes itself in as a callback. Check 401 * whether the client exercising the callback is the client we expect. 402 * @return True if the binder calling is coming from the client we expect. 403 */ verifyCallerLocked(JobCallback cb)404 private boolean verifyCallerLocked(JobCallback cb) { 405 if (mRunningCallback != cb) { 406 if (DEBUG) { 407 Slog.d(TAG, "Stale callback received, ignoring."); 408 } 409 return false; 410 } 411 return true; 412 } 413 assertCallerLocked(JobCallback cb)414 private void assertCallerLocked(JobCallback cb) { 415 if (!verifyCallerLocked(cb)) { 416 StringBuilder sb = new StringBuilder(128); 417 sb.append("Caller no longer running"); 418 if (cb.mStoppedReason != null) { 419 sb.append(", last stopped "); 420 TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb); 421 sb.append(" because: "); 422 sb.append(cb.mStoppedReason); 423 } 424 throw new SecurityException(sb.toString()); 425 } 426 } 427 428 /** 429 * Scheduling of async messages (basically timeouts at this point). 430 */ 431 private class JobServiceHandler extends Handler { JobServiceHandler(Looper looper)432 JobServiceHandler(Looper looper) { 433 super(looper); 434 } 435 436 @Override handleMessage(Message message)437 public void handleMessage(Message message) { 438 switch (message.what) { 439 case MSG_TIMEOUT: 440 synchronized (mLock) { 441 if (message.obj == mRunningCallback) { 442 handleOpTimeoutLocked(); 443 } else { 444 JobCallback jc = (JobCallback)message.obj; 445 StringBuilder sb = new StringBuilder(128); 446 sb.append("Ignoring timeout of no longer active job"); 447 if (jc.mStoppedReason != null) { 448 sb.append(", stopped "); 449 TimeUtils.formatDuration(SystemClock.elapsedRealtime() 450 - jc.mStoppedTime, sb); 451 sb.append(" because: "); 452 sb.append(jc.mStoppedReason); 453 } 454 Slog.w(TAG, sb.toString()); 455 } 456 } 457 break; 458 default: 459 Slog.e(TAG, "Unrecognised message: " + message); 460 } 461 } 462 } 463 doServiceBoundLocked()464 void doServiceBoundLocked() { 465 removeOpTimeOutLocked(); 466 handleServiceBoundLocked(); 467 } 468 doCallback(JobCallback cb, boolean reschedule, String reason)469 void doCallback(JobCallback cb, boolean reschedule, String reason) { 470 final long ident = Binder.clearCallingIdentity(); 471 try { 472 synchronized (mLock) { 473 if (!verifyCallerLocked(cb)) { 474 return; 475 } 476 doCallbackLocked(reschedule, reason); 477 } 478 } finally { 479 Binder.restoreCallingIdentity(ident); 480 } 481 } 482 doCallbackLocked(boolean reschedule, String reason)483 void doCallbackLocked(boolean reschedule, String reason) { 484 if (DEBUG) { 485 Slog.d(TAG, "doCallback of : " + mRunningJob 486 + " v:" + VERB_STRINGS[mVerb]); 487 } 488 removeOpTimeOutLocked(); 489 490 if (mVerb == VERB_STARTING) { 491 handleStartedLocked(reschedule); 492 } else if (mVerb == VERB_EXECUTING || 493 mVerb == VERB_STOPPING) { 494 handleFinishedLocked(reschedule, reason); 495 } else { 496 if (DEBUG) { 497 Slog.d(TAG, "Unrecognised callback: " + mRunningJob); 498 } 499 } 500 } 501 doCancelLocked(int arg1, String debugReason)502 void doCancelLocked(int arg1, String debugReason) { 503 if (mVerb == VERB_FINISHED) { 504 if (DEBUG) { 505 Slog.d(TAG, 506 "Trying to process cancel for torn-down context, ignoring."); 507 } 508 return; 509 } 510 mParams.setStopReason(arg1); 511 if (arg1 == JobParameters.REASON_PREEMPT) { 512 mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : 513 NO_PREFERRED_UID; 514 } 515 handleCancelLocked(debugReason); 516 } 517 518 /** Start the job on the service. */ handleServiceBoundLocked()519 private void handleServiceBoundLocked() { 520 if (DEBUG) { 521 Slog.d(TAG, "handleServiceBound for " + mRunningJob.toShortString()); 522 } 523 if (mVerb != VERB_BINDING) { 524 Slog.e(TAG, "Sending onStartJob for a job that isn't pending. " 525 + VERB_STRINGS[mVerb]); 526 closeAndCleanupJobLocked(false /* reschedule */, "started job not pending"); 527 return; 528 } 529 if (mCancelled) { 530 if (DEBUG) { 531 Slog.d(TAG, "Job cancelled while waiting for bind to complete. " 532 + mRunningJob); 533 } 534 closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind"); 535 return; 536 } 537 try { 538 mVerb = VERB_STARTING; 539 scheduleOpTimeOutLocked(); 540 service.startJob(mParams); 541 } catch (Exception e) { 542 // We catch 'Exception' because client-app malice or bugs might induce a wide 543 // range of possible exception-throw outcomes from startJob() and its handling 544 // of the client's ParcelableBundle extras. 545 Slog.e(TAG, "Error sending onStart message to '" + 546 mRunningJob.getServiceComponent().getShortClassName() + "' ", e); 547 } 548 } 549 550 /** 551 * State behaviours. 552 * VERB_STARTING -> Successful start, change job to VERB_EXECUTING and post timeout. 553 * _PENDING -> Error 554 * _EXECUTING -> Error 555 * _STOPPING -> Error 556 */ handleStartedLocked(boolean workOngoing)557 private void handleStartedLocked(boolean workOngoing) { 558 switch (mVerb) { 559 case VERB_STARTING: 560 mVerb = VERB_EXECUTING; 561 if (!workOngoing) { 562 // Job is finished already so fast-forward to handleFinished. 563 handleFinishedLocked(false, "onStartJob returned false"); 564 return; 565 } 566 if (mCancelled) { 567 if (DEBUG) { 568 Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete."); 569 } 570 // Cancelled *while* waiting for acknowledgeStartMessage from client. 571 handleCancelLocked(null); 572 return; 573 } 574 scheduleOpTimeOutLocked(); 575 break; 576 default: 577 Slog.e(TAG, "Handling started job but job wasn't starting! Was " 578 + VERB_STRINGS[mVerb] + "."); 579 return; 580 } 581 } 582 583 /** 584 * VERB_EXECUTING -> Client called jobFinished(), clean up and notify done. 585 * _STOPPING -> Successful finish, clean up and notify done. 586 * _STARTING -> Error 587 * _PENDING -> Error 588 */ handleFinishedLocked(boolean reschedule, String reason)589 private void handleFinishedLocked(boolean reschedule, String reason) { 590 switch (mVerb) { 591 case VERB_EXECUTING: 592 case VERB_STOPPING: 593 closeAndCleanupJobLocked(reschedule, reason); 594 break; 595 default: 596 Slog.e(TAG, "Got an execution complete message for a job that wasn't being" + 597 "executed. Was " + VERB_STRINGS[mVerb] + "."); 598 } 599 } 600 601 /** 602 * A job can be in various states when a cancel request comes in: 603 * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for 604 * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)} 605 * _STARTING -> Mark as cancelled and wait for 606 * {@link JobServiceContext#doAcknowledgeStartMessage} 607 * _EXECUTING -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks 608 * in the message queue. 609 * _ENDING -> No point in doing anything here, so we ignore. 610 */ handleCancelLocked(String reason)611 private void handleCancelLocked(String reason) { 612 if (JobSchedulerService.DEBUG) { 613 Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " " 614 + VERB_STRINGS[mVerb]); 615 } 616 switch (mVerb) { 617 case VERB_BINDING: 618 case VERB_STARTING: 619 mCancelled = true; 620 applyStoppedReasonLocked(reason); 621 break; 622 case VERB_EXECUTING: 623 sendStopMessageLocked(reason); 624 break; 625 case VERB_STOPPING: 626 // Nada. 627 break; 628 default: 629 Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb); 630 break; 631 } 632 } 633 634 /** Process MSG_TIMEOUT here. */ handleOpTimeoutLocked()635 private void handleOpTimeoutLocked() { 636 switch (mVerb) { 637 case VERB_BINDING: 638 Slog.w(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() + 639 ", dropping."); 640 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding"); 641 break; 642 case VERB_STARTING: 643 // Client unresponsive - wedged or failed to respond in time. We don't really 644 // know what happened so let's log it and notify the JobScheduler 645 // FINISHED/NO-RETRY. 646 Slog.w(TAG, "No response from client for onStartJob " + 647 mRunningJob != null ? mRunningJob.toShortString() : "<null>"); 648 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting"); 649 break; 650 case VERB_STOPPING: 651 // At least we got somewhere, so fail but ask the JobScheduler to reschedule. 652 Slog.w(TAG, "No response from client for onStopJob " + 653 mRunningJob != null ? mRunningJob.toShortString() : "<null>"); 654 closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping"); 655 break; 656 case VERB_EXECUTING: 657 // Not an error - client ran out of time. 658 Slog.i(TAG, "Client timed out while executing (no jobFinished received), " + 659 "sending onStop: " + 660 mRunningJob != null ? mRunningJob.toShortString() : "<null>"); 661 mParams.setStopReason(JobParameters.REASON_TIMEOUT); 662 sendStopMessageLocked("timeout while executing"); 663 break; 664 default: 665 Slog.e(TAG, "Handling timeout for an invalid job state: " + 666 mRunningJob != null ? mRunningJob.toShortString() : "<null>" 667 + ", dropping."); 668 closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout"); 669 } 670 } 671 672 /** 673 * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING -> 674 * VERB_STOPPING. 675 */ sendStopMessageLocked(String reason)676 private void sendStopMessageLocked(String reason) { 677 removeOpTimeOutLocked(); 678 if (mVerb != VERB_EXECUTING) { 679 Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob); 680 closeAndCleanupJobLocked(false /* reschedule */, reason); 681 return; 682 } 683 try { 684 mVerb = VERB_STOPPING; 685 scheduleOpTimeOutLocked(); 686 service.stopJob(mParams); 687 } catch (RemoteException e) { 688 Slog.e(TAG, "Error sending onStopJob to client.", e); 689 // The job's host app apparently crashed during the job, so we should reschedule. 690 closeAndCleanupJobLocked(true /* reschedule */, "host crashed when trying to stop"); 691 } 692 } 693 694 /** 695 * The provided job has finished, either by calling 696 * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)} 697 * or from acknowledging the stop message we sent. Either way, we're done tracking it and 698 * we want to clean up internally. 699 */ closeAndCleanupJobLocked(boolean reschedule, String reason)700 private void closeAndCleanupJobLocked(boolean reschedule, String reason) { 701 final JobStatus completedJob; 702 if (mVerb == VERB_FINISHED) { 703 return; 704 } 705 applyStoppedReasonLocked(reason); 706 completedJob = mRunningJob; 707 mJobPackageTracker.noteInactive(completedJob); 708 try { 709 mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), 710 mRunningJob.getSourceUid()); 711 } catch (RemoteException e) { 712 // Whatever. 713 } 714 if (mWakeLock != null) { 715 mWakeLock.release(); 716 } 717 mContext.unbindService(JobServiceContext.this); 718 mWakeLock = null; 719 mRunningJob = null; 720 mRunningCallback = null; 721 mParams = null; 722 mVerb = VERB_FINISHED; 723 mCancelled = false; 724 service = null; 725 mAvailable = true; 726 removeOpTimeOutLocked(); 727 mCompletedListener.onJobCompletedLocked(completedJob, reschedule); 728 } 729 applyStoppedReasonLocked(String reason)730 private void applyStoppedReasonLocked(String reason) { 731 if (reason != null && mStoppedReason == null) { 732 mStoppedReason = reason; 733 mStoppedTime = SystemClock.elapsedRealtime(); 734 if (mRunningCallback != null) { 735 mRunningCallback.mStoppedReason = mStoppedReason; 736 mRunningCallback.mStoppedTime = mStoppedTime; 737 } 738 } 739 } 740 741 /** 742 * Called when sending a message to the client, over whose execution we have no control. If 743 * we haven't received a response in a certain amount of time, we want to give up and carry 744 * on with life. 745 */ scheduleOpTimeOutLocked()746 private void scheduleOpTimeOutLocked() { 747 removeOpTimeOutLocked(); 748 749 final long timeoutMillis; 750 switch (mVerb) { 751 case VERB_EXECUTING: 752 timeoutMillis = EXECUTING_TIMESLICE_MILLIS; 753 break; 754 755 case VERB_BINDING: 756 timeoutMillis = OP_BIND_TIMEOUT_MILLIS; 757 break; 758 759 default: 760 timeoutMillis = OP_TIMEOUT_MILLIS; 761 break; 762 } 763 if (DEBUG) { 764 Slog.d(TAG, "Scheduling time out for '" + 765 mRunningJob.getServiceComponent().getShortClassName() + "' jId: " + 766 mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s"); 767 } 768 Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback); 769 mCallbackHandler.sendMessageDelayed(m, timeoutMillis); 770 mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis; 771 } 772 773 removeOpTimeOutLocked()774 private void removeOpTimeOutLocked() { 775 mCallbackHandler.removeMessages(MSG_TIMEOUT); 776 } 777 } 778