JobSchedulerService.java revision 8e64e2e6a4d2964b6a4147f5cccd03de934c86cd
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() ? 3 : 6;
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, packageName, userId);
235        try {
236            if (ActivityManagerNative.getDefault().getAppStartMode(uId,
237                    job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
238                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
239                        + " -- package not allowed to start");
240                return JobScheduler.RESULT_FAILURE;
241            }
242        } catch (RemoteException e) {
243        }
244        if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
245        JobStatus toCancel;
246        synchronized (mJobs) {
247            toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
248        }
249        startTrackingJob(jobStatus, toCancel);
250        if (toCancel != null) {
251            cancelJobImpl(toCancel);
252        }
253        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
254        return JobScheduler.RESULT_SUCCESS;
255    }
256
257    public List<JobInfo> getPendingJobs(int uid) {
258        ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
259        synchronized (mJobs) {
260            ArraySet<JobStatus> jobs = mJobs.getJobs();
261            for (int i=0; i<jobs.size(); i++) {
262                JobStatus job = jobs.valueAt(i);
263                if (job.getUid() == uid) {
264                    outList.add(job.getJob());
265                }
266            }
267        }
268        return outList;
269    }
270
271    void cancelJobsForUser(int userHandle) {
272        List<JobStatus> jobsForUser;
273        synchronized (mJobs) {
274            jobsForUser = mJobs.getJobsByUser(userHandle);
275        }
276        for (int i=0; i<jobsForUser.size(); i++) {
277            JobStatus toRemove = jobsForUser.get(i);
278            cancelJobImpl(toRemove);
279        }
280    }
281
282    /**
283     * Entry point from client to cancel all jobs originating from their uid.
284     * This will remove the job from the master list, and cancel the job if it was staged for
285     * execution or being executed.
286     * @param uid Uid to check against for removal of a job.
287     * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
288     * whose apps are stopped.
289     */
290    public void cancelJobsForUid(int uid, boolean forceAll) {
291        List<JobStatus> jobsForUid;
292        synchronized (mJobs) {
293            jobsForUid = mJobs.getJobsByUid(uid);
294        }
295        for (int i=0; i<jobsForUid.size(); i++) {
296            JobStatus toRemove = jobsForUid.get(i);
297            if (!forceAll) {
298                String packageName = toRemove.getServiceComponent().getPackageName();
299                try {
300                    if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
301                            != ActivityManager.APP_START_MODE_DISABLED) {
302                        continue;
303                    }
304                } catch (RemoteException e) {
305                }
306            }
307            cancelJobImpl(toRemove);
308        }
309    }
310
311    /**
312     * Entry point from client to cancel the job corresponding to the jobId provided.
313     * This will remove the job from the master list, and cancel the job if it was staged for
314     * execution or being executed.
315     * @param uid Uid of the calling client.
316     * @param jobId Id of the job, provided at schedule-time.
317     */
318    public void cancelJob(int uid, int jobId) {
319        JobStatus toCancel;
320        synchronized (mJobs) {
321            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
322        }
323        if (toCancel != null) {
324            cancelJobImpl(toCancel);
325        }
326    }
327
328    private void cancelJobImpl(JobStatus cancelled) {
329        if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
330        stopTrackingJob(cancelled, true /* writeBack */);
331        synchronized (mJobs) {
332            // Remove from pending queue.
333            mPendingJobs.remove(cancelled);
334            // Cancel if running.
335            stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
336            reportActive();
337        }
338    }
339
340    void updateIdleMode(boolean enabled) {
341        boolean changed = false;
342        boolean rocking;
343        synchronized (mJobs) {
344            if (mDeviceIdleMode != enabled) {
345                changed = true;
346            }
347            rocking = mReadyToRock;
348        }
349        if (changed) {
350            if (rocking) {
351                for (int i=0; i<mControllers.size(); i++) {
352                    mControllers.get(i).deviceIdleModeChanged(enabled);
353                }
354            }
355            synchronized (mJobs) {
356                mDeviceIdleMode = enabled;
357                if (enabled) {
358                    // When becoming idle, make sure no jobs are actively running.
359                    for (int i=0; i<mActiveServices.size(); i++) {
360                        JobServiceContext jsc = mActiveServices.get(i);
361                        final JobStatus executing = jsc.getRunningJob();
362                        if (executing != null) {
363                            jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
364                        }
365                    }
366                } else {
367                    // When coming out of idle, allow thing to start back up.
368                    if (rocking) {
369                        if (mLocalDeviceIdleController != null) {
370                            if (!mReportedActive) {
371                                mReportedActive = true;
372                                mLocalDeviceIdleController.setJobsActive(true);
373                            }
374                        }
375                    }
376                    mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
377                }
378            }
379        }
380    }
381
382    void reportActive() {
383        // active is true if pending queue contains jobs OR some job is running.
384        boolean active = mPendingJobs.size() > 0;
385        if (mPendingJobs.size() <= 0) {
386            for (int i=0; i<mActiveServices.size(); i++) {
387                JobServiceContext jsc = mActiveServices.get(i);
388                if (jsc.getRunningJob() != null) {
389                    active = true;
390                    break;
391                }
392            }
393        }
394
395        if (mReportedActive != active) {
396            mReportedActive = active;
397            if (mLocalDeviceIdleController != null) {
398                mLocalDeviceIdleController.setJobsActive(active);
399            }
400        }
401    }
402
403    /**
404     * Initializes the system service.
405     * <p>
406     * Subclasses must define a single argument constructor that accepts the context
407     * and passes it to super.
408     * </p>
409     *
410     * @param context The system server context.
411     */
412    public JobSchedulerService(Context context) {
413        super(context);
414        // Create the controllers.
415        mControllers = new ArrayList<StateController>();
416        mControllers.add(ConnectivityController.get(this));
417        mControllers.add(TimeController.get(this));
418        mControllers.add(IdleController.get(this));
419        mControllers.add(BatteryController.get(this));
420        mControllers.add(AppIdleController.get(this));
421        mControllers.add(ContentObserverController.get(this));
422
423        mHandler = new JobHandler(context.getMainLooper());
424        mJobSchedulerStub = new JobSchedulerStub();
425        mJobs = JobStore.initAndGet(this);
426    }
427
428    @Override
429    public void onStart() {
430        publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
431    }
432
433    @Override
434    public void onBootPhase(int phase) {
435        if (PHASE_SYSTEM_SERVICES_READY == phase) {
436            // Register br for package removals and user removals.
437            final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
438            filter.addDataScheme("package");
439            getContext().registerReceiverAsUser(
440                    mBroadcastReceiver, UserHandle.ALL, filter, null, null);
441            final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
442            userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
443            userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
444            getContext().registerReceiverAsUser(
445                    mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
446            mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
447            try {
448                ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
449                        ActivityManager.UID_OBSERVER_IDLE);
450            } catch (RemoteException e) {
451                // ignored; both services live in system_server
452            }
453        } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
454            synchronized (mJobs) {
455                // Let's go!
456                mReadyToRock = true;
457                mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
458                        BatteryStats.SERVICE_NAME));
459                mLocalDeviceIdleController
460                        = LocalServices.getService(DeviceIdleController.LocalService.class);
461                // Create the "runners".
462                for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
463                    mActiveServices.add(
464                            new JobServiceContext(this, mBatteryStats,
465                                    getContext().getMainLooper()));
466                }
467                // Attach jobs to their controllers.
468                ArraySet<JobStatus> jobs = mJobs.getJobs();
469                for (int i=0; i<jobs.size(); i++) {
470                    JobStatus job = jobs.valueAt(i);
471                    for (int controller=0; controller<mControllers.size(); controller++) {
472                        mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
473                        mControllers.get(controller).maybeStartTrackingJob(job, null);
474                    }
475                }
476                // GO GO GO!
477                mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
478            }
479        }
480    }
481
482    /**
483     * Called when we have a job status object that we need to insert in our
484     * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
485     * about.
486     */
487    private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
488        boolean update;
489        boolean rocking;
490        synchronized (mJobs) {
491            update = mJobs.add(jobStatus);
492            rocking = mReadyToRock;
493        }
494        if (rocking) {
495            for (int i=0; i<mControllers.size(); i++) {
496                StateController controller = mControllers.get(i);
497                if (update) {
498                    controller.maybeStopTrackingJob(jobStatus, true);
499                }
500                controller.maybeStartTrackingJob(jobStatus, lastJob);
501            }
502        }
503    }
504
505    /**
506     * Called when we want to remove a JobStatus object that we've finished executing. Returns the
507     * object removed.
508     */
509    private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
510        boolean removed;
511        boolean rocking;
512        synchronized (mJobs) {
513            // Remove from store as well as controllers.
514            removed = mJobs.remove(jobStatus, writeBack);
515            rocking = mReadyToRock;
516        }
517        if (removed && rocking) {
518            for (int i=0; i<mControllers.size(); i++) {
519                StateController controller = mControllers.get(i);
520                controller.maybeStopTrackingJob(jobStatus, false);
521            }
522        }
523        return removed;
524    }
525
526    private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
527        for (int i=0; i<mActiveServices.size(); i++) {
528            JobServiceContext jsc = mActiveServices.get(i);
529            final JobStatus executing = jsc.getRunningJob();
530            if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
531                jsc.cancelExecutingJob(reason);
532                return true;
533            }
534        }
535        return false;
536    }
537
538    /**
539     * @param job JobStatus we are querying against.
540     * @return Whether or not the job represented by the status object is currently being run or
541     * is pending.
542     */
543    private boolean isCurrentlyActiveLocked(JobStatus job) {
544        for (int i=0; i<mActiveServices.size(); i++) {
545            JobServiceContext serviceContext = mActiveServices.get(i);
546            final JobStatus running = serviceContext.getRunningJob();
547            if (running != null && running.matches(job.getUid(), job.getJobId())) {
548                return true;
549            }
550        }
551        return false;
552    }
553
554    /**
555     * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
556     * specify an override deadline on a failed job (the failed job will run even though it's not
557     * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
558     * ready job with {@link JobStatus#numFailures} > 0 will be executed.
559     *
560     * @param failureToReschedule Provided job status that we will reschedule.
561     * @return A newly instantiated JobStatus with the same constraints as the last job except
562     * with adjusted timing constraints.
563     *
564     * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
565     */
566    private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
567        final long elapsedNowMillis = SystemClock.elapsedRealtime();
568        final JobInfo job = failureToReschedule.getJob();
569
570        final long initialBackoffMillis = job.getInitialBackoffMillis();
571        final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
572        long delayMillis;
573
574        switch (job.getBackoffPolicy()) {
575            case JobInfo.BACKOFF_POLICY_LINEAR:
576                delayMillis = initialBackoffMillis * backoffAttempts;
577                break;
578            default:
579                if (DEBUG) {
580                    Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
581                }
582            case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
583                delayMillis =
584                        (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
585                break;
586        }
587        delayMillis =
588                Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
589        JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
590                JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
591        for (int ic=0; ic<mControllers.size(); ic++) {
592            StateController controller = mControllers.get(ic);
593            controller.rescheduleForFailure(newJob, failureToReschedule);
594        }
595        return newJob;
596    }
597
598    /**
599     * Called after a periodic has executed so we can reschedule it. We take the last execution
600     * time of the job to be the time of completion (i.e. the time at which this function is
601     * called).
602     * This could be inaccurate b/c the job can run for as long as
603     * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
604     * to underscheduling at least, rather than if we had taken the last execution time to be the
605     * start of the execution.
606     * @return A new job representing the execution criteria for this instantiation of the
607     * recurring job.
608     */
609    private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
610        final long elapsedNow = SystemClock.elapsedRealtime();
611        // Compute how much of the period is remaining.
612        long runEarly = 0L;
613
614        // If this periodic was rescheduled it won't have a deadline.
615        if (periodicToReschedule.hasDeadlineConstraint()) {
616            runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
617        }
618        long flex = periodicToReschedule.getJob().getFlexMillis();
619        long period = periodicToReschedule.getJob().getIntervalMillis();
620        long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
621        long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
622
623        if (DEBUG) {
624            Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
625                    newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
626        }
627        return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
628                newLatestRuntimeElapsed, 0 /* backoffAttempt */);
629    }
630
631    // JobCompletedListener implementations.
632
633    /**
634     * A job just finished executing. We fetch the
635     * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
636     * whether we want to reschedule we readd it to the controllers.
637     * @param jobStatus Completed job.
638     * @param needsReschedule Whether the implementing class should reschedule this job.
639     */
640    @Override
641    public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
642        if (DEBUG) {
643            Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
644        }
645        // Do not write back immediately if this is a periodic job. The job may get lost if system
646        // shuts down before it is added back.
647        if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) {
648            if (DEBUG) {
649                Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
650            }
651            // We still want to check for jobs to execute, because this job may have
652            // scheduled a new job under the same job id, and now we can run it.
653            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
654            return;
655        }
656        // Note: there is a small window of time in here where, when rescheduling a job,
657        // we will stop monitoring its content providers.  This should be fixed by stopping
658        // the old job after scheduling the new one, but since we have no lock held here
659        // that may cause ordering problems if the app removes jobStatus while in here.
660        if (needsReschedule) {
661            JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
662            startTrackingJob(rescheduled, jobStatus);
663        } else if (jobStatus.getJob().isPeriodic()) {
664            JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
665            startTrackingJob(rescheduledPeriodic, jobStatus);
666        }
667        reportActive();
668        mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
669    }
670
671    // StateChangedListener implementations.
672
673    /**
674     * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
675     * some controller's state has changed, so as to run through the list of jobs and start/stop
676     * any that are eligible.
677     */
678    @Override
679    public void onControllerStateChanged() {
680        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
681    }
682
683    @Override
684    public void onRunJobNow(JobStatus jobStatus) {
685        mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
686    }
687
688    private class JobHandler extends Handler {
689
690        public JobHandler(Looper looper) {
691            super(looper);
692        }
693
694        @Override
695        public void handleMessage(Message message) {
696            synchronized (mJobs) {
697                if (!mReadyToRock) {
698                    return;
699                }
700            }
701            switch (message.what) {
702                case MSG_JOB_EXPIRED:
703                    synchronized (mJobs) {
704                        JobStatus runNow = (JobStatus) message.obj;
705                        // runNow can be null, which is a controller's way of indicating that its
706                        // state is such that all ready jobs should be run immediately.
707                        if (runNow != null && !mPendingJobs.contains(runNow)
708                                && mJobs.containsJob(runNow)) {
709                            mPendingJobs.add(runNow);
710                        }
711                        queueReadyJobsForExecutionLockedH();
712                    }
713                    break;
714                case MSG_CHECK_JOB:
715                    synchronized (mJobs) {
716                        if (mReportedActive) {
717                            // if jobs are currently being run, queue all ready jobs for execution.
718                            queueReadyJobsForExecutionLockedH();
719                        } else {
720                            // Check the list of jobs and run some of them if we feel inclined.
721                            maybeQueueReadyJobsForExecutionLockedH();
722                        }
723                    }
724                    break;
725                case MSG_CHECK_JOB_GREEDY:
726                    synchronized (mJobs) {
727                        queueReadyJobsForExecutionLockedH();
728                    }
729                    break;
730                case MSG_STOP_JOB:
731                    cancelJobImpl((JobStatus)message.obj);
732                    break;
733            }
734            maybeRunPendingJobsH();
735            // Don't remove JOB_EXPIRED in case one came along while processing the queue.
736            removeMessages(MSG_CHECK_JOB);
737        }
738
739        /**
740         * Run through list of jobs and execute all possible - at least one is expired so we do
741         * as many as we can.
742         */
743        private void queueReadyJobsForExecutionLockedH() {
744            ArraySet<JobStatus> jobs = mJobs.getJobs();
745            mPendingJobs.clear();
746            if (DEBUG) {
747                Slog.d(TAG, "queuing all ready jobs for execution:");
748            }
749            for (int i=0; i<jobs.size(); i++) {
750                JobStatus job = jobs.valueAt(i);
751                if (isReadyToBeExecutedLocked(job)) {
752                    if (DEBUG) {
753                        Slog.d(TAG, "    queued " + job.toShortString());
754                    }
755                    mPendingJobs.add(job);
756                } else if (areJobConstraintsNotSatisfied(job)) {
757                    stopJobOnServiceContextLocked(job,
758                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
759                }
760            }
761            if (DEBUG) {
762                final int queuedJobs = mPendingJobs.size();
763                if (queuedJobs == 0) {
764                    Slog.d(TAG, "No jobs pending.");
765                } else {
766                    Slog.d(TAG, queuedJobs + " jobs queued.");
767                }
768            }
769        }
770
771        /**
772         * The state of at least one job has changed. Here is where we could enforce various
773         * policies on when we want to execute jobs.
774         * Right now the policy is such:
775         * If >1 of the ready jobs is idle mode we send all of them off
776         * if more than 2 network connectivity jobs are ready we send them all off.
777         * If more than 4 jobs total are ready we send them all off.
778         * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
779         */
780        private void maybeQueueReadyJobsForExecutionLockedH() {
781            mPendingJobs.clear();
782            int chargingCount = 0;
783            int idleCount =  0;
784            int backoffCount = 0;
785            int connectivityCount = 0;
786            int contentCount = 0;
787            List<JobStatus> runnableJobs = null;
788            ArraySet<JobStatus> jobs = mJobs.getJobs();
789            if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
790            for (int i=0; i<jobs.size(); i++) {
791                JobStatus job = jobs.valueAt(i);
792                if (isReadyToBeExecutedLocked(job)) {
793                    try {
794                        if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
795                                job.getJob().getService().getPackageName())
796                                == ActivityManager.APP_START_MODE_DISABLED) {
797                            Slog.w(TAG, "Aborting job " + job.getUid() + ":"
798                                    + job.getJob().toString() + " -- package not allowed to start");
799                            mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
800                            continue;
801                        }
802                    } catch (RemoteException e) {
803                    }
804                    if (job.getNumFailures() > 0) {
805                        backoffCount++;
806                    }
807                    if (job.hasIdleConstraint()) {
808                        idleCount++;
809                    }
810                    if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
811                        connectivityCount++;
812                    }
813                    if (job.hasChargingConstraint()) {
814                        chargingCount++;
815                    }
816                    if (job.hasContentTriggerConstraint()) {
817                        contentCount++;
818                    }
819                    if (runnableJobs == null) {
820                        runnableJobs = new ArrayList<>();
821                    }
822                    runnableJobs.add(job);
823                } else if (areJobConstraintsNotSatisfied(job)) {
824                    stopJobOnServiceContextLocked(job,
825                            JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
826                }
827            }
828            if (backoffCount > 0 ||
829                    idleCount >= MIN_IDLE_COUNT ||
830                    connectivityCount >= MIN_CONNECTIVITY_COUNT ||
831                    chargingCount >= MIN_CHARGING_COUNT ||
832                    contentCount  >= MIN_CONTENT_COUNT ||
833                    (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
834                if (DEBUG) {
835                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
836                }
837                for (int i=0; i<runnableJobs.size(); i++) {
838                    mPendingJobs.add(runnableJobs.get(i));
839                }
840            } else {
841                if (DEBUG) {
842                    Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
843                }
844            }
845        }
846
847        /**
848         * Criteria for moving a job into the pending queue:
849         *      - It's ready.
850         *      - It's not pending.
851         *      - It's not already running on a JSC.
852         *      - The user that requested the job is running.
853         */
854        private boolean isReadyToBeExecutedLocked(JobStatus job) {
855            final boolean jobReady = job.isReady();
856            final boolean jobPending = mPendingJobs.contains(job);
857            final boolean jobActive = isCurrentlyActiveLocked(job);
858            final boolean userRunning = mStartedUsers.contains(job.getUserId());
859            if (DEBUG) {
860                Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
861                        + " ready=" + jobReady + " pending=" + jobPending
862                        + " active=" + jobActive + " userRunning=" + userRunning);
863            }
864            return userRunning && jobReady && !jobPending && !jobActive;
865        }
866
867        /**
868         * Criteria for cancelling an active job:
869         *      - It's not ready
870         *      - It's running on a JSC.
871         */
872        private boolean areJobConstraintsNotSatisfied(JobStatus job) {
873            return !job.isReady() && isCurrentlyActiveLocked(job);
874        }
875
876        /**
877         * Reconcile jobs in the pending queue against available execution contexts.
878         * A controller can force a job into the pending queue even if it's already running, but
879         * here is where we decide whether to actually execute it.
880         */
881        private void maybeRunPendingJobsH() {
882            synchronized (mJobs) {
883                if (mDeviceIdleMode) {
884                    // If device is idle, we will not schedule jobs to run.
885                    return;
886                }
887                if (DEBUG) {
888                    Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
889                }
890                assignJobsToContextsH();
891                reportActive();
892            }
893        }
894    }
895
896    /**
897     * Takes jobs from pending queue and runs them on available contexts.
898     * If no contexts are available, preempts lower priority jobs to
899     * run higher priority ones.
900     * Lock on mJobs before calling this function.
901     */
902    private void assignJobsToContextsH() {
903        if (DEBUG) {
904            Slog.d(TAG, printPendingQueue());
905        }
906
907        // This array essentially stores the state of mActiveServices array.
908        // ith index stores the job present on the ith JobServiceContext.
909        // We manipulate this array until we arrive at what jobs should be running on
910        // what JobServiceContext.
911        JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
912        // Indicates whether we need to act on this jobContext id
913        boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT];
914        int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
915        for (int i=0; i<mActiveServices.size(); i++) {
916            contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob();
917            preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid();
918        }
919        if (DEBUG) {
920            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
921        }
922        Iterator<JobStatus> it = mPendingJobs.iterator();
923        while (it.hasNext()) {
924            JobStatus nextPending = it.next();
925
926            // If job is already running, go to next job.
927            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
928            if (jobRunningContext != -1) {
929                continue;
930            }
931
932            // Find a context for nextPending. The context should be available OR
933            // it should have lowest priority among all running jobs
934            // (sharing the same Uid as nextPending)
935            int minPriority = Integer.MAX_VALUE;
936            int minPriorityContextId = -1;
937            for (int i=0; i<mActiveServices.size(); i++) {
938                JobStatus job = contextIdToJobMap[i];
939                int preferredUid = preferredUidForContext[i];
940                if (job == null && (preferredUid == nextPending.getUid() ||
941                        preferredUid == JobServiceContext.NO_PREFERRED_UID) ) {
942                    minPriorityContextId = i;
943                    break;
944                }
945                if (job == null) {
946                    // No job on this context, but nextPending can't run here because
947                    // the context has a preferred Uid.
948                    continue;
949                }
950                if (job.getUid() != nextPending.getUid()) {
951                    continue;
952                }
953                if (job.getPriority() >= nextPending.getPriority()) {
954                    continue;
955                }
956                if (minPriority > nextPending.getPriority()) {
957                    minPriority = nextPending.getPriority();
958                    minPriorityContextId = i;
959                }
960            }
961            if (minPriorityContextId != -1) {
962                contextIdToJobMap[minPriorityContextId] = nextPending;
963                act[minPriorityContextId] = true;
964            }
965        }
966        if (DEBUG) {
967            Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
968        }
969        for (int i=0; i<mActiveServices.size(); i++) {
970            boolean preservePreferredUid = false;
971            if (act[i]) {
972                JobStatus js = mActiveServices.get(i).getRunningJob();
973                if (js != null) {
974                    if (DEBUG) {
975                        Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
976                    }
977                    // preferredUid will be set to uid of currently running job.
978                    mActiveServices.get(i).preemptExecutingJob();
979                    preservePreferredUid = true;
980                } else {
981                    if (DEBUG) {
982                        Slog.d(TAG, "About to run job on context "
983                                + String.valueOf(i) + ", job: " + contextIdToJobMap[i]);
984                    }
985                    for (int ic=0; ic<mControllers.size(); ic++) {
986                        StateController controller = mControllers.get(ic);
987                        controller.prepareForExecution(contextIdToJobMap[i]);
988                    }
989                    if (!mActiveServices.get(i).executeRunnableJob(contextIdToJobMap[i])) {
990                        Slog.d(TAG, "Error executing " + contextIdToJobMap[i]);
991                    }
992                    mPendingJobs.remove(contextIdToJobMap[i]);
993                }
994            }
995            if (!preservePreferredUid) {
996                mActiveServices.get(i).clearPreferredUid();
997            }
998        }
999    }
1000
1001    int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1002        for (int i=0; i<map.length; i++) {
1003            if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1004                return i;
1005            }
1006        }
1007        return -1;
1008    }
1009
1010    /**
1011     * Binder stub trampoline implementation
1012     */
1013    final class JobSchedulerStub extends IJobScheduler.Stub {
1014        /** Cache determination of whether a given app can persist jobs
1015         * key is uid of the calling app; value is undetermined/true/false
1016         */
1017        private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1018
1019        // Enforce that only the app itself (or shared uid participant) can schedule a
1020        // job that runs one of the app's services, as well as verifying that the
1021        // named service properly requires the BIND_JOB_SERVICE permission
1022        private void enforceValidJobRequest(int uid, JobInfo job) {
1023            final IPackageManager pm = AppGlobals.getPackageManager();
1024            final ComponentName service = job.getService();
1025            try {
1026                ServiceInfo si = pm.getServiceInfo(service,
1027                        PackageManager.MATCH_DEBUG_TRIAGED_MISSING, 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