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