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