• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.job;
18 
19 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
20 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
21 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
22 
23 import android.annotation.NonNull;
24 import android.annotation.UserIdInt;
25 import android.app.Activity;
26 import android.app.ActivityManager;
27 import android.app.ActivityManagerInternal;
28 import android.app.AlarmManager;
29 import android.app.AppGlobals;
30 import android.app.IUidObserver;
31 import android.app.job.IJobScheduler;
32 import android.app.job.JobInfo;
33 import android.app.job.JobParameters;
34 import android.app.job.JobProtoEnums;
35 import android.app.job.JobScheduler;
36 import android.app.job.JobService;
37 import android.app.job.JobSnapshot;
38 import android.app.job.JobWorkItem;
39 import android.app.usage.UsageStatsManager;
40 import android.app.usage.UsageStatsManagerInternal;
41 import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
42 import android.content.BroadcastReceiver;
43 import android.content.ComponentName;
44 import android.content.ContentResolver;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.content.IntentFilter;
48 import android.content.pm.IPackageManager;
49 import android.content.pm.PackageManager;
50 import android.content.pm.PackageManager.NameNotFoundException;
51 import android.content.pm.PackageManagerInternal;
52 import android.content.pm.ParceledListSlice;
53 import android.content.pm.ServiceInfo;
54 import android.database.ContentObserver;
55 import android.net.Uri;
56 import android.os.BatteryStats;
57 import android.os.BatteryStatsInternal;
58 import android.os.Binder;
59 import android.os.Handler;
60 import android.os.IThermalService;
61 import android.os.IThermalStatusListener;
62 import android.os.Looper;
63 import android.os.Message;
64 import android.os.Process;
65 import android.os.RemoteException;
66 import android.os.ResultReceiver;
67 import android.os.ServiceManager;
68 import android.os.ShellCallback;
69 import android.os.SystemClock;
70 import android.os.Temperature;
71 import android.os.UserHandle;
72 import android.os.UserManagerInternal;
73 import android.os.WorkSource;
74 import android.provider.Settings;
75 import android.text.format.DateUtils;
76 import android.util.KeyValueListParser;
77 import android.util.Log;
78 import android.util.Slog;
79 import android.util.SparseArray;
80 import android.util.SparseIntArray;
81 import android.util.StatsLog;
82 import android.util.TimeUtils;
83 import android.util.proto.ProtoOutputStream;
84 
85 import com.android.internal.annotations.GuardedBy;
86 import com.android.internal.annotations.VisibleForTesting;
87 import com.android.internal.app.IBatteryStats;
88 import com.android.internal.util.ArrayUtils;
89 import com.android.internal.util.DumpUtils;
90 import com.android.internal.util.IndentingPrintWriter;
91 import com.android.internal.util.Preconditions;
92 import com.android.server.AppStateTracker;
93 import com.android.server.DeviceIdleController;
94 import com.android.server.FgThread;
95 import com.android.server.LocalServices;
96 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
97 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
98 import com.android.server.job.JobSchedulerServiceDumpProto.RegisteredJob;
99 import com.android.server.job.controllers.BackgroundJobsController;
100 import com.android.server.job.controllers.BatteryController;
101 import com.android.server.job.controllers.ConnectivityController;
102 import com.android.server.job.controllers.ContentObserverController;
103 import com.android.server.job.controllers.DeviceIdleJobsController;
104 import com.android.server.job.controllers.IdleController;
105 import com.android.server.job.controllers.JobStatus;
106 import com.android.server.job.controllers.QuotaController;
107 import com.android.server.job.controllers.StateController;
108 import com.android.server.job.controllers.StorageController;
109 import com.android.server.job.controllers.TimeController;
110 
111 import libcore.util.EmptyArray;
112 
113 import java.io.FileDescriptor;
114 import java.io.PrintWriter;
115 import java.time.Clock;
116 import java.util.ArrayList;
117 import java.util.Arrays;
118 import java.util.Collections;
119 import java.util.Comparator;
120 import java.util.HashMap;
121 import java.util.List;
122 import java.util.function.Consumer;
123 import java.util.function.Predicate;
124 
125 /**
126  * Responsible for taking jobs representing work to be performed by a client app, and determining
127  * based on the criteria specified when that job should be run against the client application's
128  * endpoint.
129  * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
130  * about constraints, or the state of active jobs. It receives callbacks from the various
131  * controllers and completed jobs and operates accordingly.
132  *
133  * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
134  * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
135  * @hide
136  */
137 public class JobSchedulerService extends com.android.server.SystemService
138         implements StateChangedListener, JobCompletedListener {
139     public static final String TAG = "JobScheduler";
140     public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
141     public static final boolean DEBUG_STANDBY = DEBUG || false;
142 
143     /** The maximum number of concurrent jobs we run at one time. */
144     static final int MAX_JOB_CONTEXTS_COUNT = 16;
145     /** Enforce a per-app limit on scheduled jobs? */
146     private static final boolean ENFORCE_MAX_JOBS = true;
147     /** The maximum number of jobs that we allow an unprivileged app to schedule */
148     private static final int MAX_JOBS_PER_APP = 100;
149 
150     @VisibleForTesting
151     public static Clock sSystemClock = Clock.systemUTC();
152     @VisibleForTesting
153     public static Clock sUptimeMillisClock = SystemClock.uptimeMillisClock();
154     @VisibleForTesting
155     public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
156 
157     /** Global local for all job scheduler state. */
158     final Object mLock = new Object();
159     /** Master list of jobs. */
160     final JobStore mJobs;
161     /** Tracking the standby bucket state of each app */
162     final StandbyTracker mStandbyTracker;
163     /** Tracking amount of time each package runs for. */
164     final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
165     final JobConcurrencyManager mConcurrencyManager;
166 
167     static final int MSG_JOB_EXPIRED = 0;
168     static final int MSG_CHECK_JOB = 1;
169     static final int MSG_STOP_JOB = 2;
170     static final int MSG_CHECK_JOB_GREEDY = 3;
171     static final int MSG_UID_STATE_CHANGED = 4;
172     static final int MSG_UID_GONE = 5;
173     static final int MSG_UID_ACTIVE = 6;
174     static final int MSG_UID_IDLE = 7;
175 
176     /**
177      * Track Services that have currently active or pending jobs. The index is provided by
178      * {@link JobStatus#getServiceToken()}
179      */
180     final List<JobServiceContext> mActiveServices = new ArrayList<>();
181 
182     /** List of controllers that will notify this service of updates to jobs. */
183     final List<StateController> mControllers;
184     /** Need direct access to this for testing. */
185     private final BatteryController mBatteryController;
186     /** Need direct access to this for testing. */
187     private final StorageController mStorageController;
188     /** Need directly for sending uid state changes */
189     private final DeviceIdleJobsController mDeviceIdleJobsController;
190     /** Need directly for receiving thermal events */
191     private IThermalService mThermalService;
192     /** Thermal constraint. */
193     @GuardedBy("mLock")
194     private boolean mThermalConstraint = false;
195 
196     /**
197      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
198      * when ready to execute them.
199      */
200     final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
201 
202     int[] mStartedUsers = EmptyArray.INT;
203 
204     final JobHandler mHandler;
205     final JobSchedulerStub mJobSchedulerStub;
206 
207     PackageManagerInternal mLocalPM;
208     ActivityManagerInternal mActivityManagerInternal;
209     IBatteryStats mBatteryStats;
210     DeviceIdleController.LocalService mLocalDeviceIdleController;
211     AppStateTracker mAppStateTracker;
212     final UsageStatsManagerInternal mUsageStats;
213 
214     /**
215      * Set to true once we are allowed to run third party apps.
216      */
217     boolean mReadyToRock;
218 
219     /**
220      * What we last reported to DeviceIdleController about whether we are active.
221      */
222     boolean mReportedActive;
223 
224     /**
225      * Are we currently in device-wide standby parole?
226      */
227     volatile boolean mInParole;
228 
229     /**
230      * A mapping of which uids are currently in the foreground to their effective priority.
231      */
232     final SparseIntArray mUidPriorityOverride = new SparseIntArray();
233 
234     /**
235      * Which uids are currently performing backups, so we shouldn't allow their jobs to run.
236      */
237     final SparseIntArray mBackingUpUids = new SparseIntArray();
238 
239     /**
240      * Count standby heartbeats, and keep track of which beat each bucket's jobs will
241      * next become runnable.  Index into this array is by normalized bucket:
242      * { ACTIVE, WORKING, FREQUENT, RARE, NEVER }.  The ACTIVE and NEVER bucket
243      * milestones are not updated: ACTIVE apps get jobs whenever they ask for them,
244      * and NEVER apps don't get them at all.
245      */
246     final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE };
247     long mHeartbeat = 0;
248     long mLastHeartbeatTime = sElapsedRealtimeClock.millis();
249 
250     /**
251      * Named indices into the STANDBY_BEATS array, for clarity in referring to
252      * specific buckets' bookkeeping.
253      */
254     public static final int ACTIVE_INDEX = 0;
255     public static final int WORKING_INDEX = 1;
256     public static final int FREQUENT_INDEX = 2;
257     public static final int RARE_INDEX = 3;
258     public static final int NEVER_INDEX = 4;
259 
260     /**
261      * Bookkeeping about when jobs last run.  We keep our own record in heartbeat time,
262      * rather than rely on Usage Stats' timestamps, because heartbeat time can be
263      * manipulated for testing purposes and we need job runnability to track that rather
264      * than real time.
265      *
266      * Outer SparseArray slices by user handle; inner map of package name to heartbeat
267      * is a HashMap<> rather than ArrayMap<> because we expect O(hundreds) of keys
268      * and it will be accessed in a known-hot code path.
269      */
270     final SparseArray<HashMap<String, Long>> mLastJobHeartbeats = new SparseArray<>();
271 
272     static final String HEARTBEAT_TAG = "*job.heartbeat*";
273     final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener();
274 
275     // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
276 
277     private class ConstantsObserver extends ContentObserver {
278         private ContentResolver mResolver;
279 
ConstantsObserver(Handler handler)280         public ConstantsObserver(Handler handler) {
281             super(handler);
282         }
283 
start(ContentResolver resolver)284         public void start(ContentResolver resolver) {
285             mResolver = resolver;
286             mResolver.registerContentObserver(Settings.Global.getUriFor(
287                     Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
288             updateConstants();
289         }
290 
291         @Override
onChange(boolean selfChange, Uri uri)292         public void onChange(boolean selfChange, Uri uri) {
293             updateConstants();
294         }
295 
updateConstants()296         private void updateConstants() {
297             synchronized (mLock) {
298                 try {
299                     mConstants.updateConstantsLocked(Settings.Global.getString(mResolver,
300                             Settings.Global.JOB_SCHEDULER_CONSTANTS));
301                     for (int controller = 0; controller < mControllers.size(); controller++) {
302                         final StateController sc = mControllers.get(controller);
303                         sc.onConstantsUpdatedLocked();
304                     }
305                 } catch (IllegalArgumentException e) {
306                     // Failed to parse the settings string, log this and move on
307                     // with defaults.
308                     Slog.e(TAG, "Bad jobscheduler settings", e);
309                 }
310             }
311 
312             if (mConstants.USE_HEARTBEATS) {
313                 // Reset the heartbeat alarm based on the new heartbeat duration
314                 setNextHeartbeatAlarm();
315             }
316         }
317     }
318 
319     /**
320      *  Thermal event received from Thermal Service
321      */
322     private final class ThermalStatusListener extends IThermalStatusListener.Stub {
onStatusChange(int status)323         @Override public void onStatusChange(int status) {
324             // Throttle for Temperature.THROTTLING_SEVERE and above
325             synchronized (mLock) {
326                 mThermalConstraint = status >= Temperature.THROTTLING_SEVERE;
327             }
328             onControllerStateChanged();
329         }
330     }
331 
332     static class MaxJobCounts {
333         private final KeyValueListParser.IntValue mTotal;
334         private final KeyValueListParser.IntValue mMaxBg;
335         private final KeyValueListParser.IntValue mMinBg;
336 
MaxJobCounts(int totalDefault, String totalKey, int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey)337         MaxJobCounts(int totalDefault, String totalKey,
338                 int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
339             mTotal = new KeyValueListParser.IntValue(totalKey, totalDefault);
340             mMaxBg = new KeyValueListParser.IntValue(maxBgKey, maxBgDefault);
341             mMinBg = new KeyValueListParser.IntValue(minBgKey, minBgDefault);
342         }
343 
parse(KeyValueListParser parser)344         public void parse(KeyValueListParser parser) {
345             mTotal.parse(parser);
346             mMaxBg.parse(parser);
347             mMinBg.parse(parser);
348 
349             if (mTotal.getValue() < 1) {
350                 mTotal.setValue(1);
351             } else if (mTotal.getValue() > MAX_JOB_CONTEXTS_COUNT) {
352                 mTotal.setValue(MAX_JOB_CONTEXTS_COUNT);
353             }
354 
355             if (mMaxBg.getValue() < 1) {
356                 mMaxBg.setValue(1);
357             } else if (mMaxBg.getValue() > mTotal.getValue()) {
358                 mMaxBg.setValue(mTotal.getValue());
359             }
360             if (mMinBg.getValue() < 0) {
361                 mMinBg.setValue(0);
362             } else {
363                 if (mMinBg.getValue() > mMaxBg.getValue()) {
364                     mMinBg.setValue(mMaxBg.getValue());
365                 }
366                 if (mMinBg.getValue() >= mTotal.getValue()) {
367                     mMinBg.setValue(mTotal.getValue() - 1);
368                 }
369             }
370         }
371 
372         /** Total number of jobs to run simultaneously. */
getMaxTotal()373         public int getMaxTotal() {
374             return mTotal.getValue();
375         }
376 
377         /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
getMaxBg()378         public int getMaxBg() {
379             return mMaxBg.getValue();
380         }
381 
382         /**
383          * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
384          * pending, rather than always running the TOTAL number of FG jobs.
385          */
getMinBg()386         public int getMinBg() {
387             return mMinBg.getValue();
388         }
389 
dump(PrintWriter pw, String prefix)390         public void dump(PrintWriter pw, String prefix) {
391             mTotal.dump(pw, prefix);
392             mMaxBg.dump(pw, prefix);
393             mMinBg.dump(pw, prefix);
394         }
395 
dumpProto(ProtoOutputStream proto, long fieldId)396         public void dumpProto(ProtoOutputStream proto, long fieldId) {
397             final long token = proto.start(fieldId);
398             mTotal.dumpProto(proto, MaxJobCountsProto.TOTAL_JOBS);
399             mMaxBg.dumpProto(proto, MaxJobCountsProto.MAX_BG);
400             mMinBg.dumpProto(proto, MaxJobCountsProto.MIN_BG);
401             proto.end(token);
402         }
403     }
404 
405     /** {@link MaxJobCounts} for each memory trim level. */
406     static class MaxJobCountsPerMemoryTrimLevel {
407         public final MaxJobCounts normal;
408         public final MaxJobCounts moderate;
409         public final MaxJobCounts low;
410         public final MaxJobCounts critical;
411 
MaxJobCountsPerMemoryTrimLevel( MaxJobCounts normal, MaxJobCounts moderate, MaxJobCounts low, MaxJobCounts critical)412         MaxJobCountsPerMemoryTrimLevel(
413                 MaxJobCounts normal,
414                 MaxJobCounts moderate, MaxJobCounts low,
415                 MaxJobCounts critical) {
416             this.normal = normal;
417             this.moderate = moderate;
418             this.low = low;
419             this.critical = critical;
420         }
421 
dumpProto(ProtoOutputStream proto, long fieldId)422         public void dumpProto(ProtoOutputStream proto, long fieldId) {
423             final long token = proto.start(fieldId);
424             normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
425             moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
426             low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
427             critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
428             proto.end(token);
429         }
430     }
431 
432     /**
433      * All times are in milliseconds. These constants are kept synchronized with the system
434      * global Settings. Any access to this class or its fields should be done while
435      * holding the JobSchedulerService.mLock lock.
436      */
437     public static class Constants {
438         // Key names stored in the settings value.
439         private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
440         private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
441         private static final String KEY_MIN_BATTERY_NOT_LOW_COUNT = "min_battery_not_low_count";
442         private static final String KEY_MIN_STORAGE_NOT_LOW_COUNT = "min_storage_not_low_count";
443         private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
444         private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
445         private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
446         private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
447         private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
448 
449         // The following values used to be used on P and below. Do not reuse them.
450         private static final String DEPRECATED_KEY_FG_JOB_COUNT = "fg_job_count";
451         private static final String DEPRECATED_KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
452         private static final String DEPRECATED_KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
453         private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
454         private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
455 
456         private static final String KEY_MAX_STANDARD_RESCHEDULE_COUNT
457                 = "max_standard_reschedule_count";
458         private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
459         private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
460         private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
461         private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time";
462         private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
463         private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
464         private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
465         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
466         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
467         private static final String KEY_USE_HEARTBEATS = "use_heartbeats";
468 
469         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
470         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
471         private static final int DEFAULT_MIN_BATTERY_NOT_LOW_COUNT = 1;
472         private static final int DEFAULT_MIN_STORAGE_NOT_LOW_COUNT = 1;
473         private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
474         private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
475         private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
476         private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
477         private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
478         private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE;
479         private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
480         private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
481         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
482         private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L;
483         private static final int DEFAULT_STANDBY_WORKING_BEATS = 11;  // ~ 2 hours, with 11min beats
484         private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 43; // ~ 8 hours
485         private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
486         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
487         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
488         private static final boolean DEFAULT_USE_HEARTBEATS = false;
489 
490         /**
491          * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
492          * early.
493          */
494         int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT;
495         /**
496          * Minimum # of charging jobs that must be ready in order to force the JMS to schedule
497          * things early.
498          */
499         int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT;
500         /**
501          * Minimum # of "battery not low" jobs that must be ready in order to force the JMS to
502          * schedule things early.
503          */
504         int MIN_BATTERY_NOT_LOW_COUNT = DEFAULT_MIN_BATTERY_NOT_LOW_COUNT;
505         /**
506          * Minimum # of "storage not low" jobs that must be ready in order to force the JMS to
507          * schedule things early.
508          */
509         int MIN_STORAGE_NOT_LOW_COUNT = DEFAULT_MIN_STORAGE_NOT_LOW_COUNT;
510         /**
511          * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
512          * things early.  1 == Run connectivity jobs as soon as ready.
513          */
514         int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT;
515         /**
516          * Minimum # of content trigger jobs that must be ready in order to force the JMS to
517          * schedule things early.
518          */
519         int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT;
520         /**
521          * Minimum # of jobs (with no particular constraints) for which the JMS will be happy
522          * running some work early.  This (and thus the other min counts) is now set to 1, to
523          * prevent any batching at this level.  Since we now do batching through doze, that is
524          * a much better mechanism.
525          */
526         int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT;
527         /**
528          * This is the job execution factor that is considered to be heavy use of the system.
529          */
530         float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR;
531         /**
532          * This is the job execution factor that is considered to be moderate use of the system.
533          */
534         float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
535 
536         // Max job counts for screen on / off, for each memory trim level.
537         final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
538                 new MaxJobCountsPerMemoryTrimLevel(
539                         new MaxJobCounts(
540                                 8, "max_job_total_on_normal",
541                                 6, "max_job_max_bg_on_normal",
542                                 2, "max_job_min_bg_on_normal"),
543                         new MaxJobCounts(
544                                 8, "max_job_total_on_moderate",
545                                 4, "max_job_max_bg_on_moderate",
546                                 2, "max_job_min_bg_on_moderate"),
547                         new MaxJobCounts(
548                                 5, "max_job_total_on_low",
549                                 1, "max_job_max_bg_on_low",
550                                 1, "max_job_min_bg_on_low"),
551                         new MaxJobCounts(
552                                 5, "max_job_total_on_critical",
553                                 1, "max_job_max_bg_on_critical",
554                                 1, "max_job_min_bg_on_critical"));
555 
556         final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
557                 new MaxJobCountsPerMemoryTrimLevel(
558                         new MaxJobCounts(
559                                 10, "max_job_total_off_normal",
560                                 6, "max_job_max_bg_off_normal",
561                                 2, "max_job_min_bg_off_normal"),
562                         new MaxJobCounts(
563                                 10, "max_job_total_off_moderate",
564                                 4, "max_job_max_bg_off_moderate",
565                                 2, "max_job_min_bg_off_moderate"),
566                         new MaxJobCounts(
567                                 5, "max_job_total_off_low",
568                                 1, "max_job_max_bg_off_low",
569                                 1, "max_job_min_bg_off_low"),
570                         new MaxJobCounts(
571                                 5, "max_job_total_off_critical",
572                                 1, "max_job_max_bg_off_critical",
573                                 1, "max_job_min_bg_off_critical"));
574 
575 
576         /** Wait for this long after screen off before increasing the job concurrency. */
577         final KeyValueListParser.IntValue SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
578                 new KeyValueListParser.IntValue(
579                         "screen_off_job_concurrency_increase_delay_ms", 30_000);
580 
581         /**
582          * The maximum number of times we allow a job to have itself rescheduled before
583          * giving up on it, for standard jobs.
584          */
585         int MAX_STANDARD_RESCHEDULE_COUNT = DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT;
586         /**
587          * The maximum number of times we allow a job to have itself rescheduled before
588          * giving up on it, for jobs that are executing work.
589          */
590         int MAX_WORK_RESCHEDULE_COUNT = DEFAULT_MAX_WORK_RESCHEDULE_COUNT;
591         /**
592          * The minimum backoff time to allow for linear backoff.
593          */
594         long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME;
595         /**
596          * The minimum backoff time to allow for exponential backoff.
597          */
598         long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
599         /**
600          * How often we recalculate runnability based on apps' standby bucket assignment.
601          * This should be prime relative to common time interval lengths such as a quarter-
602          * hour or day, so that the heartbeat drifts relative to wall-clock milestones.
603          */
604         long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
605         /**
606          * Mapping: standby bucket -> number of heartbeats between each sweep of that
607          * bucket's jobs.
608          *
609          * Bucket assignments as recorded in the JobStatus objects are normalized to be
610          * indices into this array, rather than the raw constants used
611          * by AppIdleHistory.
612          */
613         final int[] STANDBY_BEATS = {
614                 0,
615                 DEFAULT_STANDBY_WORKING_BEATS,
616                 DEFAULT_STANDBY_FREQUENT_BEATS,
617                 DEFAULT_STANDBY_RARE_BEATS
618         };
619         /**
620          * The fraction of a job's running window that must pass before we
621          * consider running it when the network is congested.
622          */
623         public float CONN_CONGESTION_DELAY_FRAC = DEFAULT_CONN_CONGESTION_DELAY_FRAC;
624         /**
625          * The fraction of a prefetch job's running window that must pass before
626          * we consider matching it against a metered network.
627          */
628         public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
629         /**
630          * Whether to use heartbeats or rolling window for quota management. True will use
631          * heartbeats, false will use a rolling window.
632          */
633         public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS;
634 
635         private final KeyValueListParser mParser = new KeyValueListParser(',');
636 
updateConstantsLocked(String value)637         void updateConstantsLocked(String value) {
638             try {
639                 mParser.setString(value);
640             } catch (Exception e) {
641                 // Failed to parse the settings string, log this and move on
642                 // with defaults.
643                 Slog.e(TAG, "Bad jobscheduler settings", e);
644             }
645 
646             MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
647                     DEFAULT_MIN_IDLE_COUNT);
648             MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
649                     DEFAULT_MIN_CHARGING_COUNT);
650             MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
651                     DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
652             MIN_STORAGE_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_STORAGE_NOT_LOW_COUNT,
653                     DEFAULT_MIN_STORAGE_NOT_LOW_COUNT);
654             MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
655                     DEFAULT_MIN_CONNECTIVITY_COUNT);
656             MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
657                     DEFAULT_MIN_CONTENT_COUNT);
658             MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
659                     DEFAULT_MIN_READY_JOBS_COUNT);
660             HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
661                     DEFAULT_HEAVY_USE_FACTOR);
662             MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
663                     DEFAULT_MODERATE_USE_FACTOR);
664 
665             MAX_JOB_COUNTS_SCREEN_ON.normal.parse(mParser);
666             MAX_JOB_COUNTS_SCREEN_ON.moderate.parse(mParser);
667             MAX_JOB_COUNTS_SCREEN_ON.low.parse(mParser);
668             MAX_JOB_COUNTS_SCREEN_ON.critical.parse(mParser);
669 
670             MAX_JOB_COUNTS_SCREEN_OFF.normal.parse(mParser);
671             MAX_JOB_COUNTS_SCREEN_OFF.moderate.parse(mParser);
672             MAX_JOB_COUNTS_SCREEN_OFF.low.parse(mParser);
673             MAX_JOB_COUNTS_SCREEN_OFF.critical.parse(mParser);
674 
675             SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser);
676 
677             MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT,
678                     DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT);
679             MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT,
680                     DEFAULT_MAX_WORK_RESCHEDULE_COUNT);
681             MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME,
682                     DEFAULT_MIN_LINEAR_BACKOFF_TIME);
683             MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
684                     DEFAULT_MIN_EXP_BACKOFF_TIME);
685             STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
686                     DEFAULT_STANDBY_HEARTBEAT_TIME);
687             STANDBY_BEATS[WORKING_INDEX] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
688                     DEFAULT_STANDBY_WORKING_BEATS);
689             STANDBY_BEATS[FREQUENT_INDEX] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
690                     DEFAULT_STANDBY_FREQUENT_BEATS);
691             STANDBY_BEATS[RARE_INDEX] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
692                     DEFAULT_STANDBY_RARE_BEATS);
693             CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
694                     DEFAULT_CONN_CONGESTION_DELAY_FRAC);
695             CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
696                     DEFAULT_CONN_PREFETCH_RELAX_FRAC);
697             USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS);
698         }
699 
dump(IndentingPrintWriter pw)700         void dump(IndentingPrintWriter pw) {
701             pw.println("Settings:");
702             pw.increaseIndent();
703             pw.printPair(KEY_MIN_IDLE_COUNT, MIN_IDLE_COUNT).println();
704             pw.printPair(KEY_MIN_CHARGING_COUNT, MIN_CHARGING_COUNT).println();
705             pw.printPair(KEY_MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT).println();
706             pw.printPair(KEY_MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT).println();
707             pw.printPair(KEY_MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT).println();
708             pw.printPair(KEY_MIN_CONTENT_COUNT, MIN_CONTENT_COUNT).println();
709             pw.printPair(KEY_MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT).println();
710             pw.printPair(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
711             pw.printPair(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
712 
713             MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
714             MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
715             MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
716             MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
717 
718             MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
719             MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
720             MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
721             MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
722 
723             SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, "");
724 
725             pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println();
726             pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
727             pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
728             pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println();
729             pw.printPair(KEY_STANDBY_HEARTBEAT_TIME, STANDBY_HEARTBEAT_TIME).println();
730             pw.print("standby_beats={");
731             pw.print(STANDBY_BEATS[0]);
732             for (int i = 1; i < STANDBY_BEATS.length; i++) {
733                 pw.print(", ");
734                 pw.print(STANDBY_BEATS[i]);
735             }
736             pw.println('}');
737             pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
738             pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
739             pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println();
740 
741             pw.decreaseIndent();
742         }
743 
dump(ProtoOutputStream proto)744         void dump(ProtoOutputStream proto) {
745             proto.write(ConstantsProto.MIN_IDLE_COUNT, MIN_IDLE_COUNT);
746             proto.write(ConstantsProto.MIN_CHARGING_COUNT, MIN_CHARGING_COUNT);
747             proto.write(ConstantsProto.MIN_BATTERY_NOT_LOW_COUNT, MIN_BATTERY_NOT_LOW_COUNT);
748             proto.write(ConstantsProto.MIN_STORAGE_NOT_LOW_COUNT, MIN_STORAGE_NOT_LOW_COUNT);
749             proto.write(ConstantsProto.MIN_CONNECTIVITY_COUNT, MIN_CONNECTIVITY_COUNT);
750             proto.write(ConstantsProto.MIN_CONTENT_COUNT, MIN_CONTENT_COUNT);
751             proto.write(ConstantsProto.MIN_READY_JOBS_COUNT, MIN_READY_JOBS_COUNT);
752             proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
753             proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
754 
755             MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
756             MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
757 
758             SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto,
759                     ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
760 
761             proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT);
762             proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
763             proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
764             proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
765             proto.write(ConstantsProto.STANDBY_HEARTBEAT_TIME_MS, STANDBY_HEARTBEAT_TIME);
766             for (int period : STANDBY_BEATS) {
767                 proto.write(ConstantsProto.STANDBY_BEATS, period);
768             }
769             proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
770             proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
771             proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS);
772         }
773     }
774 
775     final Constants mConstants;
776     final ConstantsObserver mConstantsObserver;
777 
778     static final Comparator<JobStatus> mEnqueueTimeComparator = (o1, o2) -> {
779         if (o1.enqueueTime < o2.enqueueTime) {
780             return -1;
781         }
782         return o1.enqueueTime > o2.enqueueTime ? 1 : 0;
783     };
784 
addOrderedItem(ArrayList<T> array, T newItem, Comparator<T> comparator)785     static <T> void addOrderedItem(ArrayList<T> array, T newItem, Comparator<T> comparator) {
786         int where = Collections.binarySearch(array, newItem, comparator);
787         if (where < 0) {
788             where = ~where;
789         }
790         array.add(where, newItem);
791     }
792 
793     /**
794      * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
795      * still clean up. On reinstall the package will have a new uid.
796      */
797     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
798         @Override
799         public void onReceive(Context context, Intent intent) {
800             final String action = intent.getAction();
801             if (DEBUG) {
802                 Slog.d(TAG, "Receieved: " + action);
803             }
804             final String pkgName = getPackageName(intent);
805             final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
806 
807             if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
808                 // Purge the app's jobs if the whole package was just disabled.  When this is
809                 // the case the component name will be a bare package name.
810                 if (pkgName != null && pkgUid != -1) {
811                     final String[] changedComponents = intent.getStringArrayExtra(
812                             Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
813                     if (changedComponents != null) {
814                         for (String component : changedComponents) {
815                             if (component.equals(pkgName)) {
816                                 if (DEBUG) {
817                                     Slog.d(TAG, "Package state change: " + pkgName);
818                                 }
819                                 try {
820                                     final int userId = UserHandle.getUserId(pkgUid);
821                                     IPackageManager pm = AppGlobals.getPackageManager();
822                                     final int state = pm.getApplicationEnabledSetting(pkgName, userId);
823                                     if (state == COMPONENT_ENABLED_STATE_DISABLED
824                                             || state ==  COMPONENT_ENABLED_STATE_DISABLED_USER) {
825                                         if (DEBUG) {
826                                             Slog.d(TAG, "Removing jobs for package " + pkgName
827                                                     + " in user " + userId);
828                                         }
829                                         cancelJobsForPackageAndUid(pkgName, pkgUid,
830                                                 "app disabled");
831                                     }
832                                 } catch (RemoteException|IllegalArgumentException e) {
833                                     /*
834                                      * IllegalArgumentException means that the package doesn't exist.
835                                      * This arises when PACKAGE_CHANGED broadcast delivery has lagged
836                                      * behind outright uninstall, so by the time we try to act it's gone.
837                                      * We don't need to act on this PACKAGE_CHANGED when this happens;
838                                      * we'll get a PACKAGE_REMOVED later and clean up then.
839                                      *
840                                      * RemoteException can't actually happen; the package manager is
841                                      * running in this same process.
842                                      */
843                                 }
844                                 break;
845                             }
846                         }
847                         if (DEBUG) {
848                             Slog.d(TAG, "Something in " + pkgName
849                                     + " changed. Reevaluating controller states.");
850                         }
851                         synchronized (mLock) {
852                             for (int c = mControllers.size() - 1; c >= 0; --c) {
853                                 mControllers.get(c).reevaluateStateLocked(pkgUid);
854                             }
855                         }
856                     }
857                 } else {
858                     Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
859                 }
860             } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
861                 // If this is an outright uninstall rather than the first half of an
862                 // app update sequence, cancel the jobs associated with the app.
863                 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
864                     int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
865                     if (DEBUG) {
866                         Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
867                     }
868                     cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled");
869                     synchronized (mLock) {
870                         for (int c = 0; c < mControllers.size(); ++c) {
871                             mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
872                         }
873                     }
874                 }
875             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
876                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
877                 if (DEBUG) {
878                     Slog.d(TAG, "Removing jobs for user: " + userId);
879                 }
880                 cancelJobsForUser(userId);
881                 synchronized (mLock) {
882                     for (int c = 0; c < mControllers.size(); ++c) {
883                         mControllers.get(c).onUserRemovedLocked(userId);
884                     }
885                 }
886             } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
887                 // Has this package scheduled any jobs, such that we will take action
888                 // if it were to be force-stopped?
889                 if (pkgUid != -1) {
890                     List<JobStatus> jobsForUid;
891                     synchronized (mLock) {
892                         jobsForUid = mJobs.getJobsByUid(pkgUid);
893                     }
894                     for (int i = jobsForUid.size() - 1; i >= 0; i--) {
895                         if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
896                             if (DEBUG) {
897                                 Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
898                                         + pkgUid + " has jobs");
899                             }
900                             setResultCode(Activity.RESULT_OK);
901                             break;
902                         }
903                     }
904                 }
905             } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
906                 // possible force-stop
907                 if (pkgUid != -1) {
908                     if (DEBUG) {
909                         Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
910                     }
911                     cancelJobsForPackageAndUid(pkgName, pkgUid, "app force stopped");
912                 }
913             }
914         }
915     };
916 
getPackageName(Intent intent)917     private String getPackageName(Intent intent) {
918         Uri uri = intent.getData();
919         String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
920         return pkg;
921     }
922 
923     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
924         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
925             mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
926         }
927 
928         @Override public void onUidGone(int uid, boolean disabled) {
929             mHandler.obtainMessage(MSG_UID_GONE, uid, disabled ? 1 : 0).sendToTarget();
930         }
931 
932         @Override public void onUidActive(int uid) throws RemoteException {
933             mHandler.obtainMessage(MSG_UID_ACTIVE, uid, 0).sendToTarget();
934         }
935 
936         @Override public void onUidIdle(int uid, boolean disabled) {
937             mHandler.obtainMessage(MSG_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget();
938         }
939 
940         @Override public void onUidCachedChanged(int uid, boolean cached) {
941         }
942     };
943 
getTestableContext()944     public Context getTestableContext() {
945         return getContext();
946     }
947 
getLock()948     public Object getLock() {
949         return mLock;
950     }
951 
getJobStore()952     public JobStore getJobStore() {
953         return mJobs;
954     }
955 
getConstants()956     public Constants getConstants() {
957         return mConstants;
958     }
959 
isChainedAttributionEnabled()960     public boolean isChainedAttributionEnabled() {
961         return WorkSource.isChainedBatteryAttributionEnabled(getContext());
962     }
963 
964     @Override
onStartUser(int userHandle)965     public void onStartUser(int userHandle) {
966         synchronized (mLock) {
967             mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
968         }
969         // Let's kick any outstanding jobs for this user.
970         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
971     }
972 
973     @Override
onUnlockUser(int userHandle)974     public void onUnlockUser(int userHandle) {
975         // Let's kick any outstanding jobs for this user.
976         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
977     }
978 
979     @Override
onStopUser(int userHandle)980     public void onStopUser(int userHandle) {
981         synchronized (mLock) {
982             mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
983         }
984     }
985 
986     /**
987      * Return whether an UID is active or idle.
988      */
isUidActive(int uid)989     private boolean isUidActive(int uid) {
990         return mAppStateTracker.isUidActiveSynced(uid);
991     }
992 
993     private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
994 
scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag)995     public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
996             int userId, String tag) {
997         try {
998             if (ActivityManager.getService().isAppStartModeDisabled(uId,
999                     job.getService().getPackageName())) {
1000                 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
1001                         + " -- package not allowed to start");
1002                 return JobScheduler.RESULT_FAILURE;
1003             }
1004         } catch (RemoteException e) {
1005         }
1006 
1007         synchronized (mLock) {
1008             final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
1009 
1010             if (work != null && toCancel != null) {
1011                 // Fast path: we are adding work to an existing job, and the JobInfo is not
1012                 // changing.  We can just directly enqueue this work in to the job.
1013                 if (toCancel.getJob().equals(job)) {
1014 
1015                     toCancel.enqueueWorkLocked(ActivityManager.getService(), work);
1016 
1017                     // If any of work item is enqueued when the source is in the foreground,
1018                     // exempt the entire job.
1019                     toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
1020 
1021                     return JobScheduler.RESULT_SUCCESS;
1022                 }
1023             }
1024 
1025             JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
1026 
1027             // Give exemption if the source is in the foreground just now.
1028             // Note if it's a sync job, this method is called on the handler so it's not exactly
1029             // the state when requestSync() was called, but that should be fine because of the
1030             // 1 minute foreground grace period.
1031             jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);
1032 
1033             if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
1034             // Jobs on behalf of others don't apply to the per-app job cap
1035             if (ENFORCE_MAX_JOBS && packageName == null) {
1036                 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
1037                     Slog.w(TAG, "Too many jobs for uid " + uId);
1038                     throw new IllegalStateException("Apps may not schedule more than "
1039                                 + MAX_JOBS_PER_APP + " distinct jobs");
1040                 }
1041             }
1042 
1043             // This may throw a SecurityException.
1044             jobStatus.prepareLocked(ActivityManager.getService());
1045 
1046             if (work != null) {
1047                 // If work has been supplied, enqueue it into the new job.
1048                 jobStatus.enqueueWorkLocked(ActivityManager.getService(), work);
1049             }
1050 
1051             if (toCancel != null) {
1052                 // Implicitly replaces the existing job record with the new instance
1053                 cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
1054             } else {
1055                 startTrackingJobLocked(jobStatus, null);
1056             }
1057             StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED,
1058                     uId, null, jobStatus.getBatteryName(),
1059                     StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
1060                     JobProtoEnums.STOP_REASON_CANCELLED, jobStatus.getStandbyBucket(),
1061                     jobStatus.getJobId());
1062 
1063             // If the job is immediately ready to run, then we can just immediately
1064             // put it in the pending list and try to schedule it.  This is especially
1065             // important for jobs with a 0 deadline constraint, since they will happen a fair
1066             // amount, we want to handle them as quickly as possible, and semantically we want to
1067             // make sure we have started holding the wake lock for the job before returning to
1068             // the caller.
1069             // If the job is not yet ready to run, there is nothing more to do -- we are
1070             // now just waiting for one of its controllers to change state and schedule
1071             // the job appropriately.
1072             if (isReadyToBeExecutedLocked(jobStatus)) {
1073                 // This is a new job, we can just immediately put it on the pending
1074                 // list and try to run it.
1075                 mJobPackageTracker.notePending(jobStatus);
1076                 addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
1077                 maybeRunPendingJobsLocked();
1078             } else {
1079                 evaluateControllerStatesLocked(jobStatus);
1080             }
1081         }
1082         return JobScheduler.RESULT_SUCCESS;
1083     }
1084 
getPendingJobs(int uid)1085     public List<JobInfo> getPendingJobs(int uid) {
1086         synchronized (mLock) {
1087             List<JobStatus> jobs = mJobs.getJobsByUid(uid);
1088             ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
1089             for (int i = jobs.size() - 1; i >= 0; i--) {
1090                 JobStatus job = jobs.get(i);
1091                 outList.add(job.getJob());
1092             }
1093             return outList;
1094         }
1095     }
1096 
getPendingJob(int uid, int jobId)1097     public JobInfo getPendingJob(int uid, int jobId) {
1098         synchronized (mLock) {
1099             List<JobStatus> jobs = mJobs.getJobsByUid(uid);
1100             for (int i = jobs.size() - 1; i >= 0; i--) {
1101                 JobStatus job = jobs.get(i);
1102                 if (job.getJobId() == jobId) {
1103                     return job.getJob();
1104                 }
1105             }
1106             return null;
1107         }
1108     }
1109 
cancelJobsForUser(int userHandle)1110     void cancelJobsForUser(int userHandle) {
1111         synchronized (mLock) {
1112             final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
1113             for (int i=0; i<jobsForUser.size(); i++) {
1114                 JobStatus toRemove = jobsForUser.get(i);
1115                 cancelJobImplLocked(toRemove, null, "user removed");
1116             }
1117         }
1118     }
1119 
cancelJobsForNonExistentUsers()1120     private void cancelJobsForNonExistentUsers() {
1121         UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
1122         synchronized (mLock) {
1123             mJobs.removeJobsOfNonUsers(umi.getUserIds());
1124         }
1125     }
1126 
cancelJobsForPackageAndUid(String pkgName, int uid, String reason)1127     void cancelJobsForPackageAndUid(String pkgName, int uid, String reason) {
1128         if ("android".equals(pkgName)) {
1129             Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
1130             return;
1131         }
1132         synchronized (mLock) {
1133             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
1134             for (int i = jobsForUid.size() - 1; i >= 0; i--) {
1135                 final JobStatus job = jobsForUid.get(i);
1136                 if (job.getSourcePackageName().equals(pkgName)) {
1137                     cancelJobImplLocked(job, null, reason);
1138                 }
1139             }
1140         }
1141     }
1142 
1143     /**
1144      * Entry point from client to cancel all jobs originating from their uid.
1145      * This will remove the job from the master list, and cancel the job if it was staged for
1146      * execution or being executed.
1147      * @param uid Uid to check against for removal of a job.
1148      *
1149      */
cancelJobsForUid(int uid, String reason)1150     public boolean cancelJobsForUid(int uid, String reason) {
1151         if (uid == Process.SYSTEM_UID) {
1152             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
1153             return false;
1154         }
1155 
1156         boolean jobsCanceled = false;
1157         synchronized (mLock) {
1158             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
1159             for (int i=0; i<jobsForUid.size(); i++) {
1160                 JobStatus toRemove = jobsForUid.get(i);
1161                 cancelJobImplLocked(toRemove, null, reason);
1162                 jobsCanceled = true;
1163             }
1164         }
1165         return jobsCanceled;
1166     }
1167 
1168     /**
1169      * Entry point from client to cancel the job corresponding to the jobId provided.
1170      * This will remove the job from the master list, and cancel the job if it was staged for
1171      * execution or being executed.
1172      * @param uid Uid of the calling client.
1173      * @param jobId Id of the job, provided at schedule-time.
1174      */
cancelJob(int uid, int jobId, int callingUid)1175     public boolean cancelJob(int uid, int jobId, int callingUid) {
1176         JobStatus toCancel;
1177         synchronized (mLock) {
1178             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
1179             if (toCancel != null) {
1180                 cancelJobImplLocked(toCancel, null,
1181                         "cancel() called by app, callingUid=" + callingUid
1182                         + " uid=" + uid + " jobId=" + jobId);
1183             }
1184             return (toCancel != null);
1185         }
1186     }
1187 
1188     /**
1189      * Cancel the given job, stopping it if it's currently executing.  If {@code incomingJob}
1190      * is null, the cancelled job is removed outright from the system.  If
1191      * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of
1192      * currently scheduled jobs.
1193      */
cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason)1194     private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, String reason) {
1195         if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
1196         cancelled.unprepareLocked(ActivityManager.getService());
1197         stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
1198         // Remove from pending queue.
1199         if (mPendingJobs.remove(cancelled)) {
1200             mJobPackageTracker.noteNonpending(cancelled);
1201         }
1202         // Cancel if running.
1203         stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED, reason);
1204         // If this is a replacement, bring in the new version of the job
1205         if (incomingJob != null) {
1206             if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
1207             startTrackingJobLocked(incomingJob, cancelled);
1208         }
1209         reportActiveLocked();
1210     }
1211 
updateUidState(int uid, int procState)1212     void updateUidState(int uid, int procState) {
1213         synchronized (mLock) {
1214             if (procState == ActivityManager.PROCESS_STATE_TOP) {
1215                 // Only use this if we are exactly the top app.  All others can live
1216                 // with just the foreground priority.  This means that persistent processes
1217                 // can never be the top app priority...  that is fine.
1218                 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
1219             } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
1220                 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_SERVICE);
1221             } else if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
1222                 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE);
1223             } else {
1224                 mUidPriorityOverride.delete(uid);
1225             }
1226         }
1227     }
1228 
1229     @Override
onDeviceIdleStateChanged(boolean deviceIdle)1230     public void onDeviceIdleStateChanged(boolean deviceIdle) {
1231         synchronized (mLock) {
1232             if (DEBUG) {
1233                 Slog.d(TAG, "Doze state changed: " + deviceIdle);
1234             }
1235             if (deviceIdle) {
1236                 // When becoming idle, make sure no jobs are actively running,
1237                 // except those using the idle exemption flag.
1238                 for (int i=0; i<mActiveServices.size(); i++) {
1239                     JobServiceContext jsc = mActiveServices.get(i);
1240                     final JobStatus executing = jsc.getRunningJobLocked();
1241                     if (executing != null
1242                             && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
1243                         jsc.cancelExecutingJobLocked(JobParameters.REASON_DEVICE_IDLE,
1244                                 "cancelled due to doze");
1245                     }
1246                 }
1247             } else {
1248                 // When coming out of idle, allow thing to start back up.
1249                 if (mReadyToRock) {
1250                     if (mLocalDeviceIdleController != null) {
1251                         if (!mReportedActive) {
1252                             mReportedActive = true;
1253                             mLocalDeviceIdleController.setJobsActive(true);
1254                         }
1255                     }
1256                     mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
1257                 }
1258             }
1259         }
1260     }
1261 
reportActiveLocked()1262     void reportActiveLocked() {
1263         // active is true if pending queue contains jobs OR some job is running.
1264         boolean active = mPendingJobs.size() > 0;
1265         if (mPendingJobs.size() <= 0) {
1266             for (int i=0; i<mActiveServices.size(); i++) {
1267                 final JobServiceContext jsc = mActiveServices.get(i);
1268                 final JobStatus job = jsc.getRunningJobLocked();
1269                 if (job != null
1270                         && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
1271                         && !job.dozeWhitelisted
1272                         && !job.uidActive) {
1273                     // We will report active if we have a job running and it is not an exception
1274                     // due to being in the foreground or whitelisted.
1275                     active = true;
1276                     break;
1277                 }
1278             }
1279         }
1280 
1281         if (mReportedActive != active) {
1282             mReportedActive = active;
1283             if (mLocalDeviceIdleController != null) {
1284                 mLocalDeviceIdleController.setJobsActive(active);
1285             }
1286         }
1287     }
1288 
reportAppUsage(String packageName, int userId)1289     void reportAppUsage(String packageName, int userId) {
1290         // This app just transitioned into interactive use or near equivalent, so we should
1291         // take a look at its job state for feedback purposes.
1292     }
1293 
1294     /**
1295      * Initializes the system service.
1296      * <p>
1297      * Subclasses must define a single argument constructor that accepts the context
1298      * and passes it to super.
1299      * </p>
1300      *
1301      * @param context The system server context.
1302      */
JobSchedulerService(Context context)1303     public JobSchedulerService(Context context) {
1304         super(context);
1305 
1306         mLocalPM = LocalServices.getService(PackageManagerInternal.class);
1307         mActivityManagerInternal = Preconditions.checkNotNull(
1308                 LocalServices.getService(ActivityManagerInternal.class));
1309 
1310         mHandler = new JobHandler(context.getMainLooper());
1311         mConstants = new Constants();
1312         mConstantsObserver = new ConstantsObserver(mHandler);
1313         mJobSchedulerStub = new JobSchedulerStub();
1314 
1315         mConcurrencyManager = new JobConcurrencyManager(this);
1316 
1317         // Set up the app standby bucketing tracker
1318         mStandbyTracker = new StandbyTracker();
1319         mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
1320         mUsageStats.addAppIdleStateChangeListener(mStandbyTracker);
1321 
1322         // The job store needs to call back
1323         publishLocalService(JobSchedulerInternal.class, new LocalService());
1324 
1325         // Initialize the job store and set up any persisted jobs
1326         mJobs = JobStore.initAndGet(this);
1327 
1328         // Create the controllers.
1329         mControllers = new ArrayList<StateController>();
1330         mControllers.add(new ConnectivityController(this));
1331         mControllers.add(new TimeController(this));
1332         mControllers.add(new IdleController(this));
1333         mBatteryController = new BatteryController(this);
1334         mControllers.add(mBatteryController);
1335         mStorageController = new StorageController(this);
1336         mControllers.add(mStorageController);
1337         mControllers.add(new BackgroundJobsController(this));
1338         mControllers.add(new ContentObserverController(this));
1339         mDeviceIdleJobsController = new DeviceIdleJobsController(this);
1340         mControllers.add(mDeviceIdleJobsController);
1341         mControllers.add(new QuotaController(this));
1342 
1343         // If the job store determined that it can't yet reschedule persisted jobs,
1344         // we need to start watching the clock.
1345         if (!mJobs.jobTimesInflatedValid()) {
1346             Slog.w(TAG, "!!! RTC not yet good; tracking time updates for job scheduling");
1347             context.registerReceiver(mTimeSetReceiver, new IntentFilter(Intent.ACTION_TIME_CHANGED));
1348         }
1349     }
1350 
1351     private final BroadcastReceiver mTimeSetReceiver = new BroadcastReceiver() {
1352         @Override
1353         public void onReceive(Context context, Intent intent) {
1354             if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
1355                 // When we reach clock sanity, recalculate the temporal windows
1356                 // of all affected jobs.
1357                 if (mJobs.clockNowValidToInflate(sSystemClock.millis())) {
1358                     Slog.i(TAG, "RTC now valid; recalculating persisted job windows");
1359 
1360                     // We've done our job now, so stop watching the time.
1361                     context.unregisterReceiver(this);
1362 
1363                     // And kick off the work to update the affected jobs, using a secondary
1364                     // thread instead of chugging away here on the main looper thread.
1365                     FgThread.getHandler().post(mJobTimeUpdater);
1366                 }
1367             }
1368         }
1369     };
1370 
1371     private final Runnable mJobTimeUpdater = () -> {
1372         final ArrayList<JobStatus> toRemove = new ArrayList<>();
1373         final ArrayList<JobStatus> toAdd = new ArrayList<>();
1374         synchronized (mLock) {
1375             // Note: we intentionally both look up the existing affected jobs and replace them
1376             // with recalculated ones inside the same lock lifetime.
1377             getJobStore().getRtcCorrectedJobsLocked(toAdd, toRemove);
1378 
1379             // Now, at each position [i], we have both the existing JobStatus
1380             // and the one that replaces it.
1381             final int N = toAdd.size();
1382             for (int i = 0; i < N; i++) {
1383                 final JobStatus oldJob = toRemove.get(i);
1384                 final JobStatus newJob = toAdd.get(i);
1385                 if (DEBUG) {
1386                     Slog.v(TAG, "  replacing " + oldJob + " with " + newJob);
1387                 }
1388                 cancelJobImplLocked(oldJob, newJob, "deferred rtc calculation");
1389             }
1390         }
1391     };
1392 
1393     @Override
onStart()1394     public void onStart() {
1395         publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
1396     }
1397 
1398     @Override
onBootPhase(int phase)1399     public void onBootPhase(int phase) {
1400         if (PHASE_SYSTEM_SERVICES_READY == phase) {
1401             mConstantsObserver.start(getContext().getContentResolver());
1402             for (StateController controller : mControllers) {
1403                 controller.onSystemServicesReady();
1404             }
1405 
1406             mAppStateTracker = Preconditions.checkNotNull(
1407                     LocalServices.getService(AppStateTracker.class));
1408             if (mConstants.USE_HEARTBEATS) {
1409                 setNextHeartbeatAlarm();
1410             }
1411 
1412             // Register br for package removals and user removals.
1413             final IntentFilter filter = new IntentFilter();
1414             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1415             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1416             filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
1417             filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
1418             filter.addDataScheme("package");
1419             getContext().registerReceiverAsUser(
1420                     mBroadcastReceiver, UserHandle.ALL, filter, null, null);
1421             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
1422             getContext().registerReceiverAsUser(
1423                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
1424             try {
1425                 ActivityManager.getService().registerUidObserver(mUidObserver,
1426                         ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
1427                         | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE,
1428                         ActivityManager.PROCESS_STATE_UNKNOWN, null);
1429             } catch (RemoteException e) {
1430                 // ignored; both services live in system_server
1431             }
1432 
1433             mConcurrencyManager.onSystemReady();
1434 
1435             // Remove any jobs that are not associated with any of the current users.
1436             cancelJobsForNonExistentUsers();
1437             // Register thermal callback
1438             mThermalService = IThermalService.Stub.asInterface(
1439                     ServiceManager.getService(Context.THERMAL_SERVICE));
1440             if (mThermalService != null) {
1441                 try {
1442                     mThermalService.registerThermalStatusListener(new ThermalStatusListener());
1443                 } catch (RemoteException e) {
1444                     Slog.e(TAG, "Failed to register thermal callback.", e);
1445                 }
1446             }
1447         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
1448             synchronized (mLock) {
1449                 // Let's go!
1450                 mReadyToRock = true;
1451                 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
1452                         BatteryStats.SERVICE_NAME));
1453                 mLocalDeviceIdleController
1454                         = LocalServices.getService(DeviceIdleController.LocalService.class);
1455                 // Create the "runners".
1456                 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
1457                     mActiveServices.add(
1458                             new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
1459                                     getContext().getMainLooper()));
1460                 }
1461                 // Attach jobs to their controllers.
1462                 mJobs.forEachJob((job) -> {
1463                     for (int controller = 0; controller < mControllers.size(); controller++) {
1464                         final StateController sc = mControllers.get(controller);
1465                         sc.maybeStartTrackingJobLocked(job, null);
1466                     }
1467                 });
1468                 // GO GO GO!
1469                 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
1470             }
1471         }
1472     }
1473 
1474     /**
1475      * Called when we have a job status object that we need to insert in our
1476      * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
1477      * about.
1478      */
startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob)1479     private void startTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
1480         if (!jobStatus.isPreparedLocked()) {
1481             Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
1482         }
1483         jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
1484         final boolean update = mJobs.add(jobStatus);
1485         if (mReadyToRock) {
1486             for (int i = 0; i < mControllers.size(); i++) {
1487                 StateController controller = mControllers.get(i);
1488                 if (update) {
1489                     controller.maybeStopTrackingJobLocked(jobStatus, null, true);
1490                 }
1491                 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
1492             }
1493         }
1494     }
1495 
1496     /**
1497      * Called when we want to remove a JobStatus object that we've finished executing. Returns the
1498      * object removed.
1499      */
stopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean writeBack)1500     private boolean stopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
1501             boolean writeBack) {
1502         // Deal with any remaining work items in the old job.
1503         jobStatus.stopTrackingJobLocked(ActivityManager.getService(), incomingJob);
1504 
1505         // Remove from store as well as controllers.
1506         final boolean removed = mJobs.remove(jobStatus, writeBack);
1507         if (removed && mReadyToRock) {
1508             for (int i=0; i<mControllers.size(); i++) {
1509                 StateController controller = mControllers.get(i);
1510                 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
1511             }
1512         }
1513         return removed;
1514     }
1515 
stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason)1516     private boolean stopJobOnServiceContextLocked(JobStatus job, int reason, String debugReason) {
1517         for (int i=0; i<mActiveServices.size(); i++) {
1518             JobServiceContext jsc = mActiveServices.get(i);
1519             final JobStatus executing = jsc.getRunningJobLocked();
1520             if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
1521                 jsc.cancelExecutingJobLocked(reason, debugReason);
1522                 return true;
1523             }
1524         }
1525         return false;
1526     }
1527 
1528     /**
1529      * @param job JobStatus we are querying against.
1530      * @return Whether or not the job represented by the status object is currently being run or
1531      * is pending.
1532      */
isCurrentlyActiveLocked(JobStatus job)1533     private boolean isCurrentlyActiveLocked(JobStatus job) {
1534         for (int i=0; i<mActiveServices.size(); i++) {
1535             JobServiceContext serviceContext = mActiveServices.get(i);
1536             final JobStatus running = serviceContext.getRunningJobLocked();
1537             if (running != null && running.matches(job.getUid(), job.getJobId())) {
1538                 return true;
1539             }
1540         }
1541         return false;
1542     }
1543 
noteJobsPending(List<JobStatus> jobs)1544     void noteJobsPending(List<JobStatus> jobs) {
1545         for (int i = jobs.size() - 1; i >= 0; i--) {
1546             JobStatus job = jobs.get(i);
1547             mJobPackageTracker.notePending(job);
1548         }
1549     }
1550 
noteJobsNonpending(List<JobStatus> jobs)1551     void noteJobsNonpending(List<JobStatus> jobs) {
1552         for (int i = jobs.size() - 1; i >= 0; i--) {
1553             JobStatus job = jobs.get(i);
1554             mJobPackageTracker.noteNonpending(job);
1555         }
1556     }
1557 
1558     /**
1559      * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
1560      * specify an override deadline on a failed job (the failed job will run even though it's not
1561      * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
1562      * ready job with {@link JobStatus#getNumFailures()} > 0 will be executed.
1563      *
1564      * @param failureToReschedule Provided job status that we will reschedule.
1565      * @return A newly instantiated JobStatus with the same constraints as the last job except
1566      * with adjusted timing constraints.
1567      *
1568      * @see #maybeQueueReadyJobsForExecutionLocked
1569      */
1570     @VisibleForTesting
getRescheduleJobForFailureLocked(JobStatus failureToReschedule)1571     JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
1572         final long elapsedNowMillis = sElapsedRealtimeClock.millis();
1573         final JobInfo job = failureToReschedule.getJob();
1574 
1575         final long initialBackoffMillis = job.getInitialBackoffMillis();
1576         final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
1577         long delayMillis;
1578 
1579         if (failureToReschedule.hasWorkLocked()) {
1580             if (backoffAttempts > mConstants.MAX_WORK_RESCHEDULE_COUNT) {
1581                 Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
1582                         + backoffAttempts + " > work limit "
1583                         + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
1584                 return null;
1585             }
1586         } else if (backoffAttempts > mConstants.MAX_STANDARD_RESCHEDULE_COUNT) {
1587             Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #"
1588                     + backoffAttempts + " > std limit " + mConstants.MAX_STANDARD_RESCHEDULE_COUNT);
1589             return null;
1590         }
1591 
1592         switch (job.getBackoffPolicy()) {
1593             case JobInfo.BACKOFF_POLICY_LINEAR: {
1594                 long backoff = initialBackoffMillis;
1595                 if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME) {
1596                     backoff = mConstants.MIN_LINEAR_BACKOFF_TIME;
1597                 }
1598                 delayMillis = backoff * backoffAttempts;
1599             } break;
1600             default:
1601                 if (DEBUG) {
1602                     Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
1603                 }
1604             case JobInfo.BACKOFF_POLICY_EXPONENTIAL: {
1605                 long backoff = initialBackoffMillis;
1606                 if (backoff < mConstants.MIN_EXP_BACKOFF_TIME) {
1607                     backoff = mConstants.MIN_EXP_BACKOFF_TIME;
1608                 }
1609                 delayMillis = (long) Math.scalb(backoff, backoffAttempts - 1);
1610             } break;
1611         }
1612         delayMillis =
1613                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
1614         JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(),
1615                 elapsedNowMillis + delayMillis,
1616                 JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
1617                 failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
1618         if (job.isPeriodic()) {
1619             newJob.setOriginalLatestRunTimeElapsed(
1620                     failureToReschedule.getOriginalLatestRunTimeElapsed());
1621         }
1622         for (int ic=0; ic<mControllers.size(); ic++) {
1623             StateController controller = mControllers.get(ic);
1624             controller.rescheduleForFailureLocked(newJob, failureToReschedule);
1625         }
1626         return newJob;
1627     }
1628 
1629     /**
1630      * Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
1631      * does not cause a job's period to be larger than requested (eg: if the requested period is
1632      * shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
1633      * and try to optimize scheduling if the current job finished less than this amount of time to
1634      * the start of the next period
1635      */
1636     private static final long PERIODIC_JOB_WINDOW_BUFFER = 30 * MINUTE_IN_MILLIS;
1637 
1638     /** The maximum period a periodic job can have. Anything higher will be clamped down to this. */
1639     public static final long MAX_ALLOWED_PERIOD_MS = 365 * 24 * 60 * 60 * 1000L;
1640 
1641     /**
1642      * Called after a periodic has executed so we can reschedule it. We take the last execution
1643      * time of the job to be the time of completion (i.e. the time at which this function is
1644      * called).
1645      * <p>This could be inaccurate b/c the job can run for as long as
1646      * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
1647      * to underscheduling at least, rather than if we had taken the last execution time to be the
1648      * start of the execution.
1649      * <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat
1650      * tracking as though the job were newly-scheduled.
1651      * @return A new job representing the execution criteria for this instantiation of the
1652      * recurring job.
1653      */
1654     @VisibleForTesting
getRescheduleJobForPeriodic(JobStatus periodicToReschedule)1655     JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
1656         final long elapsedNow = sElapsedRealtimeClock.millis();
1657         final long newLatestRuntimeElapsed;
1658         // Make sure period is in the interval [min_possible_period, max_possible_period].
1659         final long period = Math.max(JobInfo.getMinPeriodMillis(),
1660                 Math.min(MAX_ALLOWED_PERIOD_MS, periodicToReschedule.getJob().getIntervalMillis()));
1661         // Make sure flex is in the interval [min_possible_flex, period].
1662         final long flex = Math.max(JobInfo.getMinFlexMillis(),
1663                 Math.min(period, periodicToReschedule.getJob().getFlexMillis()));
1664         long rescheduleBuffer = 0;
1665 
1666         long olrte = periodicToReschedule.getOriginalLatestRunTimeElapsed();
1667         if (olrte < 0 || olrte == JobStatus.NO_LATEST_RUNTIME) {
1668             Slog.wtf(TAG, "Invalid periodic job original latest run time: " + olrte);
1669             olrte = elapsedNow;
1670         }
1671         final long latestRunTimeElapsed = olrte;
1672 
1673         final long diffMs = Math.abs(elapsedNow - latestRunTimeElapsed);
1674         if (elapsedNow > latestRunTimeElapsed) {
1675             // The job ran past its expected run window. Have it count towards the current window
1676             // and schedule a new job for the next window.
1677             if (DEBUG) {
1678                 Slog.i(TAG, "Periodic job ran after its intended window.");
1679             }
1680             long numSkippedWindows = (diffMs / period) + 1; // +1 to include original window
1681             if (period != flex && diffMs > Math.min(PERIODIC_JOB_WINDOW_BUFFER,
1682                     (period - flex) / 2)) {
1683                 if (DEBUG) {
1684                     Slog.d(TAG, "Custom flex job ran too close to next window.");
1685                 }
1686                 // For custom flex periods, if the job was run too close to the next window,
1687                 // skip the next window and schedule for the following one.
1688                 numSkippedWindows += 1;
1689             }
1690             newLatestRuntimeElapsed = latestRunTimeElapsed + (period * numSkippedWindows);
1691         } else {
1692             newLatestRuntimeElapsed = latestRunTimeElapsed + period;
1693             if (diffMs < PERIODIC_JOB_WINDOW_BUFFER && diffMs < period / 6) {
1694                 // Add a little buffer to the start of the next window so the job doesn't run
1695                 // too soon after this completed one.
1696                 rescheduleBuffer = Math.min(PERIODIC_JOB_WINDOW_BUFFER, period / 6 - diffMs);
1697             }
1698         }
1699 
1700         if (newLatestRuntimeElapsed < elapsedNow) {
1701             Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: "
1702                     + newLatestRuntimeElapsed);
1703             return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
1704                     elapsedNow + period - flex, elapsedNow + period,
1705                     0 /* backoffAttempt */,
1706                     sSystemClock.millis() /* lastSuccessfulRunTime */,
1707                     periodicToReschedule.getLastFailedRunTime());
1708         }
1709 
1710         final long newEarliestRunTimeElapsed = newLatestRuntimeElapsed
1711                 - Math.min(flex, period - rescheduleBuffer);
1712 
1713         if (DEBUG) {
1714             Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
1715                     newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000
1716                     + "]s");
1717         }
1718         return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
1719                 newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
1720                 0 /* backoffAttempt */,
1721                 sSystemClock.millis() /* lastSuccessfulRunTime */,
1722                 periodicToReschedule.getLastFailedRunTime());
1723     }
1724 
1725     /*
1726      * We default to "long enough ago that every bucket's jobs are immediately runnable" to
1727      * avoid starvation of apps in uncommon-use buckets that might arise from repeated
1728      * reboot behavior.
1729      */
heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId)1730     long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) {
1731         // The furthest back in pre-boot time that we need to bother with
1732         long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX];
1733         boolean cacheHit = false;
1734         synchronized (mLock) {
1735             HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
1736             if (jobPackages != null) {
1737                 long cachedValue = jobPackages.getOrDefault(packageName, Long.MAX_VALUE);
1738                 if (cachedValue < Long.MAX_VALUE) {
1739                     cacheHit = true;
1740                     heartbeat = cachedValue;
1741                 }
1742             }
1743             if (!cacheHit) {
1744                 // We haven't seen it yet; ask usage stats about it
1745                 final long timeSinceJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
1746                 if (timeSinceJob < Long.MAX_VALUE) {
1747                     // Usage stats knows about it from before, so calculate back from that
1748                     // and go from there.
1749                     heartbeat = mHeartbeat - (timeSinceJob / mConstants.STANDBY_HEARTBEAT_TIME);
1750                 }
1751                 // If usage stats returned its "not found" MAX_VALUE, we still have the
1752                 // negative default 'heartbeat' value we established above
1753                 setLastJobHeartbeatLocked(packageName, userId, heartbeat);
1754             }
1755         }
1756         if (DEBUG_STANDBY) {
1757             Slog.v(TAG, "Last job heartbeat " + heartbeat + " for "
1758                     + packageName + "/" + userId);
1759         }
1760         return heartbeat;
1761     }
1762 
heartbeatWhenJobsLastRun(JobStatus job)1763     long heartbeatWhenJobsLastRun(JobStatus job) {
1764         return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId());
1765     }
1766 
setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat)1767     void setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat) {
1768         HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
1769         if (jobPackages == null) {
1770             jobPackages = new HashMap<>();
1771             mLastJobHeartbeats.put(userId, jobPackages);
1772         }
1773         jobPackages.put(packageName, heartbeat);
1774     }
1775 
1776     // JobCompletedListener implementations.
1777 
1778     /**
1779      * A job just finished executing. We fetch the
1780      * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
1781      * whether we want to reschedule we re-add it to the controllers.
1782      * @param jobStatus Completed job.
1783      * @param needsReschedule Whether the implementing class should reschedule this job.
1784      */
1785     @Override
onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule)1786     public void onJobCompletedLocked(JobStatus jobStatus, boolean needsReschedule) {
1787         if (DEBUG) {
1788             Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
1789         }
1790 
1791         // If the job wants to be rescheduled, we first need to make the next upcoming
1792         // job so we can transfer any appropriate state over from the previous job when
1793         // we stop it.
1794         final JobStatus rescheduledJob = needsReschedule
1795                 ? getRescheduleJobForFailureLocked(jobStatus) : null;
1796 
1797         // Do not write back immediately if this is a periodic job. The job may get lost if system
1798         // shuts down before it is added back.
1799         if (!stopTrackingJobLocked(jobStatus, rescheduledJob, !jobStatus.getJob().isPeriodic())) {
1800             if (DEBUG) {
1801                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
1802             }
1803             // We still want to check for jobs to execute, because this job may have
1804             // scheduled a new job under the same job id, and now we can run it.
1805             mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1806             return;
1807         }
1808 
1809         if (rescheduledJob != null) {
1810             try {
1811                 rescheduledJob.prepareLocked(ActivityManager.getService());
1812             } catch (SecurityException e) {
1813                 Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledJob);
1814             }
1815             startTrackingJobLocked(rescheduledJob, jobStatus);
1816         } else if (jobStatus.getJob().isPeriodic()) {
1817             JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
1818             try {
1819                 rescheduledPeriodic.prepareLocked(ActivityManager.getService());
1820             } catch (SecurityException e) {
1821                 Slog.w(TAG, "Unable to regrant job permissions for " + rescheduledPeriodic);
1822             }
1823             startTrackingJobLocked(rescheduledPeriodic, jobStatus);
1824         }
1825         jobStatus.unprepareLocked(ActivityManager.getService());
1826         reportActiveLocked();
1827         mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1828     }
1829 
1830     // StateChangedListener implementations.
1831 
1832     /**
1833      * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
1834      * some controller's state has changed, so as to run through the list of jobs and start/stop
1835      * any that are eligible.
1836      */
1837     @Override
onControllerStateChanged()1838     public void onControllerStateChanged() {
1839         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
1840     }
1841 
1842     @Override
onRunJobNow(JobStatus jobStatus)1843     public void onRunJobNow(JobStatus jobStatus) {
1844         mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
1845     }
1846 
1847     final private class JobHandler extends Handler {
1848 
JobHandler(Looper looper)1849         public JobHandler(Looper looper) {
1850             super(looper);
1851         }
1852 
1853         @Override
handleMessage(Message message)1854         public void handleMessage(Message message) {
1855             synchronized (mLock) {
1856                 if (!mReadyToRock) {
1857                     return;
1858                 }
1859                 switch (message.what) {
1860                     case MSG_JOB_EXPIRED: {
1861                         JobStatus runNow = (JobStatus) message.obj;
1862                         // runNow can be null, which is a controller's way of indicating that its
1863                         // state is such that all ready jobs should be run immediately.
1864                         if (runNow != null && isReadyToBeExecutedLocked(runNow)) {
1865                             mJobPackageTracker.notePending(runNow);
1866                             addOrderedItem(mPendingJobs, runNow, mEnqueueTimeComparator);
1867                         } else {
1868                             queueReadyJobsForExecutionLocked();
1869                         }
1870                     } break;
1871                     case MSG_CHECK_JOB:
1872                         if (DEBUG) {
1873                             Slog.d(TAG, "MSG_CHECK_JOB");
1874                         }
1875                         removeMessages(MSG_CHECK_JOB);
1876                         if (mReportedActive) {
1877                             // if jobs are currently being run, queue all ready jobs for execution.
1878                             queueReadyJobsForExecutionLocked();
1879                         } else {
1880                             // Check the list of jobs and run some of them if we feel inclined.
1881                             maybeQueueReadyJobsForExecutionLocked();
1882                         }
1883                         break;
1884                     case MSG_CHECK_JOB_GREEDY:
1885                         if (DEBUG) {
1886                             Slog.d(TAG, "MSG_CHECK_JOB_GREEDY");
1887                         }
1888                         queueReadyJobsForExecutionLocked();
1889                         break;
1890                     case MSG_STOP_JOB:
1891                         cancelJobImplLocked((JobStatus) message.obj, null,
1892                                 "app no longer allowed to run");
1893                         break;
1894 
1895                     case MSG_UID_STATE_CHANGED: {
1896                         final int uid = message.arg1;
1897                         final int procState = message.arg2;
1898                         updateUidState(uid, procState);
1899                         break;
1900                     }
1901                     case MSG_UID_GONE: {
1902                         final int uid = message.arg1;
1903                         final boolean disabled = message.arg2 != 0;
1904                         updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
1905                         if (disabled) {
1906                             cancelJobsForUid(uid, "uid gone");
1907                         }
1908                         synchronized (mLock) {
1909                             mDeviceIdleJobsController.setUidActiveLocked(uid, false);
1910                         }
1911                         break;
1912                     }
1913                     case MSG_UID_ACTIVE: {
1914                         final int uid = message.arg1;
1915                         synchronized (mLock) {
1916                             mDeviceIdleJobsController.setUidActiveLocked(uid, true);
1917                         }
1918                         break;
1919                     }
1920                     case MSG_UID_IDLE: {
1921                         final int uid = message.arg1;
1922                         final boolean disabled = message.arg2 != 0;
1923                         if (disabled) {
1924                             cancelJobsForUid(uid, "app uid idle");
1925                         }
1926                         synchronized (mLock) {
1927                             mDeviceIdleJobsController.setUidActiveLocked(uid, false);
1928                         }
1929                         break;
1930                     }
1931 
1932                 }
1933                 maybeRunPendingJobsLocked();
1934                 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
1935             }
1936         }
1937     }
1938 
isJobThermalConstrainedLocked(JobStatus job)1939     private boolean isJobThermalConstrainedLocked(JobStatus job) {
1940         return mThermalConstraint && job.hasConnectivityConstraint()
1941                 && (evaluateJobPriorityLocked(job) < JobInfo.PRIORITY_FOREGROUND_APP);
1942     }
1943 
stopNonReadyActiveJobsLocked()1944     private void stopNonReadyActiveJobsLocked() {
1945         for (int i=0; i<mActiveServices.size(); i++) {
1946             JobServiceContext serviceContext = mActiveServices.get(i);
1947             final JobStatus running = serviceContext.getRunningJobLocked();
1948             if (running == null) {
1949                 continue;
1950             }
1951             if (!running.isReady()) {
1952                 serviceContext.cancelExecutingJobLocked(
1953                         JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
1954                         "cancelled due to unsatisfied constraints");
1955             } else if (isJobThermalConstrainedLocked(running)) {
1956                 serviceContext.cancelExecutingJobLocked(
1957                         JobParameters.REASON_DEVICE_THERMAL,
1958                         "cancelled due to thermal condition");
1959             }
1960         }
1961     }
1962 
1963     /**
1964      * Run through list of jobs and execute all possible - at least one is expired so we do
1965      * as many as we can.
1966      */
queueReadyJobsForExecutionLocked()1967     private void queueReadyJobsForExecutionLocked() {
1968         if (DEBUG) {
1969             Slog.d(TAG, "queuing all ready jobs for execution:");
1970         }
1971         noteJobsNonpending(mPendingJobs);
1972         mPendingJobs.clear();
1973         stopNonReadyActiveJobsLocked();
1974         mJobs.forEachJob(mReadyQueueFunctor);
1975         mReadyQueueFunctor.postProcess();
1976 
1977         if (DEBUG) {
1978             final int queuedJobs = mPendingJobs.size();
1979             if (queuedJobs == 0) {
1980                 Slog.d(TAG, "No jobs pending.");
1981             } else {
1982                 Slog.d(TAG, queuedJobs + " jobs queued.");
1983             }
1984         }
1985     }
1986 
1987     final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
1988         ArrayList<JobStatus> newReadyJobs;
1989 
1990         @Override
accept(JobStatus job)1991         public void accept(JobStatus job) {
1992             if (isReadyToBeExecutedLocked(job)) {
1993                 if (DEBUG) {
1994                     Slog.d(TAG, "    queued " + job.toShortString());
1995                 }
1996                 if (newReadyJobs == null) {
1997                     newReadyJobs = new ArrayList<JobStatus>();
1998                 }
1999                 newReadyJobs.add(job);
2000             } else {
2001                 evaluateControllerStatesLocked(job);
2002             }
2003         }
2004 
postProcess()2005         public void postProcess() {
2006             if (newReadyJobs != null) {
2007                 noteJobsPending(newReadyJobs);
2008                 mPendingJobs.addAll(newReadyJobs);
2009                 if (mPendingJobs.size() > 1) {
2010                     mPendingJobs.sort(mEnqueueTimeComparator);
2011                 }
2012             }
2013             newReadyJobs = null;
2014         }
2015     }
2016     private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
2017 
2018     /**
2019      * The state of at least one job has changed. Here is where we could enforce various
2020      * policies on when we want to execute jobs.
2021      */
2022     final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
2023         int chargingCount;
2024         int batteryNotLowCount;
2025         int storageNotLowCount;
2026         int idleCount;
2027         int backoffCount;
2028         int connectivityCount;
2029         int contentCount;
2030         List<JobStatus> runnableJobs;
2031 
MaybeReadyJobQueueFunctor()2032         public MaybeReadyJobQueueFunctor() {
2033             reset();
2034         }
2035 
2036         // Functor method invoked for each job via JobStore.forEachJob()
2037         @Override
accept(JobStatus job)2038         public void accept(JobStatus job) {
2039             if (isReadyToBeExecutedLocked(job)) {
2040                 try {
2041                     if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
2042                             job.getJob().getService().getPackageName())) {
2043                         Slog.w(TAG, "Aborting job " + job.getUid() + ":"
2044                                 + job.getJob().toString() + " -- package not allowed to start");
2045                         mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
2046                         return;
2047                     }
2048                 } catch (RemoteException e) {
2049                 }
2050                 if (job.getNumFailures() > 0) {
2051                     backoffCount++;
2052                 }
2053                 if (job.hasIdleConstraint()) {
2054                     idleCount++;
2055                 }
2056                 if (job.hasConnectivityConstraint()) {
2057                     connectivityCount++;
2058                 }
2059                 if (job.hasChargingConstraint()) {
2060                     chargingCount++;
2061                 }
2062                 if (job.hasBatteryNotLowConstraint()) {
2063                     batteryNotLowCount++;
2064                 }
2065                 if (job.hasStorageNotLowConstraint()) {
2066                     storageNotLowCount++;
2067                 }
2068                 if (job.hasContentTriggerConstraint()) {
2069                     contentCount++;
2070                 }
2071                 if (runnableJobs == null) {
2072                     runnableJobs = new ArrayList<>();
2073                 }
2074                 runnableJobs.add(job);
2075             } else {
2076                 evaluateControllerStatesLocked(job);
2077             }
2078         }
2079 
postProcess()2080         public void postProcess() {
2081             if (backoffCount > 0 ||
2082                     idleCount >= mConstants.MIN_IDLE_COUNT ||
2083                     connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
2084                     chargingCount >= mConstants.MIN_CHARGING_COUNT ||
2085                     batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
2086                     storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT ||
2087                     contentCount >= mConstants.MIN_CONTENT_COUNT ||
2088                     (runnableJobs != null
2089                             && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
2090                 if (DEBUG) {
2091                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
2092                 }
2093                 noteJobsPending(runnableJobs);
2094                 mPendingJobs.addAll(runnableJobs);
2095                 if (mPendingJobs.size() > 1) {
2096                     mPendingJobs.sort(mEnqueueTimeComparator);
2097                 }
2098             } else {
2099                 if (DEBUG) {
2100                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
2101                 }
2102             }
2103 
2104             // Be ready for next time
2105             reset();
2106         }
2107 
reset()2108         private void reset() {
2109             chargingCount = 0;
2110             idleCount =  0;
2111             backoffCount = 0;
2112             connectivityCount = 0;
2113             batteryNotLowCount = 0;
2114             storageNotLowCount = 0;
2115             contentCount = 0;
2116             runnableJobs = null;
2117         }
2118     }
2119     private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
2120 
maybeQueueReadyJobsForExecutionLocked()2121     private void maybeQueueReadyJobsForExecutionLocked() {
2122         if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
2123 
2124         noteJobsNonpending(mPendingJobs);
2125         mPendingJobs.clear();
2126         stopNonReadyActiveJobsLocked();
2127         mJobs.forEachJob(mMaybeQueueFunctor);
2128         mMaybeQueueFunctor.postProcess();
2129     }
2130 
2131     /**
2132      * Heartbeat tracking.  The heartbeat alarm is intentionally non-wakeup.
2133      */
2134     class HeartbeatAlarmListener implements AlarmManager.OnAlarmListener {
2135 
2136         @Override
onAlarm()2137         public void onAlarm() {
2138             synchronized (mLock) {
2139                 final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime;
2140                 final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME;
2141                 if (beatsElapsed > 0) {
2142                     mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME;
2143                     advanceHeartbeatLocked(beatsElapsed);
2144                 }
2145             }
2146             setNextHeartbeatAlarm();
2147         }
2148     }
2149 
2150     // Intentionally does not touch the alarm timing
advanceHeartbeatLocked(long beatsElapsed)2151     void advanceHeartbeatLocked(long beatsElapsed) {
2152         if (!mConstants.USE_HEARTBEATS) {
2153             return;
2154         }
2155         mHeartbeat += beatsElapsed;
2156         if (DEBUG_STANDBY) {
2157             Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed
2158                     + " to " + mHeartbeat);
2159         }
2160         // Don't update ACTIVE or NEVER bucket milestones.  Note that mHeartbeat
2161         // will be equal to mNextBucketHeartbeat[bucket] for one beat, during which
2162         // new jobs scheduled by apps in that bucket will be permitted to run
2163         // immediately.
2164         boolean didAdvanceBucket = false;
2165         for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) {
2166             // Did we reach or cross a bucket boundary?
2167             if (mHeartbeat >= mNextBucketHeartbeat[i]) {
2168                 didAdvanceBucket = true;
2169             }
2170             while (mHeartbeat > mNextBucketHeartbeat[i]) {
2171                 mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i];
2172             }
2173             if (DEBUG_STANDBY) {
2174                 Slog.v(TAG, "   Bucket " + i + " next heartbeat "
2175                         + mNextBucketHeartbeat[i]);
2176             }
2177         }
2178 
2179         if (didAdvanceBucket) {
2180             if (DEBUG_STANDBY) {
2181                 Slog.v(TAG, "Hit bucket boundary; reevaluating job runnability");
2182             }
2183             mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
2184         }
2185     }
2186 
setNextHeartbeatAlarm()2187     void setNextHeartbeatAlarm() {
2188         final long heartbeatLength;
2189         synchronized (mLock) {
2190             if (!mConstants.USE_HEARTBEATS) {
2191                 return;
2192             }
2193             heartbeatLength = mConstants.STANDBY_HEARTBEAT_TIME;
2194         }
2195         final long now = sElapsedRealtimeClock.millis();
2196         final long nextBeatOrdinal = (now + heartbeatLength) / heartbeatLength;
2197         final long nextHeartbeat = nextBeatOrdinal * heartbeatLength;
2198         if (DEBUG_STANDBY) {
2199             Slog.i(TAG, "Setting heartbeat alarm for " + nextHeartbeat
2200                     + " = " + TimeUtils.formatDuration(nextHeartbeat - now));
2201         }
2202         AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
2203         am.setExact(AlarmManager.ELAPSED_REALTIME, nextHeartbeat,
2204                 HEARTBEAT_TAG, mHeartbeatAlarm, mHandler);
2205     }
2206 
2207     /** Returns true if both the calling and source users for the job are started. */
areUsersStartedLocked(final JobStatus job)2208     private boolean areUsersStartedLocked(final JobStatus job) {
2209         boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
2210         if (job.getUserId() == job.getSourceUserId()) {
2211             return sourceStarted;
2212         }
2213         return sourceStarted && ArrayUtils.contains(mStartedUsers, job.getUserId());
2214     }
2215 
2216     /**
2217      * Criteria for moving a job into the pending queue:
2218      *      - It's ready.
2219      *      - It's not pending.
2220      *      - It's not already running on a JSC.
2221      *      - The user that requested the job is running.
2222      *      - The job's standby bucket has come due to be runnable.
2223      *      - The component is enabled and runnable.
2224      */
isReadyToBeExecutedLocked(JobStatus job)2225     private boolean isReadyToBeExecutedLocked(JobStatus job) {
2226         final boolean jobReady = job.isReady();
2227 
2228         if (DEBUG) {
2229             Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
2230                     + " ready=" + jobReady);
2231         }
2232 
2233         // This is a condition that is very likely to be false (most jobs that are
2234         // scheduled are sitting there, not ready yet) and very cheap to check (just
2235         // a few conditions on data in JobStatus).
2236         if (!jobReady) {
2237             if (job.getSourcePackageName().equals("android.jobscheduler.cts.jobtestapp")) {
2238                 Slog.v(TAG, "    NOT READY: " + job);
2239             }
2240             return false;
2241         }
2242 
2243         final boolean jobExists = mJobs.containsJob(job);
2244 
2245         final boolean userStarted = areUsersStartedLocked(job);
2246 
2247         if (DEBUG) {
2248             Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
2249                     + " exists=" + jobExists + " userStarted=" + userStarted);
2250         }
2251 
2252         // These are also fairly cheap to check, though they typically will not
2253         // be conditions we fail.
2254         if (!jobExists || !userStarted) {
2255             return false;
2256         }
2257 
2258         if (isJobThermalConstrainedLocked(job)) {
2259             return false;
2260         }
2261 
2262         final boolean jobPending = mPendingJobs.contains(job);
2263         final boolean jobActive = isCurrentlyActiveLocked(job);
2264 
2265         if (DEBUG) {
2266             Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
2267                     + " pending=" + jobPending + " active=" + jobActive);
2268         }
2269 
2270         // These can be a little more expensive (especially jobActive, since we need to
2271         // go through the array of all potentially active jobs), so we are doing them
2272         // later...  but still before checking with the package manager!
2273         if (jobPending || jobActive) {
2274             return false;
2275         }
2276 
2277         if (mConstants.USE_HEARTBEATS) {
2278             // If the app is in a non-active standby bucket, make sure we've waited
2279             // an appropriate amount of time since the last invocation.  During device-
2280             // wide parole, standby bucketing is ignored.
2281             //
2282             // Jobs in 'active' apps are not subject to standby, nor are jobs that are
2283             // specifically marked as exempt.
2284             if (DEBUG_STANDBY) {
2285                 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
2286                         + " parole=" + mInParole + " active=" + job.uidActive
2287                         + " exempt=" + job.getJob().isExemptedFromAppStandby());
2288             }
2289             if (!mInParole
2290                     && !job.uidActive
2291                     && !job.getJob().isExemptedFromAppStandby()) {
2292                 final int bucket = job.getStandbyBucket();
2293                 if (DEBUG_STANDBY) {
2294                     Slog.v(TAG, "  bucket=" + bucket + " heartbeat=" + mHeartbeat
2295                             + " next=" + mNextBucketHeartbeat[bucket]);
2296                 }
2297                 if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
2298                     // Only skip this job if the app is still waiting for the end of its nominal
2299                     // bucket interval.  Once it's waited that long, we let it go ahead and clear.
2300                     // The final (NEVER) bucket is special; we never age those apps' jobs into
2301                     // runnability.
2302                     final long appLastRan = heartbeatWhenJobsLastRun(job);
2303                     if (bucket >= mConstants.STANDBY_BEATS.length
2304                             || (mHeartbeat > appLastRan
2305                             && mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) {
2306                         if (job.getWhenStandbyDeferred() == 0) {
2307                             if (DEBUG_STANDBY) {
2308                                 Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
2309                                         + (appLastRan + mConstants.STANDBY_BEATS[bucket])
2310                                         + " for " + job);
2311                             }
2312                             job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
2313                         }
2314                         return false;
2315                     } else {
2316                         if (DEBUG_STANDBY) {
2317                             Slog.v(TAG, "Bucket deferred job aged into runnability at "
2318                                     + mHeartbeat + " : " + job);
2319                         }
2320                     }
2321                 }
2322             }
2323         }
2324 
2325         // The expensive check: validate that the defined package+service is
2326         // still present & viable.
2327         return isComponentUsable(job);
2328     }
2329 
isComponentUsable(@onNull JobStatus job)2330     private boolean isComponentUsable(@NonNull JobStatus job) {
2331         final ServiceInfo service;
2332         try {
2333             // TODO: cache result until we're notified that something in the package changed.
2334             service = AppGlobals.getPackageManager().getServiceInfo(
2335                     job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
2336                     job.getUserId());
2337         } catch (RemoteException e) {
2338             throw e.rethrowAsRuntimeException();
2339         }
2340 
2341         if (service == null) {
2342             if (DEBUG) {
2343                 Slog.v(TAG, "isComponentUsable: " + job.toShortString()
2344                         + " component not present");
2345             }
2346             return false;
2347         }
2348 
2349         // Everything else checked out so far, so this is the final yes/no check
2350         final boolean appIsBad = mActivityManagerInternal.isAppBad(service.applicationInfo);
2351         if (DEBUG && appIsBad) {
2352             Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable");
2353         }
2354         return !appIsBad;
2355     }
2356 
evaluateControllerStatesLocked(final JobStatus job)2357     private void evaluateControllerStatesLocked(final JobStatus job) {
2358         for (int c = mControllers.size() - 1; c >= 0; --c) {
2359             final StateController sc = mControllers.get(c);
2360             sc.evaluateStateLocked(job);
2361         }
2362     }
2363 
2364     /**
2365      * Returns true if non-job constraint components are in place -- if job.isReady() returns true
2366      * and this method returns true, then the job is ready to be executed.
2367      */
areComponentsInPlaceLocked(JobStatus job)2368     public boolean areComponentsInPlaceLocked(JobStatus job) {
2369         // This code is very similar to the code in isReadyToBeExecutedLocked --- it uses the same
2370         // conditions.
2371 
2372         final boolean jobExists = mJobs.containsJob(job);
2373         final boolean userStarted = areUsersStartedLocked(job);
2374 
2375         if (DEBUG) {
2376             Slog.v(TAG, "areComponentsInPlaceLocked: " + job.toShortString()
2377                     + " exists=" + jobExists + " userStarted=" + userStarted);
2378         }
2379 
2380         // These are also fairly cheap to check, though they typically will not
2381         // be conditions we fail.
2382         if (!jobExists || !userStarted) {
2383             return false;
2384         }
2385 
2386         if (isJobThermalConstrainedLocked(job)) {
2387             return false;
2388         }
2389 
2390         // Job pending/active doesn't affect the readiness of a job.
2391 
2392         // Skipping the heartbeat check as this will only come into play when using the rolling
2393         // window quota management system.
2394 
2395         // The expensive check: validate that the defined package+service is
2396         // still present & viable.
2397         return isComponentUsable(job);
2398     }
2399 
2400     /**
2401      * Reconcile jobs in the pending queue against available execution contexts.
2402      * A controller can force a job into the pending queue even if it's already running, but
2403      * here is where we decide whether to actually execute it.
2404      */
maybeRunPendingJobsLocked()2405     void maybeRunPendingJobsLocked() {
2406         if (DEBUG) {
2407             Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
2408         }
2409         mConcurrencyManager.assignJobsToContextsLocked();
2410         reportActiveLocked();
2411     }
2412 
adjustJobPriority(int curPriority, JobStatus job)2413     private int adjustJobPriority(int curPriority, JobStatus job) {
2414         if (curPriority < JobInfo.PRIORITY_TOP_APP) {
2415             float factor = mJobPackageTracker.getLoadFactor(job);
2416             if (factor >= mConstants.HEAVY_USE_FACTOR) {
2417                 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
2418             } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
2419                 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
2420             }
2421         }
2422         return curPriority;
2423     }
2424 
evaluateJobPriorityLocked(JobStatus job)2425     int evaluateJobPriorityLocked(JobStatus job) {
2426         int priority = job.getPriority();
2427         if (priority >= JobInfo.PRIORITY_BOUND_FOREGROUND_SERVICE) {
2428             return adjustJobPriority(priority, job);
2429         }
2430         int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
2431         if (override != 0) {
2432             return adjustJobPriority(override, job);
2433         }
2434         return adjustJobPriority(priority, job);
2435     }
2436 
2437     final class LocalService implements JobSchedulerInternal {
2438 
2439         /**
2440          * The current bucket heartbeat ordinal
2441          */
currentHeartbeat()2442         public long currentHeartbeat() {
2443             return getCurrentHeartbeat();
2444         }
2445 
2446         /**
2447          * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
2448          */
nextHeartbeatForBucket(int bucket)2449         public long nextHeartbeatForBucket(int bucket) {
2450             synchronized (mLock) {
2451                 return mNextBucketHeartbeat[bucket];
2452             }
2453         }
2454 
2455         /**
2456          * Heartbeat ordinal for the given app.  This is typically the heartbeat at which
2457          * the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
2458          * jobs in a long time is immediately runnable even if the app is bucketed into
2459          * an infrequent time allocation.
2460          */
baseHeartbeatForApp(String packageName, @UserIdInt int userId, final int appStandbyBucket)2461         public long baseHeartbeatForApp(String packageName, @UserIdInt int userId,
2462                 final int appStandbyBucket) {
2463             if (appStandbyBucket == 0 ||
2464                     appStandbyBucket >= mConstants.STANDBY_BEATS.length) {
2465                 // ACTIVE => everything can be run right away
2466                 // NEVER => we won't run them anyway, so let them go in the future
2467                 // as soon as the app enters normal use
2468                 if (DEBUG_STANDBY) {
2469                     Slog.v(TAG, "Base heartbeat forced ZERO for new job in "
2470                             + packageName + "/" + userId);
2471                 }
2472                 return 0;
2473             }
2474 
2475             final long baseHeartbeat = heartbeatWhenJobsLastRun(packageName, userId);
2476             if (DEBUG_STANDBY) {
2477                 Slog.v(TAG, "Base heartbeat " + baseHeartbeat + " for new job in "
2478                         + packageName + "/" + userId);
2479             }
2480             return baseHeartbeat;
2481         }
2482 
noteJobStart(String packageName, int userId)2483         public void noteJobStart(String packageName, int userId) {
2484             synchronized (mLock) {
2485                 setLastJobHeartbeatLocked(packageName, userId, mHeartbeat);
2486             }
2487         }
2488 
2489         /**
2490          * Returns a list of all pending jobs. A running job is not considered pending. Periodic
2491          * jobs are always considered pending.
2492          */
2493         @Override
getSystemScheduledPendingJobs()2494         public List<JobInfo> getSystemScheduledPendingJobs() {
2495             synchronized (mLock) {
2496                 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
2497                 mJobs.forEachJob(Process.SYSTEM_UID, (job) -> {
2498                     if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
2499                         pendingJobs.add(job.getJob());
2500                     }
2501                 });
2502                 return pendingJobs;
2503             }
2504         }
2505 
2506         @Override
cancelJobsForUid(int uid, String reason)2507         public void cancelJobsForUid(int uid, String reason) {
2508             JobSchedulerService.this.cancelJobsForUid(uid, reason);
2509         }
2510 
2511         @Override
addBackingUpUid(int uid)2512         public void addBackingUpUid(int uid) {
2513             synchronized (mLock) {
2514                 // No need to actually do anything here, since for a full backup the
2515                 // activity manager will kill the process which will kill the job (and
2516                 // cause it to restart, but now it can't run).
2517                 mBackingUpUids.put(uid, uid);
2518             }
2519         }
2520 
2521         @Override
removeBackingUpUid(int uid)2522         public void removeBackingUpUid(int uid) {
2523             synchronized (mLock) {
2524                 mBackingUpUids.delete(uid);
2525                 // If there are any jobs for this uid, we need to rebuild the pending list
2526                 // in case they are now ready to run.
2527                 if (mJobs.countJobsForUid(uid) > 0) {
2528                     mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
2529                 }
2530             }
2531         }
2532 
2533         @Override
clearAllBackingUpUids()2534         public void clearAllBackingUpUids() {
2535             synchronized (mLock) {
2536                 if (mBackingUpUids.size() > 0) {
2537                     mBackingUpUids.clear();
2538                     mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
2539                 }
2540             }
2541         }
2542 
2543         @Override
reportAppUsage(String packageName, int userId)2544         public void reportAppUsage(String packageName, int userId) {
2545             JobSchedulerService.this.reportAppUsage(packageName, userId);
2546         }
2547 
2548         @Override
getPersistStats()2549         public JobStorePersistStats getPersistStats() {
2550             synchronized (mLock) {
2551                 return new JobStorePersistStats(mJobs.getPersistStats());
2552             }
2553         }
2554     }
2555 
2556     /**
2557      * Tracking of app assignments to standby buckets
2558      */
2559     final class StandbyTracker extends AppIdleStateChangeListener {
2560 
2561         // AppIdleStateChangeListener interface for live updates
2562 
2563         @Override
onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, boolean idle, int bucket, int reason)2564         public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
2565                 boolean idle, int bucket, int reason) {
2566             // QuotaController handles this now.
2567         }
2568 
2569         @Override
onParoleStateChanged(boolean isParoleOn)2570         public void onParoleStateChanged(boolean isParoleOn) {
2571             if (DEBUG_STANDBY) {
2572                 Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
2573             }
2574             mInParole = isParoleOn;
2575         }
2576 
2577         @Override
onUserInteractionStarted(String packageName, int userId)2578         public void onUserInteractionStarted(String packageName, int userId) {
2579             final int uid = mLocalPM.getPackageUid(packageName,
2580                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
2581             if (uid < 0) {
2582                 // Quietly ignore; the case is already logged elsewhere
2583                 return;
2584             }
2585 
2586             long sinceLast = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
2587             if (sinceLast > 2 * DateUtils.DAY_IN_MILLIS) {
2588                 // Too long ago, not worth logging
2589                 sinceLast = 0L;
2590             }
2591             final DeferredJobCounter counter = new DeferredJobCounter();
2592             synchronized (mLock) {
2593                 mJobs.forEachJobForSourceUid(uid, counter);
2594             }
2595             if (counter.numDeferred() > 0 || sinceLast > 0) {
2596                 BatteryStatsInternal mBatteryStatsInternal = LocalServices.getService
2597                         (BatteryStatsInternal.class);
2598                 mBatteryStatsInternal.noteJobsDeferred(uid, counter.numDeferred(), sinceLast);
2599                 StatsLog.write_non_chained(StatsLog.DEFERRED_JOB_STATS_REPORTED, uid, null,
2600                         counter.numDeferred(), sinceLast);
2601             }
2602         }
2603     }
2604 
2605     static class DeferredJobCounter implements Consumer<JobStatus> {
2606         private int mDeferred = 0;
2607 
numDeferred()2608         public int numDeferred() {
2609             return mDeferred;
2610         }
2611 
2612         @Override
accept(JobStatus job)2613         public void accept(JobStatus job) {
2614             if (job.getWhenStandbyDeferred() > 0) {
2615                 mDeferred++;
2616             }
2617         }
2618     }
2619 
standbyBucketToBucketIndex(int bucket)2620     public static int standbyBucketToBucketIndex(int bucket) {
2621         // Normalize AppStandby constants to indices into our bookkeeping
2622         if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) return NEVER_INDEX;
2623         else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) return RARE_INDEX;
2624         else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) return FREQUENT_INDEX;
2625         else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) return WORKING_INDEX;
2626         else return ACTIVE_INDEX;
2627     }
2628 
2629     // Static to support external callers
standbyBucketForPackage(String packageName, int userId, long elapsedNow)2630     public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) {
2631         UsageStatsManagerInternal usageStats = LocalServices.getService(
2632                 UsageStatsManagerInternal.class);
2633         int bucket = usageStats != null
2634                 ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow)
2635                 : 0;
2636 
2637         bucket = standbyBucketToBucketIndex(bucket);
2638 
2639         if (DEBUG_STANDBY) {
2640             Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket);
2641         }
2642         return bucket;
2643     }
2644 
2645     /**
2646      * Binder stub trampoline implementation
2647      */
2648     final class JobSchedulerStub extends IJobScheduler.Stub {
2649         /** Cache determination of whether a given app can persist jobs
2650          * key is uid of the calling app; value is undetermined/true/false
2651          */
2652         private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
2653 
2654         // Enforce that only the app itself (or shared uid participant) can schedule a
2655         // job that runs one of the app's services, as well as verifying that the
2656         // named service properly requires the BIND_JOB_SERVICE permission
enforceValidJobRequest(int uid, JobInfo job)2657         private void enforceValidJobRequest(int uid, JobInfo job) {
2658             final IPackageManager pm = AppGlobals.getPackageManager();
2659             final ComponentName service = job.getService();
2660             try {
2661                 ServiceInfo si = pm.getServiceInfo(service,
2662                         PackageManager.MATCH_DIRECT_BOOT_AWARE
2663                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
2664                         UserHandle.getUserId(uid));
2665                 if (si == null) {
2666                     throw new IllegalArgumentException("No such service " + service);
2667                 }
2668                 if (si.applicationInfo.uid != uid) {
2669                     throw new IllegalArgumentException("uid " + uid +
2670                             " cannot schedule job in " + service.getPackageName());
2671                 }
2672                 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
2673                     throw new IllegalArgumentException("Scheduled service " + service
2674                             + " does not require android.permission.BIND_JOB_SERVICE permission");
2675                 }
2676             } catch (RemoteException e) {
2677                 // Can't happen; the Package Manager is in this same process
2678             }
2679         }
2680 
canPersistJobs(int pid, int uid)2681         private boolean canPersistJobs(int pid, int uid) {
2682             // If we get this far we're good to go; all we need to do now is check
2683             // whether the app is allowed to persist its scheduled work.
2684             final boolean canPersist;
2685             synchronized (mPersistCache) {
2686                 Boolean cached = mPersistCache.get(uid);
2687                 if (cached != null) {
2688                     canPersist = cached.booleanValue();
2689                 } else {
2690                     // Persisting jobs is tantamount to running at boot, so we permit
2691                     // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
2692                     // permission
2693                     int result = getContext().checkPermission(
2694                             android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
2695                     canPersist = (result == PackageManager.PERMISSION_GRANTED);
2696                     mPersistCache.put(uid, canPersist);
2697                 }
2698             }
2699             return canPersist;
2700         }
2701 
validateJobFlags(JobInfo job, int callingUid)2702         private void validateJobFlags(JobInfo job, int callingUid) {
2703             if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
2704                 getContext().enforceCallingOrSelfPermission(
2705                         android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
2706             }
2707             if ((job.getFlags() & JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY) != 0) {
2708                 if (callingUid != Process.SYSTEM_UID) {
2709                     throw new SecurityException("Job has invalid flags");
2710                 }
2711                 if (job.isPeriodic()) {
2712                     Slog.wtf(TAG, "Periodic jobs mustn't have"
2713                             + " FLAG_EXEMPT_FROM_APP_STANDBY. Job=" + job);
2714                 }
2715             }
2716         }
2717 
2718         // IJobScheduler implementation
2719         @Override
schedule(JobInfo job)2720         public int schedule(JobInfo job) throws RemoteException {
2721             if (DEBUG) {
2722                 Slog.d(TAG, "Scheduling job: " + job.toString());
2723             }
2724             final int pid = Binder.getCallingPid();
2725             final int uid = Binder.getCallingUid();
2726             final int userId = UserHandle.getUserId(uid);
2727 
2728             enforceValidJobRequest(uid, job);
2729             if (job.isPersisted()) {
2730                 if (!canPersistJobs(pid, uid)) {
2731                     throw new IllegalArgumentException("Error: requested job be persisted without"
2732                             + " holding RECEIVE_BOOT_COMPLETED permission.");
2733                 }
2734             }
2735 
2736             validateJobFlags(job, uid);
2737 
2738             long ident = Binder.clearCallingIdentity();
2739             try {
2740                 return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
2741                         null);
2742             } finally {
2743                 Binder.restoreCallingIdentity(ident);
2744             }
2745         }
2746 
2747         // IJobScheduler implementation
2748         @Override
enqueue(JobInfo job, JobWorkItem work)2749         public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
2750             if (DEBUG) {
2751                 Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
2752             }
2753             final int uid = Binder.getCallingUid();
2754             final int userId = UserHandle.getUserId(uid);
2755 
2756             enforceValidJobRequest(uid, job);
2757             if (job.isPersisted()) {
2758                 throw new IllegalArgumentException("Can't enqueue work for persisted jobs");
2759             }
2760             if (work == null) {
2761                 throw new NullPointerException("work is null");
2762             }
2763 
2764             validateJobFlags(job, uid);
2765 
2766             long ident = Binder.clearCallingIdentity();
2767             try {
2768                 return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
2769                         null);
2770             } finally {
2771                 Binder.restoreCallingIdentity(ident);
2772             }
2773         }
2774 
2775         @Override
scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)2776         public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
2777                 throws RemoteException {
2778             final int callerUid = Binder.getCallingUid();
2779             if (DEBUG) {
2780                 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
2781                         + " on behalf of " + packageName + "/");
2782             }
2783 
2784             if (packageName == null) {
2785                 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
2786             }
2787 
2788             int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
2789                     android.Manifest.permission.UPDATE_DEVICE_STATS);
2790             if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
2791                 throw new SecurityException("Caller uid " + callerUid
2792                         + " not permitted to schedule jobs for other apps");
2793             }
2794 
2795             validateJobFlags(job, callerUid);
2796 
2797             long ident = Binder.clearCallingIdentity();
2798             try {
2799                 return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
2800                         packageName, userId, tag);
2801             } finally {
2802                 Binder.restoreCallingIdentity(ident);
2803             }
2804         }
2805 
2806         @Override
getAllPendingJobs()2807         public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException {
2808             final int uid = Binder.getCallingUid();
2809 
2810             long ident = Binder.clearCallingIdentity();
2811             try {
2812                 return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid));
2813             } finally {
2814                 Binder.restoreCallingIdentity(ident);
2815             }
2816         }
2817 
2818         @Override
getPendingJob(int jobId)2819         public JobInfo getPendingJob(int jobId) throws RemoteException {
2820             final int uid = Binder.getCallingUid();
2821 
2822             long ident = Binder.clearCallingIdentity();
2823             try {
2824                 return JobSchedulerService.this.getPendingJob(uid, jobId);
2825             } finally {
2826                 Binder.restoreCallingIdentity(ident);
2827             }
2828         }
2829 
2830         @Override
cancelAll()2831         public void cancelAll() throws RemoteException {
2832             final int uid = Binder.getCallingUid();
2833             long ident = Binder.clearCallingIdentity();
2834             try {
2835                 JobSchedulerService.this.cancelJobsForUid(uid,
2836                         "cancelAll() called by app, callingUid=" + uid);
2837             } finally {
2838                 Binder.restoreCallingIdentity(ident);
2839             }
2840         }
2841 
2842         @Override
cancel(int jobId)2843         public void cancel(int jobId) throws RemoteException {
2844             final int uid = Binder.getCallingUid();
2845 
2846             long ident = Binder.clearCallingIdentity();
2847             try {
2848                 JobSchedulerService.this.cancelJob(uid, jobId, uid);
2849             } finally {
2850                 Binder.restoreCallingIdentity(ident);
2851             }
2852         }
2853 
2854         /**
2855          * "dumpsys" infrastructure
2856          */
2857         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)2858         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2859             if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
2860 
2861             int filterUid = -1;
2862             boolean proto = false;
2863             if (!ArrayUtils.isEmpty(args)) {
2864                 int opti = 0;
2865                 while (opti < args.length) {
2866                     String arg = args[opti];
2867                     if ("-h".equals(arg)) {
2868                         dumpHelp(pw);
2869                         return;
2870                     } else if ("-a".equals(arg)) {
2871                         // Ignore, we always dump all.
2872                     } else if ("--proto".equals(arg)) {
2873                         proto = true;
2874                     } else if (arg.length() > 0 && arg.charAt(0) == '-') {
2875                         pw.println("Unknown option: " + arg);
2876                         return;
2877                     } else {
2878                         break;
2879                     }
2880                     opti++;
2881                 }
2882                 if (opti < args.length) {
2883                     String pkg = args[opti];
2884                     try {
2885                         filterUid = getContext().getPackageManager().getPackageUid(pkg,
2886                                 PackageManager.MATCH_ANY_USER);
2887                     } catch (NameNotFoundException ignored) {
2888                         pw.println("Invalid package: " + pkg);
2889                         return;
2890                     }
2891                 }
2892             }
2893 
2894             final long identityToken = Binder.clearCallingIdentity();
2895             try {
2896                 if (proto) {
2897                     JobSchedulerService.this.dumpInternalProto(fd, filterUid);
2898                 } else {
2899                     JobSchedulerService.this.dumpInternal(new IndentingPrintWriter(pw, "  "),
2900                             filterUid);
2901                 }
2902             } finally {
2903                 Binder.restoreCallingIdentity(identityToken);
2904             }
2905         }
2906 
2907         @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)2908         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
2909                 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
2910                 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
2911                         this, in, out, err, args, callback, resultReceiver);
2912         }
2913 
2914         /**
2915          * <b>For internal system user only!</b>
2916          * Returns a list of all currently-executing jobs.
2917          */
2918         @Override
getStartedJobs()2919         public List<JobInfo> getStartedJobs() {
2920             final int uid = Binder.getCallingUid();
2921             if (uid != Process.SYSTEM_UID) {
2922                 throw new SecurityException(
2923                     "getStartedJobs() is system internal use only.");
2924             }
2925 
2926             final ArrayList<JobInfo> runningJobs;
2927 
2928             synchronized (mLock) {
2929                 runningJobs = new ArrayList<>(mActiveServices.size());
2930                 for (JobServiceContext jsc : mActiveServices) {
2931                     final JobStatus job = jsc.getRunningJobLocked();
2932                     if (job != null) {
2933                         runningJobs.add(job.getJob());
2934                     }
2935                 }
2936             }
2937 
2938             return runningJobs;
2939         }
2940 
2941         /**
2942          * <b>For internal system user only!</b>
2943          * Returns a snapshot of the state of all jobs known to the system.
2944          *
2945          * <p class="note">This is a slow operation, so it should be called sparingly.
2946          */
2947         @Override
getAllJobSnapshots()2948         public ParceledListSlice<JobSnapshot> getAllJobSnapshots() {
2949             final int uid = Binder.getCallingUid();
2950             if (uid != Process.SYSTEM_UID) {
2951                 throw new SecurityException(
2952                     "getAllJobSnapshots() is system internal use only.");
2953             }
2954             synchronized (mLock) {
2955                 final ArrayList<JobSnapshot> snapshots = new ArrayList<>(mJobs.size());
2956                 mJobs.forEachJob((job) -> snapshots.add(
2957                         new JobSnapshot(job.getJob(), job.getSatisfiedConstraintFlags(),
2958                                 isReadyToBeExecutedLocked(job))));
2959                 return new ParceledListSlice<>(snapshots);
2960             }
2961         }
2962     };
2963 
2964     // Shell command infrastructure: run the given job immediately
executeRunCommand(String pkgName, int userId, int jobId, boolean force)2965     int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
2966         if (DEBUG) {
2967             Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
2968                     + " " + jobId + " f=" + force);
2969         }
2970 
2971         try {
2972             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
2973                     userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
2974             if (uid < 0) {
2975                 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
2976             }
2977 
2978             synchronized (mLock) {
2979                 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
2980                 if (js == null) {
2981                     return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
2982                 }
2983 
2984                 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
2985                 if (!js.isConstraintsSatisfied()) {
2986                     js.overrideState = 0;
2987                     return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
2988                 }
2989 
2990                 queueReadyJobsForExecutionLocked();
2991                 maybeRunPendingJobsLocked();
2992             }
2993         } catch (RemoteException e) {
2994             // can't happen
2995         }
2996         return 0;
2997     }
2998 
2999     // Shell command infrastructure: immediately timeout currently executing jobs
executeTimeoutCommand(PrintWriter pw, String pkgName, int userId, boolean hasJobId, int jobId)3000     int executeTimeoutCommand(PrintWriter pw, String pkgName, int userId,
3001             boolean hasJobId, int jobId) {
3002         if (DEBUG) {
3003             Slog.v(TAG, "executeTimeoutCommand(): " + pkgName + "/" + userId + " " + jobId);
3004         }
3005 
3006         synchronized (mLock) {
3007             boolean foundSome = false;
3008             for (int i=0; i<mActiveServices.size(); i++) {
3009                 final JobServiceContext jc = mActiveServices.get(i);
3010                 final JobStatus js = jc.getRunningJobLocked();
3011                 if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
3012                     foundSome = true;
3013                     pw.print("Timing out: ");
3014                     js.printUniqueId(pw);
3015                     pw.print(" ");
3016                     pw.println(js.getServiceComponent().flattenToShortString());
3017                 }
3018             }
3019             if (!foundSome) {
3020                 pw.println("No matching executing jobs found.");
3021             }
3022         }
3023         return 0;
3024     }
3025 
3026     // Shell command infrastructure: cancel a scheduled job
executeCancelCommand(PrintWriter pw, String pkgName, int userId, boolean hasJobId, int jobId)3027     int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
3028             boolean hasJobId, int jobId) {
3029         if (DEBUG) {
3030             Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
3031         }
3032 
3033         int pkgUid = -1;
3034         try {
3035             IPackageManager pm = AppGlobals.getPackageManager();
3036             pkgUid = pm.getPackageUid(pkgName, 0, userId);
3037         } catch (RemoteException e) { /* can't happen */ }
3038 
3039         if (pkgUid < 0) {
3040             pw.println("Package " + pkgName + " not found.");
3041             return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
3042         }
3043 
3044         if (!hasJobId) {
3045             pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
3046             if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
3047                 pw.println("No matching jobs found.");
3048             }
3049         } else {
3050             pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
3051             if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) {
3052                 pw.println("No matching job found.");
3053             }
3054         }
3055 
3056         return 0;
3057     }
3058 
setMonitorBattery(boolean enabled)3059     void setMonitorBattery(boolean enabled) {
3060         synchronized (mLock) {
3061             if (mBatteryController != null) {
3062                 mBatteryController.getTracker().setMonitorBatteryLocked(enabled);
3063             }
3064         }
3065     }
3066 
getBatterySeq()3067     int getBatterySeq() {
3068         synchronized (mLock) {
3069             return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1;
3070         }
3071     }
3072 
getBatteryCharging()3073     boolean getBatteryCharging() {
3074         synchronized (mLock) {
3075             return mBatteryController != null
3076                     ? mBatteryController.getTracker().isOnStablePower() : false;
3077         }
3078     }
3079 
getBatteryNotLow()3080     boolean getBatteryNotLow() {
3081         synchronized (mLock) {
3082             return mBatteryController != null
3083                     ? mBatteryController.getTracker().isBatteryNotLow() : false;
3084         }
3085     }
3086 
getStorageSeq()3087     int getStorageSeq() {
3088         synchronized (mLock) {
3089             return mStorageController != null ? mStorageController.getTracker().getSeq() : -1;
3090         }
3091     }
3092 
getStorageNotLow()3093     boolean getStorageNotLow() {
3094         synchronized (mLock) {
3095             return mStorageController != null
3096                     ? mStorageController.getTracker().isStorageNotLow() : false;
3097         }
3098     }
3099 
getCurrentHeartbeat()3100     long getCurrentHeartbeat() {
3101         synchronized (mLock) {
3102             return mHeartbeat;
3103         }
3104     }
3105 
3106     // Shell command infrastructure
getJobState(PrintWriter pw, String pkgName, int userId, int jobId)3107     int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
3108         try {
3109             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
3110                     userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
3111             if (uid < 0) {
3112                 pw.print("unknown("); pw.print(pkgName); pw.println(")");
3113                 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
3114             }
3115 
3116             synchronized (mLock) {
3117                 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
3118                 if (DEBUG) Slog.d(TAG, "get-job-state " + uid + "/" + jobId + ": " + js);
3119                 if (js == null) {
3120                     pw.print("unknown("); UserHandle.formatUid(pw, uid);
3121                     pw.print("/jid"); pw.print(jobId); pw.println(")");
3122                     return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
3123                 }
3124 
3125                 boolean printed = false;
3126                 if (mPendingJobs.contains(js)) {
3127                     pw.print("pending");
3128                     printed = true;
3129                 }
3130                 if (isCurrentlyActiveLocked(js)) {
3131                     if (printed) {
3132                         pw.print(" ");
3133                     }
3134                     printed = true;
3135                     pw.println("active");
3136                 }
3137                 if (!ArrayUtils.contains(mStartedUsers, js.getUserId())) {
3138                     if (printed) {
3139                         pw.print(" ");
3140                     }
3141                     printed = true;
3142                     pw.println("user-stopped");
3143                 }
3144                 if (!ArrayUtils.contains(mStartedUsers, js.getSourceUserId())) {
3145                     if (printed) {
3146                         pw.print(" ");
3147                     }
3148                     printed = true;
3149                     pw.println("source-user-stopped");
3150                 }
3151                 if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) {
3152                     if (printed) {
3153                         pw.print(" ");
3154                     }
3155                     printed = true;
3156                     pw.println("backing-up");
3157                 }
3158                 boolean componentPresent = false;
3159                 try {
3160                     componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
3161                             js.getServiceComponent(),
3162                             PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
3163                             js.getUserId()) != null);
3164                 } catch (RemoteException e) {
3165                 }
3166                 if (!componentPresent) {
3167                     if (printed) {
3168                         pw.print(" ");
3169                     }
3170                     printed = true;
3171                     pw.println("no-component");
3172                 }
3173                 if (js.isReady()) {
3174                     if (printed) {
3175                         pw.print(" ");
3176                     }
3177                     printed = true;
3178                     pw.println("ready");
3179                 }
3180                 if (!printed) {
3181                     pw.print("waiting");
3182                 }
3183                 pw.println();
3184             }
3185         } catch (RemoteException e) {
3186             // can't happen
3187         }
3188         return 0;
3189     }
3190 
3191     // Shell command infrastructure
executeHeartbeatCommand(PrintWriter pw, int numBeats)3192     int executeHeartbeatCommand(PrintWriter pw, int numBeats) {
3193         if (numBeats < 1) {
3194             pw.println(getCurrentHeartbeat());
3195             return 0;
3196         }
3197 
3198         pw.print("Advancing standby heartbeat by ");
3199         pw.println(numBeats);
3200         synchronized (mLock) {
3201             advanceHeartbeatLocked(numBeats);
3202         }
3203         return 0;
3204     }
3205 
triggerDockState(boolean idleState)3206     void triggerDockState(boolean idleState) {
3207         final Intent dockIntent;
3208         if (idleState) {
3209             dockIntent = new Intent(Intent.ACTION_DOCK_IDLE);
3210         } else {
3211             dockIntent = new Intent(Intent.ACTION_DOCK_ACTIVE);
3212         }
3213         dockIntent.setPackage("android");
3214         dockIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
3215         getContext().sendBroadcastAsUser(dockIntent, UserHandle.ALL);
3216     }
3217 
dumpHelp(PrintWriter pw)3218     static void dumpHelp(PrintWriter pw) {
3219         pw.println("Job Scheduler (jobscheduler) dump options:");
3220         pw.println("  [-h] [package] ...");
3221         pw.println("    -h: print this help");
3222         pw.println("  [package] is an optional package name to limit the output to.");
3223     }
3224 
3225     /** Sort jobs by caller UID, then by Job ID. */
sortJobs(List<JobStatus> jobs)3226     private static void sortJobs(List<JobStatus> jobs) {
3227         Collections.sort(jobs, new Comparator<JobStatus>() {
3228             @Override
3229             public int compare(JobStatus o1, JobStatus o2) {
3230                 int uid1 = o1.getUid();
3231                 int uid2 = o2.getUid();
3232                 int id1 = o1.getJobId();
3233                 int id2 = o2.getJobId();
3234                 if (uid1 != uid2) {
3235                     return uid1 < uid2 ? -1 : 1;
3236                 }
3237                 return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
3238             }
3239         });
3240     }
3241 
dumpInternal(final IndentingPrintWriter pw, int filterUid)3242     void dumpInternal(final IndentingPrintWriter pw, int filterUid) {
3243         final int filterUidFinal = UserHandle.getAppId(filterUid);
3244         final long now = sSystemClock.millis();
3245         final long nowElapsed = sElapsedRealtimeClock.millis();
3246         final long nowUptime = sUptimeMillisClock.millis();
3247 
3248         final Predicate<JobStatus> predicate = (js) -> {
3249             return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
3250                     || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
3251         };
3252         synchronized (mLock) {
3253             mConstants.dump(pw);
3254             for (StateController controller : mControllers) {
3255                 pw.increaseIndent();
3256                 controller.dumpConstants(pw);
3257                 pw.decreaseIndent();
3258             }
3259             pw.println();
3260 
3261             pw.println("  Heartbeat:");
3262             pw.print("    Current:    "); pw.println(mHeartbeat);
3263             pw.println("    Next");
3264             pw.print("      ACTIVE:   "); pw.println(mNextBucketHeartbeat[0]);
3265             pw.print("      WORKING:  "); pw.println(mNextBucketHeartbeat[1]);
3266             pw.print("      FREQUENT: "); pw.println(mNextBucketHeartbeat[2]);
3267             pw.print("      RARE:     "); pw.println(mNextBucketHeartbeat[3]);
3268             pw.print("    Last heartbeat: ");
3269             TimeUtils.formatDuration(mLastHeartbeatTime, nowElapsed, pw);
3270             pw.println();
3271             pw.print("    Next heartbeat: ");
3272             TimeUtils.formatDuration(mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME,
3273                     nowElapsed, pw);
3274             pw.println();
3275             pw.print("    In parole?: ");
3276             pw.print(mInParole);
3277             pw.println();
3278             pw.print("    In thermal throttling?: ");
3279             pw.print(mThermalConstraint);
3280             pw.println();
3281             pw.println();
3282 
3283             pw.println("Started users: " + Arrays.toString(mStartedUsers));
3284             pw.print("Registered ");
3285             pw.print(mJobs.size());
3286             pw.println(" jobs:");
3287             if (mJobs.size() > 0) {
3288                 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
3289                 sortJobs(jobs);
3290                 for (JobStatus job : jobs) {
3291                     pw.print("  JOB #"); job.printUniqueId(pw); pw.print(": ");
3292                     pw.println(job.toShortStringExceptUniqueId());
3293 
3294                     // Skip printing details if the caller requested a filter
3295                     if (!predicate.test(job)) {
3296                         continue;
3297                     }
3298 
3299                     job.dump(pw, "    ", true, nowElapsed);
3300                     pw.print("    Last run heartbeat: ");
3301                     pw.print(heartbeatWhenJobsLastRun(job));
3302                     pw.println();
3303 
3304                     pw.print("    Ready: ");
3305                     pw.print(isReadyToBeExecutedLocked(job));
3306                     pw.print(" (job=");
3307                     pw.print(job.isReady());
3308                     pw.print(" user=");
3309                     pw.print(areUsersStartedLocked(job));
3310                     pw.print(" !pending=");
3311                     pw.print(!mPendingJobs.contains(job));
3312                     pw.print(" !active=");
3313                     pw.print(!isCurrentlyActiveLocked(job));
3314                     pw.print(" !backingup=");
3315                     pw.print(!(mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0));
3316                     pw.print(" comp=");
3317                     boolean componentPresent = false;
3318                     try {
3319                         componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
3320                                 job.getServiceComponent(),
3321                                 PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
3322                                 job.getUserId()) != null);
3323                     } catch (RemoteException e) {
3324                     }
3325                     pw.print(componentPresent);
3326                     pw.println(")");
3327                 }
3328             } else {
3329                 pw.println("  None.");
3330             }
3331             for (int i=0; i<mControllers.size(); i++) {
3332                 pw.println();
3333                 pw.println(mControllers.get(i).getClass().getSimpleName() + ":");
3334                 pw.increaseIndent();
3335                 mControllers.get(i).dumpControllerStateLocked(pw, predicate);
3336                 pw.decreaseIndent();
3337             }
3338             pw.println();
3339             pw.println("Uid priority overrides:");
3340             for (int i=0; i< mUidPriorityOverride.size(); i++) {
3341                 int uid = mUidPriorityOverride.keyAt(i);
3342                 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
3343                     pw.print("  "); pw.print(UserHandle.formatUid(uid));
3344                     pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
3345                 }
3346             }
3347             if (mBackingUpUids.size() > 0) {
3348                 pw.println();
3349                 pw.println("Backing up uids:");
3350                 boolean first = true;
3351                 for (int i = 0; i < mBackingUpUids.size(); i++) {
3352                     int uid = mBackingUpUids.keyAt(i);
3353                     if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
3354                         if (first) {
3355                             pw.print("  ");
3356                             first = false;
3357                         } else {
3358                             pw.print(", ");
3359                         }
3360                         pw.print(UserHandle.formatUid(uid));
3361                     }
3362                 }
3363                 pw.println();
3364             }
3365             pw.println();
3366             mJobPackageTracker.dump(pw, "", filterUidFinal);
3367             pw.println();
3368             if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
3369                 pw.println();
3370             }
3371             pw.println("Pending queue:");
3372             for (int i=0; i<mPendingJobs.size(); i++) {
3373                 JobStatus job = mPendingJobs.get(i);
3374                 pw.print("  Pending #"); pw.print(i); pw.print(": ");
3375                 pw.println(job.toShortString());
3376                 job.dump(pw, "    ", false, nowElapsed);
3377                 int priority = evaluateJobPriorityLocked(job);
3378                 pw.print("    Evaluated priority: ");
3379                 pw.println(JobInfo.getPriorityString(priority));
3380 
3381                 pw.print("    Tag: "); pw.println(job.getTag());
3382                 pw.print("    Enq: ");
3383                 TimeUtils.formatDuration(job.madePending - nowUptime, pw);
3384                 pw.println();
3385             }
3386             pw.println();
3387             pw.println("Active jobs:");
3388             for (int i=0; i<mActiveServices.size(); i++) {
3389                 JobServiceContext jsc = mActiveServices.get(i);
3390                 pw.print("  Slot #"); pw.print(i); pw.print(": ");
3391                 final JobStatus job = jsc.getRunningJobLocked();
3392                 if (job == null) {
3393                     if (jsc.mStoppedReason != null) {
3394                         pw.print("inactive since ");
3395                         TimeUtils.formatDuration(jsc.mStoppedTime, nowElapsed, pw);
3396                         pw.print(", stopped because: ");
3397                         pw.println(jsc.mStoppedReason);
3398                     } else {
3399                         pw.println("inactive");
3400                     }
3401                     continue;
3402                 } else {
3403                     pw.println(job.toShortString());
3404                     pw.print("    Running for: ");
3405                     TimeUtils.formatDuration(nowElapsed - jsc.getExecutionStartTimeElapsed(), pw);
3406                     pw.print(", timeout at: ");
3407                     TimeUtils.formatDuration(jsc.getTimeoutElapsed() - nowElapsed, pw);
3408                     pw.println();
3409                     job.dump(pw, "    ", false, nowElapsed);
3410                     int priority = evaluateJobPriorityLocked(jsc.getRunningJobLocked());
3411                     pw.print("    Evaluated priority: ");
3412                     pw.println(JobInfo.getPriorityString(priority));
3413 
3414                     pw.print("    Active at ");
3415                     TimeUtils.formatDuration(job.madeActive - nowUptime, pw);
3416                     pw.print(", pending for ");
3417                     TimeUtils.formatDuration(job.madeActive - job.madePending, pw);
3418                     pw.println();
3419                 }
3420             }
3421             if (filterUid == -1) {
3422                 pw.println();
3423                 pw.print("mReadyToRock="); pw.println(mReadyToRock);
3424                 pw.print("mReportedActive="); pw.println(mReportedActive);
3425             }
3426             pw.println();
3427 
3428             mConcurrencyManager.dumpLocked(pw, now, nowElapsed);
3429 
3430             pw.println();
3431             pw.print("PersistStats: ");
3432             pw.println(mJobs.getPersistStats());
3433         }
3434         pw.println();
3435     }
3436 
dumpInternalProto(final FileDescriptor fd, int filterUid)3437     void dumpInternalProto(final FileDescriptor fd, int filterUid) {
3438         ProtoOutputStream proto = new ProtoOutputStream(fd);
3439         final int filterUidFinal = UserHandle.getAppId(filterUid);
3440         final long now = sSystemClock.millis();
3441         final long nowElapsed = sElapsedRealtimeClock.millis();
3442         final long nowUptime = sUptimeMillisClock.millis();
3443         final Predicate<JobStatus> predicate = (js) -> {
3444             return filterUidFinal == -1 || UserHandle.getAppId(js.getUid()) == filterUidFinal
3445                     || UserHandle.getAppId(js.getSourceUid()) == filterUidFinal;
3446         };
3447 
3448         synchronized (mLock) {
3449             final long settingsToken = proto.start(JobSchedulerServiceDumpProto.SETTINGS);
3450             mConstants.dump(proto);
3451             for (StateController controller : mControllers) {
3452                 controller.dumpConstants(proto);
3453             }
3454             proto.end(settingsToken);
3455 
3456             proto.write(JobSchedulerServiceDumpProto.CURRENT_HEARTBEAT, mHeartbeat);
3457             proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[0]);
3458             proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[1]);
3459             proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[2]);
3460             proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[3]);
3461             proto.write(JobSchedulerServiceDumpProto.LAST_HEARTBEAT_TIME_MILLIS,
3462                     mLastHeartbeatTime - nowUptime);
3463             proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT_TIME_MILLIS,
3464                     mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME - nowUptime);
3465             proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
3466             proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint);
3467 
3468             for (int u : mStartedUsers) {
3469                 proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
3470             }
3471             if (mJobs.size() > 0) {
3472                 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
3473                 sortJobs(jobs);
3474                 for (JobStatus job : jobs) {
3475                     final long rjToken = proto.start(JobSchedulerServiceDumpProto.REGISTERED_JOBS);
3476                     job.writeToShortProto(proto, JobSchedulerServiceDumpProto.RegisteredJob.INFO);
3477 
3478                     // Skip printing details if the caller requested a filter
3479                     if (!predicate.test(job)) {
3480                         continue;
3481                     }
3482 
3483                     job.dump(proto, JobSchedulerServiceDumpProto.RegisteredJob.DUMP, true, nowElapsed);
3484 
3485                     // isReadyToBeExecuted
3486                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY,
3487                             job.isReady());
3488                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_USER_STARTED,
3489                             areUsersStartedLocked(job));
3490                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
3491                             mPendingJobs.contains(job));
3492                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
3493                             isCurrentlyActiveLocked(job));
3494                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_UID_BACKING_UP,
3495                             mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0);
3496                     boolean componentPresent = false;
3497                     try {
3498                         componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
3499                                 job.getServiceComponent(),
3500                                 PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
3501                                 job.getUserId()) != null);
3502                     } catch (RemoteException e) {
3503                     }
3504                     proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_PRESENT,
3505                             componentPresent);
3506                     proto.write(RegisteredJob.LAST_RUN_HEARTBEAT, heartbeatWhenJobsLastRun(job));
3507 
3508                     proto.end(rjToken);
3509                 }
3510             }
3511             for (StateController controller : mControllers) {
3512                 controller.dumpControllerStateLocked(
3513                         proto, JobSchedulerServiceDumpProto.CONTROLLERS, predicate);
3514             }
3515             for (int i=0; i< mUidPriorityOverride.size(); i++) {
3516                 int uid = mUidPriorityOverride.keyAt(i);
3517                 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
3518                     long pToken = proto.start(JobSchedulerServiceDumpProto.PRIORITY_OVERRIDES);
3519                     proto.write(JobSchedulerServiceDumpProto.PriorityOverride.UID, uid);
3520                     proto.write(JobSchedulerServiceDumpProto.PriorityOverride.OVERRIDE_VALUE,
3521                             mUidPriorityOverride.valueAt(i));
3522                     proto.end(pToken);
3523                 }
3524             }
3525             for (int i = 0; i < mBackingUpUids.size(); i++) {
3526                 int uid = mBackingUpUids.keyAt(i);
3527                 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
3528                     proto.write(JobSchedulerServiceDumpProto.BACKING_UP_UIDS, uid);
3529                 }
3530             }
3531 
3532             mJobPackageTracker.dump(proto, JobSchedulerServiceDumpProto.PACKAGE_TRACKER,
3533                     filterUidFinal);
3534             mJobPackageTracker.dumpHistory(proto, JobSchedulerServiceDumpProto.HISTORY,
3535                     filterUidFinal);
3536 
3537             for (JobStatus job : mPendingJobs) {
3538                 final long pjToken = proto.start(JobSchedulerServiceDumpProto.PENDING_JOBS);
3539 
3540                 job.writeToShortProto(proto, PendingJob.INFO);
3541                 job.dump(proto, PendingJob.DUMP, false, nowElapsed);
3542                 proto.write(PendingJob.EVALUATED_PRIORITY, evaluateJobPriorityLocked(job));
3543                 proto.write(PendingJob.ENQUEUED_DURATION_MS, nowUptime - job.madePending);
3544 
3545                 proto.end(pjToken);
3546             }
3547             for (JobServiceContext jsc : mActiveServices) {
3548                 final long ajToken = proto.start(JobSchedulerServiceDumpProto.ACTIVE_JOBS);
3549                 final JobStatus job = jsc.getRunningJobLocked();
3550 
3551                 if (job == null) {
3552                     final long ijToken = proto.start(ActiveJob.INACTIVE);
3553 
3554                         proto.write(ActiveJob.InactiveJob.TIME_SINCE_STOPPED_MS,
3555                                 nowElapsed - jsc.mStoppedTime);
3556                     if (jsc.mStoppedReason != null) {
3557                         proto.write(ActiveJob.InactiveJob.STOPPED_REASON,
3558                                 jsc.mStoppedReason);
3559                     }
3560 
3561                     proto.end(ijToken);
3562                 } else {
3563                     final long rjToken = proto.start(ActiveJob.RUNNING);
3564 
3565                     job.writeToShortProto(proto, ActiveJob.RunningJob.INFO);
3566 
3567                     proto.write(ActiveJob.RunningJob.RUNNING_DURATION_MS,
3568                             nowElapsed - jsc.getExecutionStartTimeElapsed());
3569                     proto.write(ActiveJob.RunningJob.TIME_UNTIL_TIMEOUT_MS,
3570                             jsc.getTimeoutElapsed() - nowElapsed);
3571 
3572                     job.dump(proto, ActiveJob.RunningJob.DUMP, false, nowElapsed);
3573 
3574                     proto.write(ActiveJob.RunningJob.EVALUATED_PRIORITY,
3575                             evaluateJobPriorityLocked(jsc.getRunningJobLocked()));
3576 
3577                     proto.write(ActiveJob.RunningJob.TIME_SINCE_MADE_ACTIVE_MS,
3578                             nowUptime - job.madeActive);
3579                     proto.write(ActiveJob.RunningJob.PENDING_DURATION_MS,
3580                             job.madeActive - job.madePending);
3581 
3582                     proto.end(rjToken);
3583                 }
3584                 proto.end(ajToken);
3585             }
3586             if (filterUid == -1) {
3587                 proto.write(JobSchedulerServiceDumpProto.IS_READY_TO_ROCK, mReadyToRock);
3588                 proto.write(JobSchedulerServiceDumpProto.REPORTED_ACTIVE, mReportedActive);
3589             }
3590             mConcurrencyManager.dumpProtoLocked(proto,
3591                     JobSchedulerServiceDumpProto.CONCURRENCY_MANAGER, now, nowElapsed);
3592         }
3593 
3594         proto.flush();
3595     }
3596 }
3597