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