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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
     20 
     21 import android.app.ActivityManager;
     22 import android.app.job.IJobCallback;
     23 import android.app.job.IJobService;
     24 import android.app.job.JobInfo;
     25 import android.app.job.JobParameters;
     26 import android.app.job.JobWorkItem;
     27 import android.app.usage.UsageStatsManagerInternal;
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.ServiceConnection;
     32 import android.net.Uri;
     33 import android.os.Binder;
     34 import android.os.Handler;
     35 import android.os.IBinder;
     36 import android.os.Looper;
     37 import android.os.Message;
     38 import android.os.PowerManager;
     39 import android.os.RemoteException;
     40 import android.os.UserHandle;
     41 import android.os.WorkSource;
     42 import android.util.EventLog;
     43 import android.util.Slog;
     44 import android.util.TimeUtils;
     45 
     46 import com.android.internal.annotations.GuardedBy;
     47 import com.android.internal.annotations.VisibleForTesting;
     48 import com.android.internal.app.IBatteryStats;
     49 import com.android.server.EventLogTags;
     50 import com.android.server.LocalServices;
     51 import com.android.server.job.controllers.JobStatus;
     52 
     53 /**
     54  * Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
     55  * class.
     56  *
     57  * There are two important interactions into this class from the
     58  * {@link com.android.server.job.JobSchedulerService}. To execute a job and to cancel a job.
     59  * - Execution of a new job is handled by the {@link #mAvailable}. This bit is flipped once when a
     60  * job lands, and again when it is complete.
     61  * - Cancelling is trickier, because there are also interactions from the client. It's possible
     62  * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
     63  * {@link #doCancelLocked} after the client has already finished. This is handled by having
     64  * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelLocked} check whether
     65  * the context is still valid.
     66  * To mitigate this, we avoid sending duplicate onStopJob()
     67  * calls to the client after they've specified jobFinished().
     68  */
     69 public final class JobServiceContext implements ServiceConnection {
     70     private static final boolean DEBUG = JobSchedulerService.DEBUG;
     71     private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
     72 
     73     private static final String TAG = "JobServiceContext";
     74     /** Amount of time a job is allowed to execute for before being considered timed-out. */
     75     public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
     76     /** Amount of time the JobScheduler waits for the initial service launch+bind. */
     77     private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
     78     /** Amount of time the JobScheduler will wait for a response from an app for a message. */
     79     private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
     80 
     81     private static final String[] VERB_STRINGS = {
     82             "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
     83     };
     84 
     85     // States that a job occupies while interacting with the client.
     86     static final int VERB_BINDING = 0;
     87     static final int VERB_STARTING = 1;
     88     static final int VERB_EXECUTING = 2;
     89     static final int VERB_STOPPING = 3;
     90     static final int VERB_FINISHED = 4;
     91 
     92     // Messages that result from interactions with the client service.
     93     /** System timed out waiting for a response. */
     94     private static final int MSG_TIMEOUT = 0;
     95 
     96     public static final int NO_PREFERRED_UID = -1;
     97 
     98     private final Handler mCallbackHandler;
     99     /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
    100     private final JobCompletedListener mCompletedListener;
    101     /** Used for service binding, etc. */
    102     private final Context mContext;
    103     private final Object mLock;
    104     private final IBatteryStats mBatteryStats;
    105     private final JobPackageTracker mJobPackageTracker;
    106     private PowerManager.WakeLock mWakeLock;
    107 
    108     // Execution state.
    109     private JobParameters mParams;
    110     @VisibleForTesting
    111     int mVerb;
    112     private boolean mCancelled;
    113 
    114     /**
    115      * All the information maintained about the job currently being executed.
    116      *
    117      * Any reads (dereferences) not done from the handler thread must be synchronized on
    118      * {@link #mLock}.
    119      * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
    120      */
    121     private JobStatus mRunningJob;
    122     private JobCallback mRunningCallback;
    123     /** Used to store next job to run when current job is to be preempted. */
    124     private int mPreferredUid;
    125     IJobService service;
    126 
    127     /**
    128      * Whether this context is free. This is set to false at the start of execution, and reset to
    129      * true when execution is complete.
    130      */
    131     @GuardedBy("mLock")
    132     private boolean mAvailable;
    133     /** Track start time. */
    134     private long mExecutionStartTimeElapsed;
    135     /** Track when job will timeout. */
    136     private long mTimeoutElapsed;
    137 
    138     // Debugging: reason this job was last stopped.
    139     public String mStoppedReason;
    140 
    141     // Debugging: time this job was last stopped.
    142     public long mStoppedTime;
    143 
    144     final class JobCallback extends IJobCallback.Stub {
    145         public String mStoppedReason;
    146         public long mStoppedTime;
    147 
    148         @Override
    149         public void acknowledgeStartMessage(int jobId, boolean ongoing) {
    150             doAcknowledgeStartMessage(this, jobId, ongoing);
    151         }
    152 
    153         @Override
    154         public void acknowledgeStopMessage(int jobId, boolean reschedule) {
    155             doAcknowledgeStopMessage(this, jobId, reschedule);
    156         }
    157 
    158         @Override
    159         public JobWorkItem dequeueWork(int jobId) {
    160             return doDequeueWork(this, jobId);
    161         }
    162 
    163         @Override
    164         public boolean completeWork(int jobId, int workId) {
    165             return doCompleteWork(this, jobId, workId);
    166         }
    167 
    168         @Override
    169         public void jobFinished(int jobId, boolean reschedule) {
    170             doJobFinished(this, jobId, reschedule);
    171         }
    172     }
    173 
    174     JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
    175             JobPackageTracker tracker, Looper looper) {
    176         this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
    177     }
    178 
    179     @VisibleForTesting
    180     JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
    181             JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
    182         mContext = context;
    183         mLock = lock;
    184         mBatteryStats = batteryStats;
    185         mJobPackageTracker = tracker;
    186         mCallbackHandler = new JobServiceHandler(looper);
    187         mCompletedListener = completedListener;
    188         mAvailable = true;
    189         mVerb = VERB_FINISHED;
    190         mPreferredUid = NO_PREFERRED_UID;
    191     }
    192 
    193     /**
    194      * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()}
    195      * and ensure it is null to make sure this is a valid context.
    196      * @param job The status of the job that we are going to run.
    197      * @return True if the job is valid and is running. False if the job cannot be executed.
    198      */
    199     boolean executeRunnableJob(JobStatus job) {
    200         synchronized (mLock) {
    201             if (!mAvailable) {
    202                 Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
    203                 return false;
    204             }
    205 
    206             mPreferredUid = NO_PREFERRED_UID;
    207 
    208             mRunningJob = job;
    209             mRunningCallback = new JobCallback();
    210             final boolean isDeadlineExpired =
    211                     job.hasDeadlineConstraint() &&
    212                             (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
    213             Uri[] triggeredUris = null;
    214             if (job.changedUris != null) {
    215                 triggeredUris = new Uri[job.changedUris.size()];
    216                 job.changedUris.toArray(triggeredUris);
    217             }
    218             String[] triggeredAuthorities = null;
    219             if (job.changedAuthorities != null) {
    220                 triggeredAuthorities = new String[job.changedAuthorities.size()];
    221                 job.changedAuthorities.toArray(triggeredAuthorities);
    222             }
    223             final JobInfo ji = job.getJob();
    224             mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
    225                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
    226                     isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
    227             mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
    228 
    229             final long whenDeferred = job.getWhenStandbyDeferred();
    230             if (whenDeferred > 0) {
    231                 final long deferral = mExecutionStartTimeElapsed - whenDeferred;
    232                 EventLog.writeEvent(EventLogTags.JOB_DEFERRED_EXECUTION, deferral);
    233                 if (DEBUG_STANDBY) {
    234                     StringBuilder sb = new StringBuilder(128);
    235                     sb.append("Starting job deferred for standby by ");
    236                     TimeUtils.formatDuration(deferral, sb);
    237                     sb.append(" ms : ");
    238                     sb.append(job.toShortString());
    239                     Slog.v(TAG, sb.toString());
    240                 }
    241             }
    242 
    243             // Once we'e begun executing a job, we by definition no longer care whether
    244             // it was inflated from disk with not-yet-coherent delay/deadline bounds.
    245             job.clearPersistedUtcTimes();
    246 
    247             mVerb = VERB_BINDING;
    248             scheduleOpTimeOutLocked();
    249             final Intent intent = new Intent().setComponent(job.getServiceComponent());
    250             boolean binding = false;
    251             try {
    252                 binding = mContext.bindServiceAsUser(intent, this,
    253                         Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
    254                         | Context.BIND_NOT_PERCEPTIBLE,
    255                         new UserHandle(job.getUserId()));
    256             } catch (SecurityException e) {
    257                 // Some permission policy, for example INTERACT_ACROSS_USERS and
    258                 // android:singleUser, can result in a SecurityException being thrown from
    259                 // bindServiceAsUser().  If this happens, catch it and fail gracefully.
    260                 Slog.w(TAG, "Job service " + job.getServiceComponent().getShortClassName()
    261                         + " cannot be executed: " + e.getMessage());
    262                 binding = false;
    263             }
    264             if (!binding) {
    265                 if (DEBUG) {
    266                     Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
    267                 }
    268                 mRunningJob = null;
    269                 mRunningCallback = null;
    270                 mParams = null;
    271                 mExecutionStartTimeElapsed = 0L;
    272                 mVerb = VERB_FINISHED;
    273                 removeOpTimeOutLocked();
    274                 return false;
    275             }
    276             mJobPackageTracker.noteActive(job);
    277             try {
    278                 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid(),
    279                         job.getStandbyBucket(), job.getJobId());
    280             } catch (RemoteException e) {
    281                 // Whatever.
    282             }
    283             final String jobPackage = job.getSourcePackageName();
    284             final int jobUserId = job.getSourceUserId();
    285             UsageStatsManagerInternal usageStats =
    286                     LocalServices.getService(UsageStatsManagerInternal.class);
    287             usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
    288             JobSchedulerInternal jobScheduler =
    289                     LocalServices.getService(JobSchedulerInternal.class);
    290             jobScheduler.noteJobStart(jobPackage, jobUserId);
    291             mAvailable = false;
    292             mStoppedReason = null;
    293             mStoppedTime = 0;
    294             return true;
    295         }
    296     }
    297 
    298     /**
    299      * Used externally to query the running job. Will return null if there is no job running.
    300      */
    301     JobStatus getRunningJobLocked() {
    302         return mRunningJob;
    303     }
    304 
    305     /**
    306      * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
    307      */
    308     private String getRunningJobNameLocked() {
    309         return mRunningJob != null ? mRunningJob.toShortString() : "<null>";
    310     }
    311 
    312     /** Called externally when a job that was scheduled for execution should be cancelled. */
    313     @GuardedBy("mLock")
    314     void cancelExecutingJobLocked(int reason, String debugReason) {
    315         doCancelLocked(reason, debugReason);
    316     }
    317 
    318     @GuardedBy("mLock")
    319     void preemptExecutingJobLocked() {
    320         doCancelLocked(JobParameters.REASON_PREEMPT, "cancelled due to preemption");
    321     }
    322 
    323     int getPreferredUid() {
    324         return mPreferredUid;
    325     }
    326 
    327     void clearPreferredUid() {
    328         mPreferredUid = NO_PREFERRED_UID;
    329     }
    330 
    331     long getExecutionStartTimeElapsed() {
    332         return mExecutionStartTimeElapsed;
    333     }
    334 
    335     long getTimeoutElapsed() {
    336         return mTimeoutElapsed;
    337     }
    338 
    339     @GuardedBy("mLock")
    340     boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
    341             String reason) {
    342         final JobStatus executing = getRunningJobLocked();
    343         if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId())
    344                 && (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
    345                 && (!matchJobId || jobId == executing.getJobId())) {
    346             if (mVerb == VERB_EXECUTING) {
    347                 mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason);
    348                 sendStopMessageLocked("force timeout from shell");
    349                 return true;
    350             }
    351         }
    352         return false;
    353     }
    354 
    355     void doJobFinished(JobCallback cb, int jobId, boolean reschedule) {
    356         doCallback(cb, reschedule, "app called jobFinished");
    357     }
    358 
    359     void doAcknowledgeStopMessage(JobCallback cb, int jobId, boolean reschedule) {
    360         doCallback(cb, reschedule, null);
    361     }
    362 
    363     void doAcknowledgeStartMessage(JobCallback cb, int jobId, boolean ongoing) {
    364         doCallback(cb, ongoing, "finished start");
    365     }
    366 
    367     JobWorkItem doDequeueWork(JobCallback cb, int jobId) {
    368         final long ident = Binder.clearCallingIdentity();
    369         try {
    370             synchronized (mLock) {
    371                 assertCallerLocked(cb);
    372                 if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
    373                     // This job is either all done, or on its way out.  Either way, it
    374                     // should not dispatch any more work.  We will pick up any remaining
    375                     // work the next time we start the job again.
    376                     return null;
    377                 }
    378                 final JobWorkItem work = mRunningJob.dequeueWorkLocked();
    379                 if (work == null && !mRunningJob.hasExecutingWorkLocked()) {
    380                     // This will finish the job.
    381                     doCallbackLocked(false, "last work dequeued");
    382                 }
    383                 return work;
    384             }
    385         } finally {
    386             Binder.restoreCallingIdentity(ident);
    387         }
    388     }
    389 
    390     boolean doCompleteWork(JobCallback cb, int jobId, int workId) {
    391         final long ident = Binder.clearCallingIdentity();
    392         try {
    393             synchronized (mLock) {
    394                 assertCallerLocked(cb);
    395                 return mRunningJob.completeWorkLocked(ActivityManager.getService(), workId);
    396             }
    397         } finally {
    398             Binder.restoreCallingIdentity(ident);
    399         }
    400     }
    401 
    402     /**
    403      * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
    404      * we intend to send to the client - we stop sending work when the service is unbound so until
    405      * then we keep the wakelock.
    406      * @param name The concrete component name of the service that has been connected.
    407      * @param service The IBinder of the Service's communication channel,
    408      */
    409     @Override
    410     public void onServiceConnected(ComponentName name, IBinder service) {
    411         JobStatus runningJob;
    412         synchronized (mLock) {
    413             // This isn't strictly necessary b/c the JobServiceHandler is running on the main
    414             // looper and at this point we can't get any binder callbacks from the client. Better
    415             // safe than sorry.
    416             runningJob = mRunningJob;
    417 
    418             if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
    419                 closeAndCleanupJobLocked(true /* needsReschedule */,
    420                         "connected for different component");
    421                 return;
    422             }
    423             this.service = IJobService.Stub.asInterface(service);
    424             final PowerManager pm =
    425                     (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    426             PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    427                     runningJob.getTag());
    428             wl.setWorkSource(deriveWorkSource(runningJob));
    429             wl.setReferenceCounted(false);
    430             wl.acquire();
    431 
    432             // We use a new wakelock instance per job.  In rare cases there is a race between
    433             // teardown following job completion/cancellation and new job service spin-up
    434             // such that if we simply assign mWakeLock to be the new instance, we orphan
    435             // the currently-live lock instead of cleanly replacing it.  Watch for this and
    436             // explicitly fast-forward the release if we're in that situation.
    437             if (mWakeLock != null) {
    438                 Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock
    439                         + " tag=" + mWakeLock.getTag());
    440                 mWakeLock.release();
    441             }
    442             mWakeLock = wl;
    443             doServiceBoundLocked();
    444         }
    445     }
    446 
    447     private WorkSource deriveWorkSource(JobStatus runningJob) {
    448         final int jobUid = runningJob.getSourceUid();
    449         if (WorkSource.isChainedBatteryAttributionEnabled(mContext)) {
    450             WorkSource workSource = new WorkSource();
    451             workSource.createWorkChain()
    452                     .addNode(jobUid, null)
    453                     .addNode(android.os.Process.SYSTEM_UID, "JobScheduler");
    454             return workSource;
    455         } else {
    456             return new WorkSource(jobUid);
    457         }
    458     }
    459 
    460     /** If the client service crashes we reschedule this job and clean up. */
    461     @Override
    462     public void onServiceDisconnected(ComponentName name) {
    463         synchronized (mLock) {
    464             closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
    465         }
    466     }
    467 
    468     /**
    469      * This class is reused across different clients, and passes itself in as a callback. Check
    470      * whether the client exercising the callback is the client we expect.
    471      * @return True if the binder calling is coming from the client we expect.
    472      */
    473     private boolean verifyCallerLocked(JobCallback cb) {
    474         if (mRunningCallback != cb) {
    475             if (DEBUG) {
    476                 Slog.d(TAG, "Stale callback received, ignoring.");
    477             }
    478             return false;
    479         }
    480         return true;
    481     }
    482 
    483     private void assertCallerLocked(JobCallback cb) {
    484         if (!verifyCallerLocked(cb)) {
    485             StringBuilder sb = new StringBuilder(128);
    486             sb.append("Caller no longer running");
    487             if (cb.mStoppedReason != null) {
    488                 sb.append(", last stopped ");
    489                 TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
    490                 sb.append(" because: ");
    491                 sb.append(cb.mStoppedReason);
    492             }
    493             throw new SecurityException(sb.toString());
    494         }
    495     }
    496 
    497     /**
    498      * Scheduling of async messages (basically timeouts at this point).
    499      */
    500     private class JobServiceHandler extends Handler {
    501         JobServiceHandler(Looper looper) {
    502             super(looper);
    503         }
    504 
    505         @Override
    506         public void handleMessage(Message message) {
    507             switch (message.what) {
    508                 case MSG_TIMEOUT:
    509                     synchronized (mLock) {
    510                         if (message.obj == mRunningCallback) {
    511                             handleOpTimeoutLocked();
    512                         } else {
    513                             JobCallback jc = (JobCallback)message.obj;
    514                             StringBuilder sb = new StringBuilder(128);
    515                             sb.append("Ignoring timeout of no longer active job");
    516                             if (jc.mStoppedReason != null) {
    517                                 sb.append(", stopped ");
    518                                 TimeUtils.formatDuration(sElapsedRealtimeClock.millis()
    519                                         - jc.mStoppedTime, sb);
    520                                 sb.append(" because: ");
    521                                 sb.append(jc.mStoppedReason);
    522                             }
    523                             Slog.w(TAG, sb.toString());
    524                         }
    525                     }
    526                     break;
    527                 default:
    528                     Slog.e(TAG, "Unrecognised message: " + message);
    529             }
    530         }
    531     }
    532 
    533     @GuardedBy("mLock")
    534     void doServiceBoundLocked() {
    535         removeOpTimeOutLocked();
    536         handleServiceBoundLocked();
    537     }
    538 
    539     void doCallback(JobCallback cb, boolean reschedule, String reason) {
    540         final long ident = Binder.clearCallingIdentity();
    541         try {
    542             synchronized (mLock) {
    543                 if (!verifyCallerLocked(cb)) {
    544                     return;
    545                 }
    546                 doCallbackLocked(reschedule, reason);
    547             }
    548         } finally {
    549             Binder.restoreCallingIdentity(ident);
    550         }
    551     }
    552 
    553     @GuardedBy("mLock")
    554     void doCallbackLocked(boolean reschedule, String reason) {
    555         if (DEBUG) {
    556             Slog.d(TAG, "doCallback of : " + mRunningJob
    557                     + " v:" + VERB_STRINGS[mVerb]);
    558         }
    559         removeOpTimeOutLocked();
    560 
    561         if (mVerb == VERB_STARTING) {
    562             handleStartedLocked(reschedule);
    563         } else if (mVerb == VERB_EXECUTING ||
    564                 mVerb == VERB_STOPPING) {
    565             handleFinishedLocked(reschedule, reason);
    566         } else {
    567             if (DEBUG) {
    568                 Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
    569             }
    570         }
    571     }
    572 
    573     @GuardedBy("mLock")
    574     void doCancelLocked(int arg1, String debugReason) {
    575         if (mVerb == VERB_FINISHED) {
    576             if (DEBUG) {
    577                 Slog.d(TAG,
    578                         "Trying to process cancel for torn-down context, ignoring.");
    579             }
    580             return;
    581         }
    582         mParams.setStopReason(arg1, debugReason);
    583         if (arg1 == JobParameters.REASON_PREEMPT) {
    584             mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
    585                     NO_PREFERRED_UID;
    586         }
    587         handleCancelLocked(debugReason);
    588     }
    589 
    590     /** Start the job on the service. */
    591     @GuardedBy("mLock")
    592     private void handleServiceBoundLocked() {
    593         if (DEBUG) {
    594             Slog.d(TAG, "handleServiceBound for " + getRunningJobNameLocked());
    595         }
    596         if (mVerb != VERB_BINDING) {
    597             Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
    598                     + VERB_STRINGS[mVerb]);
    599             closeAndCleanupJobLocked(false /* reschedule */, "started job not pending");
    600             return;
    601         }
    602         if (mCancelled) {
    603             if (DEBUG) {
    604                 Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
    605                         + mRunningJob);
    606             }
    607             closeAndCleanupJobLocked(true /* reschedule */, "cancelled while waiting for bind");
    608             return;
    609         }
    610         try {
    611             mVerb = VERB_STARTING;
    612             scheduleOpTimeOutLocked();
    613             service.startJob(mParams);
    614         } catch (Exception e) {
    615             // We catch 'Exception' because client-app malice or bugs might induce a wide
    616             // range of possible exception-throw outcomes from startJob() and its handling
    617             // of the client's ParcelableBundle extras.
    618             Slog.e(TAG, "Error sending onStart message to '" +
    619                     mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
    620         }
    621     }
    622 
    623     /**
    624      * State behaviours.
    625      * VERB_STARTING   -> Successful start, change job to VERB_EXECUTING and post timeout.
    626      *     _PENDING    -> Error
    627      *     _EXECUTING  -> Error
    628      *     _STOPPING   -> Error
    629      */
    630     @GuardedBy("mLock")
    631     private void handleStartedLocked(boolean workOngoing) {
    632         switch (mVerb) {
    633             case VERB_STARTING:
    634                 mVerb = VERB_EXECUTING;
    635                 if (!workOngoing) {
    636                     // Job is finished already so fast-forward to handleFinished.
    637                     handleFinishedLocked(false, "onStartJob returned false");
    638                     return;
    639                 }
    640                 if (mCancelled) {
    641                     if (DEBUG) {
    642                         Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
    643                     }
    644                     // Cancelled *while* waiting for acknowledgeStartMessage from client.
    645                     handleCancelLocked(null);
    646                     return;
    647                 }
    648                 scheduleOpTimeOutLocked();
    649                 break;
    650             default:
    651                 Slog.e(TAG, "Handling started job but job wasn't starting! Was "
    652                         + VERB_STRINGS[mVerb] + ".");
    653                 return;
    654         }
    655     }
    656 
    657     /**
    658      * VERB_EXECUTING  -> Client called jobFinished(), clean up and notify done.
    659      *     _STOPPING   -> Successful finish, clean up and notify done.
    660      *     _STARTING   -> Error
    661      *     _PENDING    -> Error
    662      */
    663     @GuardedBy("mLock")
    664     private void handleFinishedLocked(boolean reschedule, String reason) {
    665         switch (mVerb) {
    666             case VERB_EXECUTING:
    667             case VERB_STOPPING:
    668                 closeAndCleanupJobLocked(reschedule, reason);
    669                 break;
    670             default:
    671                 Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
    672                         "executed. Was " + VERB_STRINGS[mVerb] + ".");
    673         }
    674     }
    675 
    676     /**
    677      * A job can be in various states when a cancel request comes in:
    678      * VERB_BINDING    -> Cancelled before bind completed. Mark as cancelled and wait for
    679      *                    {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
    680      *     _STARTING   -> Mark as cancelled and wait for
    681      *                    {@link JobServiceContext#doAcknowledgeStartMessage}
    682      *     _EXECUTING  -> call {@link #sendStopMessageLocked}}, but only if there are no callbacks
    683      *                      in the message queue.
    684      *     _ENDING     -> No point in doing anything here, so we ignore.
    685      */
    686     @GuardedBy("mLock")
    687     private void handleCancelLocked(String reason) {
    688         if (JobSchedulerService.DEBUG) {
    689             Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
    690                     + VERB_STRINGS[mVerb]);
    691         }
    692         switch (mVerb) {
    693             case VERB_BINDING:
    694             case VERB_STARTING:
    695                 mCancelled = true;
    696                 applyStoppedReasonLocked(reason);
    697                 break;
    698             case VERB_EXECUTING:
    699                 sendStopMessageLocked(reason);
    700                 break;
    701             case VERB_STOPPING:
    702                 // Nada.
    703                 break;
    704             default:
    705                 Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
    706                 break;
    707         }
    708     }
    709 
    710     /** Process MSG_TIMEOUT here. */
    711     @GuardedBy("mLock")
    712     private void handleOpTimeoutLocked() {
    713         switch (mVerb) {
    714             case VERB_BINDING:
    715                 Slog.w(TAG, "Time-out while trying to bind " + getRunningJobNameLocked()
    716                         + ", dropping.");
    717                 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while binding");
    718                 break;
    719             case VERB_STARTING:
    720                 // Client unresponsive - wedged or failed to respond in time. We don't really
    721                 // know what happened so let's log it and notify the JobScheduler
    722                 // FINISHED/NO-RETRY.
    723                 Slog.w(TAG, "No response from client for onStartJob "
    724                         + getRunningJobNameLocked());
    725                 closeAndCleanupJobLocked(false /* needsReschedule */, "timed out while starting");
    726                 break;
    727             case VERB_STOPPING:
    728                 // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
    729                 Slog.w(TAG, "No response from client for onStopJob "
    730                         + getRunningJobNameLocked());
    731                 closeAndCleanupJobLocked(true /* needsReschedule */, "timed out while stopping");
    732                 break;
    733             case VERB_EXECUTING:
    734                 // Not an error - client ran out of time.
    735                 Slog.i(TAG, "Client timed out while executing (no jobFinished received), " +
    736                         "sending onStop: " + getRunningJobNameLocked());
    737                 mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out");
    738                 sendStopMessageLocked("timeout while executing");
    739                 break;
    740             default:
    741                 Slog.e(TAG, "Handling timeout for an invalid job state: "
    742                         + getRunningJobNameLocked() + ", dropping.");
    743                 closeAndCleanupJobLocked(false /* needsReschedule */, "invalid timeout");
    744         }
    745     }
    746 
    747     /**
    748      * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
    749      * VERB_STOPPING.
    750      */
    751     @GuardedBy("mLock")
    752     private void sendStopMessageLocked(String reason) {
    753         removeOpTimeOutLocked();
    754         if (mVerb != VERB_EXECUTING) {
    755             Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
    756             closeAndCleanupJobLocked(false /* reschedule */, reason);
    757             return;
    758         }
    759         try {
    760             applyStoppedReasonLocked(reason);
    761             mVerb = VERB_STOPPING;
    762             scheduleOpTimeOutLocked();
    763             service.stopJob(mParams);
    764         } catch (RemoteException e) {
    765             Slog.e(TAG, "Error sending onStopJob to client.", e);
    766             // The job's host app apparently crashed during the job, so we should reschedule.
    767             closeAndCleanupJobLocked(true /* reschedule */, "host crashed when trying to stop");
    768         }
    769     }
    770 
    771     /**
    772      * The provided job has finished, either by calling
    773      * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
    774      * or from acknowledging the stop message we sent. Either way, we're done tracking it and
    775      * we want to clean up internally.
    776      */
    777     @GuardedBy("mLock")
    778     private void closeAndCleanupJobLocked(boolean reschedule, String reason) {
    779         final JobStatus completedJob;
    780         if (mVerb == VERB_FINISHED) {
    781             return;
    782         }
    783         applyStoppedReasonLocked(reason);
    784         completedJob = mRunningJob;
    785         mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason(), reason);
    786         try {
    787             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
    788                     mRunningJob.getSourceUid(), mParams.getStopReason(),
    789                     mRunningJob.getStandbyBucket(), mRunningJob.getJobId());
    790         } catch (RemoteException e) {
    791             // Whatever.
    792         }
    793         if (mWakeLock != null) {
    794             mWakeLock.release();
    795         }
    796         mContext.unbindService(JobServiceContext.this);
    797         mWakeLock = null;
    798         mRunningJob = null;
    799         mRunningCallback = null;
    800         mParams = null;
    801         mVerb = VERB_FINISHED;
    802         mCancelled = false;
    803         service = null;
    804         mAvailable = true;
    805         removeOpTimeOutLocked();
    806         mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
    807     }
    808 
    809     private void applyStoppedReasonLocked(String reason) {
    810         if (reason != null && mStoppedReason == null) {
    811             mStoppedReason = reason;
    812             mStoppedTime = sElapsedRealtimeClock.millis();
    813             if (mRunningCallback != null) {
    814                 mRunningCallback.mStoppedReason = mStoppedReason;
    815                 mRunningCallback.mStoppedTime = mStoppedTime;
    816             }
    817         }
    818     }
    819 
    820     /**
    821      * Called when sending a message to the client, over whose execution we have no control. If
    822      * we haven't received a response in a certain amount of time, we want to give up and carry
    823      * on with life.
    824      */
    825     private void scheduleOpTimeOutLocked() {
    826         removeOpTimeOutLocked();
    827 
    828         final long timeoutMillis;
    829         switch (mVerb) {
    830             case VERB_EXECUTING:
    831                 timeoutMillis = EXECUTING_TIMESLICE_MILLIS;
    832                 break;
    833 
    834             case VERB_BINDING:
    835                 timeoutMillis = OP_BIND_TIMEOUT_MILLIS;
    836                 break;
    837 
    838             default:
    839                 timeoutMillis = OP_TIMEOUT_MILLIS;
    840                 break;
    841         }
    842         if (DEBUG) {
    843             Slog.d(TAG, "Scheduling time out for '" +
    844                     mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
    845                     mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
    846         }
    847         Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
    848         mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
    849         mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
    850     }
    851 
    852 
    853     private void removeOpTimeOutLocked() {
    854         mCallbackHandler.removeMessages(MSG_TIMEOUT);
    855     }
    856 }
    857