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.JobParameters;
     21 import android.app.job.IJobCallback;
     22 import android.app.job.IJobService;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.ServiceConnection;
     27 import android.content.pm.PackageManager;
     28 import android.net.Uri;
     29 import android.os.Binder;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Looper;
     33 import android.os.Message;
     34 import android.os.PowerManager;
     35 import android.os.RemoteException;
     36 import android.os.SystemClock;
     37 import android.os.UserHandle;
     38 import android.os.WorkSource;
     39 import android.util.Slog;
     40 
     41 import com.android.internal.annotations.GuardedBy;
     42 import com.android.internal.annotations.VisibleForTesting;
     43 import com.android.internal.app.IBatteryStats;
     44 import com.android.server.job.controllers.JobStatus;
     45 
     46 import java.util.concurrent.atomic.AtomicBoolean;
     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 #MSG_CANCEL} after the client has already finished. This is handled by having
     59  * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelH} check whether
     60  * the context is still valid.
     61  * To mitigate this, tearing down the context removes all messages from the handler, including any
     62  * tardy {@link #MSG_CANCEL}s. Additionally, we avoid sending duplicate onStopJob()
     63  * calls to the client after they've specified jobFinished().
     64  */
     65 public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
     66     private static final boolean DEBUG = JobSchedulerService.DEBUG;
     67     private static final String TAG = "JobServiceContext";
     68     /** Define the maximum # of jobs allowed to run on a service at once. */
     69     private static final int defaultMaxActiveJobsPerService =
     70             ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
     71     /** Amount of time a job is allowed to execute for before being considered timed-out. */
     72     private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
     73     /** Amount of time the JobScheduler will wait for a response from an app for a message. */
     74     private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
     75 
     76     private static final String[] VERB_STRINGS = {
     77             "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
     78     };
     79 
     80     // States that a job occupies while interacting with the client.
     81     static final int VERB_BINDING = 0;
     82     static final int VERB_STARTING = 1;
     83     static final int VERB_EXECUTING = 2;
     84     static final int VERB_STOPPING = 3;
     85     static final int VERB_FINISHED = 4;
     86 
     87     // Messages that result from interactions with the client service.
     88     /** System timed out waiting for a response. */
     89     private static final int MSG_TIMEOUT = 0;
     90     /** Received a callback from client. */
     91     private static final int MSG_CALLBACK = 1;
     92     /** Run through list and start any ready jobs.*/
     93     private static final int MSG_SERVICE_BOUND = 2;
     94     /** Cancel a job. */
     95     private static final int MSG_CANCEL = 3;
     96     /** Shutdown the job. Used when the client crashes and we can't die gracefully.*/
     97     private static final int MSG_SHUTDOWN_EXECUTION = 4;
     98 
     99     public static final int NO_PREFERRED_UID = -1;
    100 
    101     private final Handler mCallbackHandler;
    102     /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
    103     private final JobCompletedListener mCompletedListener;
    104     /** Used for service binding, etc. */
    105     private final Context mContext;
    106     private final Object mLock;
    107     private final IBatteryStats mBatteryStats;
    108     private final JobPackageTracker mJobPackageTracker;
    109     private PowerManager.WakeLock mWakeLock;
    110 
    111     // Execution state.
    112     private JobParameters mParams;
    113     @VisibleForTesting
    114     int mVerb;
    115     private AtomicBoolean mCancelled = new AtomicBoolean();
    116 
    117     /**
    118      * All the information maintained about the job currently being executed.
    119      *
    120      * Any reads (dereferences) not done from the handler thread must be synchronized on
    121      * {@link #mLock}.
    122      * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
    123      */
    124     private JobStatus mRunningJob;
    125     /** Used to store next job to run when current job is to be preempted. */
    126     private int mPreferredUid;
    127     IJobService service;
    128 
    129     /**
    130      * Whether this context is free. This is set to false at the start of execution, and reset to
    131      * true when execution is complete.
    132      */
    133     @GuardedBy("mLock")
    134     private boolean mAvailable;
    135     /** Track start time. */
    136     private long mExecutionStartTimeElapsed;
    137     /** Track when job will timeout. */
    138     private long mTimeoutElapsed;
    139 
    140     JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
    141             JobPackageTracker tracker, Looper looper) {
    142         this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
    143     }
    144 
    145     @VisibleForTesting
    146     JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
    147             JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
    148         mContext = context;
    149         mLock = lock;
    150         mBatteryStats = batteryStats;
    151         mJobPackageTracker = tracker;
    152         mCallbackHandler = new JobServiceHandler(looper);
    153         mCompletedListener = completedListener;
    154         mAvailable = true;
    155         mVerb = VERB_FINISHED;
    156         mPreferredUid = NO_PREFERRED_UID;
    157     }
    158 
    159     /**
    160      * Give a job to this context for execution. Callers must first check {@link #getRunningJob()}
    161      * and ensure it is null to make sure this is a valid context.
    162      * @param job The status of the job that we are going to run.
    163      * @return True if the job is valid and is running. False if the job cannot be executed.
    164      */
    165     boolean executeRunnableJob(JobStatus job) {
    166         synchronized (mLock) {
    167             if (!mAvailable) {
    168                 Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
    169                 return false;
    170             }
    171 
    172             mPreferredUid = NO_PREFERRED_UID;
    173 
    174             mRunningJob = job;
    175             final boolean isDeadlineExpired =
    176                     job.hasDeadlineConstraint() &&
    177                             (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
    178             Uri[] triggeredUris = null;
    179             if (job.changedUris != null) {
    180                 triggeredUris = new Uri[job.changedUris.size()];
    181                 job.changedUris.toArray(triggeredUris);
    182             }
    183             String[] triggeredAuthorities = null;
    184             if (job.changedAuthorities != null) {
    185                 triggeredAuthorities = new String[job.changedAuthorities.size()];
    186                 job.changedAuthorities.toArray(triggeredAuthorities);
    187             }
    188             mParams = new JobParameters(this, job.getJobId(), job.getExtras(), isDeadlineExpired,
    189                     triggeredUris, triggeredAuthorities);
    190             mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
    191 
    192             mVerb = VERB_BINDING;
    193             scheduleOpTimeOut();
    194             final Intent intent = new Intent().setComponent(job.getServiceComponent());
    195             boolean binding = mContext.bindServiceAsUser(intent, this,
    196                     Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
    197                     new UserHandle(job.getUserId()));
    198             if (!binding) {
    199                 if (DEBUG) {
    200                     Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
    201                 }
    202                 mRunningJob = null;
    203                 mParams = null;
    204                 mExecutionStartTimeElapsed = 0L;
    205                 mVerb = VERB_FINISHED;
    206                 removeOpTimeOut();
    207                 return false;
    208             }
    209             try {
    210                 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
    211             } catch (RemoteException e) {
    212                 // Whatever.
    213             }
    214             mJobPackageTracker.noteActive(job);
    215             mAvailable = false;
    216             return true;
    217         }
    218     }
    219 
    220     /**
    221      * Used externally to query the running job. Will return null if there is no job running.
    222      * Be careful when using this function, at any moment it's possible that the job returned may
    223      * stop executing.
    224      */
    225     JobStatus getRunningJob() {
    226         final JobStatus job;
    227         synchronized (mLock) {
    228             job = mRunningJob;
    229         }
    230         return job == null ? null : new JobStatus(job);
    231     }
    232 
    233     /** Called externally when a job that was scheduled for execution should be cancelled. */
    234     void cancelExecutingJob(int reason) {
    235         mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
    236     }
    237 
    238     void preemptExecutingJob() {
    239         Message m = mCallbackHandler.obtainMessage(MSG_CANCEL);
    240         m.arg1 = JobParameters.REASON_PREEMPT;
    241         m.sendToTarget();
    242     }
    243 
    244     int getPreferredUid() {
    245         return mPreferredUid;
    246     }
    247 
    248     void clearPreferredUid() {
    249         mPreferredUid = NO_PREFERRED_UID;
    250     }
    251 
    252     long getExecutionStartTimeElapsed() {
    253         return mExecutionStartTimeElapsed;
    254     }
    255 
    256     long getTimeoutElapsed() {
    257         return mTimeoutElapsed;
    258     }
    259 
    260     @Override
    261     public void jobFinished(int jobId, boolean reschedule) {
    262         if (!verifyCallingUid()) {
    263             return;
    264         }
    265         mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
    266                 .sendToTarget();
    267     }
    268 
    269     @Override
    270     public void acknowledgeStopMessage(int jobId, boolean reschedule) {
    271         if (!verifyCallingUid()) {
    272             return;
    273         }
    274         mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
    275                 .sendToTarget();
    276     }
    277 
    278     @Override
    279     public void acknowledgeStartMessage(int jobId, boolean ongoing) {
    280         if (!verifyCallingUid()) {
    281             return;
    282         }
    283         mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget();
    284     }
    285 
    286     /**
    287      * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
    288      * we intend to send to the client - we stop sending work when the service is unbound so until
    289      * then we keep the wakelock.
    290      * @param name The concrete component name of the service that has been connected.
    291      * @param service The IBinder of the Service's communication channel,
    292      */
    293     @Override
    294     public void onServiceConnected(ComponentName name, IBinder service) {
    295         JobStatus runningJob;
    296         synchronized (mLock) {
    297             // This isn't strictly necessary b/c the JobServiceHandler is running on the main
    298             // looper and at this point we can't get any binder callbacks from the client. Better
    299             // safe than sorry.
    300             runningJob = mRunningJob;
    301         }
    302         if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
    303             mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
    304             return;
    305         }
    306         this.service = IJobService.Stub.asInterface(service);
    307         final PowerManager pm =
    308                 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    309         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, runningJob.getTag());
    310         mWakeLock.setWorkSource(new WorkSource(runningJob.getSourceUid()));
    311         mWakeLock.setReferenceCounted(false);
    312         mWakeLock.acquire();
    313         mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
    314     }
    315 
    316     /** If the client service crashes we reschedule this job and clean up. */
    317     @Override
    318     public void onServiceDisconnected(ComponentName name) {
    319         mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
    320     }
    321 
    322     /**
    323      * This class is reused across different clients, and passes itself in as a callback. Check
    324      * whether the client exercising the callback is the client we expect.
    325      * @return True if the binder calling is coming from the client we expect.
    326      */
    327     private boolean verifyCallingUid() {
    328         synchronized (mLock) {
    329             if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
    330                 if (DEBUG) {
    331                     Slog.d(TAG, "Stale callback received, ignoring.");
    332                 }
    333                 return false;
    334             }
    335             return true;
    336         }
    337     }
    338 
    339     /**
    340      * Handles the lifecycle of the JobService binding/callbacks, etc. The convention within this
    341      * class is to append 'H' to each function name that can only be called on this handler. This
    342      * isn't strictly necessary because all of these functions are private, but helps clarity.
    343      */
    344     private class JobServiceHandler extends Handler {
    345         JobServiceHandler(Looper looper) {
    346             super(looper);
    347         }
    348 
    349         @Override
    350         public void handleMessage(Message message) {
    351             switch (message.what) {
    352                 case MSG_SERVICE_BOUND:
    353                     removeOpTimeOut();
    354                     handleServiceBoundH();
    355                     break;
    356                 case MSG_CALLBACK:
    357                     if (DEBUG) {
    358                         Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
    359                                 + " v:" + VERB_STRINGS[mVerb]);
    360                     }
    361                     removeOpTimeOut();
    362 
    363                     if (mVerb == VERB_STARTING) {
    364                         final boolean workOngoing = message.arg2 == 1;
    365                         handleStartedH(workOngoing);
    366                     } else if (mVerb == VERB_EXECUTING ||
    367                             mVerb == VERB_STOPPING) {
    368                         final boolean reschedule = message.arg2 == 1;
    369                         handleFinishedH(reschedule);
    370                     } else {
    371                         if (DEBUG) {
    372                             Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
    373                         }
    374                     }
    375                     break;
    376                 case MSG_CANCEL:
    377                     if (mVerb == VERB_FINISHED) {
    378                         if (DEBUG) {
    379                             Slog.d(TAG,
    380                                    "Trying to process cancel for torn-down context, ignoring.");
    381                         }
    382                         return;
    383                     }
    384                     mParams.setStopReason(message.arg1);
    385                     if (message.arg1 == JobParameters.REASON_PREEMPT) {
    386                         mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
    387                                 NO_PREFERRED_UID;
    388                     }
    389                     handleCancelH();
    390                     break;
    391                 case MSG_TIMEOUT:
    392                     handleOpTimeoutH();
    393                     break;
    394                 case MSG_SHUTDOWN_EXECUTION:
    395                     closeAndCleanupJobH(true /* needsReschedule */);
    396                     break;
    397                 default:
    398                     Slog.e(TAG, "Unrecognised message: " + message);
    399             }
    400         }
    401 
    402         /** Start the job on the service. */
    403         private void handleServiceBoundH() {
    404             if (DEBUG) {
    405                 Slog.d(TAG, "MSG_SERVICE_BOUND for " + mRunningJob.toShortString());
    406             }
    407             if (mVerb != VERB_BINDING) {
    408                 Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
    409                         + VERB_STRINGS[mVerb]);
    410                 closeAndCleanupJobH(false /* reschedule */);
    411                 return;
    412             }
    413             if (mCancelled.get()) {
    414                 if (DEBUG) {
    415                     Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
    416                             + mRunningJob);
    417                 }
    418                 closeAndCleanupJobH(true /* reschedule */);
    419                 return;
    420             }
    421             try {
    422                 mVerb = VERB_STARTING;
    423                 scheduleOpTimeOut();
    424                 service.startJob(mParams);
    425             } catch (RemoteException e) {
    426                 Slog.e(TAG, "Error sending onStart message to '" +
    427                         mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
    428             }
    429         }
    430 
    431         /**
    432          * State behaviours.
    433          * VERB_STARTING   -> Successful start, change job to VERB_EXECUTING and post timeout.
    434          *     _PENDING    -> Error
    435          *     _EXECUTING  -> Error
    436          *     _STOPPING   -> Error
    437          */
    438         private void handleStartedH(boolean workOngoing) {
    439             switch (mVerb) {
    440                 case VERB_STARTING:
    441                     mVerb = VERB_EXECUTING;
    442                     if (!workOngoing) {
    443                         // Job is finished already so fast-forward to handleFinished.
    444                         handleFinishedH(false);
    445                         return;
    446                     }
    447                     if (mCancelled.get()) {
    448                         if (DEBUG) {
    449                             Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
    450                         }
    451                         // Cancelled *while* waiting for acknowledgeStartMessage from client.
    452                         handleCancelH();
    453                         return;
    454                     }
    455                     scheduleOpTimeOut();
    456                     break;
    457                 default:
    458                     Slog.e(TAG, "Handling started job but job wasn't starting! Was "
    459                             + VERB_STRINGS[mVerb] + ".");
    460                     return;
    461             }
    462         }
    463 
    464         /**
    465          * VERB_EXECUTING  -> Client called jobFinished(), clean up and notify done.
    466          *     _STOPPING   -> Successful finish, clean up and notify done.
    467          *     _STARTING   -> Error
    468          *     _PENDING    -> Error
    469          */
    470         private void handleFinishedH(boolean reschedule) {
    471             switch (mVerb) {
    472                 case VERB_EXECUTING:
    473                 case VERB_STOPPING:
    474                     closeAndCleanupJobH(reschedule);
    475                     break;
    476                 default:
    477                     Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
    478                             "executed. Was " + VERB_STRINGS[mVerb] + ".");
    479             }
    480         }
    481 
    482         /**
    483          * A job can be in various states when a cancel request comes in:
    484          * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
    485          *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
    486          *     _STARTING   -> Mark as cancelled and wait for
    487          *                    {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
    488          *     _EXECUTING  -> call {@link #sendStopMessageH}}, but only if there are no callbacks
    489          *                      in the message queue.
    490          *     _ENDING     -> No point in doing anything here, so we ignore.
    491          */
    492         private void handleCancelH() {
    493             if (JobSchedulerService.DEBUG) {
    494                 Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
    495                         + VERB_STRINGS[mVerb]);
    496             }
    497             switch (mVerb) {
    498                 case VERB_BINDING:
    499                 case VERB_STARTING:
    500                     mCancelled.set(true);
    501                     break;
    502                 case VERB_EXECUTING:
    503                     if (hasMessages(MSG_CALLBACK)) {
    504                         // If the client has called jobFinished, ignore this cancel.
    505                         return;
    506                     }
    507                     sendStopMessageH();
    508                     break;
    509                 case VERB_STOPPING:
    510                     // Nada.
    511                     break;
    512                 default:
    513                     Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
    514                     break;
    515             }
    516         }
    517 
    518         /** Process MSG_TIMEOUT here. */
    519         private void handleOpTimeoutH() {
    520             switch (mVerb) {
    521                 case VERB_BINDING:
    522                     Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
    523                             ", dropping.");
    524                     closeAndCleanupJobH(false /* needsReschedule */);
    525                     break;
    526                 case VERB_STARTING:
    527                     // Client unresponsive - wedged or failed to respond in time. We don't really
    528                     // know what happened so let's log it and notify the JobScheduler
    529                     // FINISHED/NO-RETRY.
    530                     Slog.e(TAG, "No response from client for onStartJob '" +
    531                             mRunningJob.toShortString());
    532                     closeAndCleanupJobH(false /* needsReschedule */);
    533                     break;
    534                 case VERB_STOPPING:
    535                     // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
    536                     Slog.e(TAG, "No response from client for onStopJob, '" +
    537                             mRunningJob.toShortString());
    538                     closeAndCleanupJobH(true /* needsReschedule */);
    539                     break;
    540                 case VERB_EXECUTING:
    541                     // Not an error - client ran out of time.
    542                     Slog.i(TAG, "Client timed out while executing (no jobFinished received)." +
    543                             " sending onStop. "  + mRunningJob.toShortString());
    544                     mParams.setStopReason(JobParameters.REASON_TIMEOUT);
    545                     sendStopMessageH();
    546                     break;
    547                 default:
    548                     Slog.e(TAG, "Handling timeout for an invalid job state: " +
    549                             mRunningJob.toShortString() + ", dropping.");
    550                     closeAndCleanupJobH(false /* needsReschedule */);
    551             }
    552         }
    553 
    554         /**
    555          * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
    556          * VERB_STOPPING.
    557          */
    558         private void sendStopMessageH() {
    559             removeOpTimeOut();
    560             if (mVerb != VERB_EXECUTING) {
    561                 Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
    562                 closeAndCleanupJobH(false /* reschedule */);
    563                 return;
    564             }
    565             try {
    566                 mVerb = VERB_STOPPING;
    567                 scheduleOpTimeOut();
    568                 service.stopJob(mParams);
    569             } catch (RemoteException e) {
    570                 Slog.e(TAG, "Error sending onStopJob to client.", e);
    571                 closeAndCleanupJobH(false /* reschedule */);
    572             }
    573         }
    574 
    575         /**
    576          * The provided job has finished, either by calling
    577          * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
    578          * or from acknowledging the stop message we sent. Either way, we're done tracking it and
    579          * we want to clean up internally.
    580          */
    581         private void closeAndCleanupJobH(boolean reschedule) {
    582             final JobStatus completedJob;
    583             synchronized (mLock) {
    584                 if (mVerb == VERB_FINISHED) {
    585                     return;
    586                 }
    587                 completedJob = mRunningJob;
    588                 mJobPackageTracker.noteInactive(completedJob);
    589                 try {
    590                     mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
    591                             mRunningJob.getSourceUid());
    592                 } catch (RemoteException e) {
    593                     // Whatever.
    594                 }
    595                 if (mWakeLock != null) {
    596                     mWakeLock.release();
    597                 }
    598                 mContext.unbindService(JobServiceContext.this);
    599                 mWakeLock = null;
    600                 mRunningJob = null;
    601                 mParams = null;
    602                 mVerb = VERB_FINISHED;
    603                 mCancelled.set(false);
    604                 service = null;
    605                 mAvailable = true;
    606             }
    607             removeOpTimeOut();
    608             removeMessages(MSG_CALLBACK);
    609             removeMessages(MSG_SERVICE_BOUND);
    610             removeMessages(MSG_CANCEL);
    611             removeMessages(MSG_SHUTDOWN_EXECUTION);
    612             mCompletedListener.onJobCompleted(completedJob, reschedule);
    613         }
    614     }
    615 
    616     /**
    617      * Called when sending a message to the client, over whose execution we have no control. If
    618      * we haven't received a response in a certain amount of time, we want to give up and carry
    619      * on with life.
    620      */
    621     private void scheduleOpTimeOut() {
    622         removeOpTimeOut();
    623 
    624         final long timeoutMillis = (mVerb == VERB_EXECUTING) ?
    625                 EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
    626         if (DEBUG) {
    627             Slog.d(TAG, "Scheduling time out for '" +
    628                     mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
    629                     mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
    630         }
    631         Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
    632         mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
    633         mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
    634     }
    635 
    636 
    637     private void removeOpTimeOut() {
    638         mCallbackHandler.removeMessages(MSG_TIMEOUT);
    639     }
    640 }
    641