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