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