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

JobSchedulerService

public class JobSchedulerService extends com.android.server.SystemService implements JobCompletedListener, StateChangedListener
Responsible for taking jobs representing work to be performed by a client app, and determining based on the criteria specified when that job should be run against the client application's endpoint. Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing about constraints, or the state of active jobs. It receives callbacks from the various controllers and completed jobs and operates accordingly. Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object. Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
hide

Fields Summary
static final boolean
DEBUG
private static final int
MAX_JOB_CONTEXTS_COUNT
The number of concurrent jobs we run at one time.
static final String
TAG
final JobStore
mJobs
Master list of jobs.
static final int
MSG_JOB_EXPIRED
static final int
MSG_CHECK_JOB
static final int
MIN_IDLE_COUNT
Minimum # of idle jobs that must be ready in order to force the JMS to schedule things early.
static final int
MIN_CHARGING_COUNT
Minimum # of charging jobs that must be ready in order to force the JMS to schedule things early.
static final int
MIN_CONNECTIVITY_COUNT
Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule things early.
static final int
MIN_READY_JOBS_COUNT
Minimum # of jobs (with no particular constraints) for which the JMS will be happy running some work early. This is correlated with the amount of batching we'll be able to do.
final List
mActiveServices
Track Services that have currently active or pending jobs. The index is provided by {@link JobStatus#getServiceToken()}
List
mControllers
List of controllers that will notify this service of updates to jobs.
final ArrayList
mPendingJobs
Queue of pending jobs. The JobServiceContext class will receive jobs from this list when ready to execute them.
final ArrayList
mStartedUsers
final JobHandler
mHandler
final JobSchedulerStub
mJobSchedulerStub
com.android.internal.app.IBatteryStats
mBatteryStats
boolean
mReadyToRock
Set to true once we are allowed to run third party apps.
private final android.content.BroadcastReceiver
mBroadcastReceiver
Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we still clean up. On reinstall the package will have a new uid.
Constructors Summary
public JobSchedulerService(android.content.Context context)
Initializes the system service.

Subclasses must define a single argument constructor that accepts the context and passes it to super.

param
context The system server context.

        super(context);
        // Create the controllers.
        mControllers = new ArrayList<StateController>();
        mControllers.add(ConnectivityController.get(this));
        mControllers.add(TimeController.get(this));
        mControllers.add(IdleController.get(this));
        mControllers.add(BatteryController.get(this));

        mHandler = new JobHandler(context.getMainLooper());
        mJobSchedulerStub = new JobSchedulerStub();
        mJobs = JobStore.initAndGet(this);
    
Methods Summary
public voidcancelJob(int uid, int jobId)
Entry point from client to cancel the job corresponding to the jobId provided. This will remove the job from the master list, and cancel the job if it was staged for execution or being executed.

param
uid Uid of the calling client.
param
jobId Id of the job, provided at schedule-time.

        JobStatus toCancel;
        synchronized (mJobs) {
            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
        }
        if (toCancel != null) {
            cancelJobImpl(toCancel);
        }
    
private voidcancelJobImpl(com.android.server.job.controllers.JobStatus cancelled)

        if (DEBUG) {
            Slog.d(TAG, "Cancelling: " + cancelled);
        }
        stopTrackingJob(cancelled);
        synchronized (mJobs) {
            // Remove from pending queue.
            mPendingJobs.remove(cancelled);
            // Cancel if running.
            stopJobOnServiceContextLocked(cancelled);
        }
    
public voidcancelJobsForUid(int uid)
Entry point from client to cancel all jobs originating from their uid. This will remove the job from the master list, and cancel the job if it was staged for execution or being executed.

param
uid Uid to check against for removal of a job.

        List<JobStatus> jobsForUid;
        synchronized (mJobs) {
            jobsForUid = mJobs.getJobsByUid(uid);
        }
        for (int i=0; i<jobsForUid.size(); i++) {
            JobStatus toRemove = jobsForUid.get(i);
            cancelJobImpl(toRemove);
        }
    
private voidcancelJobsForUser(int userHandle)

        List<JobStatus> jobsForUser;
        synchronized (mJobs) {
            jobsForUser = mJobs.getJobsByUser(userHandle);
        }
        for (int i=0; i<jobsForUser.size(); i++) {
            JobStatus toRemove = jobsForUser.get(i);
            cancelJobImpl(toRemove);
        }
    
voiddumpInternal(java.io.PrintWriter pw)

        final long now = SystemClock.elapsedRealtime();
        synchronized (mJobs) {
            pw.print("Started users: ");
            for (int i=0; i<mStartedUsers.size(); i++) {
                pw.print("u" + mStartedUsers.get(i) + " ");
            }
            pw.println();
            pw.println("Registered jobs:");
            if (mJobs.size() > 0) {
                ArraySet<JobStatus> jobs = mJobs.getJobs();
                for (int i=0; i<jobs.size(); i++) {
                    JobStatus job = jobs.valueAt(i);
                    job.dump(pw, "  ");
                }
            } else {
                pw.println("  None.");
            }
            for (int i=0; i<mControllers.size(); i++) {
                pw.println();
                mControllers.get(i).dumpControllerState(pw);
            }
            pw.println();
            pw.println("Pending:");
            for (int i=0; i<mPendingJobs.size(); i++) {
                pw.println(mPendingJobs.get(i).hashCode());
            }
            pw.println();
            pw.println("Active jobs:");
            for (int i=0; i<mActiveServices.size(); i++) {
                JobServiceContext jsc = mActiveServices.get(i);
                if (jsc.isAvailable()) {
                    continue;
                } else {
                    final long timeout = jsc.getTimeoutElapsed();
                    pw.print("Running for: ");
                    pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
                    pw.print("s timeout=");
                    pw.print(timeout);
                    pw.print(" fromnow=");
                    pw.println(timeout-now);
                    jsc.getRunningJob().dump(pw, "  ");
                }
            }
            pw.println();
            pw.print("mReadyToRock="); pw.println(mReadyToRock);
        }
        pw.println();
    
public java.util.ListgetPendingJobs(int uid)

        ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
        synchronized (mJobs) {
            ArraySet<JobStatus> jobs = mJobs.getJobs();
            for (int i=0; i<jobs.size(); i++) {
                JobStatus job = jobs.valueAt(i);
                if (job.getUid() == uid) {
                    outList.add(job.getJob());
                }
            }
        }
        return outList;
    
private com.android.server.job.controllers.JobStatusgetRescheduleJobForFailure(com.android.server.job.controllers.JobStatus failureToReschedule)
A job is rescheduled with exponential back-off if the client requests this from their execution logic. A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the timeliness of the reschedule. For an idle-mode job, no deadline is given.

param
failureToReschedule Provided job status that we will reschedule.
return
A newly instantiated JobStatus with the same constraints as the last job except with adjusted timing constraints.

        final long elapsedNowMillis = SystemClock.elapsedRealtime();
        final JobInfo job = failureToReschedule.getJob();

        final long initialBackoffMillis = job.getInitialBackoffMillis();
        final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
        long delayMillis;

        switch (job.getBackoffPolicy()) {
            case JobInfo.BACKOFF_POLICY_LINEAR:
                delayMillis = initialBackoffMillis * backoffAttempts;
                break;
            default:
                if (DEBUG) {
                    Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
                }
            case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
                delayMillis =
                        (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
                break;
        }
        delayMillis =
                Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
        return new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
                JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
    
private com.android.server.job.controllers.JobStatusgetRescheduleJobForPeriodic(com.android.server.job.controllers.JobStatus periodicToReschedule)
Called after a periodic has executed so we can to re-add it. We take the last execution time of the job to be the time of completion (i.e. the time at which this function is called). This could be inaccurate b/c the job can run for as long as {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead to underscheduling at least, rather than if we had taken the last execution time to be the start of the execution.

return
A new job representing the execution criteria for this instantiation of the recurring job.

        final long elapsedNow = SystemClock.elapsedRealtime();
        // Compute how much of the period is remaining.
        long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
        long newEarliestRunTimeElapsed = elapsedNow + runEarly;
        long period = periodicToReschedule.getJob().getIntervalMillis();
        long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;

        if (DEBUG) {
            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
                    newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
        }
        return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
                newLatestRuntimeElapsed, 0 /* backoffAttempt */);
    
private booleanisCurrentlyActiveLocked(com.android.server.job.controllers.JobStatus job)

param
job JobStatus we are querying against.
return
Whether or not the job represented by the status object is currently being run or is pending.

        for (int i=0; i<mActiveServices.size(); i++) {
            JobServiceContext serviceContext = mActiveServices.get(i);
            final JobStatus running = serviceContext.getRunningJob();
            if (running != null && running.matches(job.getUid(), job.getJobId())) {
                return true;
            }
        }
        return false;
    
public voidonBootPhase(int phase)

        if (PHASE_SYSTEM_SERVICES_READY == phase) {
            // Register br for package removals and user removals.
            final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
            filter.addDataScheme("package");
            getContext().registerReceiverAsUser(
                    mBroadcastReceiver, UserHandle.ALL, filter, null, null);
            final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
            getContext().registerReceiverAsUser(
                    mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
        } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
            synchronized (mJobs) {
                // Let's go!
                mReadyToRock = true;
                mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                        BatteryStats.SERVICE_NAME));
                // Create the "runners".
                for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
                    mActiveServices.add(
                            new JobServiceContext(this, mBatteryStats,
                                    getContext().getMainLooper()));
                }
                // Attach jobs to their controllers.
                ArraySet<JobStatus> jobs = mJobs.getJobs();
                for (int i=0; i<jobs.size(); i++) {
                    JobStatus job = jobs.valueAt(i);
                    for (int controller=0; controller<mControllers.size(); controller++) {
                        mControllers.get(controller).maybeStartTrackingJob(job);
                    }
                }
                // GO GO GO!
                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
            }
        }
    
