blob: c7ef0e26971f3a34d7712477ccd3bb1a11ad94ab [file] [log] [blame]
Matthew Williams6de79e22014-05-01 10:47:00 -07001/*
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
Christopher Tate7060b042014-06-09 19:50:00 -070017package com.android.server.job;
Matthew Williams6de79e22014-05-01 10:47:00 -070018
Matthew Williams691e93e2014-05-12 15:33:09 -070019import android.app.ActivityManager;
Dianne Hackborna47223f2017-03-30 13:49:13 -070020import android.app.job.JobInfo;
Christopher Tate7060b042014-06-09 19:50:00 -070021import android.app.job.JobParameters;
22import android.app.job.IJobCallback;
23import android.app.job.IJobService;
Dianne Hackborn7da13d72017-04-04 17:17:35 -070024import android.app.job.JobWorkItem;
Matthew Williams6de79e22014-05-01 10:47:00 -070025import android.content.ComponentName;
Matthew Williams691e93e2014-05-12 15:33:09 -070026import android.content.Context;
27import android.content.Intent;
Matthew Williams6de79e22014-05-01 10:47:00 -070028import android.content.ServiceConnection;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080029import android.net.Uri;
Matthew Williams9b9244b62014-05-14 11:06:04 -070030import android.os.Binder;
Matthew Williams691e93e2014-05-12 15:33:09 -070031import android.os.Handler;
Matthew Williams6de79e22014-05-01 10:47:00 -070032import android.os.IBinder;
Matthew Williams691e93e2014-05-12 15:33:09 -070033import android.os.Looper;
34import android.os.Message;
35import android.os.PowerManager;
36import android.os.RemoteException;
Matthew Williamseffacfa2014-06-05 20:56:40 -070037import android.os.SystemClock;
Matthew Williams691e93e2014-05-12 15:33:09 -070038import android.os.UserHandle;
39import android.os.WorkSource;
Matthew Williams691e93e2014-05-12 15:33:09 -070040import android.util.Slog;
Matthew Williams6de79e22014-05-01 10:47:00 -070041
Matthew Williams9b9244b62014-05-14 11:06:04 -070042import com.android.internal.annotations.GuardedBy;
43import com.android.internal.annotations.VisibleForTesting;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070044import com.android.internal.app.IBatteryStats;
Christopher Tate7060b042014-06-09 19:50:00 -070045import com.android.server.job.controllers.JobStatus;
Matthew Williams6de79e22014-05-01 10:47:00 -070046
Matthew Williams691e93e2014-05-12 15:33:09 -070047import java.util.concurrent.atomic.AtomicBoolean;
48
Matthew Williams6de79e22014-05-01 10:47:00 -070049/**
Matthew Williamsee410da2014-07-25 11:30:40 -070050 * Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
51 * class.
52 *
53 * There are two important interactions into this class from the
54 * {@link com.android.server.job.JobSchedulerService}. To execute a job and to cancel a job.
55 * - Execution of a new job is handled by the {@link #mAvailable}. This bit is flipped once when a
56 * job lands, and again when it is complete.
57 * - Cancelling is trickier, because there are also interactions from the client. It's possible
58 * the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
59 * {@link #MSG_CANCEL} after the client has already finished. This is handled by having
60 * {@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelH} check whether
61 * the context is still valid.
62 * To mitigate this, tearing down the context removes all messages from the handler, including any
63 * tardy {@link #MSG_CANCEL}s. Additionally, we avoid sending duplicate onStopJob()
64 * calls to the client after they've specified jobFinished().
Matthew Williams6de79e22014-05-01 10:47:00 -070065 */
Christopher Tate7060b042014-06-09 19:50:00 -070066public class JobServiceContext extends IJobCallback.Stub implements ServiceConnection {
Matthew Williamsfa8e5082015-10-15 15:59:12 -070067 private static final boolean DEBUG = JobSchedulerService.DEBUG;
Christopher Tate7060b042014-06-09 19:50:00 -070068 private static final String TAG = "JobServiceContext";
69 /** Define the maximum # of jobs allowed to run on a service at once. */
70 private static final int defaultMaxActiveJobsPerService =
Matthew Williams691e93e2014-05-12 15:33:09 -070071 ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
Christopher Tate7060b042014-06-09 19:50:00 -070072 /** Amount of time a job is allowed to execute for before being considered timed-out. */
Matthew Williams1bde39a2015-10-07 14:29:30 -070073 private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000; // 10mins.
Christopher Tate2022bad2017-02-02 18:07:42 -080074 /** Amount of time the JobScheduler waits for the initial service launch+bind. */
75 private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
Christopher Tate7060b042014-06-09 19:50:00 -070076 /** Amount of time the JobScheduler will wait for a response from an app for a message. */
Matthew Williams691e93e2014-05-12 15:33:09 -070077 private static final long OP_TIMEOUT_MILLIS = 8 * 1000;
Matthew Williams6de79e22014-05-01 10:47:00 -070078
Matthew Williams691e93e2014-05-12 15:33:09 -070079 private static final String[] VERB_STRINGS = {
riddle_hsu6a94aba2015-05-01 01:52:58 +080080 "VERB_BINDING", "VERB_STARTING", "VERB_EXECUTING", "VERB_STOPPING", "VERB_FINISHED"
Matthew Williams691e93e2014-05-12 15:33:09 -070081 };
82
Christopher Tate7060b042014-06-09 19:50:00 -070083 // States that a job occupies while interacting with the client.
Matthew Williams9b9244b62014-05-14 11:06:04 -070084 static final int VERB_BINDING = 0;
85 static final int VERB_STARTING = 1;
86 static final int VERB_EXECUTING = 2;
87 static final int VERB_STOPPING = 3;
riddle_hsu6a94aba2015-05-01 01:52:58 +080088 static final int VERB_FINISHED = 4;
Matthew Williams691e93e2014-05-12 15:33:09 -070089
90 // Messages that result from interactions with the client service.
91 /** System timed out waiting for a response. */
92 private static final int MSG_TIMEOUT = 0;
93 /** Received a callback from client. */
94 private static final int MSG_CALLBACK = 1;
Christopher Tate7060b042014-06-09 19:50:00 -070095 /** Run through list and start any ready jobs.*/
Matthew Williams9b9244b62014-05-14 11:06:04 -070096 private static final int MSG_SERVICE_BOUND = 2;
Christopher Tate7060b042014-06-09 19:50:00 -070097 /** Cancel a job. */
Matthew Williams691e93e2014-05-12 15:33:09 -070098 private static final int MSG_CANCEL = 3;
Christopher Tate7060b042014-06-09 19:50:00 -070099 /** Shutdown the job. Used when the client crashes and we can't die gracefully.*/
Matthew Williams9b9244b62014-05-14 11:06:04 -0700100 private static final int MSG_SHUTDOWN_EXECUTION = 4;
Matthew Williams691e93e2014-05-12 15:33:09 -0700101
Shreyas Basarge5db09082016-01-07 13:38:29 +0000102 public static final int NO_PREFERRED_UID = -1;
103
Matthew Williams691e93e2014-05-12 15:33:09 -0700104 private final Handler mCallbackHandler;
Christopher Tate7060b042014-06-09 19:50:00 -0700105 /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
106 private final JobCompletedListener mCompletedListener;
Matthew Williams691e93e2014-05-12 15:33:09 -0700107 /** Used for service binding, etc. */
108 private final Context mContext;
Dianne Hackbornd506b2b2016-02-16 10:30:33 -0800109 private final Object mLock;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700110 private final IBatteryStats mBatteryStats;
Dianne Hackborn807de782016-04-07 17:54:41 -0700111 private final JobPackageTracker mJobPackageTracker;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700112 private PowerManager.WakeLock mWakeLock;
Matthew Williams6de79e22014-05-01 10:47:00 -0700113
Matthew Williams9b9244b62014-05-14 11:06:04 -0700114 // Execution state.
Christopher Tate7060b042014-06-09 19:50:00 -0700115 private JobParameters mParams;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700116 @VisibleForTesting
117 int mVerb;
118 private AtomicBoolean mCancelled = new AtomicBoolean();
Matthew Williams6de79e22014-05-01 10:47:00 -0700119
Matthew Williams0cc76542015-10-16 21:04:51 -0700120 /**
121 * All the information maintained about the job currently being executed.
122 *
123 * Any reads (dereferences) not done from the handler thread must be synchronized on
124 * {@link #mLock}.
125 * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
126 */
Christopher Tate7060b042014-06-09 19:50:00 -0700127 private JobStatus mRunningJob;
Shreyas Basarge5db09082016-01-07 13:38:29 +0000128 /** Used to store next job to run when current job is to be preempted. */
129 private int mPreferredUid;
Christopher Tate7060b042014-06-09 19:50:00 -0700130 IJobService service;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700131
Matthew Williamsee410da2014-07-25 11:30:40 -0700132 /**
133 * Whether this context is free. This is set to false at the start of execution, and reset to
134 * true when execution is complete.
135 */
Matthew Williamseffacfa2014-06-05 20:56:40 -0700136 @GuardedBy("mLock")
Matthew Williams9b9244b62014-05-14 11:06:04 -0700137 private boolean mAvailable;
Matthew Williamseffacfa2014-06-05 20:56:40 -0700138 /** Track start time. */
139 private long mExecutionStartTimeElapsed;
140 /** Track when job will timeout. */
141 private long mTimeoutElapsed;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700142
Dianne Hackborn807de782016-04-07 17:54:41 -0700143 JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
144 JobPackageTracker tracker, Looper looper) {
145 this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700146 }
147
148 @VisibleForTesting
Dianne Hackbornd506b2b2016-02-16 10:30:33 -0800149 JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
Dianne Hackborn807de782016-04-07 17:54:41 -0700150 JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700151 mContext = context;
Dianne Hackbornd506b2b2016-02-16 10:30:33 -0800152 mLock = lock;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700153 mBatteryStats = batteryStats;
Dianne Hackborn807de782016-04-07 17:54:41 -0700154 mJobPackageTracker = tracker;
Christopher Tate7060b042014-06-09 19:50:00 -0700155 mCallbackHandler = new JobServiceHandler(looper);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700156 mCompletedListener = completedListener;
Matthew Williamseffacfa2014-06-05 20:56:40 -0700157 mAvailable = true;
Shreyas Basarge5db09082016-01-07 13:38:29 +0000158 mVerb = VERB_FINISHED;
159 mPreferredUid = NO_PREFERRED_UID;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700160 }
161
162 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +0000163 * Give a job to this context for execution. Callers must first check {@link #getRunningJob()}
164 * and ensure it is null to make sure this is a valid context.
Christopher Tate7060b042014-06-09 19:50:00 -0700165 * @param job The status of the job that we are going to run.
166 * @return True if the job is valid and is running. False if the job cannot be executed.
Matthew Williams9b9244b62014-05-14 11:06:04 -0700167 */
Christopher Tate7060b042014-06-09 19:50:00 -0700168 boolean executeRunnableJob(JobStatus job) {
Matthew Williamseffacfa2014-06-05 20:56:40 -0700169 synchronized (mLock) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700170 if (!mAvailable) {
171 Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
172 return false;
173 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700174
Shreyas Basarge5db09082016-01-07 13:38:29 +0000175 mPreferredUid = NO_PREFERRED_UID;
176
Christopher Tate7060b042014-06-09 19:50:00 -0700177 mRunningJob = job;
Matthew Williams673ef9e2015-04-03 21:52:02 -0700178 final boolean isDeadlineExpired =
Matthew Williamsd9db9d72015-05-14 17:47:33 -0700179 job.hasDeadlineConstraint() &&
180 (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800181 Uri[] triggeredUris = null;
182 if (job.changedUris != null) {
183 triggeredUris = new Uri[job.changedUris.size()];
184 job.changedUris.toArray(triggeredUris);
185 }
186 String[] triggeredAuthorities = null;
187 if (job.changedAuthorities != null) {
188 triggeredAuthorities = new String[job.changedAuthorities.size()];
189 job.changedAuthorities.toArray(triggeredAuthorities);
190 }
Dianne Hackborna47223f2017-03-30 13:49:13 -0700191 final JobInfo ji = job.getJob();
192 mParams = new JobParameters(this, job.getJobId(), ji.getExtras(),
193 ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
194 isDeadlineExpired, triggeredUris, triggeredAuthorities);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700195 mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
Matthew Williams9b9244b62014-05-14 11:06:04 -0700196
Matthew Williamseffacfa2014-06-05 20:56:40 -0700197 mVerb = VERB_BINDING;
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700198 scheduleOpTimeOutLocked();
Christopher Tate7060b042014-06-09 19:50:00 -0700199 final Intent intent = new Intent().setComponent(job.getServiceComponent());
Matthew Williamseffacfa2014-06-05 20:56:40 -0700200 boolean binding = mContext.bindServiceAsUser(intent, this,
201 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
Christopher Tate7060b042014-06-09 19:50:00 -0700202 new UserHandle(job.getUserId()));
Matthew Williamseffacfa2014-06-05 20:56:40 -0700203 if (!binding) {
204 if (DEBUG) {
Christopher Tate7060b042014-06-09 19:50:00 -0700205 Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
Matthew Williamseffacfa2014-06-05 20:56:40 -0700206 }
Christopher Tate7060b042014-06-09 19:50:00 -0700207 mRunningJob = null;
Matthew Williamseffacfa2014-06-05 20:56:40 -0700208 mParams = null;
209 mExecutionStartTimeElapsed = 0L;
riddle_hsu6a94aba2015-05-01 01:52:58 +0800210 mVerb = VERB_FINISHED;
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700211 removeOpTimeOutLocked();
Matthew Williamseffacfa2014-06-05 20:56:40 -0700212 return false;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700213 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700214 try {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800215 mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700216 } catch (RemoteException e) {
217 // Whatever.
218 }
Dianne Hackborn807de782016-04-07 17:54:41 -0700219 mJobPackageTracker.noteActive(job);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700220 mAvailable = false;
221 return true;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700222 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700223 }
224
Matthew Williamsee410da2014-07-25 11:30:40 -0700225 /**
226 * Used externally to query the running job. Will return null if there is no job running.
227 * Be careful when using this function, at any moment it's possible that the job returned may
228 * stop executing.
229 */
Christopher Tate7060b042014-06-09 19:50:00 -0700230 JobStatus getRunningJob() {
Christopher Tate3eddecc2016-05-25 10:44:24 -0700231 final JobStatus job;
Matthew Williamsee410da2014-07-25 11:30:40 -0700232 synchronized (mLock) {
Christopher Tate3eddecc2016-05-25 10:44:24 -0700233 job = mRunningJob;
Matthew Williamsee410da2014-07-25 11:30:40 -0700234 }
Christopher Tate3eddecc2016-05-25 10:44:24 -0700235 return job == null ? null : new JobStatus(job);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700236 }
237
Christopher Tateeafb5352016-10-04 16:34:48 -0700238 /**
239 * Internal non-cloning inspection of the currently running job, if any. The lock
240 * must be held when calling this *and* for the entire lifetime of using its returned
241 * JobStatus object!
242 */
243 JobStatus getRunningJobUnsafeLocked() {
244 return mRunningJob;
245 }
246
Christopher Tate7060b042014-06-09 19:50:00 -0700247 /** Called externally when a job that was scheduled for execution should be cancelled. */
Shreyas Basarge5db09082016-01-07 13:38:29 +0000248 void cancelExecutingJob(int reason) {
249 mCallbackHandler.obtainMessage(MSG_CANCEL, reason, 0 /* unused */).sendToTarget();
Matthew Williams9b9244b62014-05-14 11:06:04 -0700250 }
251
Shreyas Basarge5db09082016-01-07 13:38:29 +0000252 void preemptExecutingJob() {
253 Message m = mCallbackHandler.obtainMessage(MSG_CANCEL);
254 m.arg1 = JobParameters.REASON_PREEMPT;
255 m.sendToTarget();
256 }
257
258 int getPreferredUid() {
259 return mPreferredUid;
260 }
261
262 void clearPreferredUid() {
263 mPreferredUid = NO_PREFERRED_UID;
Matthew Williams6de79e22014-05-01 10:47:00 -0700264 }
265
Matthew Williamseffacfa2014-06-05 20:56:40 -0700266 long getExecutionStartTimeElapsed() {
267 return mExecutionStartTimeElapsed;
268 }
269
270 long getTimeoutElapsed() {
271 return mTimeoutElapsed;
272 }
273
Matthew Williams6de79e22014-05-01 10:47:00 -0700274 @Override
Christopher Tate7060b042014-06-09 19:50:00 -0700275 public void jobFinished(int jobId, boolean reschedule) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700276 if (!verifyCallingUid()) {
277 return;
278 }
Christopher Tate7060b042014-06-09 19:50:00 -0700279 mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
Matthew Williams691e93e2014-05-12 15:33:09 -0700280 .sendToTarget();
Matthew Williams6de79e22014-05-01 10:47:00 -0700281 }
282
283 @Override
Christopher Tate7060b042014-06-09 19:50:00 -0700284 public void acknowledgeStopMessage(int jobId, boolean reschedule) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700285 if (!verifyCallingUid()) {
286 return;
287 }
Christopher Tate7060b042014-06-09 19:50:00 -0700288 mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
Matthew Williams691e93e2014-05-12 15:33:09 -0700289 .sendToTarget();
Matthew Williams6de79e22014-05-01 10:47:00 -0700290 }
291
292 @Override
Christopher Tate7060b042014-06-09 19:50:00 -0700293 public void acknowledgeStartMessage(int jobId, boolean ongoing) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700294 if (!verifyCallingUid()) {
295 return;
296 }
Christopher Tate7060b042014-06-09 19:50:00 -0700297 mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget();
Matthew Williams691e93e2014-05-12 15:33:09 -0700298 }
Matthew Williams6de79e22014-05-01 10:47:00 -0700299
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700300 @Override
301 public JobWorkItem dequeueWork(int jobId) {
302 if (!verifyCallingUid()) {
303 throw new SecurityException("Bad calling uid: " + Binder.getCallingUid());
304 }
305 JobWorkItem work = null;
306 boolean stillWorking = false;
307 synchronized (mLock) {
308 if (mRunningJob != null) {
309 work = mRunningJob.dequeueWorkLocked();
310 stillWorking = mRunningJob.hasExecutingWorkLocked();
311 }
312 }
313 if (work == null && !stillWorking) {
314 jobFinished(jobId, false);
315 }
316 return work;
317 }
318
319 @Override
320 public boolean completeWork(int jobId, int workId) {
321 if (!verifyCallingUid()) {
322 throw new SecurityException("Bad calling uid: " + Binder.getCallingUid());
323 }
324 synchronized (mLock) {
325 if (mRunningJob != null) {
326 return mRunningJob.completeWorkLocked(workId);
327 }
328 return false;
329 }
330 }
331
Matthew Williams691e93e2014-05-12 15:33:09 -0700332 /**
Matthew Williams9b9244b62014-05-14 11:06:04 -0700333 * We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
Matthew Williams691e93e2014-05-12 15:33:09 -0700334 * we intend to send to the client - we stop sending work when the service is unbound so until
335 * then we keep the wakelock.
Matthew Williams9b9244b62014-05-14 11:06:04 -0700336 * @param name The concrete component name of the service that has been connected.
Matthew Williams691e93e2014-05-12 15:33:09 -0700337 * @param service The IBinder of the Service's communication channel,
338 */
Matthew Williams6de79e22014-05-01 10:47:00 -0700339 @Override
340 public void onServiceConnected(ComponentName name, IBinder service) {
Matthew Williams0cc76542015-10-16 21:04:51 -0700341 JobStatus runningJob;
342 synchronized (mLock) {
343 // This isn't strictly necessary b/c the JobServiceHandler is running on the main
344 // looper and at this point we can't get any binder callbacks from the client. Better
345 // safe than sorry.
346 runningJob = mRunningJob;
347 }
348 if (runningJob == null || !name.equals(runningJob.getServiceComponent())) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700349 mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
350 return;
351 }
Christopher Tate7060b042014-06-09 19:50:00 -0700352 this.service = IJobService.Stub.asInterface(service);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700353 final PowerManager pm =
354 (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
Christopher Tate4b17e982016-09-26 12:59:10 -0700355 PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
356 runningJob.getTag());
357 wl.setWorkSource(new WorkSource(runningJob.getSourceUid()));
358 wl.setReferenceCounted(false);
359 wl.acquire();
360 synchronized (mLock) {
361 // We use a new wakelock instance per job. In rare cases there is a race between
362 // teardown following job completion/cancellation and new job service spin-up
363 // such that if we simply assign mWakeLock to be the new instance, we orphan
364 // the currently-live lock instead of cleanly replacing it. Watch for this and
365 // explicitly fast-forward the release if we're in that situation.
366 if (mWakeLock != null) {
367 Slog.w(TAG, "Bound new job " + runningJob + " but live wakelock " + mWakeLock
368 + " tag=" + mWakeLock.getTag());
369 mWakeLock.release();
370 }
371 mWakeLock = wl;
372 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700373 mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
Matthew Williams6de79e22014-05-01 10:47:00 -0700374 }
375
Matthew Williamsee410da2014-07-25 11:30:40 -0700376 /** If the client service crashes we reschedule this job and clean up. */
Matthew Williams6de79e22014-05-01 10:47:00 -0700377 @Override
378 public void onServiceDisconnected(ComponentName name) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700379 mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
Matthew Williams691e93e2014-05-12 15:33:09 -0700380 }
381
382 /**
Matthew Williams9b9244b62014-05-14 11:06:04 -0700383 * This class is reused across different clients, and passes itself in as a callback. Check
384 * whether the client exercising the callback is the client we expect.
385 * @return True if the binder calling is coming from the client we expect.
Matthew Williams691e93e2014-05-12 15:33:09 -0700386 */
Matthew Williams9b9244b62014-05-14 11:06:04 -0700387 private boolean verifyCallingUid() {
Matthew Williams0cc76542015-10-16 21:04:51 -0700388 synchronized (mLock) {
389 if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
390 if (DEBUG) {
391 Slog.d(TAG, "Stale callback received, ignoring.");
392 }
393 return false;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700394 }
Matthew Williams0cc76542015-10-16 21:04:51 -0700395 return true;
Matthew Williams691e93e2014-05-12 15:33:09 -0700396 }
Matthew Williams691e93e2014-05-12 15:33:09 -0700397 }
398
399 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700400 * Handles the lifecycle of the JobService binding/callbacks, etc. The convention within this
Matthew Williams691e93e2014-05-12 15:33:09 -0700401 * class is to append 'H' to each function name that can only be called on this handler. This
402 * isn't strictly necessary because all of these functions are private, but helps clarity.
403 */
Christopher Tate7060b042014-06-09 19:50:00 -0700404 private class JobServiceHandler extends Handler {
405 JobServiceHandler(Looper looper) {
Matthew Williams691e93e2014-05-12 15:33:09 -0700406 super(looper);
407 }
408
409 @Override
410 public void handleMessage(Message message) {
411 switch (message.what) {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700412 case MSG_SERVICE_BOUND:
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700413 doServiceBound();
Matthew Williams691e93e2014-05-12 15:33:09 -0700414 break;
415 case MSG_CALLBACK:
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700416 doCallback(message.arg2);
Matthew Williams691e93e2014-05-12 15:33:09 -0700417 break;
418 case MSG_CANCEL:
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700419 doCancel(message.arg1);
Matthew Williams691e93e2014-05-12 15:33:09 -0700420 break;
421 case MSG_TIMEOUT:
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700422 synchronized (mLock) {
423 handleOpTimeoutH();
424 }
Matthew Williams691e93e2014-05-12 15:33:09 -0700425 break;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700426 case MSG_SHUTDOWN_EXECUTION:
Christopher Tate7060b042014-06-09 19:50:00 -0700427 closeAndCleanupJobH(true /* needsReschedule */);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700428 break;
Matthew Williams691e93e2014-05-12 15:33:09 -0700429 default:
Matthew Williamsee410da2014-07-25 11:30:40 -0700430 Slog.e(TAG, "Unrecognised message: " + message);
Matthew Williams691e93e2014-05-12 15:33:09 -0700431 }
432 }
433
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700434 void doServiceBound() {
435 synchronized (mLock) {
436 removeOpTimeOutLocked();
437 handleServiceBoundH();
438 }
439 }
440
441 void doCallback(int arg2) {
442 synchronized (mLock) {
443 if (DEBUG) {
444 Slog.d(TAG, "MSG_CALLBACK of : " + mRunningJob
445 + " v:" + VERB_STRINGS[mVerb]);
446 }
447 removeOpTimeOutLocked();
448
449 if (mVerb == VERB_STARTING) {
450 final boolean workOngoing = arg2 == 1;
451 handleStartedH(workOngoing);
452 } else if (mVerb == VERB_EXECUTING ||
453 mVerb == VERB_STOPPING) {
454 final boolean reschedule = arg2 == 1;
455 handleFinishedH(reschedule);
456 } else {
457 if (DEBUG) {
458 Slog.d(TAG, "Unrecognised callback: " + mRunningJob);
459 }
460 }
461 }
462 }
463
464 void doCancel(int arg1) {
465 synchronized (mLock) {
466 if (mVerb == VERB_FINISHED) {
467 if (DEBUG) {
468 Slog.d(TAG,
469 "Trying to process cancel for torn-down context, ignoring.");
470 }
471 return;
472 }
473 mParams.setStopReason(arg1);
474 if (arg1 == JobParameters.REASON_PREEMPT) {
475 mPreferredUid = mRunningJob != null ? mRunningJob.getUid() :
476 NO_PREFERRED_UID;
477 }
478 handleCancelH();
479 }
480
481 }
482
Christopher Tate7060b042014-06-09 19:50:00 -0700483 /** Start the job on the service. */
Matthew Williams9b9244b62014-05-14 11:06:04 -0700484 private void handleServiceBoundH() {
Matthew Williams75fc5252014-09-02 16:17:53 -0700485 if (DEBUG) {
486 Slog.d(TAG, "MSG_SERVICE_BOUND for " + mRunningJob.toShortString());
487 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700488 if (mVerb != VERB_BINDING) {
Christopher Tate7060b042014-06-09 19:50:00 -0700489 Slog.e(TAG, "Sending onStartJob for a job that isn't pending. "
Matthew Williams9b9244b62014-05-14 11:06:04 -0700490 + VERB_STRINGS[mVerb]);
Christopher Tate7060b042014-06-09 19:50:00 -0700491 closeAndCleanupJobH(false /* reschedule */);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700492 return;
493 }
494 if (mCancelled.get()) {
495 if (DEBUG) {
Christopher Tate7060b042014-06-09 19:50:00 -0700496 Slog.d(TAG, "Job cancelled while waiting for bind to complete. "
497 + mRunningJob);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700498 }
Christopher Tate7060b042014-06-09 19:50:00 -0700499 closeAndCleanupJobH(true /* reschedule */);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700500 return;
501 }
502 try {
503 mVerb = VERB_STARTING;
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700504 scheduleOpTimeOutLocked();
Christopher Tate7060b042014-06-09 19:50:00 -0700505 service.startJob(mParams);
Christopher Tatea8e8e4a2017-01-04 11:18:22 -0800506 } catch (Exception e) {
507 // We catch 'Exception' because client-app malice or bugs might induce a wide
508 // range of possible exception-throw outcomes from startJob() and its handling
509 // of the client's ParcelableBundle extras.
Matthew Williamsee410da2014-07-25 11:30:40 -0700510 Slog.e(TAG, "Error sending onStart message to '" +
Christopher Tate7060b042014-06-09 19:50:00 -0700511 mRunningJob.getServiceComponent().getShortClassName() + "' ", e);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700512 }
513 }
514
Matthew Williams691e93e2014-05-12 15:33:09 -0700515 /**
516 * State behaviours.
Christopher Tate7060b042014-06-09 19:50:00 -0700517 * VERB_STARTING -> Successful start, change job to VERB_EXECUTING and post timeout.
Matthew Williams691e93e2014-05-12 15:33:09 -0700518 * _PENDING -> Error
519 * _EXECUTING -> Error
520 * _STOPPING -> Error
521 */
Matthew Williams9b9244b62014-05-14 11:06:04 -0700522 private void handleStartedH(boolean workOngoing) {
523 switch (mVerb) {
Matthew Williams691e93e2014-05-12 15:33:09 -0700524 case VERB_STARTING:
Matthew Williams9b9244b62014-05-14 11:06:04 -0700525 mVerb = VERB_EXECUTING;
Matthew Williams691e93e2014-05-12 15:33:09 -0700526 if (!workOngoing) {
Christopher Tate7060b042014-06-09 19:50:00 -0700527 // Job is finished already so fast-forward to handleFinished.
Matthew Williams9b9244b62014-05-14 11:06:04 -0700528 handleFinishedH(false);
Matthew Williams691e93e2014-05-12 15:33:09 -0700529 return;
Matthew Williams691e93e2014-05-12 15:33:09 -0700530 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700531 if (mCancelled.get()) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700532 if (DEBUG) {
533 Slog.d(TAG, "Job cancelled while waiting for onStartJob to complete.");
534 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700535 // Cancelled *while* waiting for acknowledgeStartMessage from client.
536 handleCancelH();
537 return;
538 }
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700539 scheduleOpTimeOutLocked();
Matthew Williams691e93e2014-05-12 15:33:09 -0700540 break;
541 default:
Matthew Williamsee410da2014-07-25 11:30:40 -0700542 Slog.e(TAG, "Handling started job but job wasn't starting! Was "
Matthew Williams9b9244b62014-05-14 11:06:04 -0700543 + VERB_STRINGS[mVerb] + ".");
Matthew Williams691e93e2014-05-12 15:33:09 -0700544 return;
545 }
546 }
547
548 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700549 * VERB_EXECUTING -> Client called jobFinished(), clean up and notify done.
Matthew Williams691e93e2014-05-12 15:33:09 -0700550 * _STOPPING -> Successful finish, clean up and notify done.
551 * _STARTING -> Error
552 * _PENDING -> Error
553 */
Matthew Williams9b9244b62014-05-14 11:06:04 -0700554 private void handleFinishedH(boolean reschedule) {
555 switch (mVerb) {
Matthew Williams691e93e2014-05-12 15:33:09 -0700556 case VERB_EXECUTING:
557 case VERB_STOPPING:
Christopher Tate7060b042014-06-09 19:50:00 -0700558 closeAndCleanupJobH(reschedule);
Matthew Williams691e93e2014-05-12 15:33:09 -0700559 break;
560 default:
Christopher Tate7060b042014-06-09 19:50:00 -0700561 Slog.e(TAG, "Got an execution complete message for a job that wasn't being" +
Matthew Williams9b9244b62014-05-14 11:06:04 -0700562 "executed. Was " + VERB_STRINGS[mVerb] + ".");
Matthew Williams691e93e2014-05-12 15:33:09 -0700563 }
564 }
565
566 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700567 * A job can be in various states when a cancel request comes in:
Matthew Williams9b9244b62014-05-14 11:06:04 -0700568 * VERB_BINDING -> Cancelled before bind completed. Mark as cancelled and wait for
569 * {@link #onServiceConnected(android.content.ComponentName, android.os.IBinder)}
570 * _STARTING -> Mark as cancelled and wait for
Christopher Tate7060b042014-06-09 19:50:00 -0700571 * {@link JobServiceContext#acknowledgeStartMessage(int, boolean)}
Matthew Williamsee410da2014-07-25 11:30:40 -0700572 * _EXECUTING -> call {@link #sendStopMessageH}}, but only if there are no callbacks
573 * in the message queue.
Matthew Williams691e93e2014-05-12 15:33:09 -0700574 * _ENDING -> No point in doing anything here, so we ignore.
575 */
Matthew Williams9b9244b62014-05-14 11:06:04 -0700576 private void handleCancelH() {
Matthew Williamsee410da2014-07-25 11:30:40 -0700577 if (JobSchedulerService.DEBUG) {
578 Slog.d(TAG, "Handling cancel for: " + mRunningJob.getJobId() + " "
579 + VERB_STRINGS[mVerb]);
580 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700581 switch (mVerb) {
582 case VERB_BINDING:
Matthew Williams691e93e2014-05-12 15:33:09 -0700583 case VERB_STARTING:
Matthew Williams9b9244b62014-05-14 11:06:04 -0700584 mCancelled.set(true);
Matthew Williams691e93e2014-05-12 15:33:09 -0700585 break;
586 case VERB_EXECUTING:
Matthew Williamsee410da2014-07-25 11:30:40 -0700587 if (hasMessages(MSG_CALLBACK)) {
588 // If the client has called jobFinished, ignore this cancel.
589 return;
590 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700591 sendStopMessageH();
Matthew Williams691e93e2014-05-12 15:33:09 -0700592 break;
593 case VERB_STOPPING:
594 // Nada.
595 break;
596 default:
Christopher Tate7060b042014-06-09 19:50:00 -0700597 Slog.e(TAG, "Cancelling a job without a valid verb: " + mVerb);
Matthew Williams691e93e2014-05-12 15:33:09 -0700598 break;
599 }
600 }
601
Matthew Williams9b9244b62014-05-14 11:06:04 -0700602 /** Process MSG_TIMEOUT here. */
603 private void handleOpTimeoutH() {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700604 switch (mVerb) {
Matthew Williams75fc5252014-09-02 16:17:53 -0700605 case VERB_BINDING:
606 Slog.e(TAG, "Time-out while trying to bind " + mRunningJob.toShortString() +
607 ", dropping.");
608 closeAndCleanupJobH(false /* needsReschedule */);
609 break;
Matthew Williams691e93e2014-05-12 15:33:09 -0700610 case VERB_STARTING:
611 // Client unresponsive - wedged or failed to respond in time. We don't really
Christopher Tate7060b042014-06-09 19:50:00 -0700612 // know what happened so let's log it and notify the JobScheduler
Matthew Williams691e93e2014-05-12 15:33:09 -0700613 // FINISHED/NO-RETRY.
Matthew Williamsee410da2014-07-25 11:30:40 -0700614 Slog.e(TAG, "No response from client for onStartJob '" +
Matthew Williams75fc5252014-09-02 16:17:53 -0700615 mRunningJob.toShortString());
Christopher Tate7060b042014-06-09 19:50:00 -0700616 closeAndCleanupJobH(false /* needsReschedule */);
Matthew Williams691e93e2014-05-12 15:33:09 -0700617 break;
618 case VERB_STOPPING:
Christopher Tate7060b042014-06-09 19:50:00 -0700619 // At least we got somewhere, so fail but ask the JobScheduler to reschedule.
Matthew Williamsee410da2014-07-25 11:30:40 -0700620 Slog.e(TAG, "No response from client for onStopJob, '" +
Matthew Williams75fc5252014-09-02 16:17:53 -0700621 mRunningJob.toShortString());
Christopher Tate7060b042014-06-09 19:50:00 -0700622 closeAndCleanupJobH(true /* needsReschedule */);
Matthew Williams691e93e2014-05-12 15:33:09 -0700623 break;
624 case VERB_EXECUTING:
625 // Not an error - client ran out of time.
Matthew Williamsee410da2014-07-25 11:30:40 -0700626 Slog.i(TAG, "Client timed out while executing (no jobFinished received)." +
Matthew Williams75fc5252014-09-02 16:17:53 -0700627 " sending onStop. " + mRunningJob.toShortString());
Shreyas Basarge7d089052016-02-22 22:29:39 +0000628 mParams.setStopReason(JobParameters.REASON_TIMEOUT);
Matthew Williams9b9244b62014-05-14 11:06:04 -0700629 sendStopMessageH();
Matthew Williams691e93e2014-05-12 15:33:09 -0700630 break;
631 default:
Matthew Williams75fc5252014-09-02 16:17:53 -0700632 Slog.e(TAG, "Handling timeout for an invalid job state: " +
633 mRunningJob.toShortString() + ", dropping.");
634 closeAndCleanupJobH(false /* needsReschedule */);
Matthew Williams691e93e2014-05-12 15:33:09 -0700635 }
636 }
637
638 /**
Matthew Williams9b9244b62014-05-14 11:06:04 -0700639 * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
640 * VERB_STOPPING.
Matthew Williams691e93e2014-05-12 15:33:09 -0700641 */
Matthew Williams9b9244b62014-05-14 11:06:04 -0700642 private void sendStopMessageH() {
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700643 removeOpTimeOutLocked();
Matthew Williams9b9244b62014-05-14 11:06:04 -0700644 if (mVerb != VERB_EXECUTING) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700645 Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
Christopher Tate7060b042014-06-09 19:50:00 -0700646 closeAndCleanupJobH(false /* reschedule */);
Matthew Williams691e93e2014-05-12 15:33:09 -0700647 return;
648 }
649 try {
Matthew Williams9b9244b62014-05-14 11:06:04 -0700650 mVerb = VERB_STOPPING;
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700651 scheduleOpTimeOutLocked();
Christopher Tate7060b042014-06-09 19:50:00 -0700652 service.stopJob(mParams);
Matthew Williams691e93e2014-05-12 15:33:09 -0700653 } catch (RemoteException e) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700654 Slog.e(TAG, "Error sending onStopJob to client.", e);
Christopher Tateb2087f82017-03-29 17:42:39 -0700655 // The job's host app apparently crashed during the job, so we should reschedule.
656 closeAndCleanupJobH(true /* reschedule */);
Matthew Williams691e93e2014-05-12 15:33:09 -0700657 }
658 }
659
660 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700661 * The provided job has finished, either by calling
662 * {@link android.app.job.JobService#jobFinished(android.app.job.JobParameters, boolean)}
Matthew Williams691e93e2014-05-12 15:33:09 -0700663 * or from acknowledging the stop message we sent. Either way, we're done tracking it and
664 * we want to clean up internally.
665 */
Christopher Tate7060b042014-06-09 19:50:00 -0700666 private void closeAndCleanupJobH(boolean reschedule) {
riddle_hsu6a94aba2015-05-01 01:52:58 +0800667 final JobStatus completedJob;
Matthew Williamseffacfa2014-06-05 20:56:40 -0700668 synchronized (mLock) {
riddle_hsu6a94aba2015-05-01 01:52:58 +0800669 if (mVerb == VERB_FINISHED) {
670 return;
671 }
672 completedJob = mRunningJob;
Dianne Hackborn807de782016-04-07 17:54:41 -0700673 mJobPackageTracker.noteInactive(completedJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700674 try {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800675 mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
676 mRunningJob.getSourceUid());
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700677 } catch (RemoteException e) {
678 // Whatever.
679 }
Matthew Williams75fc5252014-09-02 16:17:53 -0700680 if (mWakeLock != null) {
681 mWakeLock.release();
682 }
Christopher Tate7060b042014-06-09 19:50:00 -0700683 mContext.unbindService(JobServiceContext.this);
Matthew Williamseffacfa2014-06-05 20:56:40 -0700684 mWakeLock = null;
Christopher Tate7060b042014-06-09 19:50:00 -0700685 mRunningJob = null;
Matthew Williamseffacfa2014-06-05 20:56:40 -0700686 mParams = null;
riddle_hsu6a94aba2015-05-01 01:52:58 +0800687 mVerb = VERB_FINISHED;
Matthew Williamseffacfa2014-06-05 20:56:40 -0700688 mCancelled.set(false);
689 service = null;
Matthew Williams9b9244b62014-05-14 11:06:04 -0700690 mAvailable = true;
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700691 removeOpTimeOutLocked();
692 removeMessages(MSG_CALLBACK);
693 removeMessages(MSG_SERVICE_BOUND);
694 removeMessages(MSG_CANCEL);
695 removeMessages(MSG_SHUTDOWN_EXECUTION);
696 mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
Matthew Williams691e93e2014-05-12 15:33:09 -0700697 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700698 }
Matthew Williams75fc5252014-09-02 16:17:53 -0700699 }
Matthew Williams9b9244b62014-05-14 11:06:04 -0700700
Matthew Williams75fc5252014-09-02 16:17:53 -0700701 /**
702 * Called when sending a message to the client, over whose execution we have no control. If
703 * we haven't received a response in a certain amount of time, we want to give up and carry
704 * on with life.
705 */
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700706 private void scheduleOpTimeOutLocked() {
707 removeOpTimeOutLocked();
Matthew Williams9b9244b62014-05-14 11:06:04 -0700708
Christopher Tate2022bad2017-02-02 18:07:42 -0800709 final long timeoutMillis;
710 switch (mVerb) {
711 case VERB_EXECUTING:
712 timeoutMillis = EXECUTING_TIMESLICE_MILLIS;
713 break;
714
715 case VERB_BINDING:
716 timeoutMillis = OP_BIND_TIMEOUT_MILLIS;
717 break;
718
719 default:
720 timeoutMillis = OP_TIMEOUT_MILLIS;
721 break;
722 }
Matthew Williams75fc5252014-09-02 16:17:53 -0700723 if (DEBUG) {
724 Slog.d(TAG, "Scheduling time out for '" +
725 mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
726 mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
Matthew Williams691e93e2014-05-12 15:33:09 -0700727 }
Matthew Williams75fc5252014-09-02 16:17:53 -0700728 Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
729 mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
730 mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
Matthew Williams6de79e22014-05-01 10:47:00 -0700731 }
Matthew Williams7ac52d52014-09-12 14:40:18 -0700732
733
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700734 private void removeOpTimeOutLocked() {
Matthew Williams7ac52d52014-09-12 14:40:18 -0700735 mCallbackHandler.removeMessages(MSG_TIMEOUT);
736 }
Matthew Williams6de79e22014-05-01 10:47:00 -0700737}