JobServiceContextpublic class JobServiceContext extends IJobCallback.Stub implements android.content.ServiceConnectionHandles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
class.
There are two important interactions into this class from the
{@link com.android.server.job.JobSchedulerService}. To execute a job and to cancel a job.
- Execution of a new job is handled by the {@link #mAvailable}. This bit is flipped once when a
job lands, and again when it is complete.
- Cancelling is trickier, because there are also interactions from the client. It's possible
the {@link com.android.server.job.JobServiceContext.JobServiceHandler} tries to process a
{@link #MSG_CANCEL} after the client has already finished. This is handled by having
{@link com.android.server.job.JobServiceContext.JobServiceHandler#handleCancelH} check whether
the context is still valid.
To mitigate this, tearing down the context removes all messages from the handler, including any
tardy {@link #MSG_CANCEL}s. Additionally, we avoid sending duplicate onStopJob()
calls to the client after they've specified jobFinished(). |
Fields Summary |
---|
private static final boolean | DEBUG | private static final String | TAG | private static final int | defaultMaxActiveJobsPerServiceDefine the maximum # of jobs allowed to run on a service at once. | private static final long | EXECUTING_TIMESLICE_MILLISAmount of time a job is allowed to execute for before being considered timed-out. | private static final long | OP_TIMEOUT_MILLISAmount of time the JobScheduler will wait for a response from an app for a message. | private static final String[] | VERB_STRINGS | static final int | VERB_BINDING | static final int | VERB_STARTING | static final int | VERB_EXECUTING | static final int | VERB_STOPPING | private static final int | MSG_TIMEOUTSystem timed out waiting for a response. | private static final int | MSG_CALLBACKReceived a callback from client. | private static final int | MSG_SERVICE_BOUNDRun through list and start any ready jobs. | private static final int | MSG_CANCELCancel a job. | private static final int | MSG_SHUTDOWN_EXECUTIONShutdown the job. Used when the client crashes and we can't die gracefully. | private final android.os.Handler | mCallbackHandler | private final JobCompletedListener | mCompletedListenerMake callbacks to {@link JobSchedulerService} to inform on job completion status. | private final android.content.Context | mContextUsed for service binding, etc. | private final com.android.internal.app.IBatteryStats | mBatteryStats | private PowerManager.WakeLock | mWakeLock | private android.app.job.JobParameters | mParams | int | mVerb | private AtomicBoolean | mCancelled | private com.android.server.job.controllers.JobStatus | mRunningJobAll the information maintained about the job currently being executed. | android.app.job.IJobService | serviceBinder to the client service. | private final Object | mLock | private boolean | mAvailableWhether this context is free. This is set to false at the start of execution, and reset to
true when execution is complete. | private long | mExecutionStartTimeElapsedTrack start time. | private long | mTimeoutElapsedTrack when job will timeout. |
Methods Summary |
---|
public void | acknowledgeStartMessage(int jobId, boolean ongoing)
if (!verifyCallingUid()) {
return;
}
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget();
| public void | acknowledgeStopMessage(int jobId, boolean reschedule)
if (!verifyCallingUid()) {
return;
}
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
.sendToTarget();
| void | cancelExecutingJob()Called externally when a job that was scheduled for execution should be cancelled.
mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
| boolean | executeRunnableJob(com.android.server.job.controllers.JobStatus job)Give a job to this context for execution. Callers must first check {@link #isAvailable()}
to make sure this is a valid context.
synchronized (mLock) {
if (!mAvailable) {
Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
return false;
}
mRunningJob = job;
mParams = new JobParameters(this, job.getJobId(), job.getExtras(),
!job.isConstraintsSatisfied());
mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
mVerb = VERB_BINDING;
scheduleOpTimeOut();
final Intent intent = new Intent().setComponent(job.getServiceComponent());
boolean binding = mContext.bindServiceAsUser(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND,
new UserHandle(job.getUserId()));
if (!binding) {
if (DEBUG) {
Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
}
mRunningJob = null;
mParams = null;
mExecutionStartTimeElapsed = 0L;
removeOpTimeOut();
return false;
}
try {
mBatteryStats.noteJobStart(job.getName(), job.getUid());
} catch (RemoteException e) {
// Whatever.
}
mAvailable = false;
return true;
}
| long | getExecutionStartTimeElapsed()
return mExecutionStartTimeElapsed;
| com.android.server.job.controllers.JobStatus | getRunningJob()Used externally to query the running job. Will return null if there is no job running.
Be careful when using this function, at any moment it's possible that the job returned may
stop executing.
synchronized (mLock) {
return mRunningJob;
}
| long | getTimeoutElapsed()
return mTimeoutElapsed;
| boolean | isAvailable()
synchronized (mLock) {
return mAvailable;
}
| public void | jobFinished(int jobId, boolean reschedule)
if (!verifyCallingUid()) {
return;
}
mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
.sendToTarget();
| public void | onServiceConnected(android.content.ComponentName name, android.os.IBinder service)We acquire/release a wakelock on onServiceConnected/unbindService. This mirrors the work
we intend to send to the client - we stop sending work when the service is unbound so until
then we keep the wakelock.
if (!name.equals(mRunningJob.getServiceComponent())) {
mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
return;
}
this.service = IJobService.Stub.asInterface(service);
final PowerManager pm =
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mRunningJob.getTag());
mWakeLock.setWorkSource(new WorkSource(mRunningJob.getUid()));
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
mCallbackHandler.obtainMessage(MSG_SERVICE_BOUND).sendToTarget();
| public void | onServiceDisconnected(android.content.ComponentName name)If the client service crashes we reschedule this job and clean up.
mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
| private void | removeOpTimeOut()
mCallbackHandler.removeMessages(MSG_TIMEOUT);
| private void | scheduleOpTimeOut()Called when sending a message to the client, over whose execution we have no control. If
we haven't received a response in a certain amount of time, we want to give up and carry
on with life.
removeOpTimeOut();
final long timeoutMillis = (mVerb == VERB_EXECUTING) ?
EXECUTING_TIMESLICE_MILLIS : OP_TIMEOUT_MILLIS;
if (DEBUG) {
Slog.d(TAG, "Scheduling time out for '" +
mRunningJob.getServiceComponent().getShortClassName() + "' jId: " +
mParams.getJobId() + ", in " + (timeoutMillis / 1000) + " s");
}
Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT);
mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
| private boolean | verifyCallingUid()This class is reused across different clients, and passes itself in as a callback. Check
whether the client exercising the callback is the client we expect.
if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
if (DEBUG) {
Slog.d(TAG, "Stale callback received, ignoring.");
}
return false;
}
return true;
|
|