FileDocCategorySizeDatePackage
JobServiceContext.javaAPI DocAndroid 5.1 API23508Thu Mar 12 22:22:42 GMT 2015com.android.server.job

JobServiceContext

public class JobServiceContext extends IJobCallback.Stub implements android.content.ServiceConnection
Handles 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
defaultMaxActiveJobsPerService
Define the maximum # of jobs allowed to run on a service at once.
private static final long
EXECUTING_TIMESLICE_MILLIS
Amount of time a job is allowed to execute for before being considered timed-out.
private static final long
OP_TIMEOUT_MILLIS
Amount 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_TIMEOUT
System timed out waiting for a response.
private static final int
MSG_CALLBACK
Received a callback from client.
private static final int
MSG_SERVICE_BOUND
Run through list and start any ready jobs.
private static final int
MSG_CANCEL
Cancel a job.
private static final int
MSG_SHUTDOWN_EXECUTION
Shutdown the job. Used when the client crashes and we can't die gracefully.
private final android.os.Handler
mCallbackHandler
private final JobCompletedListener
mCompletedListener
Make callbacks to {@link JobSchedulerService} to inform on job completion status.
private final android.content.Context
mContext
Used 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
mRunningJob
All the information maintained about the job currently being executed.
android.app.job.IJobService
service
Binder to the client service.
private final Object
mLock
private boolean
mAvailable
Whether this context is free. This is set to false at the start of execution, and reset to true when execution is complete.
private long
mExecutionStartTimeElapsed
Track start time.
private long
mTimeoutElapsed
Track when job will timeout.
Constructors Summary
JobServiceContext(JobSchedulerService service, com.android.internal.app.IBatteryStats batteryStats, android.os.Looper looper)

        this(service.getContext(), batteryStats, service, looper);
    
JobServiceContext(android.content.Context context, com.android.internal.app.IBatteryStats batteryStats, JobCompletedListener completedListener, android.os.Looper looper)

        mContext = context;
        mBatteryStats = batteryStats;
        mCallbackHandler = new JobServiceHandler(looper);
        mCompletedListener = completedListener;
        mAvailable = true;
    
Methods Summary
public voidacknowledgeStartMessage(int jobId, boolean ongoing)

        if (!verifyCallingUid()) {
            return;
        }
        mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, ongoing ? 1 : 0).sendToTarget();
    
public voidacknowledgeStopMessage(int jobId, boolean reschedule)

        if (!verifyCallingUid()) {
            return;
        }
        mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
                .sendToTarget();
    
voidcancelExecutingJob()
Called externally when a job that was scheduled for execution should be cancelled.

        mCallbackHandler.obtainMessage(MSG_CANCEL).sendToTarget();
    
booleanexecuteRunnableJob(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.

param
job The status of the job that we are going to run.
return
True if the job is valid and is running. False if the job cannot be executed.

        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;
        }
    
longgetExecutionStartTimeElapsed()

        return mExecutionStartTimeElapsed;
    
com.android.server.job.controllers.JobStatusgetRunningJob()
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;
        }
    
longgetTimeoutElapsed()

        return mTimeoutElapsed;
    
booleanisAvailable()

return
Whether this context is available to handle incoming work.

        synchronized (mLock) {
            return mAvailable;
        }
    
public voidjobFinished(int jobId, boolean reschedule)

        if (!verifyCallingUid()) {
            return;
        }
        mCallbackHandler.obtainMessage(MSG_CALLBACK, jobId, reschedule ? 1 : 0)
                .sendToTarget();
    
public voidonServiceConnected(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.

param
name The concrete component name of the service that has been connected.
param
service The IBinder of the Service's communication channel,

        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 voidonServiceDisconnected(android.content.ComponentName name)
If the client service crashes we reschedule this job and clean up.

        mCallbackHandler.obtainMessage(MSG_SHUTDOWN_EXECUTION).sendToTarget();
    
private voidremoveOpTimeOut()

        mCallbackHandler.removeMessages(MSG_TIMEOUT);
    
private voidscheduleOpTimeOut()
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 booleanverifyCallingUid()
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.

return
True if the binder calling is coming from the client we expect.

        if (mRunningJob == null || Binder.getCallingUid() != mRunningJob.getUid()) {
            if (DEBUG) {
                Slog.d(TAG, "Stale callback received, ignoring.");
            }
            return false;
        }
        return true;