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     /**
    234      * Internal non-cloning inspection of the currently running job, if any.  The lock
    235      * must be held when calling this *and* for the entire lifetime of using its returned
    236      * JobStatus object!
    237      */
    238     JobStatus getRunningJobUnsafeLocked() {
    239         return mRunningJob;
    240     }
    241 
    242     /** Called externally when a job that was scheduled for execution should be cancelled. */
    243     void cancelExecutingJob(int reason) {
    244         mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
    245     }
    246 
    247     void preemptExecutingJob() {
    248         Message m = mCallbackHandler.obtainMessage(MSG_CANCEL);
    249         m.arg1 = JobParameters.REASON_PREEMPT;
    250         m.sendToTarget();
    251     }
    252 
    253     int getPreferredUid() {
    254         return mPreferredUid;
    255     }
    256 
    257     void clearPreferredUid() {
    258         mPreferredUid = NO_PREFERRED_UID;
    259     }
    260 
    261     long getExecutionStartTimeElapsed() {
    262         return mExecutionStartTimeElapsed;
    263     }
    264 
    265     long getTimeoutElapsed() {
    266         return mTimeoutElapsed;
    267     }
    268 
    269     @Override
    270     public void jobFinished(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 acknowledgeStopMessage(int jobId, boolean reschedule) {
    280         if (!verifyCallingUid()) {
    281             return;
    282         }
    283         mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
    284                 .sendToTarget();
    285     }
    286 
    287     @Override
    288     public void acknowledgeStartMessage(int jobId, boolean ongoing) {
    289         if (!verifyCallingUid()) {
    290             return;
    291         }
    292         mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget();
    293     }
    294 
    295     /**
    296      * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
    297      * we intend to send to the client - we stop sending work when the service is unbound so until
    298      * then we keep the wakelock.
    299      * @param name The concrete component name of the service that has been connected.
    300      * @param service The IBinder of the Service's communication channel,
    301      */
    302     @Override
    303     public void onServiceConnected(ComponentName name, IBinder service) {
    304         JobStatus runningJob;
    305         synchronized (mLock) {
    306             // This isn't strictly necessary b/c the JobServiceHandler is running on the main
    307             // looper and at this point we can't get any binder callbacks from the client. Better
    308             // safe than sorry.
    309             runningJob = mRunningJob;
    310         }
    311         if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
    312             mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
    313             return;
    314         }
    315         this.service = IJobService.Stub.asInterface(service);
    316         final PowerManager pm =
    317                 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    318         PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    319                 runningJob.getTag());
    320         wl.setWorkSource(new WorkSource(runningJob.getSourceUid()));
    321         wl.setReferenceCounted(false);
    322         wl.acquire();
    323         synchronized (mLock) {
    324             // We use a new wakelock instance per job.  In rare cases there is a race between
    325             // teardown following job completion/cancellation and new job service spin-up
    326             // such that if we simply assign mWakeLock to be the new instance, we orphan
    327             // the currently-live lock instead of cleanly replacing it.  Watch for this and
    328             // explicitly fast-forward the release if we're in that situation.
    329             if (mWakeLock != null) {
    330                 Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock
    331                         + " tag=" + mWakeLock.getTag());
    332                 mWakeLock.release();
    333             }
    334             mWakeLock = wl;
    335         }
    336         mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
    337     }
    338 
    339     /** If the client service crashes we reschedule this job and clean up. */
    340     @Override
    341     public void onServiceDisconnected(ComponentName name) {
    342         mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
    343     }
    344 
    345     /**
    346      * This class is reused across different clients, and passes itself in as a callback. Check
    347      * whether the client exercising the callback is the client we expect.
    348      * @return True if the binder calling is coming from the client we expect.
    349      */
    350     private boolean verifyCallingUid() {
    351         synchronized (mLock) {
    352             if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
    353                 if (DEBUG) {
    354                     Slog.d(TAG, "Stale callback received, ignoring.");
    355                 }
    356                 return false;
    357             }
    358             return true;
    359         }
    360     }
    361 
    362     /**
    363      * Handles the lifecycle of the JobService binding/callbacks, etc. The convention within this
    364      * class is to append 'H' to each function name that can only be called on this handler. This
    365      * isn't strictly necessary because all of these functions are private, but helps clarity.
    366      */
    367     private class JobServiceHandler extends Handler {
    368         JobServiceHandler(Looper looper) {
    369             super(looper);
    370         }
    371 
    372         @Override
    373         public void handleMessage(Message message) {
    374             switch (message.what) {
    375                 case MSG_SERVICE_BOUND:
    376                     removeOpTimeOut();
    377                     handleServiceBoundH();
    378                     break;
    379                 case MSG_CALLBACK:
    380                     if (DEBUG) {
    381                         Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
    382                                 + " v:" + VERB_STRINGS[mVerb]);
    383                     }
    384                     removeOpTimeOut();
    385 
    386                     if (mVerb == VERB_STARTING) {
    387                         final boolean workOngoing = message.arg2 == 1;
    388                         handleStartedH(workOngoing);
    389                     } else if (mVerb == VERB_EXECUTING ||
    390                             mVerb == VERB_STOPPING) {
    391                         final boolean reschedule = message.arg2 == 1;
    392                         handleFinishedH(reschedule);
    393                     } else {
    394                         if (DEBUG) {
    395                             Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
    396                         }
    397                     }
    398                     break;
    399                 case MSG_CANCEL:
    400                     if (mVerb == VERB_FINISHED) {
    401                         if (DEBUG) {
    402                             Slog.d(TAG,
    403                                    "Trying to process cancel for torn-down context, ignoring.");
    404                         }
    405                         return;
    406                     }
    407                     mParams.setStopReason(message.arg1);
    408                     if (message.arg1 == JobParameters.REASON_PREEMPT) {
    409                         mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
    410                                 NO_PREFERRED_UID;
    411                     }
    412                     handleCancelH();
    413                     break;
    414                 case MSG_TIMEOUT:
    415                     handleOpTimeoutH();
    416                     break;
    417                 case MSG_SHUTDOWN_EXECUTION:
    418                     closeAndCleanupJobH(true /* needsReschedule */);
    419                     break;
    420                 default:
    421                     Slog.e(TAG, "Unrecognised message: " + message);
    422             }
    423         }
    424 
    425         /** Start the job on the service. */
    426         private void handleServiceBoundH() {
    427             if (DEBUG) {
    428                 Slog.d(TAG, "MSG_SERVICE_BOUND for " + mRunningJob.toShortString());
    429             }
    430             if (mVerb != VERB_BINDING) {
    431                 Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
    432                         + VERB_STRINGS[mVerb]);
    433                 closeAndCleanupJobH(false /* reschedule */);
    434                 return;
    435             }
    436             if (mCancelled.get()) {
    437                 if (DEBUG) {
    438                     Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
    439                             + mRunningJob);
    440                 }
    441                 closeAndCleanupJobH(true /* reschedule */);
    442                 return;
    443             }
    444             try {
    445                 mVerb = VERB_STARTING;
    446                 scheduleOpTimeOut();
    447                 service.startJob(mParams);
    448             } catch (RemoteException e) {
    449                 Slog.e(TAG, "Error sending onStart message to '" +
    450                         mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
    451             }
    452         }
    453 
    454         /**
    455          * State behaviours.
    456          * VERB_STARTING   -> Successful start, change job to VERB_EXECUTING and post timeout.
    457          *     _PENDING    -> Error
    458          *     _EXECUTING  -> Error
    459          *     _STOPPING   -> Error
    460          */
    461         private void handleStartedH(boolean workOngoing) {
    462             switch (mVerb) {
    463                 case VERB_STARTING:
    464                     mVerb = VERB_EXECUTING;
    465                     if (!workOngoing) {
    466                         // Job is finished already so fast-forward to handleFinished.
    467                         handleFinishedH(false);
    468                         return;
    469                     }
    470                     if (mCancelled.get()) {
    471                         if (DEBUG) {
    472                             Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
    473                         }
    474                         // Cancelled *while* waiting for acknowledgeStartMessage from client.
    475                         handleCancelH();
    476                         return;
    477                     }
    478                     scheduleOpTimeOut();
    479                     break;
    480                 default:
    481                     Slog.e(TAG, "Handling started job but job wasn't starting! Was "
    482                             + VERB_STRINGS[mVerb] + ".");
    483                     return;
    484             }
    485         }
    486 
    487         /**
    488          * VERB_EXECUTING  -> Client called jobFinished(), clean up and notify done.
    489          *     _STOPPING   -> Successful finish, clean up and notify done.
    490          *     _STARTING   -> Error
    491          *     _PENDING    -> Error
    492          */
    493         private void handleFinishedH(boolean reschedule) {
    494             switch (mVerb) {
    495                 case VERB_EXECUTING:
    496                 case VERB_STOPPING:
    497                     closeAndCleanupJobH(reschedule);
    498                     break;
    499                 default:
    500                     Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
    501                             "executed. Was " + VERB_STRINGS[mVerb] + ".");
    502             }
    503         }
    504 
    505         /**
    506          * A job can be in various states when a cancel request comes in:
    507          * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
    508          *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
    509          *     _STARTING   -> Mark as cancelled and wait for
    510          *                    {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
    511          *     _EXECUTING  -> call {@link #sendStopMessageH}}, but only if there are no callbacks
    512          *                      in the message queue.
    513          *     _ENDING     -> No point in doing anything here, so we ignore.
    514          */
    515         private void handleCancelH() {
    516             if (JobSchedulerService.DEBUG) {
    517                 Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
    518                         + VERB_STRINGS[mVerb]);
    519             }
    520             switch (mVerb) {
    521                 case VERB_BINDING:
    522                 case VERB_STARTING:
    523                     mCancelled.set(true);
    524                     break;
    525                 case VERB_EXECUTING:
    526                     if (hasMessages(MSG_CALLBACK)) {
    527                         // If the client has called jobFinished, ignore this cancel.
    528                         return;
    529                     }
    530                     sendStopMessageH();
    531                     break;
    532                 case VERB_STOPPING:
    533                     // Nada.
    534                     break;
    535                 default:
    536                     Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
    537                     break;
    538             }
    539         }
    540 
    541         /** Process MSG_TIMEOUT here. */
    542         private void handleOpTimeoutH() {
    543             switch (mVerb) {
    544                 case VERB_BINDING:
    545                     Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
    546                             ", dropping.");
    547                     closeAndCleanupJobH(false /* needsReschedule */);
    548                     break;
    549                 case VERB_STARTING:
    550                     // Client unresponsive - wedged or failed to respond in time. We don't really
    551                     // know what happened so let's log it and notify the JobScheduler
    552                     // FINISHED/NO-RETRY.
    553                     Slog.e(TAG, "No response from client for onStartJob '" +
    554                             mRunningJob.toShortString());
    555                     closeAndCleanupJobH(false /* needsReschedule */);
    556                     break;
    557                 case VERB_STOPPING:
    558                     // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
    559                     Slog.e(TAG, "No response from client for onStopJob, '" +
    560                             mRunningJob.toShortString());
    561                     closeAndCleanupJobH(true /* needsReschedule */);
    562                     break;
    563                 case VERB_EXECUTING:
    564                     // Not an error - client ran out of time.
    565                     Slog.i(TAG, "Client timed out while executing (no jobFinished received)." +
    566                             " sending onStop. "  + mRunningJob.toShortString());
    567                     mParams.setStopReason(JobParameters.REASON_TIMEOUT);
    568                     sendStopMessageH();
    569                     break;
    570                 default:
    571                     Slog.e(TAG, "Handling timeout for an invalid job state: " +
    572                             mRunningJob.toShortString() + ", dropping.");
    573                     closeAndCleanupJobH(false /* needsReschedule */);
    574             }
    575         }
    576 
    577         /**
    578          * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
    579          * VERB_STOPPING.
    580          */
    581         private void sendStopMessageH() {
    582             removeOpTimeOut();
    583             if (mVerb != VERB_EXECUTING) {
    584                 Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
    585                 closeAndCleanupJobH(false /* reschedule */);
    586                 return;
    587             }
    588             try {
    589                 mVerb = VERB_STOPPING;
    590                 scheduleOpTimeOut();
    591                 service.stopJob(mParams);
    592             } catch (RemoteException e) {
    593                 Slog.e(TAG, "Error sending onStopJob to client.", e);
    594                 closeAndCleanupJobH(false /* reschedule */);
    595             }
    596         }
    597 
    598         /**
    599          * The provided job has finished, either by calling
    600          * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
    601          * or from acknowledging the stop message we sent. Either way, we're done tracking it and
    602          * we want to clean up internally.
    603          */
    604         private void closeAndCleanupJobH(boolean reschedule) {
    605             final JobStatus completedJob;
    606             synchronized (mLock) {
    607                 if (mVerb == VERB_FINISHED) {
    608                     return;
    609                 }
    610                 completedJob = mRunningJob;
    611                 mJobPackageTracker.noteInactive(completedJob);
    612                 try {
    613                     mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
    614                             mRunningJob.getSourceUid());
    615                 } catch (RemoteException e) {
    616                     // Whatever.
    617                 }
    618                 if (mWakeLock != null) {
    619                     mWakeLock.release();
    620                 }
    621                 mContext.unbindService(JobServiceContext.this);
    622                 mWakeLock = null;
    623                 mRunningJob = null;
    624                 mParams = null;
    625                 mVerb = VERB_FINISHED;
    626                 mCancelled.set(false);
    627                 service = null;
    628                 mAvailable = true;
    629             }
    630             removeOpTimeOut();
    631             removeMessages(MSG_CALLBACK);
    632             removeMessages(MSG_SERVICE_BOUND);
    633             removeMessages(MSG_CANCEL);
    634             removeMessages(MSG_SHUTDOWN_EXECUTION);
    635             mCompletedListener.onJobCompleted(completedJob, reschedule);
    636         }
    637     }
    638 
    639     /**
    640      * Called when sending a message to the client, over whose execution we have no control. If
    641      * we haven't received a response in a certain amount of time, we want to give up and carry
    642      * on with life.
    643      */
    644     private void scheduleOpTimeOut() {
    645         removeOpTimeOut();
    646 
    647         final long timeoutMillis = (mVerb == VERB_EXECUTING) ?
    648                 EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
    649         if (DEBUG) {
    650             Slog.d(TAG, "Scheduling time out for '" +
    651                     mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
    652                     mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
    653         }
    654         Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
    655         mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
    656         mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
    657     }
    658 
    659 
    660     private void removeOpTimeOut() {
    661         mCallbackHandler.removeMessages(MSG_TIMEOUT);
    662     }
    663 }
    664