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