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