Home | History | Annotate | Download | only in job
      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             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      */
    256     JobStatus getRunningJobLocked() {
    257         return mRunningJob;
    258     }
    259 
    260     /** Called externally when a job that was scheduled for execution should be cancelled. */
    261     void cancelExecutingJobLocked(int reason, String debugReason) {
    262         doCancelLocked(reason, debugReason);
    263     }
    264 
    265     void preemptExecutingJobLocked() {
    266         doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption");
    267     }
    268 
    269     int getPreferredUid() {
    270         return mPreferredUid;
    271     }
    272 
    273     void clearPreferredUid() {
    274         mPreferredUid = NO_PREFERRED_UID;
    275     }
    276 
    277     long getExecutionStartTimeElapsed() {
    278         return mExecutionStartTimeElapsed;
    279     }
    280 
    281     long getTimeoutElapsed() {
    282         return mTimeoutElapsed;
    283     }
    284 
    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 
    299     void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
    300         doCallback(cb, reschedule, "app called jobFinished");
    301     }
    302 
    303     void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
    304         doCallback(cb, reschedule, null);
    305     }
    306 
    307     void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
    308         doCallback(cb, ongoing, "finished start");
    309     }
    310 
    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 
    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
    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
    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      */
    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 
    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 {
    432         JobServiceHandler(Looper looper) {
    433             super(looper);
    434         }
    435 
    436         @Override
    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 
    464     void doServiceBoundLocked() {
    465         removeOpTimeOutLocked();
    466         handleServiceBoundLocked();
    467     }
    468 
    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 
    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 
    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. */
    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      */
    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      */
    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      */
    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. */
    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      */
    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      */
    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 
    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      */
    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 
    774     private void removeOpTimeOutLocked() {
    775         mCallbackHandler.removeMessages(MSG_TIMEOUT);
    776     }
    777 }
    778