public voidonControllerStateChanged()
Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that some controller's state has changed, so as to run through the list of jobs and start/stop any that are eligible.

        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    
public voidonJobCompleted(com.android.server.job.controllers.JobStatus jobStatus, boolean needsReschedule)
A job just finished executing. We fetch the {@link com.android.server.job.controllers.JobStatus} from the store and depending on whether we want to reschedule we readd it to the controllers.

param
jobStatus Completed job.
param
needsReschedule Whether the implementing class should reschedule this job.

        if (DEBUG) {
            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
        }
        if (!stopTrackingJob(jobStatus)) {
            if (DEBUG) {
                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
            }
            return;
        }
        if (needsReschedule) {
            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
            startTrackingJob(rescheduled);
        } else if (jobStatus.getJob().isPeriodic()) {
            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
            startTrackingJob(rescheduledPeriodic);
        }
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    
public voidonRunJobNow(com.android.server.job.controllers.JobStatus jobStatus)

        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
    
public voidonStart()

        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
    
public voidonStartUser(int userHandle)


    
        
        mStartedUsers.add(userHandle);
        // Let's kick any outstanding jobs for this user.
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    
public voidonStopUser(int userHandle)

        mStartedUsers.remove(Integer.valueOf(userHandle));
    
public intschedule(android.app.job.JobInfo job, int uId)
Entry point from client to schedule the provided job. This cancels the job if it's already been scheduled, and replaces it with the one provided.

