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