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