OSDN Git Service

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