JobSchedulerService.java revision b0ff32245cb6b51e43dd3ee40b86d683c62de2b9
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
17package com.android.server.job;
18
19import java.io.FileDescriptor;
20import java.io.PrintWriter;
21import java.util.ArrayList;
22import java.util.Iterator;
23import java.util.List;
24
25import android.app.ActivityManager;
26import android.app.AppGlobals;
27import android.app.job.JobInfo;
28import android.app.job.JobScheduler;
29import android.app.job.JobService;
30import android.app.job.IJobScheduler;
31import android.content.BroadcastReceiver;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.pm.IPackageManager;
37import android.content.pm.PackageManager;
38import android.content.pm.ServiceInfo;
39import android.os.BatteryStats;
40import android.os.Binder;
41import android.os.Handler;
42import android.os.Looper;
43import android.os.Message;
44import android.os.PowerManager;
45import android.os.RemoteException;
46import android.os.ServiceManager;
47import android.os.SystemClock;
48import android.os.UserHandle;
49import android.util.ArraySet;
50import android.util.Slog;
51import android.util.SparseArray;
52
53import com.android.internal.app.IBatteryStats;
54import com.android.server.job.controllers.AppIdleController;
55import com.android.server.job.controllers.BatteryController;
56import com.android.server.job.controllers.ConnectivityController;
57import com.android.server.job.controllers.IdleController;
58import com.android.server.job.controllers.JobStatus;
59import com.android.server.job.controllers.StateController;
60import com.android.server.job.controllers.TimeController;
61
62/**
63 * Responsible for taking jobs representing work to be performed by a client app, and determining
64 * based on the criteria specified when that job should be run against the client application's
65 * endpoint.
66 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
67 * about constraints, or the state of active jobs. It receives callbacks from the various
68 * controllers and completed jobs and operates accordingly.
69 *
70 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
71 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
72 * @hide
73 */
74public class JobSchedulerService extends com.android.server.SystemService
75        implements StateChangedListener, JobCompletedListener {
76    static final boolean DEBUG = false;
77    /** The number of concurrent jobs we run at one time. */
78    private static final int MAX_JOB_CONTEXTS_COUNT
79            = ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
80    static final String TAG = "JobSchedulerService";
81    /** Master list of jobs. */
82    final JobStore mJobs;
83
84    static final int MSG_JOB_EXPIRED = 0;
85    static final int MSG_CHECK_JOB = 1;
86
87    // Policy constants
88    /**
89     * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
90     * early.
91     */
92    static final int MIN_IDLE_COUNT = 1;
93    /**
94     * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things
95     * early.
96     */
97    static final int MIN_CHARGING_COUNT = 1;
98    /**
99     * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
100     * things early.
101     */
102    static final int MIN_CONNECTIVITY_COUNT = 2;
103    /**
104     * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
105     * some work early.
106     * This is correlated with the amount of batching we'll be able to do.
107     */
108    static final int MIN_READY_JOBS_COUNT = 2;
109
110    /**
111     * Track Services that have currently active or pending jobs. The index is provided by
112     * {@link JobStatus#getServiceToken()}
113     */
114    final List<JobServiceContext> mActiveServices = new ArrayList<>();
115    /** List of controllers that will notify this service of updates to jobs. */
116    List<StateController> mControllers;
117    /**
118     * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
119     * when ready to execute them.
120     */
121    final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
122
123    final ArrayList<Integer> mStartedUsers = new ArrayList<>();
124
125    final JobHandler mHandler;
126    final JobSchedulerStub mJobSchedulerStub;
127
128    IBatteryStats mBatteryStats;
129    PowerManager mPowerManager;
130
131    /**
132     * Set to true once we are allowed to run third party apps.
133     */
134    boolean mReadyToRock;
135
136    /**
137     * True when in device idle mode, so we don't want to schedule any jobs.
138     */
139    boolean mDeviceIdleMode;
140
141    /**
142     * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
143     * still clean up. On reinstall the package will have a new uid.
144     */
145    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
146        @Override
147        public void onReceive(Context context, Intent intent) {
148            Slog.d(TAG, "Receieved: " + intent.getAction());
149            if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
150                // If this is an outright uninstall rather than the first half of an
151                // app update sequence, cancel the jobs associated with the app.
152                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
153                    int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
154                    if (DEBUG) {
155                        Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
156                    }
157                    cancelJobsForUid(uidRemoved);
158                }
159            } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
160                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
161                if (DEBUG) {
162                    Slog.d(TAG, "Removing jobs for user: " + userId);
163                }
164                cancelJobsForUser(userId);
165            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
166                updateIdleMode(mPowerManager != null ? mPowerManager.isDeviceIdleMode() : false);
167            }
168        }
169    };
170
171    @Override
172    public void onStartUser(int userHandle) {
173        mStartedUsers.add(userHandle);
174        // Let's kick any outstanding jobs for this user.
175        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
176    }
177
178    @Override
179    public void onStopUser(int userHandle) {
180        mStartedUsers.remove(Integer.valueOf(userHandle));
181    }
182
183    /**
184     * Entry point from client to schedule the provided job.
185     * This cancels the job if it's already been scheduled, and replaces it with the one provided.
186     * @param job JobInfo object containing execution parameters
187     * @param uId The package identifier of the application this job is for.
188     * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
189     */
190    public int schedule(JobInfo job, int uId) {
191        JobStatus jobStatus = new JobStatus(job, uId);
192        cancelJob(uId, job.getId());
193        startTrackingJob(jobStatus);
194        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
195        return JobScheduler.RESULT_SUCCESS;
196    }
197
198    public List<JobInfo> getPendingJobs(int uid) {
199        ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
200        synchronized (mJobs) {
201            ArraySet<JobStatus> jobs = mJobs.getJobs();
202            for (int i=0; i<jobs.size(); i++) {
203                JobStatus job = jobs.valueAt(i);
204                if (job.getUid() == uid) {
205                    outList.add(job.getJob());
206                }
207            }
208        }
209        return outList;
210    }
211
212    void cancelJobsForUser(int userHandle) {
213        List<JobStatus> jobsForUser;
214        synchronized (mJobs) {
215            jobsForUser = mJobs.getJobsByUser(userHandle);
216        }
217        for (int i=0; i<jobsForUser.size(); i++) {
218            JobStatus toRemove = jobsForUser.get(i);
219            cancelJobImpl(toRemove);
220        }
221    }
222
223    /**
224     * Entry point from client to cancel all jobs originating from their uid.
225     * This will remove the job from the master list, and cancel the job if it was staged for
226     * execution or being executed.
227     * @param uid Uid to check against for removal of a job.
228     */
229    public void cancelJobsForUid(int uid) {
230        List<JobStatus> jobsForUid;
231        synchronized (mJobs) {
232            jobsForUid = mJobs.getJobsByUid(uid);
233        }
234        for (int i=0; i<jobsForUid.size(); i++) {
235            JobStatus toRemove = jobsForUid.get(i);
236            cancelJobImpl(toRemove);
237        }
238    }
239
240    /**
241     * Entry point from client to cancel the job corresponding to the jobId provided.
242     * This will remove the job from the master list, and cancel the job if it was staged for
243     * execution or being executed.
244     * @param uid Uid of the calling client.
245     * @param jobId Id of the job, provided at schedule-time.
246     */
247    public void cancelJob(int uid, int jobId) {
248        JobStatus toCancel;
249        synchronized (mJobs) {
250            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
251        }
252        if (toCancel != null) {
253            cancelJobImpl(toCancel);
254        }
255    }
256
257    private void cancelJobImpl(JobStatus cancelled) {
258        if (DEBUG) {
259            Slog.d(TAG, "Cancelling: " + cancelled);
260        }
261        stopTrackingJob(cancelled);
262        synchronized (mJobs) {
263            // Remove from pending queue.
264            mPendingJobs.remove(cancelled);
265            // Cancel if running.
266            stopJobOnServiceContextLocked(cancelled);
267        }
268    }
269
270    void updateIdleMode(boolean enabled) {
271        boolean changed = false;
272        boolean rocking;
273        synchronized (mJobs) {
274            if (mDeviceIdleMode != enabled) {
275                changed = true;
276            }
277            rocking = mReadyToRock;
278        }
279        if (changed) {
280            if (rocking) {
281                for (int i=0; i<mControllers.size(); i++) {
282                    mControllers.get(i).deviceIdleModeChanged(enabled);
283                }
284            }
285            synchronized (mJobs) {
286                mDeviceIdleMode = enabled;
287                if (enabled) {
288                    // When becoming idle, make sure no jobs are actively running.
289                    for (int i=0; i<mActiveServices.size(); i++) {
290                        JobServiceContext jsc = mActiveServices.get(i);
291                        final JobStatus executing = jsc.getRunningJob();
292                        if (executing != null) {
293                            jsc.cancelExecutingJob();
294                        }
295                    }
296                } else {
297                    // When coming out of idle, allow thing to start back up.
298                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
299                }
300            }
301        }
302    }
303
304    /**
305     * Initializes the system service.
306     * <p>
307     * Subclasses must define a single argument constructor that accepts the context
308     * and passes it to super.
309     * </p>
310     *
311     * @param context The system server context.
312     */
313    public JobSchedulerService(Context context) {
314        super(context);
315        // Create the controllers.
316        mControllers = new ArrayList<StateController>();
317        mControllers.add(ConnectivityController.get(this));
318        mControllers.add(TimeController.get(this));
319        mControllers.add(IdleController.get(this));
320        mControllers.add(BatteryController.get(this));
321        mControllers.add(AppIdleController.get(this));
322
323        mHandler = new JobHandler(context.getMainLooper());
324        mJobSchedulerStub = new JobSchedulerStub();
325        mJobs = JobStore.initAndGet(this);
326    }
327
328    @Override
329    public void onStart() {
330        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
331    }
332
333    @Override
334    public void onBootPhase(int phase) {
335        if (PHASE_SYSTEM_SERVICES_READY == phase) {
336            // Register br for package removals and user removals.
337            final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
338            filter.addDataScheme("package");
339            getContext().registerReceiverAsUser(
340                    mBroadcastReceiver, UserHandle.ALL, filter, null, null);
341            final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
342            userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
343            getContext().registerReceiverAsUser(
344                    mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
345            mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
346        } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
347            synchronized (mJobs) {
348                // Let's go!
349                mReadyToRock = true;
350                mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
351                        BatteryStats.SERVICE_NAME));
352                // Create the "runners".
353                for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
354                    mActiveServices.add(
355                            new JobServiceContext(this, mBatteryStats,
356                                    getContext().getMainLooper()));
357                }
358                // Attach jobs to their controllers.
359                ArraySet<JobStatus> jobs = mJobs.getJobs();
360                for (int i=0; i<jobs.size(); i++) {
361                    JobStatus job = jobs.valueAt(i);
362                    for (int controller=0; controller<mControllers.size(); controller++) {
363                        mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
364                        mControllers.get(controller).maybeStartTrackingJob(job);
365                    }
366                }
367                // GO GO GO!
368                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
369            }
370        }
371    }
372
373    /**
374     * Called when we have a job status object that we need to insert in our
375     * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
376     * about.
377     */
378    private void startTrackingJob(JobStatus jobStatus) {
379        boolean update;
380        boolean rocking;
381        synchronized (mJobs) {
382            update = mJobs.add(jobStatus);
383            rocking = mReadyToRock;
384        }
385        if (rocking) {
386            for (int i=0; i<mControllers.size(); i++) {
387                StateController controller = mControllers.get(i);
388                if (update) {
389                    controller.maybeStopTrackingJob(jobStatus);
390                }
391                controller.maybeStartTrackingJob(jobStatus);
392            }
393        }
394    }
395
396    /**
397     * Called when we want to remove a JobStatus object that we've finished executing. Returns the
398     * object removed.
399     */
400    private boolean stopTrackingJob(JobStatus jobStatus) {
401        boolean removed;
402        boolean rocking;
403        synchronized (mJobs) {
404            // Remove from store as well as controllers.
405            removed = mJobs.remove(jobStatus);
406            rocking = mReadyToRock;
407        }
408        if (removed && rocking) {
409            for (int i=0; i<mControllers.size(); i++) {
410                StateController controller = mControllers.get(i);
411                controller.maybeStopTrackingJob(jobStatus);
412            }
413        }
414        return removed;
415    }
416
417    private boolean stopJobOnServiceContextLocked(JobStatus job) {
418        for (int i=0; i<mActiveServices.size(); i++) {
419            JobServiceContext jsc = mActiveServices.get(i);
420            final JobStatus executing = jsc.getRunningJob();
421            if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
422                jsc.cancelExecutingJob();
423                return true;
424            }
425        }
426        return false;
427    }
428
429    /**
430     * @param job JobStatus we are querying against.
431     * @return Whether or not the job represented by the status object is currently being run or
432     * is pending.
433     */
434    private boolean isCurrentlyActiveLocked(JobStatus job) {
435        for (int i=0; i<mActiveServices.size(); i++) {
436            JobServiceContext serviceContext = mActiveServices.get(i);
437            final JobStatus running = serviceContext.getRunningJob();
438            if (running != null && running.matches(job.getUid(), job.getJobId())) {
439                return true;
440            }
441        }
442        return false;
443    }
444
445    /**
446     * A job is rescheduled with exponential back-off if the client requests this from their
447     * execution logic.
448     * A caveat is for idle-mode jobs, for which the idle-mode constraint will usurp the
449     * timeliness of the reschedule. For an idle-mode job, no deadline is given.
450     * @param failureToReschedule Provided job status that we will reschedule.
451     * @return A newly instantiated JobStatus with the same constraints as the last job except
452     * with adjusted timing constraints.
453     */
454    private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
455        final long elapsedNowMillis = SystemClock.elapsedRealtime();
456        final JobInfo job = failureToReschedule.getJob();
457
458        final long initialBackoffMillis = job.getInitialBackoffMillis();
459        final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
460        long delayMillis;
461
462        switch (job.getBackoffPolicy()) {
463            case JobInfo.BACKOFF_POLICY_LINEAR:
464                delayMillis = initialBackoffMillis * backoffAttempts;
465                break;
466            default:
467                if (DEBUG) {
468                    Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
469                }
470            case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
471                delayMillis =
472                        (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
473                break;
474        }
475        delayMillis =
476                Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
477        return new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
478                JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
479    }
480
481    /**
482     * Called after a periodic has executed so we can to re-add it. We take the last execution time
483     * of the job to be the time of completion (i.e. the time at which this function is called).
484     * This could be inaccurate b/c the job can run for as long as
485     * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
486     * to underscheduling at least, rather than if we had taken the last execution time to be the
487     * start of the execution.
488     * @return A new job representing the execution criteria for this instantiation of the
489     * recurring job.
490     */
491    private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
492        final long elapsedNow = SystemClock.elapsedRealtime();
493        // Compute how much of the period is remaining.
494        long runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0);
495        long newEarliestRunTimeElapsed = elapsedNow + runEarly;
496        long period = periodicToReschedule.getJob().getIntervalMillis();
497        long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
498
499        if (DEBUG) {
500            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
501                    newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
502        }
503        return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
504                newLatestRuntimeElapsed, 0 /* backoffAttempt */);
505    }
506
507    // JobCompletedListener implementations.
508
509    /**
510     * A job just finished executing. We fetch the
511     * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
512     * whether we want to reschedule we readd it to the controllers.
513     * @param jobStatus Completed job.
514     * @param needsReschedule Whether the implementing class should reschedule this job.
515     */
516    @Override
517    public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
518        if (DEBUG) {
519            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
520        }
521        if (!stopTrackingJob(jobStatus)) {
522            if (DEBUG) {
523                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
524            }
525            return;
526        }
527        if (needsReschedule) {
528            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
529            startTrackingJob(rescheduled);
530        } else if (jobStatus.getJob().isPeriodic()) {
531            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
532            startTrackingJob(rescheduledPeriodic);
533        }
534        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
535    }
536
537    // StateChangedListener implementations.
538
539    /**
540     * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
541     * some controller's state has changed, so as to run through the list of jobs and start/stop
542     * any that are eligible.
543     */
544    @Override
545    public void onControllerStateChanged() {
546        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
547    }
548
549    @Override
550    public void onRunJobNow(JobStatus jobStatus) {
551        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
552    }
553
554    private class JobHandler extends Handler {
555
556        public JobHandler(Looper looper) {
557            super(looper);
558        }
559
560        @Override
561        public void handleMessage(Message message) {
562            synchronized (mJobs) {
563                if (!mReadyToRock) {
564                    return;
565                }
566            }
567            switch (message.what) {
568                case MSG_JOB_EXPIRED:
569                    synchronized (mJobs) {
570                        JobStatus runNow = (JobStatus) message.obj;
571                        // runNow can be null, which is a controller's way of indicating that its
572                        // state is such that all ready jobs should be run immediately.
573                        if (runNow != null && !mPendingJobs.contains(runNow)
574                                && mJobs.containsJob(runNow)) {
575                            mPendingJobs.add(runNow);
576                        }
577                        queueReadyJobsForExecutionLockedH();
578                    }
579                    break;
580                case MSG_CHECK_JOB:
581                    synchronized (mJobs) {
582                        // Check the list of jobs and run some of them if we feel inclined.
583                        maybeQueueReadyJobsForExecutionLockedH();
584                    }
585                    break;
586            }
587            maybeRunPendingJobsH();
588            // Don't remove JOB_EXPIRED in case one came along while processing the queue.
589            removeMessages(MSG_CHECK_JOB);
590        }
591
592        /**
593         * Run through list of jobs and execute all possible - at least one is expired so we do
594         * as many as we can.
595         */
596        private void queueReadyJobsForExecutionLockedH() {
597            ArraySet<JobStatus> jobs = mJobs.getJobs();
598            if (DEBUG) {
599                Slog.d(TAG, "queuing all ready jobs for execution:");
600            }
601            for (int i=0; i<jobs.size(); i++) {
602                JobStatus job = jobs.valueAt(i);
603                if (isReadyToBeExecutedLocked(job)) {
604                    if (DEBUG) {
605                        Slog.d(TAG, "    queued " + job.toShortString());
606                    }
607                    mPendingJobs.add(job);
608                } else if (isReadyToBeCancelledLocked(job)) {
609                    stopJobOnServiceContextLocked(job);
610                }
611            }
612            if (DEBUG) {
613                final int queuedJobs = mPendingJobs.size();
614                if (queuedJobs == 0) {
615                    Slog.d(TAG, "No jobs pending.");
616                } else {
617                    Slog.d(TAG, queuedJobs + " jobs queued.");
618                }
619            }
620        }
621
622        /**
623         * The state of at least one job has changed. Here is where we could enforce various
624         * policies on when we want to execute jobs.
625         * Right now the policy is such:
626         * If >1 of the ready jobs is idle mode we send all of them off
627         * if more than 2 network connectivity jobs are ready we send them all off.
628         * If more than 4 jobs total are ready we send them all off.
629         * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
630         */
631        private void maybeQueueReadyJobsForExecutionLockedH() {
632            int chargingCount = 0;
633            int idleCount =  0;
634            int backoffCount = 0;
635            int connectivityCount = 0;
636            List<JobStatus> runnableJobs = new ArrayList<JobStatus>();
637            ArraySet<JobStatus> jobs = mJobs.getJobs();
638            for (int i=0; i<jobs.size(); i++) {
639                JobStatus job = jobs.valueAt(i);
640                if (isReadyToBeExecutedLocked(job)) {
641                    if (job.getNumFailures() > 0) {
642                        backoffCount++;
643                    }
644                    if (job.hasIdleConstraint()) {
645                        idleCount++;
646                    }
647                    if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
648                        connectivityCount++;
649                    }
650                    if (job.hasChargingConstraint()) {
651                        chargingCount++;
652                    }
653                    runnableJobs.add(job);
654                } else if (isReadyToBeCancelledLocked(job)) {
655                    stopJobOnServiceContextLocked(job);
656                }
657            }
658            if (backoffCount > 0 ||
659                    idleCount >= MIN_IDLE_COUNT ||
660                    connectivityCount >= MIN_CONNECTIVITY_COUNT ||
661                    chargingCount >= MIN_CHARGING_COUNT ||
662                    runnableJobs.size() >= MIN_READY_JOBS_COUNT) {
663                if (DEBUG) {
664                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
665                }
666                for (int i=0; i<runnableJobs.size(); i++) {
667                    mPendingJobs.add(runnableJobs.get(i));
668                }
669            } else {
670                if (DEBUG) {
671                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
672                }
673            }
674            if (DEBUG) {
675                Slog.d(TAG, "idle=" + idleCount + " connectivity=" +
676                connectivityCount + " charging=" + chargingCount + " tot=" +
677                        runnableJobs.size());
678            }
679        }
680
681        /**
682         * Criteria for moving a job into the pending queue:
683         *      - It's ready.
684         *      - It's not pending.
685         *      - It's not already running on a JSC.
686         *      - The user that requested the job is running.
687         */
688        private boolean isReadyToBeExecutedLocked(JobStatus job) {
689            final boolean jobReady = job.isReady();
690            final boolean jobPending = mPendingJobs.contains(job);
691            final boolean jobActive = isCurrentlyActiveLocked(job);
692            final boolean userRunning = mStartedUsers.contains(job.getUserId());
693            if (DEBUG) {
694                Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
695                        + " ready=" + jobReady + " pending=" + jobPending
696                        + " active=" + jobActive + " userRunning=" + userRunning);
697            }
698            return userRunning && jobReady && !jobPending && !jobActive;
699        }
700
701        /**
702         * Criteria for cancelling an active job:
703         *      - It's not ready
704         *      - It's running on a JSC.
705         */
706        private boolean isReadyToBeCancelledLocked(JobStatus job) {
707            return !job.isReady() && isCurrentlyActiveLocked(job);
708        }
709
710        /**
711         * Reconcile jobs in the pending queue against available execution contexts.
712         * A controller can force a job into the pending queue even if it's already running, but
713         * here is where we decide whether to actually execute it.
714         */
715        private void maybeRunPendingJobsH() {
716            synchronized (mJobs) {
717                if (mDeviceIdleMode) {
718                    // If device is idle, we will not schedule jobs to run.
719                    return;
720                }
721                Iterator<JobStatus> it = mPendingJobs.iterator();
722                if (DEBUG) {
723                    Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
724                }
725                while (it.hasNext()) {
726                    JobStatus nextPending = it.next();
727                    JobServiceContext availableContext = null;
728                    for (int i=0; i<mActiveServices.size(); i++) {
729                        JobServiceContext jsc = mActiveServices.get(i);
730                        final JobStatus running = jsc.getRunningJob();
731                        if (running != null && running.matches(nextPending.getUid(),
732                                nextPending.getJobId())) {
733                            // Already running this job for this uId, skip.
734                            availableContext = null;
735                            break;
736                        }
737                        if (jsc.isAvailable()) {
738                            availableContext = jsc;
739                        }
740                    }
741                    if (availableContext != null) {
742                        if (DEBUG) {
743                            Slog.d(TAG, "About to run job "
744                                    + nextPending.getJob().getService().toString());
745                        }
746                        if (!availableContext.executeRunnableJob(nextPending)) {
747                            if (DEBUG) {
748                                Slog.d(TAG, "Error executing " + nextPending);
749                            }
750                            mJobs.remove(nextPending);
751                        }
752                        it.remove();
753                    }
754                }
755            }
756        }
757    }
758
759    /**
760     * Binder stub trampoline implementation
761     */
762    final class JobSchedulerStub extends IJobScheduler.Stub {
763        /** Cache determination of whether a given app can persist jobs
764         * key is uid of the calling app; value is undetermined/true/false
765         */
766        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
767
768        // Enforce that only the app itself (or shared uid participant) can schedule a
769        // job that runs one of the app's services, as well as verifying that the
770        // named service properly requires the BIND_JOB_SERVICE permission
771        private void enforceValidJobRequest(int uid, JobInfo job) {
772            final IPackageManager pm = AppGlobals.getPackageManager();
773            final ComponentName service = job.getService();
774            try {
775                ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
776                if (si == null) {
777                    throw new IllegalArgumentException("No such service " + service);
778                }
779                if (si.applicationInfo.uid != uid) {
780                    throw new IllegalArgumentException("uid " + uid +
781                            " cannot schedule job in " + service.getPackageName());
782                }
783                if (!JobService.PERMISSION_BIND.equals(si.permission)) {
784                    throw new IllegalArgumentException("Scheduled service " + service
785                            + " does not require android.permission.BIND_JOB_SERVICE permission");
786                }
787            } catch (RemoteException e) {
788                // Can't happen; the Package Manager is in this same process
789            }
790        }
791
792        private boolean canPersistJobs(int pid, int uid) {
793            // If we get this far we're good to go; all we need to do now is check
794            // whether the app is allowed to persist its scheduled work.
795            final boolean canPersist;
796            synchronized (mPersistCache) {
797                Boolean cached = mPersistCache.get(uid);
798                if (cached != null) {
799                    canPersist = cached.booleanValue();
800                } else {
801                    // Persisting jobs is tantamount to running at boot, so we permit
802                    // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
803                    // permission
804                    int result = getContext().checkPermission(
805                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
806                    canPersist = (result == PackageManager.PERMISSION_GRANTED);
807                    mPersistCache.put(uid, canPersist);
808                }
809            }
810            return canPersist;
811        }
812
813        // IJobScheduler implementation
814        @Override
815        public int schedule(JobInfo job) throws RemoteException {
816            if (DEBUG) {
817                Slog.d(TAG, "Scheduling job: " + job.toString());
818            }
819            final int pid = Binder.getCallingPid();
820            final int uid = Binder.getCallingUid();
821
822            enforceValidJobRequest(uid, job);
823            if (job.isPersisted()) {
824                if (!canPersistJobs(pid, uid)) {
825                    throw new IllegalArgumentException("Error: requested job be persisted without"
826                            + " holding RECEIVE_BOOT_COMPLETED permission.");
827                }
828            }
829
830            long ident = Binder.clearCallingIdentity();
831            try {
832                return JobSchedulerService.this.schedule(job, uid);
833            } finally {
834                Binder.restoreCallingIdentity(ident);
835            }
836        }
837
838        @Override
839        public List<JobInfo> getAllPendingJobs() throws RemoteException {
840            final int uid = Binder.getCallingUid();
841
842            long ident = Binder.clearCallingIdentity();
843            try {
844                return JobSchedulerService.this.getPendingJobs(uid);
845            } finally {
846                Binder.restoreCallingIdentity(ident);
847            }
848        }
849
850        @Override
851        public void cancelAll() throws RemoteException {
852            final int uid = Binder.getCallingUid();
853
854            long ident = Binder.clearCallingIdentity();
855            try {
856                JobSchedulerService.this.cancelJobsForUid(uid);
857            } finally {
858                Binder.restoreCallingIdentity(ident);
859            }
860        }
861
862        @Override
863        public void cancel(int jobId) throws RemoteException {
864            final int uid = Binder.getCallingUid();
865
866            long ident = Binder.clearCallingIdentity();
867            try {
868                JobSchedulerService.this.cancelJob(uid, jobId);
869            } finally {
870                Binder.restoreCallingIdentity(ident);
871            }
872        }
873
874        /**
875         * "dumpsys" infrastructure
876         */
877        @Override
878        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
879            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
880
881            long identityToken = Binder.clearCallingIdentity();
882            try {
883                JobSchedulerService.this.dumpInternal(pw);
884            } finally {
885                Binder.restoreCallingIdentity(identityToken);
886            }
887        }
888    };
889
890    void dumpInternal(PrintWriter pw) {
891        final long now = SystemClock.elapsedRealtime();
892        synchronized (mJobs) {
893            pw.print("Started users: ");
894            for (int i=0; i<mStartedUsers.size(); i++) {
895                pw.print("u" + mStartedUsers.get(i) + " ");
896            }
897            pw.println();
898            pw.println("Registered jobs:");
899            if (mJobs.size() > 0) {
900                ArraySet<JobStatus> jobs = mJobs.getJobs();
901                for (int i=0; i<jobs.size(); i++) {
902                    JobStatus job = jobs.valueAt(i);
903                    job.dump(pw, "  ");
904                }
905            } else {
906                pw.println("  None.");
907            }
908            for (int i=0; i<mControllers.size(); i++) {
909                pw.println();
910                mControllers.get(i).dumpControllerState(pw);
911            }
912            pw.println();
913            pw.println("Pending:");
914            for (int i=0; i<mPendingJobs.size(); i++) {
915                pw.println(mPendingJobs.get(i).hashCode());
916            }
917            pw.println();
918            pw.println("Active jobs:");
919            for (int i=0; i<mActiveServices.size(); i++) {
920                JobServiceContext jsc = mActiveServices.get(i);
921                if (jsc.isAvailable()) {
922                    continue;
923                } else {
924                    final long timeout = jsc.getTimeoutElapsed();
925                    pw.print("Running for: ");
926                    pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
927                    pw.print("s timeout=");
928                    pw.print(timeout);
929                    pw.print(" fromnow=");
930                    pw.println(timeout-now);
931                    jsc.getRunningJob().dump(pw, "  ");
932                }
933            }
934            pw.println();
935            pw.print("mReadyToRock="); pw.println(mReadyToRock);
936            pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
937        }
938        pw.println();
939    }
940}
941