param
job JobInfo object containing execution parameters
param
uId The package identifier of the application this job is for.
return
Result of this operation. See JobScheduler#RESULT_* return codes.

        JobStatus jobStatus = new JobStatus(job, uId);
        cancelJob(uId, job.getId());
        startTrackingJob(jobStatus);
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
        return JobScheduler.RESULT_SUCCESS;
    
private voidstartTrackingJob(com.android.server.job.controllers.JobStatus jobStatus)
Called when we have a job status object that we need to insert in our {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know about.

        boolean update;
        boolean rocking;
        synchronized (mJobs) {
            update = mJobs.add(jobStatus);
            rocking = mReadyToRock;
        }
        if (rocking) {
            for (int i=0; i<mControllers.size(); i++) {
                StateController controller = mControllers.get(i);
                if (update) {
                    controller.maybeStopTrackingJob(jobStatus);
                }
                controller.maybeStartTrackingJob(jobStatus);
            }
        }
    
private booleanstopJobOnServiceContextLocked(com.android.server.job.controllers.JobStatus job)

        for (int i=0; i<mActiveServices.size(); i++) {
            JobServiceContext jsc = mActiveServices.get(i);
            final JobStatus executing = jsc.getRunningJob();
            if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
                jsc.cancelExecutingJob();
                return true;
            }
        }
        return false;
    
private booleanstopTrackingJob(com.android.server.job.controllers.JobStatus jobStatus)
Called when we want to remove a JobStatus object that we've finished executing. Returns the object removed.

        boolean removed;
        boolean rocking;
        synchronized (mJobs) {
            // Remove from store as well as controllers.
            removed = mJobs.remove(jobStatus);
            rocking = mReadyToRock;
        }
        if (removed && rocking) {
            for (int i=0; i<mControllers.size(); i++) {
                StateController controller = mControllers.get(i);
                controller.maybeStopTrackingJob(jobStatus);
            }
        }
        return removed;