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