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