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