blob: c9739110c03a2f95d3bfd3b11b2f678225ea3c5b [file] [log] [blame]
Christopher Tate7060b042014-06-09 19:50:00 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.job;
18
Christopher Tateb5c07882016-05-26 17:11:09 -070019import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
20import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
21
Shreyas Basarge5db09082016-01-07 13:38:29 +000022import java.io.FileDescriptor;
23import java.io.PrintWriter;
24import java.util.ArrayList;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070025import java.util.Arrays;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070026import java.util.Collections;
27import java.util.Comparator;
Shreyas Basarge5db09082016-01-07 13:38:29 +000028import java.util.Iterator;
29import java.util.List;
30
Christopher Tateee7805b2016-07-15 16:56:56 -070031import android.app.Activity;
Dianne Hackborn8ad2af72015-03-17 17:00:24 -070032import android.app.ActivityManager;
Christopher Tate5568f542014-06-18 13:53:31 -070033import android.app.AppGlobals;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -070034import android.app.IUidObserver;
Christopher Tate7060b042014-06-09 19:50:00 -070035import android.app.job.JobInfo;
Shreyas Basarge5db09082016-01-07 13:38:29 +000036import android.app.job.JobParameters;
Christopher Tate7060b042014-06-09 19:50:00 -070037import android.app.job.JobScheduler;
38import android.app.job.JobService;
Shreyas Basarge5db09082016-01-07 13:38:29 +000039import android.app.job.IJobScheduler;
Christopher Tate7060b042014-06-09 19:50:00 -070040import android.content.BroadcastReceiver;
41import android.content.ComponentName;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070042import android.content.ContentResolver;
Christopher Tate7060b042014-06-09 19:50:00 -070043import android.content.Context;
44import android.content.Intent;
45import android.content.IntentFilter;
Christopher Tate5568f542014-06-18 13:53:31 -070046import android.content.pm.IPackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070047import android.content.pm.PackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070048import android.content.pm.ServiceInfo;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060049import android.content.pm.PackageManager.NameNotFoundException;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070050import android.database.ContentObserver;
Christopher Tateb5c07882016-05-26 17:11:09 -070051import android.net.Uri;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070052import android.os.BatteryStats;
Christopher Tate7060b042014-06-09 19:50:00 -070053import android.os.Binder;
54import android.os.Handler;
55import android.os.Looper;
56import android.os.Message;
Shreyas Basargecbf5ae92016-03-08 16:13:06 +000057import android.os.Process;
Dianne Hackborn88e98df2015-03-23 13:29:14 -070058import android.os.PowerManager;
Christopher Tate7060b042014-06-09 19:50:00 -070059import android.os.RemoteException;
Christopher Tate5d346052016-03-08 12:56:08 -080060import android.os.ResultReceiver;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070061import android.os.ServiceManager;
Dianne Hackborn354736e2016-08-22 17:00:05 -070062import android.os.ShellCallback;
Christopher Tate7060b042014-06-09 19:50:00 -070063import android.os.SystemClock;
64import android.os.UserHandle;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070065import android.provider.Settings;
66import android.util.KeyValueListParser;
Christopher Tate7060b042014-06-09 19:50:00 -070067import android.util.Slog;
68import android.util.SparseArray;
Dianne Hackborn970510b2016-02-24 16:56:42 -080069import android.util.SparseIntArray;
70import android.util.TimeUtils;
Christopher Tate5d346052016-03-08 12:56:08 -080071
Dianne Hackbornfdb19562014-07-11 16:03:36 -070072import com.android.internal.app.IBatteryStats;
Joe Onorato4eb64fd2016-03-21 15:30:09 -070073import com.android.internal.app.procstats.ProcessStats;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070074import com.android.internal.util.ArrayUtils;
Dianne Hackborn627dfa12015-11-11 18:10:30 -080075import com.android.server.DeviceIdleController;
76import com.android.server.LocalServices;
Christopher Tate2f36fd62016-02-18 18:36:08 -080077import com.android.server.job.JobStore.JobStatusFunctor;
Amith Yamasanib0ff3222015-03-04 09:56:14 -080078import com.android.server.job.controllers.AppIdleController;
Christopher Tate7060b042014-06-09 19:50:00 -070079import com.android.server.job.controllers.BatteryController;
80import com.android.server.job.controllers.ConnectivityController;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080081import com.android.server.job.controllers.ContentObserverController;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070082import com.android.server.job.controllers.DeviceIdleJobsController;
Christopher Tate7060b042014-06-09 19:50:00 -070083import com.android.server.job.controllers.IdleController;
84import com.android.server.job.controllers.JobStatus;
85import com.android.server.job.controllers.StateController;
86import com.android.server.job.controllers.TimeController;
87
Jeff Sharkey822cbd12016-02-25 11:09:55 -070088import libcore.util.EmptyArray;
89
Christopher Tate7060b042014-06-09 19:50:00 -070090/**
91 * Responsible for taking jobs representing work to be performed by a client app, and determining
92 * based on the criteria specified when that job should be run against the client application's
93 * endpoint.
94 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
95 * about constraints, or the state of active jobs. It receives callbacks from the various
96 * controllers and completed jobs and operates accordingly.
97 *
98 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
99 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
100 * @hide
101 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800102public final class JobSchedulerService extends com.android.server.SystemService
Matthew Williams01ac45b2014-07-22 20:44:12 -0700103 implements StateChangedListener, JobCompletedListener {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800104 static final String TAG = "JobSchedulerService";
Matthew Williamsaa984312015-10-15 16:08:05 -0700105 public static final boolean DEBUG = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800106
Dianne Hackborn970510b2016-02-24 16:56:42 -0800107 /** The maximum number of concurrent jobs we run at one time. */
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700108 private static final int MAX_JOB_CONTEXTS_COUNT = 16;
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800109 /** Enforce a per-app limit on scheduled jobs? */
Christopher Tate0213ace02016-02-24 14:18:35 -0800110 private static final boolean ENFORCE_MAX_JOBS = true;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800111 /** The maximum number of jobs that we allow an unprivileged app to schedule */
112 private static final int MAX_JOBS_PER_APP = 100;
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700113
Christopher Tate2f36fd62016-02-18 18:36:08 -0800114
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800115 /** Global local for all job scheduler state. */
116 final Object mLock = new Object();
Christopher Tate7060b042014-06-09 19:50:00 -0700117 /** Master list of jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700118 final JobStore mJobs;
Dianne Hackborn807de782016-04-07 17:54:41 -0700119 /** Tracking amount of time each package runs for. */
120 final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
Christopher Tate7060b042014-06-09 19:50:00 -0700121
122 static final int MSG_JOB_EXPIRED = 0;
123 static final int MSG_CHECK_JOB = 1;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700124 static final int MSG_STOP_JOB = 2;
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000125 static final int MSG_CHECK_JOB_GREEDY = 3;
Christopher Tate7060b042014-06-09 19:50:00 -0700126
Christopher Tate7060b042014-06-09 19:50:00 -0700127 /**
128 * Track Services that have currently active or pending jobs. The index is provided by
129 * {@link JobStatus#getServiceToken()}
130 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700131 final List<JobServiceContext> mActiveServices = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700132 /** List of controllers that will notify this service of updates to jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700133 List<StateController> mControllers;
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800134 /** Need direct access to this for testing. */
135 BatteryController mBatteryController;
Christopher Tate7060b042014-06-09 19:50:00 -0700136 /**
137 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
138 * when ready to execute them.
139 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700140 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700141
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700142 int[] mStartedUsers = EmptyArray.INT;
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700143
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700144 final JobHandler mHandler;
145 final JobSchedulerStub mJobSchedulerStub;
146
147 IBatteryStats mBatteryStats;
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700148 PowerManager mPowerManager;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800149 DeviceIdleController.LocalService mLocalDeviceIdleController;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700150
151 /**
152 * Set to true once we are allowed to run third party apps.
153 */
154 boolean mReadyToRock;
155
Christopher Tate7060b042014-06-09 19:50:00 -0700156 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800157 * What we last reported to DeviceIdleController about whether we are active.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800158 */
159 boolean mReportedActive;
160
161 /**
Dianne Hackborn970510b2016-02-24 16:56:42 -0800162 * Current limit on the number of concurrent JobServiceContext entries we want to
163 * keep actively running a job.
164 */
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700165 int mMaxActiveJobs = 1;
Dianne Hackborn970510b2016-02-24 16:56:42 -0800166
167 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800168 * Which uids are currently in the foreground.
169 */
Dianne Hackborn970510b2016-02-24 16:56:42 -0800170 final SparseIntArray mUidPriorityOverride = new SparseIntArray();
171
172 // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
173
174 /**
175 * This array essentially stores the state of mActiveServices array.
176 * The ith index stores the job present on the ith JobServiceContext.
177 * We manipulate this array until we arrive at what jobs should be running on
178 * what JobServiceContext.
179 */
180 JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
181 /**
182 * Indicates whether we need to act on this jobContext id
183 */
184 boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
185 /**
186 * The uid whose jobs we would like to assign to a context.
187 */
188 int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800189
190 /**
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700191 * All times are in milliseconds. These constants are kept synchronized with the system
192 * global Settings. Any access to this class or its fields should be done while
193 * holding the JobSchedulerService.mLock lock.
194 */
195 private final class Constants extends ContentObserver {
196 // Key names stored in the settings value.
197 private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
198 private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800199 private static final String KEY_MIN_BATTERY_NOT_LOW_COUNT = "min_battery_not_low_count";
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700200 private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
201 private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
202 private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
203 private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
204 private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
205 private static final String KEY_FG_JOB_COUNT = "fg_job_count";
206 private static final String KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
207 private static final String KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
208 private static final String KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
209 private static final String KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
210
211 private static final int DEFAULT_MIN_IDLE_COUNT = 1;
212 private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800213 private static final int DEFAULT_MIN_BATTERY_NOT_LOW_COUNT = 1;
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700214 private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
215 private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
216 private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
217 private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
218 private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
219 private static final int DEFAULT_FG_JOB_COUNT = 4;
220 private static final int DEFAULT_BG_NORMAL_JOB_COUNT = 6;
221 private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4;
Nancy Zhenge39a8a42016-10-05 16:27:14 -0700222 private static final int DEFAULT_BG_LOW_JOB_COUNT = 1;
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700223 private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1;
224
225 /**
226 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
227 * early.
228 */
229 int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT;
230 /**
231 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule
232 * things early.
233 */
234 int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT;
235 /**
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800236 * Minimum # of "battery not low" jobs that must be ready in order to force the JMS to
237 * schedule things early.
238 */
239 int MIN_BATTERY_NOT_LOW_COUNT = DEFAULT_MIN_BATTERY_NOT_LOW_COUNT;
240 /**
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700241 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
242 * things early. 1 == Run connectivity jobs as soon as ready.
243 */
244 int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT;
245 /**
246 * Minimum # of content trigger jobs that must be ready in order to force the JMS to
247 * schedule things early.
248 */
249 int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT;
250 /**
251 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy
252 * running some work early. This (and thus the other min counts) is now set to 1, to
253 * prevent any batching at this level. Since we now do batching through doze, that is
254 * a much better mechanism.
255 */
256 int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT;
257 /**
258 * This is the job execution factor that is considered to be heavy use of the system.
259 */
260 float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR;
261 /**
262 * This is the job execution factor that is considered to be moderate use of the system.
263 */
264 float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
265 /**
266 * The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app.
267 */
268 int FG_JOB_COUNT = DEFAULT_FG_JOB_COUNT;
269 /**
270 * The maximum number of background jobs we allow when the system is in a normal
271 * memory state.
272 */
273 int BG_NORMAL_JOB_COUNT = DEFAULT_BG_NORMAL_JOB_COUNT;
274 /**
275 * The maximum number of background jobs we allow when the system is in a moderate
276 * memory state.
277 */
278 int BG_MODERATE_JOB_COUNT = DEFAULT_BG_MODERATE_JOB_COUNT;
279 /**
280 * The maximum number of background jobs we allow when the system is in a low
281 * memory state.
282 */
283 int BG_LOW_JOB_COUNT = DEFAULT_BG_LOW_JOB_COUNT;
284 /**
285 * The maximum number of background jobs we allow when the system is in a critical
286 * memory state.
287 */
288 int BG_CRITICAL_JOB_COUNT = DEFAULT_BG_CRITICAL_JOB_COUNT;
289
290 private ContentResolver mResolver;
291 private final KeyValueListParser mParser = new KeyValueListParser(',');
292
293 public Constants(Handler handler) {
294 super(handler);
295 }
296
297 public void start(ContentResolver resolver) {
298 mResolver = resolver;
299 mResolver.registerContentObserver(Settings.Global.getUriFor(
300 Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
301 updateConstants();
302 }
303
304 @Override
305 public void onChange(boolean selfChange, Uri uri) {
306 updateConstants();
307 }
308
309 private void updateConstants() {
310 synchronized (mLock) {
311 try {
312 mParser.setString(Settings.Global.getString(mResolver,
313 Settings.Global.ALARM_MANAGER_CONSTANTS));
314 } catch (IllegalArgumentException e) {
315 // Failed to parse the settings string, log this and move on
316 // with defaults.
317 Slog.e(TAG, "Bad device idle settings", e);
318 }
319
320 MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
321 DEFAULT_MIN_IDLE_COUNT);
322 MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
323 DEFAULT_MIN_CHARGING_COUNT);
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800324 MIN_BATTERY_NOT_LOW_COUNT = mParser.getInt(KEY_MIN_BATTERY_NOT_LOW_COUNT,
325 DEFAULT_MIN_BATTERY_NOT_LOW_COUNT);
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700326 MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
327 DEFAULT_MIN_CONNECTIVITY_COUNT);
328 MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
329 DEFAULT_MIN_CONTENT_COUNT);
330 MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
331 DEFAULT_MIN_READY_JOBS_COUNT);
332 HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
333 DEFAULT_HEAVY_USE_FACTOR);
334 MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
335 DEFAULT_MODERATE_USE_FACTOR);
336 FG_JOB_COUNT = mParser.getInt(KEY_FG_JOB_COUNT,
337 DEFAULT_FG_JOB_COUNT);
338 BG_NORMAL_JOB_COUNT = mParser.getInt(KEY_BG_NORMAL_JOB_COUNT,
339 DEFAULT_BG_NORMAL_JOB_COUNT);
340 if ((FG_JOB_COUNT+BG_NORMAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
341 BG_NORMAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
342 }
343 BG_MODERATE_JOB_COUNT = mParser.getInt(KEY_BG_MODERATE_JOB_COUNT,
344 DEFAULT_BG_MODERATE_JOB_COUNT);
345 if ((FG_JOB_COUNT+BG_MODERATE_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
346 BG_MODERATE_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
347 }
348 BG_LOW_JOB_COUNT = mParser.getInt(KEY_BG_LOW_JOB_COUNT,
349 DEFAULT_BG_LOW_JOB_COUNT);
350 if ((FG_JOB_COUNT+BG_LOW_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
351 BG_LOW_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
352 }
353 BG_CRITICAL_JOB_COUNT = mParser.getInt(KEY_BG_CRITICAL_JOB_COUNT,
354 DEFAULT_BG_CRITICAL_JOB_COUNT);
355 if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
356 BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
357 }
358 }
359 }
360
361 void dump(PrintWriter pw) {
362 pw.println(" Settings:");
363
364 pw.print(" "); pw.print(KEY_MIN_IDLE_COUNT); pw.print("=");
365 pw.print(MIN_IDLE_COUNT); pw.println();
366
367 pw.print(" "); pw.print(KEY_MIN_CHARGING_COUNT); pw.print("=");
368 pw.print(MIN_CHARGING_COUNT); pw.println();
369
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800370 pw.print(" "); pw.print(KEY_MIN_BATTERY_NOT_LOW_COUNT); pw.print("=");
371 pw.print(MIN_BATTERY_NOT_LOW_COUNT); pw.println();
372
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700373 pw.print(" "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("=");
374 pw.print(MIN_CONNECTIVITY_COUNT); pw.println();
375
376 pw.print(" "); pw.print(KEY_MIN_CONTENT_COUNT); pw.print("=");
377 pw.print(MIN_CONTENT_COUNT); pw.println();
378
379 pw.print(" "); pw.print(KEY_MIN_READY_JOBS_COUNT); pw.print("=");
380 pw.print(MIN_READY_JOBS_COUNT); pw.println();
381
382 pw.print(" "); pw.print(KEY_HEAVY_USE_FACTOR); pw.print("=");
383 pw.print(HEAVY_USE_FACTOR); pw.println();
384
385 pw.print(" "); pw.print(KEY_MODERATE_USE_FACTOR); pw.print("=");
386 pw.print(MODERATE_USE_FACTOR); pw.println();
387
388 pw.print(" "); pw.print(KEY_FG_JOB_COUNT); pw.print("=");
389 pw.print(FG_JOB_COUNT); pw.println();
390
391 pw.print(" "); pw.print(KEY_BG_NORMAL_JOB_COUNT); pw.print("=");
392 pw.print(BG_NORMAL_JOB_COUNT); pw.println();
393
394 pw.print(" "); pw.print(KEY_BG_MODERATE_JOB_COUNT); pw.print("=");
395 pw.print(BG_MODERATE_JOB_COUNT); pw.println();
396
397 pw.print(" "); pw.print(KEY_BG_LOW_JOB_COUNT); pw.print("=");
398 pw.print(BG_LOW_JOB_COUNT); pw.println();
399
400 pw.print(" "); pw.print(KEY_BG_CRITICAL_JOB_COUNT); pw.print("=");
401 pw.print(BG_CRITICAL_JOB_COUNT); pw.println();
402 }
403 }
404
405 final Constants mConstants;
406
407 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700408 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
409 * still clean up. On reinstall the package will have a new uid.
410 */
411 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
412 @Override
413 public void onReceive(Context context, Intent intent) {
Christopher Tateee7805b2016-07-15 16:56:56 -0700414 final String action = intent.getAction();
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700415 if (DEBUG) {
Christopher Tateee7805b2016-07-15 16:56:56 -0700416 Slog.d(TAG, "Receieved: " + action);
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700417 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700418 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
Christopher Tateb5c07882016-05-26 17:11:09 -0700419 // Purge the app's jobs if the whole package was just disabled. When this is
420 // the case the component name will be a bare package name.
421 final String pkgName = getPackageName(intent);
422 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
423 if (pkgName != null && pkgUid != -1) {
424 final String[] changedComponents = intent.getStringArrayExtra(
425 Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
426 if (changedComponents != null) {
427 for (String component : changedComponents) {
428 if (component.equals(pkgName)) {
429 if (DEBUG) {
430 Slog.d(TAG, "Package state change: " + pkgName);
431 }
432 try {
433 final int userId = UserHandle.getUserId(pkgUid);
434 IPackageManager pm = AppGlobals.getPackageManager();
435 final int state = pm.getApplicationEnabledSetting(pkgName, userId);
436 if (state == COMPONENT_ENABLED_STATE_DISABLED
437 || state == COMPONENT_ENABLED_STATE_DISABLED_USER) {
438 if (DEBUG) {
439 Slog.d(TAG, "Removing jobs for package " + pkgName
440 + " in user " + userId);
441 }
Dianne Hackborne07641d2016-11-09 15:07:23 -0800442 cancelJobsForUid(pkgUid);
Christopher Tateb5c07882016-05-26 17:11:09 -0700443 }
Christopher Tate652c5ad2016-10-05 14:45:46 -0700444 } catch (RemoteException|IllegalArgumentException e) {
445 /*
446 * IllegalArgumentException means that the package doesn't exist.
447 * This arises when PACKAGE_CHANGED broadcast delivery has lagged
448 * behind outright uninstall, so by the time we try to act it's gone.
449 * We don't need to act on this PACKAGE_CHANGED when this happens;
450 * we'll get a PACKAGE_REMOVED later and clean up then.
451 *
452 * RemoteException can't actually happen; the package manager is
453 * running in this same process.
454 */
455 }
Christopher Tateb5c07882016-05-26 17:11:09 -0700456 break;
457 }
458 }
459 }
460 } else {
461 Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
462 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700463 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700464 // If this is an outright uninstall rather than the first half of an
465 // app update sequence, cancel the jobs associated with the app.
466 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
467 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
468 if (DEBUG) {
469 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
470 }
Dianne Hackborne07641d2016-11-09 15:07:23 -0800471 cancelJobsForUid(uidRemoved);
Christopher Tate7060b042014-06-09 19:50:00 -0700472 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700473 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
Christopher Tate7060b042014-06-09 19:50:00 -0700474 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
475 if (DEBUG) {
476 Slog.d(TAG, "Removing jobs for user: " + userId);
477 }
478 cancelJobsForUser(userId);
Christopher Tateee7805b2016-07-15 16:56:56 -0700479 } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
480 // Has this package scheduled any jobs, such that we will take action
481 // if it were to be force-stopped?
482 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
483 final String pkgName = intent.getData().getSchemeSpecificPart();
484 if (pkgUid != -1) {
485 List<JobStatus> jobsForUid;
486 synchronized (mLock) {
487 jobsForUid = mJobs.getJobsByUid(pkgUid);
488 }
489 for (int i = jobsForUid.size() - 1; i >= 0; i--) {
490 if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
491 if (DEBUG) {
492 Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
493 + pkgUid + " has jobs");
494 }
495 setResultCode(Activity.RESULT_OK);
496 break;
497 }
498 }
499 }
500 } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
501 // possible force-stop
502 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
503 final String pkgName = intent.getData().getSchemeSpecificPart();
504 if (pkgUid != -1) {
505 if (DEBUG) {
506 Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
507 }
508 cancelJobsForPackageAndUid(pkgName, pkgUid);
509 }
Christopher Tate7060b042014-06-09 19:50:00 -0700510 }
511 }
512 };
513
Christopher Tateb5c07882016-05-26 17:11:09 -0700514 private String getPackageName(Intent intent) {
515 Uri uri = intent.getData();
516 String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
517 return pkg;
518 }
519
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700520 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
521 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800522 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700523 }
524
Dianne Hackborne07641d2016-11-09 15:07:23 -0800525 @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800526 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackborne07641d2016-11-09 15:07:23 -0800527 if (disabled) {
528 cancelJobsForUid(uid);
529 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700530 }
531
532 @Override public void onUidActive(int uid) throws RemoteException {
533 }
534
Dianne Hackborne07641d2016-11-09 15:07:23 -0800535 @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
536 if (disabled) {
537 cancelJobsForUid(uid);
538 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700539 }
540 };
541
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800542 public Object getLock() {
543 return mLock;
544 }
545
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700546 public JobStore getJobStore() {
547 return mJobs;
548 }
549
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700550 @Override
551 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700552 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
553 // Let's kick any outstanding jobs for this user.
554 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
555 }
556
557 @Override
558 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700559 // Let's kick any outstanding jobs for this user.
560 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
561 }
562
563 @Override
564 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700565 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700566 }
567
Christopher Tate7060b042014-06-09 19:50:00 -0700568 /**
569 * Entry point from client to schedule the provided job.
570 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
571 * @param job JobInfo object containing execution parameters
572 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700573 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
574 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700575 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800576 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000577 }
578
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800579 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
580 String tag) {
581 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700582 try {
Dianne Hackbornc3af19a2017-01-20 17:00:44 -0800583 if (ActivityManager.getService().isAppStartModeDisabled(uId,
584 job.getService().getPackageName())) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700585 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
586 + " -- package not allowed to start");
587 return JobScheduler.RESULT_FAILURE;
588 }
589 } catch (RemoteException e) {
590 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800591 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
592 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800593 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800594 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800595 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800596 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
597 Slog.w(TAG, "Too many jobs for uid " + uId);
598 throw new IllegalStateException("Apps may not schedule more than "
599 + MAX_JOBS_PER_APP + " distinct jobs");
600 }
601 }
602
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800603 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700604 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700605 cancelJobImpl(toCancel, jobStatus);
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700606 }
607 startTrackingJob(jobStatus, toCancel);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800608 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700609 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700610 return JobScheduler.RESULT_SUCCESS;
611 }
612
613 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800614 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800615 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
616 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
617 for (int i = jobs.size() - 1; i >= 0; i--) {
618 JobStatus job = jobs.get(i);
619 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700620 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800621 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700622 }
Christopher Tate7060b042014-06-09 19:50:00 -0700623 }
624
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600625 public JobInfo getPendingJob(int uid, int jobId) {
626 synchronized (mLock) {
627 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
628 for (int i = jobs.size() - 1; i >= 0; i--) {
629 JobStatus job = jobs.get(i);
630 if (job.getJobId() == jobId) {
631 return job.getJob();
632 }
633 }
634 return null;
635 }
636 }
637
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700638 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700639 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800640 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700641 jobsForUser = mJobs.getJobsByUser(userHandle);
642 }
643 for (int i=0; i<jobsForUser.size(); i++) {
644 JobStatus toRemove = jobsForUser.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700645 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700646 }
647 }
648
Christopher Tateee7805b2016-07-15 16:56:56 -0700649 void cancelJobsForPackageAndUid(String pkgName, int uid) {
650 List<JobStatus> jobsForUid;
651 synchronized (mLock) {
652 jobsForUid = mJobs.getJobsByUid(uid);
653 }
654 for (int i = jobsForUid.size() - 1; i >= 0; i--) {
655 final JobStatus job = jobsForUid.get(i);
656 if (job.getSourcePackageName().equals(pkgName)) {
657 cancelJobImpl(job, null);
658 }
659 }
660 }
661
Christopher Tate7060b042014-06-09 19:50:00 -0700662 /**
663 * Entry point from client to cancel all jobs originating from their uid.
664 * This will remove the job from the master list, and cancel the job if it was staged for
665 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700666 * @param uid Uid to check against for removal of a job.
Dianne Hackborne07641d2016-11-09 15:07:23 -0800667 *
Christopher Tate7060b042014-06-09 19:50:00 -0700668 */
Dianne Hackborne07641d2016-11-09 15:07:23 -0800669 public void cancelJobsForUid(int uid) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700670 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800671 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700672 jobsForUid = mJobs.getJobsByUid(uid);
673 }
674 for (int i=0; i<jobsForUid.size(); i++) {
675 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700676 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700677 }
678 }
679
680 /**
681 * Entry point from client to cancel the job corresponding to the jobId provided.
682 * This will remove the job from the master list, and cancel the job if it was staged for
683 * execution or being executed.
684 * @param uid Uid of the calling client.
685 * @param jobId Id of the job, provided at schedule-time.
686 */
687 public void cancelJob(int uid, int jobId) {
688 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800689 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700690 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700691 }
692 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700693 cancelJobImpl(toCancel, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700694 }
695 }
696
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700697 private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800698 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700699 stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800700 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700701 // Remove from pending queue.
Dianne Hackborn807de782016-04-07 17:54:41 -0700702 if (mPendingJobs.remove(cancelled)) {
703 mJobPackageTracker.noteNonpending(cancelled);
704 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700705 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000706 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800707 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700708 }
Christopher Tate7060b042014-06-09 19:50:00 -0700709 }
710
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800711 void updateUidState(int uid, int procState) {
712 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800713 if (procState == ActivityManager.PROCESS_STATE_TOP) {
714 // Only use this if we are exactly the top app. All others can live
715 // with just the foreground priority. This means that persistent processes
716 // can never be the top app priority... that is fine.
717 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
718 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
719 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800720 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800721 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800722 }
723 }
724 }
725
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700726 @Override
727 public void onDeviceIdleStateChanged(boolean deviceIdle) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800728 synchronized (mLock) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700729 if (deviceIdle) {
Jeff Sharkey34618b52016-06-01 15:51:19 -0600730 // When becoming idle, make sure no jobs are actively running,
731 // except those using the idle exemption flag.
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700732 for (int i=0; i<mActiveServices.size(); i++) {
733 JobServiceContext jsc = mActiveServices.get(i);
734 final JobStatus executing = jsc.getRunningJob();
Jeff Sharkey34618b52016-06-01 15:51:19 -0600735 if (executing != null
736 && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700737 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
738 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700739 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700740 } else {
741 // When coming out of idle, allow thing to start back up.
742 if (mReadyToRock) {
743 if (mLocalDeviceIdleController != null) {
744 if (!mReportedActive) {
745 mReportedActive = true;
746 mLocalDeviceIdleController.setJobsActive(true);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700747 }
748 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700749 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700750 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700751 }
752 }
753 }
754
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800755 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000756 // active is true if pending queue contains jobs OR some job is running.
757 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800758 if (mPendingJobs.size() <= 0) {
759 for (int i=0; i<mActiveServices.size(); i++) {
Dianne Hackborn7ab40252016-06-15 17:30:24 -0700760 final JobServiceContext jsc = mActiveServices.get(i);
761 final JobStatus job = jsc.getRunningJob();
762 if (job != null
763 && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
764 && !job.dozeWhitelisted) {
765 // We will report active if we have a job running and it is not an exception
766 // due to being in the foreground or whitelisted.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800767 active = true;
768 break;
769 }
770 }
771 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000772
773 if (mReportedActive != active) {
774 mReportedActive = active;
775 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800776 mLocalDeviceIdleController.setJobsActive(active);
777 }
778 }
779 }
780
Christopher Tate7060b042014-06-09 19:50:00 -0700781 /**
782 * Initializes the system service.
783 * <p>
784 * Subclasses must define a single argument constructor that accepts the context
785 * and passes it to super.
786 * </p>
787 *
788 * @param context The system server context.
789 */
790 public JobSchedulerService(Context context) {
791 super(context);
Dianne Hackborn970e3f42016-06-01 10:55:13 -0700792 mHandler = new JobHandler(context.getMainLooper());
793 mConstants = new Constants(mHandler);
794 mJobSchedulerStub = new JobSchedulerStub();
795 mJobs = JobStore.initAndGet(this);
796
Christopher Tate7060b042014-06-09 19:50:00 -0700797 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700798 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700799 mControllers.add(ConnectivityController.get(this));
800 mControllers.add(TimeController.get(this));
801 mControllers.add(IdleController.get(this));
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800802 mBatteryController = BatteryController.get(this);
803 mControllers.add(mBatteryController);
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800804 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800805 mControllers.add(ContentObserverController.get(this));
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700806 mControllers.add(DeviceIdleJobsController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700807 }
808
809 @Override
810 public void onStart() {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000811 publishLocalService(JobSchedulerInternal.class, new LocalService());
Christopher Tate7060b042014-06-09 19:50:00 -0700812 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
813 }
814
815 @Override
816 public void onBootPhase(int phase) {
817 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700818 mConstants.start(getContext().getContentResolver());
Shreyas Basarge5db09082016-01-07 13:38:29 +0000819 // Register br for package removals and user removals.
Christopher Tateb5c07882016-05-26 17:11:09 -0700820 final IntentFilter filter = new IntentFilter();
821 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
822 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
Christopher Tateee7805b2016-07-15 16:56:56 -0700823 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
824 filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
Christopher Tate7060b042014-06-09 19:50:00 -0700825 filter.addDataScheme("package");
826 getContext().registerReceiverAsUser(
827 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
828 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
829 getContext().registerReceiverAsUser(
830 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000831 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700832 try {
Sudheer Shankadc589ac2016-11-10 15:30:17 -0800833 ActivityManager.getService().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800834 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
Dianne Hackborn5614bf52016-11-07 17:26:41 -0800835 | ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN,
836 null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700837 } catch (RemoteException e) {
838 // ignored; both services live in system_server
839 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700840 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800841 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700842 // Let's go!
843 mReadyToRock = true;
844 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
845 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800846 mLocalDeviceIdleController
847 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700848 // Create the "runners".
849 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
850 mActiveServices.add(
Dianne Hackborn807de782016-04-07 17:54:41 -0700851 new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700852 getContext().getMainLooper()));
853 }
854 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800855 mJobs.forEachJob(new JobStatusFunctor() {
856 @Override
857 public void process(JobStatus job) {
858 for (int controller = 0; controller < mControllers.size(); controller++) {
859 final StateController sc = mControllers.get(controller);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800860 sc.maybeStartTrackingJobLocked(job, null);
861 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700862 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800863 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700864 // GO GO GO!
865 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
866 }
Christopher Tate7060b042014-06-09 19:50:00 -0700867 }
868 }
869
870 /**
871 * Called when we have a job status object that we need to insert in our
872 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
873 * about.
874 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800875 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800876 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800877 final boolean update = mJobs.add(jobStatus);
878 if (mReadyToRock) {
879 for (int i = 0; i < mControllers.size(); i++) {
880 StateController controller = mControllers.get(i);
881 if (update) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700882 controller.maybeStopTrackingJobLocked(jobStatus, null, true);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800883 }
884 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700885 }
Christopher Tate7060b042014-06-09 19:50:00 -0700886 }
Christopher Tate7060b042014-06-09 19:50:00 -0700887 }
888 }
889
890 /**
891 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
892 * object removed.
893 */
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700894 private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
895 boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800896 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700897 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800898 final boolean removed = mJobs.remove(jobStatus, writeBack);
899 if (removed && mReadyToRock) {
900 for (int i=0; i<mControllers.size(); i++) {
901 StateController controller = mControllers.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700902 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800903 }
Christopher Tate7060b042014-06-09 19:50:00 -0700904 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800905 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700906 }
Christopher Tate7060b042014-06-09 19:50:00 -0700907 }
908
Shreyas Basarge5db09082016-01-07 13:38:29 +0000909 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700910 for (int i=0; i<mActiveServices.size(); i++) {
911 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700912 final JobStatus executing = jsc.getRunningJob();
913 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000914 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700915 return true;
916 }
917 }
918 return false;
919 }
920
921 /**
922 * @param job JobStatus we are querying against.
923 * @return Whether or not the job represented by the status object is currently being run or
924 * is pending.
925 */
926 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700927 for (int i=0; i<mActiveServices.size(); i++) {
928 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tateeafb5352016-10-04 16:34:48 -0700929 // The 'unsafe' direct-internal-reference running-job inspector is okay to
930 // use here because we are already holding the necessary lock *and* we
931 // immediately discard the returned object reference, if any; we return
932 // only a boolean state indicator to the caller.
933 final JobStatus running = serviceContext.getRunningJobUnsafeLocked();
Christopher Tate7060b042014-06-09 19:50:00 -0700934 if (running != null && running.matches(job.getUid(), job.getJobId())) {
935 return true;
936 }
937 }
938 return false;
939 }
940
Dianne Hackborn807de782016-04-07 17:54:41 -0700941 void noteJobsPending(List<JobStatus> jobs) {
942 for (int i = jobs.size() - 1; i >= 0; i--) {
943 JobStatus job = jobs.get(i);
944 mJobPackageTracker.notePending(job);
945 }
946 }
947
948 void noteJobsNonpending(List<JobStatus> jobs) {
949 for (int i = jobs.size() - 1; i >= 0; i--) {
950 JobStatus job = jobs.get(i);
951 mJobPackageTracker.noteNonpending(job);
952 }
953 }
954
Christopher Tate7060b042014-06-09 19:50:00 -0700955 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700956 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
957 * specify an override deadline on a failed job (the failed job will run even though it's not
958 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
959 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
960 *
Christopher Tate7060b042014-06-09 19:50:00 -0700961 * @param failureToReschedule Provided job status that we will reschedule.
962 * @return A newly instantiated JobStatus with the same constraints as the last job except
963 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700964 *
965 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700966 */
967 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
968 final long elapsedNowMillis = SystemClock.elapsedRealtime();
969 final JobInfo job = failureToReschedule.getJob();
970
971 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700972 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
973 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700974
975 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700976 case JobInfo.BACKOFF_POLICY_LINEAR:
977 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700978 break;
979 default:
980 if (DEBUG) {
981 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
982 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700983 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
984 delayMillis =
985 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700986 break;
987 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700988 delayMillis =
989 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800990 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700991 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800992 for (int ic=0; ic<mControllers.size(); ic++) {
993 StateController controller = mControllers.get(ic);
994 controller.rescheduleForFailure(newJob, failureToReschedule);
995 }
996 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700997 }
998
999 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -07001000 * Called after a periodic has executed so we can reschedule it. We take the last execution
1001 * time of the job to be the time of completion (i.e. the time at which this function is
1002 * called).
Christopher Tate7060b042014-06-09 19:50:00 -07001003 * This could be inaccurate b/c the job can run for as long as
1004 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
1005 * to underscheduling at least, rather than if we had taken the last execution time to be the
1006 * start of the execution.
1007 * @return A new job representing the execution criteria for this instantiation of the
1008 * recurring job.
1009 */
1010 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
1011 final long elapsedNow = SystemClock.elapsedRealtime();
1012 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -07001013 long runEarly = 0L;
1014
1015 // If this periodic was rescheduled it won't have a deadline.
1016 if (periodicToReschedule.hasDeadlineConstraint()) {
1017 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
1018 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001019 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -07001020 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001021 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
1022 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -07001023
1024 if (DEBUG) {
1025 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
1026 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
1027 }
1028 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
1029 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
1030 }
1031
1032 // JobCompletedListener implementations.
1033
1034 /**
1035 * A job just finished executing. We fetch the
1036 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
1037 * whether we want to reschedule we readd it to the controllers.
1038 * @param jobStatus Completed job.
1039 * @param needsReschedule Whether the implementing class should reschedule this job.
1040 */
1041 @Override
1042 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
1043 if (DEBUG) {
1044 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
1045 }
Shreyas Basarge73f10252016-02-11 17:06:13 +00001046 // Do not write back immediately if this is a periodic job. The job may get lost if system
1047 // shuts down before it is added back.
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001048 if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -07001049 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001050 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -07001051 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001052 // We still want to check for jobs to execute, because this job may have
1053 // scheduled a new job under the same job id, and now we can run it.
1054 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001055 return;
1056 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001057 // Note: there is a small window of time in here where, when rescheduling a job,
1058 // we will stop monitoring its content providers. This should be fixed by stopping
1059 // the old job after scheduling the new one, but since we have no lock held here
1060 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -07001061 if (needsReschedule) {
1062 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001063 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001064 } else if (jobStatus.getJob().isPeriodic()) {
1065 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001066 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001067 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001068 reportActive();
1069 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001070 }
1071
1072 // StateChangedListener implementations.
1073
1074 /**
Matthew Williams48a30db2014-09-23 13:39:36 -07001075 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
1076 * some controller's state has changed, so as to run through the list of jobs and start/stop
1077 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -07001078 */
1079 @Override
1080 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001081 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001082 }
1083
1084 @Override
1085 public void onRunJobNow(JobStatus jobStatus) {
1086 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
1087 }
1088
Christopher Tate7060b042014-06-09 19:50:00 -07001089 private class JobHandler extends Handler {
1090
1091 public JobHandler(Looper looper) {
1092 super(looper);
1093 }
1094
1095 @Override
1096 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001097 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001098 if (!mReadyToRock) {
1099 return;
1100 }
1101 }
Christopher Tate7060b042014-06-09 19:50:00 -07001102 switch (message.what) {
1103 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001104 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -07001105 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -07001106 // runNow can be null, which is a controller's way of indicating that its
1107 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -07001108 if (runNow != null && !mPendingJobs.contains(runNow)
1109 && mJobs.containsJob(runNow)) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001110 mJobPackageTracker.notePending(runNow);
Christopher Tate7060b042014-06-09 19:50:00 -07001111 mPendingJobs.add(runNow);
1112 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001113 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -07001114 }
Christopher Tate7060b042014-06-09 19:50:00 -07001115 break;
1116 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001117 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001118 if (mReportedActive) {
1119 // if jobs are currently being run, queue all ready jobs for execution.
1120 queueReadyJobsForExecutionLockedH();
1121 } else {
1122 // Check the list of jobs and run some of them if we feel inclined.
1123 maybeQueueReadyJobsForExecutionLockedH();
1124 }
1125 }
1126 break;
1127 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001128 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001129 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -07001130 }
Christopher Tate7060b042014-06-09 19:50:00 -07001131 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001132 case MSG_STOP_JOB:
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001133 cancelJobImpl((JobStatus)message.obj, null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001134 break;
Christopher Tate7060b042014-06-09 19:50:00 -07001135 }
1136 maybeRunPendingJobsH();
1137 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
1138 removeMessages(MSG_CHECK_JOB);
1139 }
1140
1141 /**
1142 * Run through list of jobs and execute all possible - at least one is expired so we do
1143 * as many as we can.
1144 */
Matthew Williams48a30db2014-09-23 13:39:36 -07001145 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001146 if (DEBUG) {
1147 Slog.d(TAG, "queuing all ready jobs for execution:");
1148 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001149 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001150 mPendingJobs.clear();
1151 mJobs.forEachJob(mReadyQueueFunctor);
1152 mReadyQueueFunctor.postProcess();
1153
Matthew Williams48a30db2014-09-23 13:39:36 -07001154 if (DEBUG) {
1155 final int queuedJobs = mPendingJobs.size();
1156 if (queuedJobs == 0) {
1157 Slog.d(TAG, "No jobs pending.");
1158 } else {
1159 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -07001160 }
Christopher Tate7060b042014-06-09 19:50:00 -07001161 }
1162 }
1163
Christopher Tate2f36fd62016-02-18 18:36:08 -08001164 class ReadyJobQueueFunctor implements JobStatusFunctor {
1165 ArrayList<JobStatus> newReadyJobs;
1166
1167 @Override
1168 public void process(JobStatus job) {
1169 if (isReadyToBeExecutedLocked(job)) {
1170 if (DEBUG) {
1171 Slog.d(TAG, " queued " + job.toShortString());
1172 }
1173 if (newReadyJobs == null) {
1174 newReadyJobs = new ArrayList<JobStatus>();
1175 }
1176 newReadyJobs.add(job);
1177 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
1178 stopJobOnServiceContextLocked(job,
1179 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
1180 }
1181 }
1182
1183 public void postProcess() {
1184 if (newReadyJobs != null) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001185 noteJobsPending(newReadyJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001186 mPendingJobs.addAll(newReadyJobs);
1187 }
1188 newReadyJobs = null;
1189 }
1190 }
1191 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
1192
Christopher Tate7060b042014-06-09 19:50:00 -07001193 /**
1194 * The state of at least one job has changed. Here is where we could enforce various
1195 * policies on when we want to execute jobs.
1196 * Right now the policy is such:
1197 * If >1 of the ready jobs is idle mode we send all of them off
1198 * if more than 2 network connectivity jobs are ready we send them all off.
1199 * If more than 4 jobs total are ready we send them all off.
1200 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
1201 */
Christopher Tate2f36fd62016-02-18 18:36:08 -08001202 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
1203 int chargingCount;
Dianne Hackborna06ec6a2017-02-13 10:08:42 -08001204 int batteryNotLowCount;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001205 int idleCount;
1206 int backoffCount;
1207 int connectivityCount;
1208 int contentCount;
1209 List<JobStatus> runnableJobs;
1210
1211 public MaybeReadyJobQueueFunctor() {
1212 reset();
1213 }
1214
1215 // Functor method invoked for each job via JobStore.forEachJob()
1216 @Override
1217 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001218 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001219 try {
Dianne Hackbornc3af19a2017-01-20 17:00:44 -08001220 if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
1221 job.getJob().getService().getPackageName())) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001222 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
1223 + job.getJob().toString() + " -- package not allowed to start");
1224 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001225 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001226 }
1227 } catch (RemoteException e) {
1228 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001229 if (job.getNumFailures() > 0) {
1230 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -07001231 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001232 if (job.hasIdleConstraint()) {
1233 idleCount++;
1234 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001235 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()
1236 || job.hasNotRoamingConstraint()) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001237 connectivityCount++;
1238 }
1239 if (job.hasChargingConstraint()) {
1240 chargingCount++;
1241 }
Dianne Hackborna06ec6a2017-02-13 10:08:42 -08001242 if (job.hasBatteryNotLowConstraint()) {
1243 batteryNotLowCount++;
1244 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001245 if (job.hasContentTriggerConstraint()) {
1246 contentCount++;
1247 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001248 if (runnableJobs == null) {
1249 runnableJobs = new ArrayList<>();
1250 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001251 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001252 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001253 stopJobOnServiceContextLocked(job,
1254 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -07001255 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001256 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001257
1258 public void postProcess() {
1259 if (backoffCount > 0 ||
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001260 idleCount >= mConstants.MIN_IDLE_COUNT ||
1261 connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
1262 chargingCount >= mConstants.MIN_CHARGING_COUNT ||
Dianne Hackborna06ec6a2017-02-13 10:08:42 -08001263 batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001264 contentCount >= mConstants.MIN_CONTENT_COUNT ||
1265 (runnableJobs != null
1266 && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001267 if (DEBUG) {
1268 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
1269 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001270 noteJobsPending(runnableJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001271 mPendingJobs.addAll(runnableJobs);
1272 } else {
1273 if (DEBUG) {
1274 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
1275 }
Christopher Tate7060b042014-06-09 19:50:00 -07001276 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001277
1278 // Be ready for next time
1279 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -07001280 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001281
1282 private void reset() {
1283 chargingCount = 0;
1284 idleCount = 0;
1285 backoffCount = 0;
1286 connectivityCount = 0;
Dianne Hackborna06ec6a2017-02-13 10:08:42 -08001287 batteryNotLowCount = 0;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001288 contentCount = 0;
1289 runnableJobs = null;
1290 }
1291 }
1292 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
1293
1294 private void maybeQueueReadyJobsForExecutionLockedH() {
1295 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
1296
Dianne Hackborn807de782016-04-07 17:54:41 -07001297 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001298 mPendingJobs.clear();
1299 mJobs.forEachJob(mMaybeQueueFunctor);
1300 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -07001301 }
1302
1303 /**
1304 * Criteria for moving a job into the pending queue:
1305 * - It's ready.
1306 * - It's not pending.
1307 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001308 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001309 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -07001310 */
1311 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001312 final boolean jobReady = job.isReady();
1313 final boolean jobPending = mPendingJobs.contains(job);
1314 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001315
1316 final int userId = job.getUserId();
1317 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
1318 final boolean componentPresent;
1319 try {
1320 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
1321 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
1322 userId) != null);
1323 } catch (RemoteException e) {
1324 throw e.rethrowAsRuntimeException();
1325 }
1326
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001327 if (DEBUG) {
1328 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
1329 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001330 + " active=" + jobActive + " userStarted=" + userStarted
1331 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001332 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001333 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -07001334 }
1335
1336 /**
1337 * Criteria for cancelling an active job:
1338 * - It's not ready
1339 * - It's running on a JSC.
1340 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001341 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001342 return !job.isReady() && isCurrentlyActiveLocked(job);
1343 }
1344
1345 /**
1346 * Reconcile jobs in the pending queue against available execution contexts.
1347 * A controller can force a job into the pending queue even if it's already running, but
1348 * here is where we decide whether to actually execute it.
1349 */
1350 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001351 synchronized (mLock) {
Matthew Williams75fc5252014-09-02 16:17:53 -07001352 if (DEBUG) {
1353 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1354 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001355 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001356 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001357 }
1358 }
1359 }
1360
Dianne Hackborn807de782016-04-07 17:54:41 -07001361 private int adjustJobPriority(int curPriority, JobStatus job) {
1362 if (curPriority < JobInfo.PRIORITY_TOP_APP) {
1363 float factor = mJobPackageTracker.getLoadFactor(job);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001364 if (factor >= mConstants.HEAVY_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001365 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001366 } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001367 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
1368 }
1369 }
1370 return curPriority;
1371 }
1372
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001373 private int evaluateJobPriorityLocked(JobStatus job) {
1374 int priority = job.getPriority();
1375 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001376 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001377 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001378 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1379 if (override != 0) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001380 return adjustJobPriority(override, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001381 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001382 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001383 }
1384
Christopher Tate7060b042014-06-09 19:50:00 -07001385 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001386 * Takes jobs from pending queue and runs them on available contexts.
1387 * If no contexts are available, preempts lower priority jobs to
1388 * run higher priority ones.
1389 * Lock on mJobs before calling this function.
1390 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001391 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001392 if (DEBUG) {
1393 Slog.d(TAG, printPendingQueue());
1394 }
1395
Dianne Hackborn970510b2016-02-24 16:56:42 -08001396 int memLevel;
1397 try {
Sudheer Shankadc589ac2016-11-10 15:30:17 -08001398 memLevel = ActivityManager.getService().getMemoryTrimLevel();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001399 } catch (RemoteException e) {
1400 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1401 }
1402 switch (memLevel) {
1403 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001404 mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001405 break;
1406 case ProcessStats.ADJ_MEM_FACTOR_LOW:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001407 mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001408 break;
1409 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001410 mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001411 break;
1412 default:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001413 mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001414 break;
1415 }
1416
1417 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1418 boolean[] act = mTmpAssignAct;
1419 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1420 int numActive = 0;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001421 int numForeground = 0;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001422 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1423 final JobServiceContext js = mActiveServices.get(i);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001424 final JobStatus status = js.getRunningJob();
1425 if ((contextIdToJobMap[i] = status) != null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001426 numActive++;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001427 if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
1428 numForeground++;
1429 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001430 }
1431 act[i] = false;
1432 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001433 }
1434 if (DEBUG) {
1435 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1436 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001437 for (int i=0; i<mPendingJobs.size(); i++) {
1438 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001439
1440 // If job is already running, go to next job.
1441 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1442 if (jobRunningContext != -1) {
1443 continue;
1444 }
1445
Dianne Hackborn970510b2016-02-24 16:56:42 -08001446 final int priority = evaluateJobPriorityLocked(nextPending);
1447 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001448
Shreyas Basarge5db09082016-01-07 13:38:29 +00001449 // Find a context for nextPending. The context should be available OR
1450 // it should have lowest priority among all running jobs
1451 // (sharing the same Uid as nextPending)
1452 int minPriority = Integer.MAX_VALUE;
1453 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001454 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1455 JobStatus job = contextIdToJobMap[j];
1456 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001457 if (job == null) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001458 if ((numActive < mMaxActiveJobs ||
1459 (priority >= JobInfo.PRIORITY_TOP_APP &&
1460 numForeground < mConstants.FG_JOB_COUNT)) &&
Dianne Hackborn970510b2016-02-24 16:56:42 -08001461 (preferredUid == nextPending.getUid() ||
1462 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1463 // This slot is free, and we haven't yet hit the limit on
1464 // concurrent jobs... we can just throw the job in to here.
1465 minPriorityContextId = j;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001466 break;
1467 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001468 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001469 // the context has a preferred Uid or we have reached the limit on
1470 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001471 continue;
1472 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001473 if (job.getUid() != nextPending.getUid()) {
1474 continue;
1475 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001476 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001477 continue;
1478 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001479 if (minPriority > nextPending.lastEvaluatedPriority) {
1480 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001481 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001482 }
1483 }
1484 if (minPriorityContextId != -1) {
1485 contextIdToJobMap[minPriorityContextId] = nextPending;
1486 act[minPriorityContextId] = true;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001487 numActive++;
1488 if (priority >= JobInfo.PRIORITY_TOP_APP) {
1489 numForeground++;
1490 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001491 }
1492 }
1493 if (DEBUG) {
1494 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1495 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001496 mJobPackageTracker.noteConcurrency(numActive, numForeground);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001497 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001498 boolean preservePreferredUid = false;
1499 if (act[i]) {
1500 JobStatus js = mActiveServices.get(i).getRunningJob();
1501 if (js != null) {
1502 if (DEBUG) {
1503 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1504 }
1505 // preferredUid will be set to uid of currently running job.
1506 mActiveServices.get(i).preemptExecutingJob();
1507 preservePreferredUid = true;
1508 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001509 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001510 if (DEBUG) {
1511 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001512 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001513 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001514 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001515 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001516 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001517 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1518 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001519 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001520 if (mPendingJobs.remove(pendingJob)) {
1521 mJobPackageTracker.noteNonpending(pendingJob);
1522 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001523 }
1524 }
1525 if (!preservePreferredUid) {
1526 mActiveServices.get(i).clearPreferredUid();
1527 }
1528 }
1529 }
1530
1531 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1532 for (int i=0; i<map.length; i++) {
1533 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1534 return i;
1535 }
1536 }
1537 return -1;
1538 }
1539
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001540 final class LocalService implements JobSchedulerInternal {
1541
1542 /**
1543 * Returns a list of all pending jobs. A running job is not considered pending. Periodic
1544 * jobs are always considered pending.
1545 */
Amith Yamasanicb926fc2016-03-14 17:15:20 -07001546 @Override
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001547 public List<JobInfo> getSystemScheduledPendingJobs() {
1548 synchronized (mLock) {
1549 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
1550 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
1551 @Override
1552 public void process(JobStatus job) {
1553 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
1554 pendingJobs.add(job.getJob());
1555 }
1556 }
1557 });
1558 return pendingJobs;
1559 }
1560 }
1561 }
1562
Shreyas Basarge5db09082016-01-07 13:38:29 +00001563 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001564 * Binder stub trampoline implementation
1565 */
1566 final class JobSchedulerStub extends IJobScheduler.Stub {
1567 /** Cache determination of whether a given app can persist jobs
1568 * key is uid of the calling app; value is undetermined/true/false
1569 */
1570 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1571
1572 // Enforce that only the app itself (or shared uid participant) can schedule a
1573 // job that runs one of the app's services, as well as verifying that the
1574 // named service properly requires the BIND_JOB_SERVICE permission
1575 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001576 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001577 final ComponentName service = job.getService();
1578 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001579 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey8a372a02016-03-16 16:25:45 -06001580 PackageManager.MATCH_DIRECT_BOOT_AWARE
1581 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001582 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001583 if (si == null) {
1584 throw new IllegalArgumentException("No such service " + service);
1585 }
Christopher Tate7060b042014-06-09 19:50:00 -07001586 if (si.applicationInfo.uid != uid) {
1587 throw new IllegalArgumentException("uid " + uid +
1588 " cannot schedule job in " + service.getPackageName());
1589 }
1590 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1591 throw new IllegalArgumentException("Scheduled service " + service
1592 + " does not require android.permission.BIND_JOB_SERVICE permission");
1593 }
Christopher Tate5568f542014-06-18 13:53:31 -07001594 } catch (RemoteException e) {
1595 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001596 }
1597 }
1598
1599 private boolean canPersistJobs(int pid, int uid) {
1600 // If we get this far we're good to go; all we need to do now is check
1601 // whether the app is allowed to persist its scheduled work.
1602 final boolean canPersist;
1603 synchronized (mPersistCache) {
1604 Boolean cached = mPersistCache.get(uid);
1605 if (cached != null) {
1606 canPersist = cached.booleanValue();
1607 } else {
1608 // Persisting jobs is tantamount to running at boot, so we permit
1609 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1610 // permission
1611 int result = getContext().checkPermission(
1612 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1613 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1614 mPersistCache.put(uid, canPersist);
1615 }
1616 }
1617 return canPersist;
1618 }
1619
1620 // IJobScheduler implementation
1621 @Override
1622 public int schedule(JobInfo job) throws RemoteException {
1623 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001624 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001625 }
1626 final int pid = Binder.getCallingPid();
1627 final int uid = Binder.getCallingUid();
1628
1629 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001630 if (job.isPersisted()) {
1631 if (!canPersistJobs(pid, uid)) {
1632 throw new IllegalArgumentException("Error: requested job be persisted without"
1633 + " holding RECEIVE_BOOT_COMPLETED permission.");
1634 }
1635 }
Christopher Tate7060b042014-06-09 19:50:00 -07001636
Jeff Sharkey785f4942016-07-14 10:31:15 -06001637 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1638 getContext().enforceCallingOrSelfPermission(
1639 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1640 }
1641
Christopher Tate7060b042014-06-09 19:50:00 -07001642 long ident = Binder.clearCallingIdentity();
1643 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001644 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001645 } finally {
1646 Binder.restoreCallingIdentity(ident);
1647 }
1648 }
1649
1650 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001651 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001652 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001653 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001654 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001655 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1656 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001657 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001658
1659 if (packageName == null) {
1660 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001661 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001662
1663 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1664 android.Manifest.permission.UPDATE_DEVICE_STATS);
1665 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1666 throw new SecurityException("Caller uid " + callerUid
1667 + " not permitted to schedule jobs for other apps");
1668 }
1669
Jeff Sharkey4f100402016-05-03 17:44:23 -06001670 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1671 getContext().enforceCallingOrSelfPermission(
1672 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1673 }
1674
Shreyas Basarge968ac752016-01-11 23:09:26 +00001675 long ident = Binder.clearCallingIdentity();
1676 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001677 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001678 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001679 } finally {
1680 Binder.restoreCallingIdentity(ident);
1681 }
1682 }
1683
1684 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001685 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1686 final int uid = Binder.getCallingUid();
1687
1688 long ident = Binder.clearCallingIdentity();
1689 try {
1690 return JobSchedulerService.this.getPendingJobs(uid);
1691 } finally {
1692 Binder.restoreCallingIdentity(ident);
1693 }
1694 }
1695
1696 @Override
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001697 public JobInfo getPendingJob(int jobId) throws RemoteException {
1698 final int uid = Binder.getCallingUid();
1699
1700 long ident = Binder.clearCallingIdentity();
1701 try {
1702 return JobSchedulerService.this.getPendingJob(uid, jobId);
1703 } finally {
1704 Binder.restoreCallingIdentity(ident);
1705 }
1706 }
1707
1708 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001709 public void cancelAll() throws RemoteException {
1710 final int uid = Binder.getCallingUid();
1711
1712 long ident = Binder.clearCallingIdentity();
1713 try {
Dianne Hackborne07641d2016-11-09 15:07:23 -08001714 JobSchedulerService.this.cancelJobsForUid(uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001715 } finally {
1716 Binder.restoreCallingIdentity(ident);
1717 }
1718 }
1719
1720 @Override
1721 public void cancel(int jobId) throws RemoteException {
1722 final int uid = Binder.getCallingUid();
1723
1724 long ident = Binder.clearCallingIdentity();
1725 try {
1726 JobSchedulerService.this.cancelJob(uid, jobId);
1727 } finally {
1728 Binder.restoreCallingIdentity(ident);
1729 }
1730 }
1731
1732 /**
1733 * "dumpsys" infrastructure
1734 */
1735 @Override
1736 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1737 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1738
1739 long identityToken = Binder.clearCallingIdentity();
1740 try {
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001741 JobSchedulerService.this.dumpInternal(pw, args);
Christopher Tate7060b042014-06-09 19:50:00 -07001742 } finally {
1743 Binder.restoreCallingIdentity(identityToken);
1744 }
1745 }
Christopher Tate5d346052016-03-08 12:56:08 -08001746
1747 @Override
1748 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
Dianne Hackborn354736e2016-08-22 17:00:05 -07001749 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
Christopher Tate5d346052016-03-08 12:56:08 -08001750 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
Dianne Hackborn354736e2016-08-22 17:00:05 -07001751 this, in, out, err, args, callback, resultReceiver);
Christopher Tate5d346052016-03-08 12:56:08 -08001752 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001753 };
1754
Christopher Tate5d346052016-03-08 12:56:08 -08001755 // Shell command infrastructure: run the given job immediately
1756 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1757 if (DEBUG) {
1758 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1759 + " " + jobId + " f=" + force);
1760 }
1761
1762 try {
1763 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1764 if (uid < 0) {
1765 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1766 }
1767
1768 synchronized (mLock) {
1769 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1770 if (js == null) {
1771 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1772 }
1773
1774 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1775 if (!js.isConstraintsSatisfied()) {
1776 js.overrideState = 0;
1777 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1778 }
1779
1780 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1781 }
1782 } catch (RemoteException e) {
1783 // can't happen
1784 }
1785 return 0;
1786 }
1787
Dianne Hackborna06ec6a2017-02-13 10:08:42 -08001788 void setMonitorBattery(boolean enabled) {
1789 synchronized (mLock) {
1790 if (mBatteryController != null) {
1791 mBatteryController.getTracker().setMonitorBatteryLocked(enabled);
1792 }
1793 }
1794 }
1795
1796 int getBatterySeq() {
1797 synchronized (mLock) {
1798 return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1;
1799 }
1800 }
1801
1802 boolean getBatteryCharging() {
1803 synchronized (mLock) {
1804 return mBatteryController != null
1805 ? mBatteryController.getTracker().isOnStablePower() : false;
1806 }
1807 }
1808
1809 boolean getBatteryNotLow() {
1810 synchronized (mLock) {
1811 return mBatteryController != null
1812 ? mBatteryController.getTracker().isBatteryNotLow() : false;
1813 }
1814 }
1815
Shreyas Basarge5db09082016-01-07 13:38:29 +00001816 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1817 StringBuilder s = new StringBuilder(initial + ": ");
1818 for (int i=0; i<map.length; i++) {
1819 s.append("(")
1820 .append(map[i] == null? -1: map[i].getJobId())
1821 .append(map[i] == null? -1: map[i].getUid())
1822 .append(")" );
1823 }
1824 return s.toString();
1825 }
1826
1827 private String printPendingQueue() {
1828 StringBuilder s = new StringBuilder("Pending queue: ");
1829 Iterator<JobStatus> it = mPendingJobs.iterator();
1830 while (it.hasNext()) {
1831 JobStatus js = it.next();
1832 s.append("(")
1833 .append(js.getJob().getId())
1834 .append(", ")
1835 .append(js.getUid())
1836 .append(") ");
1837 }
1838 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001839 }
Christopher Tate7060b042014-06-09 19:50:00 -07001840
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001841 static void dumpHelp(PrintWriter pw) {
1842 pw.println("Job Scheduler (jobscheduler) dump options:");
1843 pw.println(" [-h] [package] ...");
1844 pw.println(" -h: print this help");
1845 pw.println(" [package] is an optional package name to limit the output to.");
1846 }
1847
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001848 void dumpInternal(final PrintWriter pw, String[] args) {
1849 int filterUid = -1;
1850 if (!ArrayUtils.isEmpty(args)) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001851 int opti = 0;
1852 while (opti < args.length) {
1853 String arg = args[opti];
1854 if ("-h".equals(arg)) {
1855 dumpHelp(pw);
1856 return;
1857 } else if ("-a".equals(arg)) {
1858 // Ignore, we always dump all.
1859 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
1860 pw.println("Unknown option: " + arg);
1861 return;
1862 } else {
1863 break;
1864 }
1865 opti++;
1866 }
1867 if (opti < args.length) {
1868 String pkg = args[opti];
1869 try {
1870 filterUid = getContext().getPackageManager().getPackageUid(pkg,
Amith Yamasani0d1fd8d2016-10-12 14:21:51 -07001871 PackageManager.MATCH_ANY_USER);
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001872 } catch (NameNotFoundException ignored) {
1873 pw.println("Invalid package: " + pkg);
1874 return;
1875 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001876 }
1877 }
1878
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001879 final int filterUidFinal = UserHandle.getAppId(filterUid);
Christopher Tatef973a7b2014-08-29 12:54:08 -07001880 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001881 synchronized (mLock) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001882 mConstants.dump(pw);
1883 pw.println();
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001884 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001885 pw.print("Registered ");
1886 pw.print(mJobs.size());
1887 pw.println(" jobs:");
Christopher Tate7060b042014-06-09 19:50:00 -07001888 if (mJobs.size() > 0) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001889 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
1890 Collections.sort(jobs, new Comparator<JobStatus>() {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001891 @Override
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001892 public int compare(JobStatus o1, JobStatus o2) {
1893 int uid1 = o1.getUid();
1894 int uid2 = o2.getUid();
1895 int id1 = o1.getJobId();
1896 int id2 = o2.getJobId();
1897 if (uid1 != uid2) {
1898 return uid1 < uid2 ? -1 : 1;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001899 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001900 return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001901 }
1902 });
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001903 for (JobStatus job : jobs) {
1904 pw.print(" JOB #"); job.printUniqueId(pw); pw.print(": ");
1905 pw.println(job.toShortStringExceptUniqueId());
1906
1907 // Skip printing details if the caller requested a filter
1908 if (!job.shouldDump(filterUidFinal)) {
1909 continue;
1910 }
1911
1912 job.dump(pw, " ", true);
1913 pw.print(" Ready: ");
1914 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1915 pw.print(" (job=");
1916 pw.print(job.isReady());
1917 pw.print(" pending=");
1918 pw.print(mPendingJobs.contains(job));
1919 pw.print(" active=");
1920 pw.print(isCurrentlyActiveLocked(job));
1921 pw.print(" user=");
1922 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
1923 pw.println(")");
1924 }
Christopher Tate7060b042014-06-09 19:50:00 -07001925 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001926 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001927 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001928 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001929 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001930 mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal);
Christopher Tate7060b042014-06-09 19:50:00 -07001931 }
1932 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001933 pw.println("Uid priority overrides:");
1934 for (int i=0; i< mUidPriorityOverride.size(); i++) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001935 int uid = mUidPriorityOverride.keyAt(i);
1936 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
1937 pw.print(" "); pw.print(UserHandle.formatUid(uid));
1938 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
1939 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001940 }
1941 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001942 mJobPackageTracker.dump(pw, "", filterUidFinal);
Dianne Hackborn807de782016-04-07 17:54:41 -07001943 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001944 if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
1945 pw.println();
1946 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001947 pw.println("Pending queue:");
1948 for (int i=0; i<mPendingJobs.size(); i++) {
1949 JobStatus job = mPendingJobs.get(i);
1950 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1951 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001952 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001953 int priority = evaluateJobPriorityLocked(job);
1954 if (priority != JobInfo.PRIORITY_DEFAULT) {
1955 pw.print(" Evaluated priority: "); pw.println(priority);
1956 }
1957 pw.print(" Tag: "); pw.println(job.getTag());
1958 }
Christopher Tate7060b042014-06-09 19:50:00 -07001959 pw.println();
1960 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001961 for (int i=0; i<mActiveServices.size(); i++) {
1962 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001963 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001964 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001965 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001966 continue;
1967 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001968 pw.println(jsc.getRunningJob().toShortString());
1969 pw.print(" Running for: ");
1970 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1971 pw.print(", timeout at: ");
1972 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1973 pw.println();
1974 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001975 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1976 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001977 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001978 }
Christopher Tate7060b042014-06-09 19:50:00 -07001979 }
1980 }
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001981 if (filterUid == -1) {
1982 pw.println();
1983 pw.print("mReadyToRock="); pw.println(mReadyToRock);
1984 pw.print("mReportedActive="); pw.println(mReportedActive);
1985 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
1986 }
Christopher Tate7060b042014-06-09 19:50:00 -07001987 }
1988 pw.println();
1989 }
1990}