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