JobSchedulerService.java revision 5db09084c8e4efc6311754243c39962fc8e7a766
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 newEarliestRunTimeElapsed = elapsedNow + runEarly;
597        long period = periodicToReschedule.getJob().getIntervalMillis();
598        long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
599
600        if (DEBUG) {
601            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
602                    newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
603        }
604        return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
605                newLatestRuntimeElapsed, 0 /* backoffAttempt */);
606    }
607
608    // JobCompletedListener implementations.
609
610    /**
611     * A job just finished executing. We fetch the
612     * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
613     * whether we want to reschedule we readd it to the controllers.
614     * @param jobStatus Completed job.
615     * @param needsReschedule Whether the implementing class should reschedule this job.
616     */
617    @Override
618    public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
619        if (DEBUG) {
620            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
621        }
622        if (!stopTrackingJob(jobStatus)) {
623            if (DEBUG) {
624                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
625            }
626            return;
627        }
628        if (needsReschedule) {
629            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
630            startTrackingJob(rescheduled);
631        } else if (jobStatus.getJob().isPeriodic()) {
632            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
633            startTrackingJob(rescheduledPeriodic);
634        }
635        reportActive();
636        mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
637    }
638
639    // StateChangedListener implementations.
640
641    /**
642     * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
643     * some controller's state has changed, so as to run through the list of jobs and start/stop
644     * any that are eligible.
645     */
646    @Override
647    public void onControllerStateChanged() {
648        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
649    }
650
651    @Override
652    public void onRunJobNow(JobStatus jobStatus) {
653        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
654    }
655
656    private class JobHandler extends Handler {
657
658        public JobHandler(Looper looper) {
659            super(looper);
660        }
661
662        @Override
663        public void handleMessage(Message message) {
664            synchronized (mJobs) {
665                if (!mReadyToRock) {
666                    return;
667                }
668            }
669            switch (message.what) {
670                case MSG_JOB_EXPIRED:
671                    synchronized (mJobs) {
672                        JobStatus runNow = (JobStatus) message.obj;
673                        // runNow can be null, which is a controller's way of indicating that its
674                        // state is such that all ready jobs should be run immediately.
675                        if (runNow != null && !mPendingJobs.contains(runNow)
676                                && mJobs.containsJob(runNow)) {
677                            mPendingJobs.add(runNow);
678                        }
679                        queueReadyJobsForExecutionLockedH();
680                    }
681                    break;
682                case MSG_CHECK_JOB:
683                    synchronized (mJobs) {
684                        if (mReportedActive) {
685                            // if jobs are currently being run, queue all ready jobs for execution.
686                            queueReadyJobsForExecutionLockedH();
687                        } else {
688                            // Check the list of jobs and run some of them if we feel inclined.
689                            maybeQueueReadyJobsForExecutionLockedH();
690                        }
691                    }
692                    break;
693                case MSG_CHECK_JOB_GREEDY:
694                    synchronized (mJobs) {
695                        queueReadyJobsForExecutionLockedH();
696                    }
697                    break;
698                case MSG_STOP_JOB:
699                    cancelJobImpl((JobStatus)message.obj);
700                    break;
701            }
702            maybeRunPendingJobsH();
703            // Don't remove JOB_EXPIRED in case one came along while processing the queue.
704            removeMessages(MSG_CHECK_JOB);
705        }
706
707        /**
708         * Run through list of jobs and execute all possible - at least one is expired so we do
709         * as many as we can.
710         */
711        private void queueReadyJobsForExecutionLockedH() {
712            ArraySet<JobStatus> jobs = mJobs.getJobs();
713            mPendingJobs.clear();
714            if (DEBUG) {
715                Slog.d(TAG, "queuing all ready jobs for execution:");
716            }
717            for (int i=0; i<jobs.size(); i++) {
718                JobStatus job = jobs.valueAt(i);
719                if (isReadyToBeExecutedLocked(job)) {
720                    if (DEBUG) {
721                        Slog.d(TAG, "    queued " + job.toShortString());
722                    }
723                    mPendingJobs.add(job);
724                } else if (areJobConstraintsNotSatisfied(job)) {
725                    stopJobOnServiceContextLocked(job,
726                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
727                }
728            }
729            if (DEBUG) {
730                final int queuedJobs = mPendingJobs.size();
731                if (queuedJobs == 0) {
732                    Slog.d(TAG, "No jobs pending.");
733                } else {
734                    Slog.d(TAG, queuedJobs + " jobs queued.");
735                }
736            }
737        }
738
739        /**
740         * The state of at least one job has changed. Here is where we could enforce various
741         * policies on when we want to execute jobs.
742         * Right now the policy is such:
743         * If >1 of the ready jobs is idle mode we send all of them off
744         * if more than 2 network connectivity jobs are ready we send them all off.
745         * If more than 4 jobs total are ready we send them all off.
746         * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
747         */
748        private void maybeQueueReadyJobsForExecutionLockedH() {
749            mPendingJobs.clear();
750            int chargingCount = 0;
751            int idleCount =  0;
752            int backoffCount = 0;
753            int connectivityCount = 0;
754            List<JobStatus> runnableJobs = null;
755            ArraySet<JobStatus> jobs = mJobs.getJobs();
756            for (int i=0; i<jobs.size(); i++) {
757                JobStatus job = jobs.valueAt(i);
758                if (isReadyToBeExecutedLocked(job)) {
759                    try {
760                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
761                                job.getJob().getService().getPackageName())
762                                == ActivityManager.APP_START_MODE_DISABLED) {
763                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
764                                    + job.getJob().toString() + " -- package not allowed to start");
765                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
766                            continue;
767                        }
768                    } catch (RemoteException e) {
769                    }
770                    if (job.getNumFailures() > 0) {
771                        backoffCount++;
772                    }
773                    if (job.hasIdleConstraint()) {
774                        idleCount++;
775                    }
776                    if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
777                        connectivityCount++;
778                    }
779                    if (job.hasChargingConstraint()) {
780                        chargingCount++;
781                    }
782                    if (runnableJobs == null) {
783                        runnableJobs = new ArrayList<>();
784                    }
785                    runnableJobs.add(job);
786                } else if (areJobConstraintsNotSatisfied(job)) {
787                    stopJobOnServiceContextLocked(job,
788                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
789                }
790            }
791            if (backoffCount > 0 ||
792                    idleCount >= MIN_IDLE_COUNT ||
793                    connectivityCount >= MIN_CONNECTIVITY_COUNT ||
794                    chargingCount >= MIN_CHARGING_COUNT ||
795                    (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
796                if (DEBUG) {
797                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
798                }
799                for (int i=0; i<runnableJobs.size(); i++) {
800                    mPendingJobs.add(runnableJobs.get(i));
801                }
802            } else {
803                if (DEBUG) {
804                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
805                }
806            }
807        }
808
809        /**
810         * Criteria for moving a job into the pending queue:
811         *      - It's ready.
812         *      - It's not pending.
813         *      - It's not already running on a JSC.
814         *      - The user that requested the job is running.
815         */
816        private boolean isReadyToBeExecutedLocked(JobStatus job) {
817            final boolean jobReady = job.isReady();
818            final boolean jobPending = mPendingJobs.contains(job);
819            final boolean jobActive = isCurrentlyActiveLocked(job);
820            final boolean userRunning = mStartedUsers.contains(job.getUserId());
821            if (DEBUG) {
822                Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
823                        + " ready=" + jobReady + " pending=" + jobPending
824                        + " active=" + jobActive + " userRunning=" + userRunning);
825            }
826            return userRunning && jobReady && !jobPending && !jobActive;
827        }
828
829        /**
830         * Criteria for cancelling an active job:
831         *      - It's not ready
832         *      - It's running on a JSC.
833         */
834        private boolean areJobConstraintsNotSatisfied(JobStatus job) {
835            return !job.isReady() && isCurrentlyActiveLocked(job);
836        }
837
838        /**
839         * Reconcile jobs in the pending queue against available execution contexts.
840         * A controller can force a job into the pending queue even if it's already running, but
841         * here is where we decide whether to actually execute it.
842         */
843        private void maybeRunPendingJobsH() {
844            synchronized (mJobs) {
845                if (mDeviceIdleMode) {
846                    // If device is idle, we will not schedule jobs to run.
847                    return;
848                }
849                if (DEBUG) {
850                    Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
851                }
852                assignJobsToContextsH();
853                reportActive();
854            }
855        }
856    }
857
858    /**
859     * Takes jobs from pending queue and runs them on available contexts.
860     * If no contexts are available, preempts lower priority jobs to
861     * run higher priority ones.
862     * Lock on mJobs before calling this function.
863     */
864    private void assignJobsToContextsH() {
865        if (DEBUG) {
866            Slog.d(TAG, printPendingQueue());
867        }
868
869        // This array essentially stores the state of mActiveServices array.
870        // ith index stores the job present on the ith JobServiceContext.
871        // We manipulate this array until we arrive at what jobs should be running on
872        // what JobServiceContext.
873        JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
874        // Indicates whether we need to act on this jobContext id
875        boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT];
876        int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
877        for (int i=0; i<mActiveServices.size(); i++) {
878            contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob();
879            preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid();
880        }
881        if (DEBUG) {
882            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
883        }
884        Iterator<JobStatus> it = mPendingJobs.iterator();
885        while (it.hasNext()) {
886            JobStatus nextPending = it.next();
887
888            // If job is already running, go to next job.
889            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
890            if (jobRunningContext != -1) {
891                continue;
892            }
893
894            // Find a context for nextPending. The context should be available OR
895            // it should have lowest priority among all running jobs
896            // (sharing the same Uid as nextPending)
897            int minPriority = Integer.MAX_VALUE;
898            int minPriorityContextId = -1;
899            for (int i=0; i<mActiveServices.size(); i++) {
900                JobStatus job = contextIdToJobMap[i];
901                int preferredUid = preferredUidForContext[i];
902                if (job == null && (preferredUid == nextPending.getUid() ||
903                        preferredUid == JobServiceContext.NO_PREFERRED_UID) ) {
904                    minPriorityContextId = i;
905                    break;
906                }
907                if (job.getUid() != nextPending.getUid()) {
908                    continue;
909                }
910                if (job.getPriority() >= nextPending.getPriority()) {
911                    continue;
912                }
913                if (minPriority > nextPending.getPriority()) {
914                    minPriority = nextPending.getPriority();
915                    minPriorityContextId = i;
916                }
917            }
918            if (minPriorityContextId != -1) {
919                contextIdToJobMap[minPriorityContextId] = nextPending;
920                act[minPriorityContextId] = true;
921            }
922        }
923        if (DEBUG) {
924            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
925        }
926        for (int i=0; i<mActiveServices.size(); i++) {
927            boolean preservePreferredUid = false;
928            if (act[i]) {
929                JobStatus js = mActiveServices.get(i).getRunningJob();
930                if (js != null) {
931                    if (DEBUG) {
932                        Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
933                    }
934                    // preferredUid will be set to uid of currently running job.
935                    mActiveServices.get(i).preemptExecutingJob();
936                    preservePreferredUid = true;
937                } else {
938                    if (DEBUG) {
939                        Slog.d(TAG, "About to run job on context "
940                                + String.valueOf(i) + ", job: " + contextIdToJobMap[i]);
941                    }
942                    if (!mActiveServices.get(i).executeRunnableJob(contextIdToJobMap[i])) {
943                        Slog.d(TAG, "Error executing " + contextIdToJobMap[i]);
944                    }
945                    mPendingJobs.remove(contextIdToJobMap[i]);
946                }
947            }
948            if (!preservePreferredUid) {
949                mActiveServices.get(i).clearPreferredUid();
950            }
951        }
952    }
953
954    int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
955        for (int i=0; i<map.length; i++) {
956            if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
957                return i;
958            }
959        }
960        return -1;
961    }
962
963    /**
964     * Binder stub trampoline implementation
965     */
966    final class JobSchedulerStub extends IJobScheduler.Stub {
967        /** Cache determination of whether a given app can persist jobs
968         * key is uid of the calling app; value is undetermined/true/false
969         */
970        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
971
972        // Enforce that only the app itself (or shared uid participant) can schedule a
973        // job that runs one of the app's services, as well as verifying that the
974        // named service properly requires the BIND_JOB_SERVICE permission
975        private void enforceValidJobRequest(int uid, JobInfo job) {
976            final IPackageManager pm = AppGlobals.getPackageManager();
977            final ComponentName service = job.getService();
978            try {
979                ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
980                if (si == null) {
981                    throw new IllegalArgumentException("No such service " + service);
982                }
983                if (si.applicationInfo.uid != uid) {
984                    throw new IllegalArgumentException("uid " + uid +
985                            " cannot schedule job in " + service.getPackageName());
986                }
987                if (!JobService.PERMISSION_BIND.equals(si.permission)) {
988                    throw new IllegalArgumentException("Scheduled service " + service
989                            + " does not require android.permission.BIND_JOB_SERVICE permission");
990                }
991            } catch (RemoteException e) {
992                // Can't happen; the Package Manager is in this same process
993            }
994        }
995
996        private boolean canPersistJobs(int pid, int uid) {
997            // If we get this far we're good to go; all we need to do now is check
998            // whether the app is allowed to persist its scheduled work.
999            final boolean canPersist;
1000            synchronized (mPersistCache) {
1001                Boolean cached = mPersistCache.get(uid);
1002                if (cached != null) {
1003                    canPersist = cached.booleanValue();
1004                } else {
1005                    // Persisting jobs is tantamount to running at boot, so we permit
1006                    // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1007                    // permission
1008                    int result = getContext().checkPermission(
1009                            android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1010                    canPersist = (result == PackageManager.PERMISSION_GRANTED);
1011                    mPersistCache.put(uid, canPersist);
1012                }
1013            }
1014            return canPersist;
1015        }
1016
1017        // IJobScheduler implementation
1018        @Override
1019        public int schedule(JobInfo job) throws RemoteException {
1020            if (DEBUG) {
1021                Slog.d(TAG, "Scheduling job: " + job.toString());
1022            }
1023            final int pid = Binder.getCallingPid();
1024            final int uid = Binder.getCallingUid();
1025
1026            enforceValidJobRequest(uid, job);
1027            if (job.isPersisted()) {
1028                if (!canPersistJobs(pid, uid)) {
1029                    throw new IllegalArgumentException("Error: requested job be persisted without"
1030                            + " holding RECEIVE_BOOT_COMPLETED permission.");
1031                }
1032            }
1033
1034            long ident = Binder.clearCallingIdentity();
1035            try {
1036                return JobSchedulerService.this.schedule(job, uid);
1037            } finally {
1038                Binder.restoreCallingIdentity(ident);
1039            }
1040        }
1041
1042        @Override
1043        public List<JobInfo> getAllPendingJobs() throws RemoteException {
1044            final int uid = Binder.getCallingUid();
1045
1046            long ident = Binder.clearCallingIdentity();
1047            try {
1048                return JobSchedulerService.this.getPendingJobs(uid);
1049            } finally {
1050                Binder.restoreCallingIdentity(ident);
1051            }
1052        }
1053
1054        @Override
1055        public void cancelAll() throws RemoteException {
1056            final int uid = Binder.getCallingUid();
1057
1058            long ident = Binder.clearCallingIdentity();
1059            try {
1060                JobSchedulerService.this.cancelJobsForUid(uid, true);
1061            } finally {
1062                Binder.restoreCallingIdentity(ident);
1063            }
1064        }
1065
1066        @Override
1067        public void cancel(int jobId) throws RemoteException {
1068            final int uid = Binder.getCallingUid();
1069
1070            long ident = Binder.clearCallingIdentity();
1071            try {
1072                JobSchedulerService.this.cancelJob(uid, jobId);
1073            } finally {
1074                Binder.restoreCallingIdentity(ident);
1075            }
1076        }
1077
1078        /**
1079         * "dumpsys" infrastructure
1080         */
1081        @Override
1082        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1083            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1084
1085            long identityToken = Binder.clearCallingIdentity();
1086            try {
1087                JobSchedulerService.this.dumpInternal(pw);
1088            } finally {
1089                Binder.restoreCallingIdentity(identityToken);
1090            }
1091        }
1092    };
1093
1094    private String printContextIdToJobMap(JobStatus[] map, String initial) {
1095        StringBuilder s = new StringBuilder(initial + ": ");
1096        for (int i=0; i<map.length; i++) {
1097            s.append("(")
1098                    .append(map[i] == null? -1: map[i].getJobId())
1099                    .append(map[i] == null? -1: map[i].getUid())
1100                    .append(")" );
1101        }
1102        return s.toString();
1103    }
1104
1105    private String printPendingQueue() {
1106        StringBuilder s = new StringBuilder("Pending queue: ");
1107        Iterator<JobStatus> it = mPendingJobs.iterator();
1108        while (it.hasNext()) {
1109            JobStatus js = it.next();
1110            s.append("(")
1111                    .append(js.getJob().getId())
1112                    .append(", ")
1113                    .append(js.getUid())
1114                    .append(") ");
1115        }
1116        return s.toString();
1117    }
1118
1119    void dumpInternal(PrintWriter pw) {
1120        final long now = SystemClock.elapsedRealtime();
1121        synchronized (mJobs) {
1122            pw.print("Started users: ");
1123            for (int i=0; i<mStartedUsers.size(); i++) {
1124                pw.print("u" + mStartedUsers.get(i) + " ");
1125            }
1126            pw.println();
1127            pw.println("Registered jobs:");
1128            if (mJobs.size() > 0) {
1129                ArraySet<JobStatus> jobs = mJobs.getJobs();
1130                for (int i=0; i<jobs.size(); i++) {
1131                    JobStatus job = jobs.valueAt(i);
1132                    job.dump(pw, "  ");
1133                }
1134            } else {
1135                pw.println("  None.");
1136            }
1137            for (int i=0; i<mControllers.size(); i++) {
1138                pw.println();
1139                mControllers.get(i).dumpControllerState(pw);
1140            }
1141            pw.println();
1142            pw.println(printPendingQueue());
1143            pw.println();
1144            pw.println("Active jobs:");
1145            for (int i=0; i<mActiveServices.size(); i++) {
1146                JobServiceContext jsc = mActiveServices.get(i);
1147                if (jsc.getRunningJob() == null) {
1148                    continue;
1149                } else {
1150                    final long timeout = jsc.getTimeoutElapsed();
1151                    pw.print("Running for: ");
1152                    pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
1153                    pw.print("s timeout=");
1154                    pw.print(timeout);
1155                    pw.print(" fromnow=");
1156                    pw.println(timeout-now);
1157                    jsc.getRunningJob().dump(pw, "  ");
1158                }
1159            }
1160            pw.println();
1161            pw.print("mReadyToRock="); pw.println(mReadyToRock);
1162            pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
1163            pw.print("mReportedActive="); pw.println(mReportedActive);
1164        }
1165        pw.println();
1166    }
1167}
1168