blob: a37dfed7c8299eeb6448cea7d2e726333c0a99c1 [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;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -070033import android.app.ActivityManagerNative;
Christopher Tate5568f542014-06-18 13:53:31 -070034import android.app.AppGlobals;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -070035import android.app.IUidObserver;
Christopher Tate7060b042014-06-09 19:50:00 -070036import android.app.job.JobInfo;
Shreyas Basarge5db09082016-01-07 13:38:29 +000037import android.app.job.JobParameters;
Christopher Tate7060b042014-06-09 19:50:00 -070038import android.app.job.JobScheduler;
39import android.app.job.JobService;
Shreyas Basarge5db09082016-01-07 13:38:29 +000040import android.app.job.IJobScheduler;
Christopher Tate7060b042014-06-09 19:50:00 -070041import android.content.BroadcastReceiver;
42import android.content.ComponentName;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070043import android.content.ContentResolver;
Christopher Tate7060b042014-06-09 19:50:00 -070044import android.content.Context;
45import android.content.Intent;
46import android.content.IntentFilter;
Christopher Tate5568f542014-06-18 13:53:31 -070047import android.content.pm.IPackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070048import android.content.pm.PackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070049import android.content.pm.ServiceInfo;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060050import android.content.pm.PackageManager.NameNotFoundException;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070051import android.database.ContentObserver;
Christopher Tateb5c07882016-05-26 17:11:09 -070052import android.net.Uri;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070053import android.os.BatteryStats;
Christopher Tate7060b042014-06-09 19:50:00 -070054import android.os.Binder;
55import android.os.Handler;
56import android.os.Looper;
57import android.os.Message;
Shreyas Basargecbf5ae92016-03-08 16:13:06 +000058import android.os.Process;
Dianne Hackborn88e98df2015-03-23 13:29:14 -070059import android.os.PowerManager;
Christopher Tate7060b042014-06-09 19:50:00 -070060import android.os.RemoteException;
Christopher Tate5d346052016-03-08 12:56:08 -080061import android.os.ResultReceiver;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070062import android.os.ServiceManager;
Dianne Hackborn354736e2016-08-22 17:00:05 -070063import android.os.ShellCallback;
Christopher Tate7060b042014-06-09 19:50:00 -070064import android.os.SystemClock;
65import android.os.UserHandle;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070066import android.provider.Settings;
67import android.util.KeyValueListParser;
Christopher Tate7060b042014-06-09 19:50:00 -070068import android.util.Slog;
69import android.util.SparseArray;
Dianne Hackborn970510b2016-02-24 16:56:42 -080070import android.util.SparseIntArray;
71import android.util.TimeUtils;
Christopher Tate5d346052016-03-08 12:56:08 -080072
Dianne Hackbornfdb19562014-07-11 16:03:36 -070073import com.android.internal.app.IBatteryStats;
Joe Onorato4eb64fd2016-03-21 15:30:09 -070074import com.android.internal.app.procstats.ProcessStats;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070075import com.android.internal.util.ArrayUtils;
Dianne Hackborn627dfa12015-11-11 18:10:30 -080076import com.android.server.DeviceIdleController;
77import com.android.server.LocalServices;
Christopher Tate2f36fd62016-02-18 18:36:08 -080078import com.android.server.job.JobStore.JobStatusFunctor;
Amith Yamasanib0ff3222015-03-04 09:56:14 -080079import com.android.server.job.controllers.AppIdleController;
Christopher Tate7060b042014-06-09 19:50:00 -070080import com.android.server.job.controllers.BatteryController;
81import com.android.server.job.controllers.ConnectivityController;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080082import com.android.server.job.controllers.ContentObserverController;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070083import com.android.server.job.controllers.DeviceIdleJobsController;
Christopher Tate7060b042014-06-09 19:50:00 -070084import com.android.server.job.controllers.IdleController;
85import com.android.server.job.controllers.JobStatus;
86import com.android.server.job.controllers.StateController;
87import com.android.server.job.controllers.TimeController;
88
Jeff Sharkey822cbd12016-02-25 11:09:55 -070089import libcore.util.EmptyArray;
90
Christopher Tate7060b042014-06-09 19:50:00 -070091/**
92 * Responsible for taking jobs representing work to be performed by a client app, and determining
93 * based on the criteria specified when that job should be run against the client application's
94 * endpoint.
95 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
96 * about constraints, or the state of active jobs. It receives callbacks from the various
97 * controllers and completed jobs and operates accordingly.
98 *
99 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
100 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
101 * @hide
102 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800103public final class JobSchedulerService extends com.android.server.SystemService
Matthew Williams01ac45b2014-07-22 20:44:12 -0700104 implements StateChangedListener, JobCompletedListener {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800105 static final String TAG = "JobSchedulerService";
Matthew Williamsaa984312015-10-15 16:08:05 -0700106 public static final boolean DEBUG = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800107
Dianne Hackborn970510b2016-02-24 16:56:42 -0800108 /** The maximum number of concurrent jobs we run at one time. */
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700109 private static final int MAX_JOB_CONTEXTS_COUNT = 16;
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800110 /** Enforce a per-app limit on scheduled jobs? */
Christopher Tate0213ace02016-02-24 14:18:35 -0800111 private static final boolean ENFORCE_MAX_JOBS = true;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800112 /** The maximum number of jobs that we allow an unprivileged app to schedule */
113 private static final int MAX_JOBS_PER_APP = 100;
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700114
Christopher Tate2f36fd62016-02-18 18:36:08 -0800115
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800116 /** Global local for all job scheduler state. */
117 final Object mLock = new Object();
Christopher Tate7060b042014-06-09 19:50:00 -0700118 /** Master list of jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700119 final JobStore mJobs;
Dianne Hackborn807de782016-04-07 17:54:41 -0700120 /** Tracking amount of time each package runs for. */
121 final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
Christopher Tate7060b042014-06-09 19:50:00 -0700122
123 static final int MSG_JOB_EXPIRED = 0;
124 static final int MSG_CHECK_JOB = 1;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700125 static final int MSG_STOP_JOB = 2;
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000126 static final int MSG_CHECK_JOB_GREEDY = 3;
Christopher Tate7060b042014-06-09 19:50:00 -0700127
Christopher Tate7060b042014-06-09 19:50:00 -0700128 /**
129 * Track Services that have currently active or pending jobs. The index is provided by
130 * {@link JobStatus#getServiceToken()}
131 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700132 final List<JobServiceContext> mActiveServices = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700133 /** List of controllers that will notify this service of updates to jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700134 List<StateController> mControllers;
Christopher Tate7060b042014-06-09 19:50:00 -0700135 /**
136 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
137 * when ready to execute them.
138 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700139 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700140
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700141 int[] mStartedUsers = EmptyArray.INT;
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700142
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700143 final JobHandler mHandler;
144 final JobSchedulerStub mJobSchedulerStub;
145
146 IBatteryStats mBatteryStats;
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700147 PowerManager mPowerManager;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800148 DeviceIdleController.LocalService mLocalDeviceIdleController;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700149
150 /**
151 * Set to true once we are allowed to run third party apps.
152 */
153 boolean mReadyToRock;
154
Christopher Tate7060b042014-06-09 19:50:00 -0700155 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800156 * What we last reported to DeviceIdleController about whether we are active.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800157 */
158 boolean mReportedActive;
159
160 /**
Dianne Hackborn970510b2016-02-24 16:56:42 -0800161 * Current limit on the number of concurrent JobServiceContext entries we want to
162 * keep actively running a job.
163 */
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700164 int mMaxActiveJobs = 1;
Dianne Hackborn970510b2016-02-24 16:56:42 -0800165
166 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800167 * Which uids are currently in the foreground.
168 */
Dianne Hackborn970510b2016-02-24 16:56:42 -0800169 final SparseIntArray mUidPriorityOverride = new SparseIntArray();
170
171 // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
172
173 /**
174 * This array essentially stores the state of mActiveServices array.
175 * The ith index stores the job present on the ith JobServiceContext.
176 * We manipulate this array until we arrive at what jobs should be running on
177 * what JobServiceContext.
178 */
179 JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
180 /**
181 * Indicates whether we need to act on this jobContext id
182 */
183 boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
184 /**
185 * The uid whose jobs we would like to assign to a context.
186 */
187 int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800188
189 /**
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700190 * All times are in milliseconds. These constants are kept synchronized with the system
191 * global Settings. Any access to this class or its fields should be done while
192 * holding the JobSchedulerService.mLock lock.
193 */
194 private final class Constants extends ContentObserver {
195 // Key names stored in the settings value.
196 private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
197 private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
198 private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
199 private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
200 private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
201 private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
202 private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
203 private static final String KEY_FG_JOB_COUNT = "fg_job_count";
204 private static final String KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
205 private static final String KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
206 private static final String KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
207 private static final String KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
208
209 private static final int DEFAULT_MIN_IDLE_COUNT = 1;
210 private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
211 private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
212 private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
213 private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
214 private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
215 private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
216 private static final int DEFAULT_FG_JOB_COUNT = 4;
217 private static final int DEFAULT_BG_NORMAL_JOB_COUNT = 6;
218 private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4;
Nancy Zhenge39a8a42016-10-05 16:27:14 -0700219 private static final int DEFAULT_BG_LOW_JOB_COUNT = 1;
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700220 private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1;
221
222 /**
223 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
224 * early.
225 */
226 int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT;
227 /**
228 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule
229 * things early.
230 */
231 int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT;
232 /**
233 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
234 * things early. 1 == Run connectivity jobs as soon as ready.
235 */
236 int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT;
237 /**
238 * Minimum # of content trigger jobs that must be ready in order to force the JMS to
239 * schedule things early.
240 */
241 int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT;
242 /**
243 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy
244 * running some work early. This (and thus the other min counts) is now set to 1, to
245 * prevent any batching at this level. Since we now do batching through doze, that is
246 * a much better mechanism.
247 */
248 int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT;
249 /**
250 * This is the job execution factor that is considered to be heavy use of the system.
251 */
252 float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR;
253 /**
254 * This is the job execution factor that is considered to be moderate use of the system.
255 */
256 float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
257 /**
258 * The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app.
259 */
260 int FG_JOB_COUNT = DEFAULT_FG_JOB_COUNT;
261 /**
262 * The maximum number of background jobs we allow when the system is in a normal
263 * memory state.
264 */
265 int BG_NORMAL_JOB_COUNT = DEFAULT_BG_NORMAL_JOB_COUNT;
266 /**
267 * The maximum number of background jobs we allow when the system is in a moderate
268 * memory state.
269 */
270 int BG_MODERATE_JOB_COUNT = DEFAULT_BG_MODERATE_JOB_COUNT;
271 /**
272 * The maximum number of background jobs we allow when the system is in a low
273 * memory state.
274 */
275 int BG_LOW_JOB_COUNT = DEFAULT_BG_LOW_JOB_COUNT;
276 /**
277 * The maximum number of background jobs we allow when the system is in a critical
278 * memory state.
279 */
280 int BG_CRITICAL_JOB_COUNT = DEFAULT_BG_CRITICAL_JOB_COUNT;
281
282 private ContentResolver mResolver;
283 private final KeyValueListParser mParser = new KeyValueListParser(',');
284
285 public Constants(Handler handler) {
286 super(handler);
287 }
288
289 public void start(ContentResolver resolver) {
290 mResolver = resolver;
291 mResolver.registerContentObserver(Settings.Global.getUriFor(
292 Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
293 updateConstants();
294 }
295
296 @Override
297 public void onChange(boolean selfChange, Uri uri) {
298 updateConstants();
299 }
300
301 private void updateConstants() {
302 synchronized (mLock) {
303 try {
304 mParser.setString(Settings.Global.getString(mResolver,
305 Settings.Global.ALARM_MANAGER_CONSTANTS));
306 } catch (IllegalArgumentException e) {
307 // Failed to parse the settings string, log this and move on
308 // with defaults.
309 Slog.e(TAG, "Bad device idle settings", e);
310 }
311
312 MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
313 DEFAULT_MIN_IDLE_COUNT);
314 MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
315 DEFAULT_MIN_CHARGING_COUNT);
316 MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
317 DEFAULT_MIN_CONNECTIVITY_COUNT);
318 MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
319 DEFAULT_MIN_CONTENT_COUNT);
320 MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
321 DEFAULT_MIN_READY_JOBS_COUNT);
322 HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
323 DEFAULT_HEAVY_USE_FACTOR);
324 MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
325 DEFAULT_MODERATE_USE_FACTOR);
326 FG_JOB_COUNT = mParser.getInt(KEY_FG_JOB_COUNT,
327 DEFAULT_FG_JOB_COUNT);
328 BG_NORMAL_JOB_COUNT = mParser.getInt(KEY_BG_NORMAL_JOB_COUNT,
329 DEFAULT_BG_NORMAL_JOB_COUNT);
330 if ((FG_JOB_COUNT+BG_NORMAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
331 BG_NORMAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
332 }
333 BG_MODERATE_JOB_COUNT = mParser.getInt(KEY_BG_MODERATE_JOB_COUNT,
334 DEFAULT_BG_MODERATE_JOB_COUNT);
335 if ((FG_JOB_COUNT+BG_MODERATE_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
336 BG_MODERATE_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
337 }
338 BG_LOW_JOB_COUNT = mParser.getInt(KEY_BG_LOW_JOB_COUNT,
339 DEFAULT_BG_LOW_JOB_COUNT);
340 if ((FG_JOB_COUNT+BG_LOW_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
341 BG_LOW_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
342 }
343 BG_CRITICAL_JOB_COUNT = mParser.getInt(KEY_BG_CRITICAL_JOB_COUNT,
344 DEFAULT_BG_CRITICAL_JOB_COUNT);
345 if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
346 BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
347 }
348 }
349 }
350
351 void dump(PrintWriter pw) {
352 pw.println(" Settings:");
353
354 pw.print(" "); pw.print(KEY_MIN_IDLE_COUNT); pw.print("=");
355 pw.print(MIN_IDLE_COUNT); pw.println();
356
357 pw.print(" "); pw.print(KEY_MIN_CHARGING_COUNT); pw.print("=");
358 pw.print(MIN_CHARGING_COUNT); pw.println();
359
360 pw.print(" "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("=");
361 pw.print(MIN_CONNECTIVITY_COUNT); pw.println();
362
363 pw.print(" "); pw.print(KEY_MIN_CONTENT_COUNT); pw.print("=");
364 pw.print(MIN_CONTENT_COUNT); pw.println();
365
366 pw.print(" "); pw.print(KEY_MIN_READY_JOBS_COUNT); pw.print("=");
367 pw.print(MIN_READY_JOBS_COUNT); pw.println();
368
369 pw.print(" "); pw.print(KEY_HEAVY_USE_FACTOR); pw.print("=");
370 pw.print(HEAVY_USE_FACTOR); pw.println();
371
372 pw.print(" "); pw.print(KEY_MODERATE_USE_FACTOR); pw.print("=");
373 pw.print(MODERATE_USE_FACTOR); pw.println();
374
375 pw.print(" "); pw.print(KEY_FG_JOB_COUNT); pw.print("=");
376 pw.print(FG_JOB_COUNT); pw.println();
377
378 pw.print(" "); pw.print(KEY_BG_NORMAL_JOB_COUNT); pw.print("=");
379 pw.print(BG_NORMAL_JOB_COUNT); pw.println();
380
381 pw.print(" "); pw.print(KEY_BG_MODERATE_JOB_COUNT); pw.print("=");
382 pw.print(BG_MODERATE_JOB_COUNT); pw.println();
383
384 pw.print(" "); pw.print(KEY_BG_LOW_JOB_COUNT); pw.print("=");
385 pw.print(BG_LOW_JOB_COUNT); pw.println();
386
387 pw.print(" "); pw.print(KEY_BG_CRITICAL_JOB_COUNT); pw.print("=");
388 pw.print(BG_CRITICAL_JOB_COUNT); pw.println();
389 }
390 }
391
392 final Constants mConstants;
393
394 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700395 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
396 * still clean up. On reinstall the package will have a new uid.
397 */
398 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
399 @Override
400 public void onReceive(Context context, Intent intent) {
Christopher Tateee7805b2016-07-15 16:56:56 -0700401 final String action = intent.getAction();
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700402 if (DEBUG) {
Christopher Tateee7805b2016-07-15 16:56:56 -0700403 Slog.d(TAG, "Receieved: " + action);
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700404 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700405 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
Christopher Tateb5c07882016-05-26 17:11:09 -0700406 // Purge the app's jobs if the whole package was just disabled. When this is
407 // the case the component name will be a bare package name.
408 final String pkgName = getPackageName(intent);
409 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
410 if (pkgName != null && pkgUid != -1) {
411 final String[] changedComponents = intent.getStringArrayExtra(
412 Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
413 if (changedComponents != null) {
414 for (String component : changedComponents) {
415 if (component.equals(pkgName)) {
416 if (DEBUG) {
417 Slog.d(TAG, "Package state change: " + pkgName);
418 }
419 try {
420 final int userId = UserHandle.getUserId(pkgUid);
421 IPackageManager pm = AppGlobals.getPackageManager();
422 final int state = pm.getApplicationEnabledSetting(pkgName, userId);
423 if (state == COMPONENT_ENABLED_STATE_DISABLED
424 || state == COMPONENT_ENABLED_STATE_DISABLED_USER) {
425 if (DEBUG) {
426 Slog.d(TAG, "Removing jobs for package " + pkgName
427 + " in user " + userId);
428 }
429 cancelJobsForUid(pkgUid, true);
430 }
Christopher Tate652c5ad2016-10-05 14:45:46 -0700431 } catch (RemoteException|IllegalArgumentException e) {
432 /*
433 * IllegalArgumentException means that the package doesn't exist.
434 * This arises when PACKAGE_CHANGED broadcast delivery has lagged
435 * behind outright uninstall, so by the time we try to act it's gone.
436 * We don't need to act on this PACKAGE_CHANGED when this happens;
437 * we'll get a PACKAGE_REMOVED later and clean up then.
438 *
439 * RemoteException can't actually happen; the package manager is
440 * running in this same process.
441 */
442 }
Christopher Tateb5c07882016-05-26 17:11:09 -0700443 break;
444 }
445 }
446 }
447 } else {
448 Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
449 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700450 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700451 // If this is an outright uninstall rather than the first half of an
452 // app update sequence, cancel the jobs associated with the app.
453 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
454 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
455 if (DEBUG) {
456 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
457 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700458 cancelJobsForUid(uidRemoved, true);
Christopher Tate7060b042014-06-09 19:50:00 -0700459 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700460 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
Christopher Tate7060b042014-06-09 19:50:00 -0700461 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
462 if (DEBUG) {
463 Slog.d(TAG, "Removing jobs for user: " + userId);
464 }
465 cancelJobsForUser(userId);
Christopher Tateee7805b2016-07-15 16:56:56 -0700466 } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
467 // Has this package scheduled any jobs, such that we will take action
468 // if it were to be force-stopped?
469 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
470 final String pkgName = intent.getData().getSchemeSpecificPart();
471 if (pkgUid != -1) {
472 List<JobStatus> jobsForUid;
473 synchronized (mLock) {
474 jobsForUid = mJobs.getJobsByUid(pkgUid);
475 }
476 for (int i = jobsForUid.size() - 1; i >= 0; i--) {
477 if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
478 if (DEBUG) {
479 Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
480 + pkgUid + " has jobs");
481 }
482 setResultCode(Activity.RESULT_OK);
483 break;
484 }
485 }
486 }
487 } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
488 // possible force-stop
489 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
490 final String pkgName = intent.getData().getSchemeSpecificPart();
491 if (pkgUid != -1) {
492 if (DEBUG) {
493 Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
494 }
495 cancelJobsForPackageAndUid(pkgName, pkgUid);
496 }
Christopher Tate7060b042014-06-09 19:50:00 -0700497 }
498 }
499 };
500
Christopher Tateb5c07882016-05-26 17:11:09 -0700501 private String getPackageName(Intent intent) {
502 Uri uri = intent.getData();
503 String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
504 return pkg;
505 }
506
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700507 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
508 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800509 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700510 }
511
512 @Override public void onUidGone(int uid) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800513 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700514 }
515
516 @Override public void onUidActive(int uid) throws RemoteException {
517 }
518
519 @Override public void onUidIdle(int uid) throws RemoteException {
520 cancelJobsForUid(uid, false);
521 }
522 };
523
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800524 public Object getLock() {
525 return mLock;
526 }
527
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700528 public JobStore getJobStore() {
529 return mJobs;
530 }
531
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700532 @Override
533 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700534 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
535 // Let's kick any outstanding jobs for this user.
536 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
537 }
538
539 @Override
540 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700541 // Let's kick any outstanding jobs for this user.
542 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
543 }
544
545 @Override
546 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700547 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700548 }
549
Christopher Tate7060b042014-06-09 19:50:00 -0700550 /**
551 * Entry point from client to schedule the provided job.
552 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
553 * @param job JobInfo object containing execution parameters
554 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700555 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
556 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700557 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800558 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000559 }
560
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800561 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
562 String tag) {
563 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700564 try {
565 if (ActivityManagerNative.getDefault().getAppStartMode(uId,
566 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
567 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
568 + " -- package not allowed to start");
569 return JobScheduler.RESULT_FAILURE;
570 }
571 } catch (RemoteException e) {
572 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800573 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
574 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800575 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800576 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800577 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800578 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
579 Slog.w(TAG, "Too many jobs for uid " + uId);
580 throw new IllegalStateException("Apps may not schedule more than "
581 + MAX_JOBS_PER_APP + " distinct jobs");
582 }
583 }
584
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800585 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700586 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700587 cancelJobImpl(toCancel, jobStatus);
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700588 }
589 startTrackingJob(jobStatus, toCancel);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800590 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700591 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700592 return JobScheduler.RESULT_SUCCESS;
593 }
594
595 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800596 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800597 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
598 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
599 for (int i = jobs.size() - 1; i >= 0; i--) {
600 JobStatus job = jobs.get(i);
601 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700602 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800603 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700604 }
Christopher Tate7060b042014-06-09 19:50:00 -0700605 }
606
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600607 public JobInfo getPendingJob(int uid, int jobId) {
608 synchronized (mLock) {
609 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
610 for (int i = jobs.size() - 1; i >= 0; i--) {
611 JobStatus job = jobs.get(i);
612 if (job.getJobId() == jobId) {
613 return job.getJob();
614 }
615 }
616 return null;
617 }
618 }
619
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700620 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700621 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800622 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700623 jobsForUser = mJobs.getJobsByUser(userHandle);
624 }
625 for (int i=0; i<jobsForUser.size(); i++) {
626 JobStatus toRemove = jobsForUser.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700627 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700628 }
629 }
630
Christopher Tateee7805b2016-07-15 16:56:56 -0700631 void cancelJobsForPackageAndUid(String pkgName, int uid) {
632 List<JobStatus> jobsForUid;
633 synchronized (mLock) {
634 jobsForUid = mJobs.getJobsByUid(uid);
635 }
636 for (int i = jobsForUid.size() - 1; i >= 0; i--) {
637 final JobStatus job = jobsForUid.get(i);
638 if (job.getSourcePackageName().equals(pkgName)) {
639 cancelJobImpl(job, null);
640 }
641 }
642 }
643
Christopher Tate7060b042014-06-09 19:50:00 -0700644 /**
645 * Entry point from client to cancel all jobs originating from their uid.
646 * This will remove the job from the master list, and cancel the job if it was staged for
647 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700648 * @param uid Uid to check against for removal of a job.
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700649 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
650 * whose apps are stopped.
Christopher Tate7060b042014-06-09 19:50:00 -0700651 */
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700652 public void cancelJobsForUid(int uid, boolean forceAll) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700653 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800654 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700655 jobsForUid = mJobs.getJobsByUid(uid);
656 }
657 for (int i=0; i<jobsForUid.size(); i++) {
658 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700659 if (!forceAll) {
660 String packageName = toRemove.getServiceComponent().getPackageName();
661 try {
662 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
663 != ActivityManager.APP_START_MODE_DISABLED) {
664 continue;
665 }
666 } catch (RemoteException e) {
667 }
668 }
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700669 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700670 }
671 }
672
673 /**
674 * Entry point from client to cancel the job corresponding to the jobId provided.
675 * This will remove the job from the master list, and cancel the job if it was staged for
676 * execution or being executed.
677 * @param uid Uid of the calling client.
678 * @param jobId Id of the job, provided at schedule-time.
679 */
680 public void cancelJob(int uid, int jobId) {
681 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800682 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700683 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700684 }
685 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700686 cancelJobImpl(toCancel, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700687 }
688 }
689
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700690 private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800691 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700692 stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800693 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700694 // Remove from pending queue.
Dianne Hackborn807de782016-04-07 17:54:41 -0700695 if (mPendingJobs.remove(cancelled)) {
696 mJobPackageTracker.noteNonpending(cancelled);
697 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700698 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000699 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800700 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700701 }
Christopher Tate7060b042014-06-09 19:50:00 -0700702 }
703
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800704 void updateUidState(int uid, int procState) {
705 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800706 if (procState == ActivityManager.PROCESS_STATE_TOP) {
707 // Only use this if we are exactly the top app. All others can live
708 // with just the foreground priority. This means that persistent processes
709 // can never be the top app priority... that is fine.
710 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
711 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
712 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800713 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800714 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800715 }
716 }
717 }
718
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700719 @Override
720 public void onDeviceIdleStateChanged(boolean deviceIdle) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800721 synchronized (mLock) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700722 if (deviceIdle) {
Jeff Sharkey34618b52016-06-01 15:51:19 -0600723 // When becoming idle, make sure no jobs are actively running,
724 // except those using the idle exemption flag.
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700725 for (int i=0; i<mActiveServices.size(); i++) {
726 JobServiceContext jsc = mActiveServices.get(i);
727 final JobStatus executing = jsc.getRunningJob();
Jeff Sharkey34618b52016-06-01 15:51:19 -0600728 if (executing != null
729 && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700730 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
731 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700732 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700733 } else {
734 // When coming out of idle, allow thing to start back up.
735 if (mReadyToRock) {
736 if (mLocalDeviceIdleController != null) {
737 if (!mReportedActive) {
738 mReportedActive = true;
739 mLocalDeviceIdleController.setJobsActive(true);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700740 }
741 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700742 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700743 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700744 }
745 }
746 }
747
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800748 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000749 // active is true if pending queue contains jobs OR some job is running.
750 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800751 if (mPendingJobs.size() <= 0) {
752 for (int i=0; i<mActiveServices.size(); i++) {
Dianne Hackborn7ab40252016-06-15 17:30:24 -0700753 final JobServiceContext jsc = mActiveServices.get(i);
754 final JobStatus job = jsc.getRunningJob();
755 if (job != null
756 && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
757 && !job.dozeWhitelisted) {
758 // We will report active if we have a job running and it is not an exception
759 // due to being in the foreground or whitelisted.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800760 active = true;
761 break;
762 }
763 }
764 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000765
766 if (mReportedActive != active) {
767 mReportedActive = active;
768 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800769 mLocalDeviceIdleController.setJobsActive(active);
770 }
771 }
772 }
773
Christopher Tate7060b042014-06-09 19:50:00 -0700774 /**
775 * Initializes the system service.
776 * <p>
777 * Subclasses must define a single argument constructor that accepts the context
778 * and passes it to super.
779 * </p>
780 *
781 * @param context The system server context.
782 */
783 public JobSchedulerService(Context context) {
784 super(context);
Dianne Hackborn970e3f42016-06-01 10:55:13 -0700785 mHandler = new JobHandler(context.getMainLooper());
786 mConstants = new Constants(mHandler);
787 mJobSchedulerStub = new JobSchedulerStub();
788 mJobs = JobStore.initAndGet(this);
789
Christopher Tate7060b042014-06-09 19:50:00 -0700790 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700791 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700792 mControllers.add(ConnectivityController.get(this));
793 mControllers.add(TimeController.get(this));
794 mControllers.add(IdleController.get(this));
795 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800796 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800797 mControllers.add(ContentObserverController.get(this));
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700798 mControllers.add(DeviceIdleJobsController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700799 }
800
801 @Override
802 public void onStart() {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000803 publishLocalService(JobSchedulerInternal.class, new LocalService());
Christopher Tate7060b042014-06-09 19:50:00 -0700804 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
805 }
806
807 @Override
808 public void onBootPhase(int phase) {
809 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700810 mConstants.start(getContext().getContentResolver());
Shreyas Basarge5db09082016-01-07 13:38:29 +0000811 // Register br for package removals and user removals.
Christopher Tateb5c07882016-05-26 17:11:09 -0700812 final IntentFilter filter = new IntentFilter();
813 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
814 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
Christopher Tateee7805b2016-07-15 16:56:56 -0700815 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
816 filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
Christopher Tate7060b042014-06-09 19:50:00 -0700817 filter.addDataScheme("package");
818 getContext().registerReceiverAsUser(
819 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
820 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
821 getContext().registerReceiverAsUser(
822 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000823 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700824 try {
825 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800826 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
Dianne Hackborn058f1e42016-11-02 17:18:35 -0700827 | ActivityManager.UID_OBSERVER_IDLE, null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700828 } catch (RemoteException e) {
829 // ignored; both services live in system_server
830 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700831 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800832 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700833 // Let's go!
834 mReadyToRock = true;
835 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
836 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800837 mLocalDeviceIdleController
838 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700839 // Create the "runners".
840 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
841 mActiveServices.add(
Dianne Hackborn807de782016-04-07 17:54:41 -0700842 new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700843 getContext().getMainLooper()));
844 }
845 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800846 mJobs.forEachJob(new JobStatusFunctor() {
847 @Override
848 public void process(JobStatus job) {
849 for (int controller = 0; controller < mControllers.size(); controller++) {
850 final StateController sc = mControllers.get(controller);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800851 sc.maybeStartTrackingJobLocked(job, null);
852 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700853 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800854 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700855 // GO GO GO!
856 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
857 }
Christopher Tate7060b042014-06-09 19:50:00 -0700858 }
859 }
860
861 /**
862 * Called when we have a job status object that we need to insert in our
863 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
864 * about.
865 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800866 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800867 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800868 final boolean update = mJobs.add(jobStatus);
869 if (mReadyToRock) {
870 for (int i = 0; i < mControllers.size(); i++) {
871 StateController controller = mControllers.get(i);
872 if (update) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700873 controller.maybeStopTrackingJobLocked(jobStatus, null, true);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800874 }
875 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700876 }
Christopher Tate7060b042014-06-09 19:50:00 -0700877 }
Christopher Tate7060b042014-06-09 19:50:00 -0700878 }
879 }
880
881 /**
882 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
883 * object removed.
884 */
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700885 private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
886 boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800887 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700888 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800889 final boolean removed = mJobs.remove(jobStatus, writeBack);
890 if (removed && mReadyToRock) {
891 for (int i=0; i<mControllers.size(); i++) {
892 StateController controller = mControllers.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700893 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800894 }
Christopher Tate7060b042014-06-09 19:50:00 -0700895 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800896 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700897 }
Christopher Tate7060b042014-06-09 19:50:00 -0700898 }
899
Shreyas Basarge5db09082016-01-07 13:38:29 +0000900 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700901 for (int i=0; i<mActiveServices.size(); i++) {
902 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700903 final JobStatus executing = jsc.getRunningJob();
904 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000905 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700906 return true;
907 }
908 }
909 return false;
910 }
911
912 /**
913 * @param job JobStatus we are querying against.
914 * @return Whether or not the job represented by the status object is currently being run or
915 * is pending.
916 */
917 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700918 for (int i=0; i<mActiveServices.size(); i++) {
919 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tateeafb5352016-10-04 16:34:48 -0700920 // The 'unsafe' direct-internal-reference running-job inspector is okay to
921 // use here because we are already holding the necessary lock *and* we
922 // immediately discard the returned object reference, if any; we return
923 // only a boolean state indicator to the caller.
924 final JobStatus running = serviceContext.getRunningJobUnsafeLocked();
Christopher Tate7060b042014-06-09 19:50:00 -0700925 if (running != null && running.matches(job.getUid(), job.getJobId())) {
926 return true;
927 }
928 }
929 return false;
930 }
931
Dianne Hackborn807de782016-04-07 17:54:41 -0700932 void noteJobsPending(List<JobStatus> jobs) {
933 for (int i = jobs.size() - 1; i >= 0; i--) {
934 JobStatus job = jobs.get(i);
935 mJobPackageTracker.notePending(job);
936 }
937 }
938
939 void noteJobsNonpending(List<JobStatus> jobs) {
940 for (int i = jobs.size() - 1; i >= 0; i--) {
941 JobStatus job = jobs.get(i);
942 mJobPackageTracker.noteNonpending(job);
943 }
944 }
945
Christopher Tate7060b042014-06-09 19:50:00 -0700946 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700947 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
948 * specify an override deadline on a failed job (the failed job will run even though it's not
949 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
950 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
951 *
Christopher Tate7060b042014-06-09 19:50:00 -0700952 * @param failureToReschedule Provided job status that we will reschedule.
953 * @return A newly instantiated JobStatus with the same constraints as the last job except
954 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700955 *
956 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700957 */
958 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
959 final long elapsedNowMillis = SystemClock.elapsedRealtime();
960 final JobInfo job = failureToReschedule.getJob();
961
962 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700963 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
964 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700965
966 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700967 case JobInfo.BACKOFF_POLICY_LINEAR:
968 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700969 break;
970 default:
971 if (DEBUG) {
972 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
973 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700974 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
975 delayMillis =
976 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700977 break;
978 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700979 delayMillis =
980 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800981 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700982 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800983 for (int ic=0; ic<mControllers.size(); ic++) {
984 StateController controller = mControllers.get(ic);
985 controller.rescheduleForFailure(newJob, failureToReschedule);
986 }
987 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700988 }
989
990 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700991 * Called after a periodic has executed so we can reschedule it. We take the last execution
992 * time of the job to be the time of completion (i.e. the time at which this function is
993 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700994 * This could be inaccurate b/c the job can run for as long as
995 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
996 * to underscheduling at least, rather than if we had taken the last execution time to be the
997 * start of the execution.
998 * @return A new job representing the execution criteria for this instantiation of the
999 * recurring job.
1000 */
1001 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
1002 final long elapsedNow = SystemClock.elapsedRealtime();
1003 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -07001004 long runEarly = 0L;
1005
1006 // If this periodic was rescheduled it won't have a deadline.
1007 if (periodicToReschedule.hasDeadlineConstraint()) {
1008 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
1009 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001010 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -07001011 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001012 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
1013 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -07001014
1015 if (DEBUG) {
1016 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
1017 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
1018 }
1019 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
1020 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
1021 }
1022
1023 // JobCompletedListener implementations.
1024
1025 /**
1026 * A job just finished executing. We fetch the
1027 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
1028 * whether we want to reschedule we readd it to the controllers.
1029 * @param jobStatus Completed job.
1030 * @param needsReschedule Whether the implementing class should reschedule this job.
1031 */
1032 @Override
1033 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
1034 if (DEBUG) {
1035 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
1036 }
Shreyas Basarge73f10252016-02-11 17:06:13 +00001037 // Do not write back immediately if this is a periodic job. The job may get lost if system
1038 // shuts down before it is added back.
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001039 if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -07001040 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001041 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -07001042 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001043 // We still want to check for jobs to execute, because this job may have
1044 // scheduled a new job under the same job id, and now we can run it.
1045 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001046 return;
1047 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001048 // Note: there is a small window of time in here where, when rescheduling a job,
1049 // we will stop monitoring its content providers. This should be fixed by stopping
1050 // the old job after scheduling the new one, but since we have no lock held here
1051 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -07001052 if (needsReschedule) {
1053 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001054 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001055 } else if (jobStatus.getJob().isPeriodic()) {
1056 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001057 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001058 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001059 reportActive();
1060 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001061 }
1062
1063 // StateChangedListener implementations.
1064
1065 /**
Matthew Williams48a30db2014-09-23 13:39:36 -07001066 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
1067 * some controller's state has changed, so as to run through the list of jobs and start/stop
1068 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -07001069 */
1070 @Override
1071 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001072 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001073 }
1074
1075 @Override
1076 public void onRunJobNow(JobStatus jobStatus) {
1077 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
1078 }
1079
Christopher Tate7060b042014-06-09 19:50:00 -07001080 private class JobHandler extends Handler {
1081
1082 public JobHandler(Looper looper) {
1083 super(looper);
1084 }
1085
1086 @Override
1087 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001088 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001089 if (!mReadyToRock) {
1090 return;
1091 }
1092 }
Christopher Tate7060b042014-06-09 19:50:00 -07001093 switch (message.what) {
1094 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001095 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -07001096 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -07001097 // runNow can be null, which is a controller's way of indicating that its
1098 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -07001099 if (runNow != null && !mPendingJobs.contains(runNow)
1100 && mJobs.containsJob(runNow)) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001101 mJobPackageTracker.notePending(runNow);
Christopher Tate7060b042014-06-09 19:50:00 -07001102 mPendingJobs.add(runNow);
1103 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001104 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -07001105 }
Christopher Tate7060b042014-06-09 19:50:00 -07001106 break;
1107 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001108 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001109 if (mReportedActive) {
1110 // if jobs are currently being run, queue all ready jobs for execution.
1111 queueReadyJobsForExecutionLockedH();
1112 } else {
1113 // Check the list of jobs and run some of them if we feel inclined.
1114 maybeQueueReadyJobsForExecutionLockedH();
1115 }
1116 }
1117 break;
1118 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001119 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001120 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -07001121 }
Christopher Tate7060b042014-06-09 19:50:00 -07001122 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001123 case MSG_STOP_JOB:
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001124 cancelJobImpl((JobStatus)message.obj, null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001125 break;
Christopher Tate7060b042014-06-09 19:50:00 -07001126 }
1127 maybeRunPendingJobsH();
1128 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
1129 removeMessages(MSG_CHECK_JOB);
1130 }
1131
1132 /**
1133 * Run through list of jobs and execute all possible - at least one is expired so we do
1134 * as many as we can.
1135 */
Matthew Williams48a30db2014-09-23 13:39:36 -07001136 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001137 if (DEBUG) {
1138 Slog.d(TAG, "queuing all ready jobs for execution:");
1139 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001140 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001141 mPendingJobs.clear();
1142 mJobs.forEachJob(mReadyQueueFunctor);
1143 mReadyQueueFunctor.postProcess();
1144
Matthew Williams48a30db2014-09-23 13:39:36 -07001145 if (DEBUG) {
1146 final int queuedJobs = mPendingJobs.size();
1147 if (queuedJobs == 0) {
1148 Slog.d(TAG, "No jobs pending.");
1149 } else {
1150 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -07001151 }
Christopher Tate7060b042014-06-09 19:50:00 -07001152 }
1153 }
1154
Christopher Tate2f36fd62016-02-18 18:36:08 -08001155 class ReadyJobQueueFunctor implements JobStatusFunctor {
1156 ArrayList<JobStatus> newReadyJobs;
1157
1158 @Override
1159 public void process(JobStatus job) {
1160 if (isReadyToBeExecutedLocked(job)) {
1161 if (DEBUG) {
1162 Slog.d(TAG, " queued " + job.toShortString());
1163 }
1164 if (newReadyJobs == null) {
1165 newReadyJobs = new ArrayList<JobStatus>();
1166 }
1167 newReadyJobs.add(job);
1168 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
1169 stopJobOnServiceContextLocked(job,
1170 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
1171 }
1172 }
1173
1174 public void postProcess() {
1175 if (newReadyJobs != null) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001176 noteJobsPending(newReadyJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001177 mPendingJobs.addAll(newReadyJobs);
1178 }
1179 newReadyJobs = null;
1180 }
1181 }
1182 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
1183
Christopher Tate7060b042014-06-09 19:50:00 -07001184 /**
1185 * The state of at least one job has changed. Here is where we could enforce various
1186 * policies on when we want to execute jobs.
1187 * Right now the policy is such:
1188 * If >1 of the ready jobs is idle mode we send all of them off
1189 * if more than 2 network connectivity jobs are ready we send them all off.
1190 * If more than 4 jobs total are ready we send them all off.
1191 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
1192 */
Christopher Tate2f36fd62016-02-18 18:36:08 -08001193 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
1194 int chargingCount;
1195 int idleCount;
1196 int backoffCount;
1197 int connectivityCount;
1198 int contentCount;
1199 List<JobStatus> runnableJobs;
1200
1201 public MaybeReadyJobQueueFunctor() {
1202 reset();
1203 }
1204
1205 // Functor method invoked for each job via JobStore.forEachJob()
1206 @Override
1207 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001208 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001209 try {
1210 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
1211 job.getJob().getService().getPackageName())
1212 == ActivityManager.APP_START_MODE_DISABLED) {
1213 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
1214 + job.getJob().toString() + " -- package not allowed to start");
1215 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001216 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001217 }
1218 } catch (RemoteException e) {
1219 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001220 if (job.getNumFailures() > 0) {
1221 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -07001222 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001223 if (job.hasIdleConstraint()) {
1224 idleCount++;
1225 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001226 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()
1227 || job.hasNotRoamingConstraint()) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001228 connectivityCount++;
1229 }
1230 if (job.hasChargingConstraint()) {
1231 chargingCount++;
1232 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001233 if (job.hasContentTriggerConstraint()) {
1234 contentCount++;
1235 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001236 if (runnableJobs == null) {
1237 runnableJobs = new ArrayList<>();
1238 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001239 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001240 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001241 stopJobOnServiceContextLocked(job,
1242 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -07001243 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001244 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001245
1246 public void postProcess() {
1247 if (backoffCount > 0 ||
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001248 idleCount >= mConstants.MIN_IDLE_COUNT ||
1249 connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
1250 chargingCount >= mConstants.MIN_CHARGING_COUNT ||
1251 contentCount >= mConstants.MIN_CONTENT_COUNT ||
1252 (runnableJobs != null
1253 && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001254 if (DEBUG) {
1255 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
1256 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001257 noteJobsPending(runnableJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001258 mPendingJobs.addAll(runnableJobs);
1259 } else {
1260 if (DEBUG) {
1261 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
1262 }
Christopher Tate7060b042014-06-09 19:50:00 -07001263 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001264
1265 // Be ready for next time
1266 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -07001267 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001268
1269 private void reset() {
1270 chargingCount = 0;
1271 idleCount = 0;
1272 backoffCount = 0;
1273 connectivityCount = 0;
1274 contentCount = 0;
1275 runnableJobs = null;
1276 }
1277 }
1278 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
1279
1280 private void maybeQueueReadyJobsForExecutionLockedH() {
1281 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
1282
Dianne Hackborn807de782016-04-07 17:54:41 -07001283 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001284 mPendingJobs.clear();
1285 mJobs.forEachJob(mMaybeQueueFunctor);
1286 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -07001287 }
1288
1289 /**
1290 * Criteria for moving a job into the pending queue:
1291 * - It's ready.
1292 * - It's not pending.
1293 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001294 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001295 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -07001296 */
1297 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001298 final boolean jobReady = job.isReady();
1299 final boolean jobPending = mPendingJobs.contains(job);
1300 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001301
1302 final int userId = job.getUserId();
1303 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
1304 final boolean componentPresent;
1305 try {
1306 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
1307 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
1308 userId) != null);
1309 } catch (RemoteException e) {
1310 throw e.rethrowAsRuntimeException();
1311 }
1312
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001313 if (DEBUG) {
1314 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
1315 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001316 + " active=" + jobActive + " userStarted=" + userStarted
1317 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001318 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001319 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -07001320 }
1321
1322 /**
1323 * Criteria for cancelling an active job:
1324 * - It's not ready
1325 * - It's running on a JSC.
1326 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001327 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001328 return !job.isReady() && isCurrentlyActiveLocked(job);
1329 }
1330
1331 /**
1332 * Reconcile jobs in the pending queue against available execution contexts.
1333 * A controller can force a job into the pending queue even if it's already running, but
1334 * here is where we decide whether to actually execute it.
1335 */
1336 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001337 synchronized (mLock) {
Matthew Williams75fc5252014-09-02 16:17:53 -07001338 if (DEBUG) {
1339 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1340 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001341 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001342 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001343 }
1344 }
1345 }
1346
Dianne Hackborn807de782016-04-07 17:54:41 -07001347 private int adjustJobPriority(int curPriority, JobStatus job) {
1348 if (curPriority < JobInfo.PRIORITY_TOP_APP) {
1349 float factor = mJobPackageTracker.getLoadFactor(job);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001350 if (factor >= mConstants.HEAVY_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001351 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001352 } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001353 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
1354 }
1355 }
1356 return curPriority;
1357 }
1358
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001359 private int evaluateJobPriorityLocked(JobStatus job) {
1360 int priority = job.getPriority();
1361 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001362 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001363 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001364 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1365 if (override != 0) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001366 return adjustJobPriority(override, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001367 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001368 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001369 }
1370
Christopher Tate7060b042014-06-09 19:50:00 -07001371 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001372 * Takes jobs from pending queue and runs them on available contexts.
1373 * If no contexts are available, preempts lower priority jobs to
1374 * run higher priority ones.
1375 * Lock on mJobs before calling this function.
1376 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001377 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001378 if (DEBUG) {
1379 Slog.d(TAG, printPendingQueue());
1380 }
1381
Dianne Hackborn970510b2016-02-24 16:56:42 -08001382 int memLevel;
1383 try {
1384 memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
1385 } catch (RemoteException e) {
1386 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1387 }
1388 switch (memLevel) {
1389 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001390 mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001391 break;
1392 case ProcessStats.ADJ_MEM_FACTOR_LOW:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001393 mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001394 break;
1395 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001396 mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001397 break;
1398 default:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001399 mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001400 break;
1401 }
1402
1403 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1404 boolean[] act = mTmpAssignAct;
1405 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1406 int numActive = 0;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001407 int numForeground = 0;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001408 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1409 final JobServiceContext js = mActiveServices.get(i);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001410 final JobStatus status = js.getRunningJob();
1411 if ((contextIdToJobMap[i] = status) != null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001412 numActive++;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001413 if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
1414 numForeground++;
1415 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001416 }
1417 act[i] = false;
1418 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001419 }
1420 if (DEBUG) {
1421 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1422 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001423 for (int i=0; i<mPendingJobs.size(); i++) {
1424 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001425
1426 // If job is already running, go to next job.
1427 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1428 if (jobRunningContext != -1) {
1429 continue;
1430 }
1431
Dianne Hackborn970510b2016-02-24 16:56:42 -08001432 final int priority = evaluateJobPriorityLocked(nextPending);
1433 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001434
Shreyas Basarge5db09082016-01-07 13:38:29 +00001435 // Find a context for nextPending. The context should be available OR
1436 // it should have lowest priority among all running jobs
1437 // (sharing the same Uid as nextPending)
1438 int minPriority = Integer.MAX_VALUE;
1439 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001440 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1441 JobStatus job = contextIdToJobMap[j];
1442 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001443 if (job == null) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001444 if ((numActive < mMaxActiveJobs ||
1445 (priority >= JobInfo.PRIORITY_TOP_APP &&
1446 numForeground < mConstants.FG_JOB_COUNT)) &&
Dianne Hackborn970510b2016-02-24 16:56:42 -08001447 (preferredUid == nextPending.getUid() ||
1448 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1449 // This slot is free, and we haven't yet hit the limit on
1450 // concurrent jobs... we can just throw the job in to here.
1451 minPriorityContextId = j;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001452 break;
1453 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001454 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001455 // the context has a preferred Uid or we have reached the limit on
1456 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001457 continue;
1458 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001459 if (job.getUid() != nextPending.getUid()) {
1460 continue;
1461 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001462 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001463 continue;
1464 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001465 if (minPriority > nextPending.lastEvaluatedPriority) {
1466 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001467 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001468 }
1469 }
1470 if (minPriorityContextId != -1) {
1471 contextIdToJobMap[minPriorityContextId] = nextPending;
1472 act[minPriorityContextId] = true;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001473 numActive++;
1474 if (priority >= JobInfo.PRIORITY_TOP_APP) {
1475 numForeground++;
1476 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001477 }
1478 }
1479 if (DEBUG) {
1480 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1481 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001482 mJobPackageTracker.noteConcurrency(numActive, numForeground);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001483 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001484 boolean preservePreferredUid = false;
1485 if (act[i]) {
1486 JobStatus js = mActiveServices.get(i).getRunningJob();
1487 if (js != null) {
1488 if (DEBUG) {
1489 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1490 }
1491 // preferredUid will be set to uid of currently running job.
1492 mActiveServices.get(i).preemptExecutingJob();
1493 preservePreferredUid = true;
1494 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001495 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001496 if (DEBUG) {
1497 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001498 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001499 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001500 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001501 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001502 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001503 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1504 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001505 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001506 if (mPendingJobs.remove(pendingJob)) {
1507 mJobPackageTracker.noteNonpending(pendingJob);
1508 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001509 }
1510 }
1511 if (!preservePreferredUid) {
1512 mActiveServices.get(i).clearPreferredUid();
1513 }
1514 }
1515 }
1516
1517 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1518 for (int i=0; i<map.length; i++) {
1519 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1520 return i;
1521 }
1522 }
1523 return -1;
1524 }
1525
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001526 final class LocalService implements JobSchedulerInternal {
1527
1528 /**
1529 * Returns a list of all pending jobs. A running job is not considered pending. Periodic
1530 * jobs are always considered pending.
1531 */
Amith Yamasanicb926fc2016-03-14 17:15:20 -07001532 @Override
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001533 public List<JobInfo> getSystemScheduledPendingJobs() {
1534 synchronized (mLock) {
1535 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
1536 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
1537 @Override
1538 public void process(JobStatus job) {
1539 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
1540 pendingJobs.add(job.getJob());
1541 }
1542 }
1543 });
1544 return pendingJobs;
1545 }
1546 }
1547 }
1548
Shreyas Basarge5db09082016-01-07 13:38:29 +00001549 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001550 * Binder stub trampoline implementation
1551 */
1552 final class JobSchedulerStub extends IJobScheduler.Stub {
1553 /** Cache determination of whether a given app can persist jobs
1554 * key is uid of the calling app; value is undetermined/true/false
1555 */
1556 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1557
1558 // Enforce that only the app itself (or shared uid participant) can schedule a
1559 // job that runs one of the app's services, as well as verifying that the
1560 // named service properly requires the BIND_JOB_SERVICE permission
1561 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001562 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001563 final ComponentName service = job.getService();
1564 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001565 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey8a372a02016-03-16 16:25:45 -06001566 PackageManager.MATCH_DIRECT_BOOT_AWARE
1567 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001568 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001569 if (si == null) {
1570 throw new IllegalArgumentException("No such service " + service);
1571 }
Christopher Tate7060b042014-06-09 19:50:00 -07001572 if (si.applicationInfo.uid != uid) {
1573 throw new IllegalArgumentException("uid " + uid +
1574 " cannot schedule job in " + service.getPackageName());
1575 }
1576 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1577 throw new IllegalArgumentException("Scheduled service " + service
1578 + " does not require android.permission.BIND_JOB_SERVICE permission");
1579 }
Christopher Tate5568f542014-06-18 13:53:31 -07001580 } catch (RemoteException e) {
1581 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001582 }
1583 }
1584
1585 private boolean canPersistJobs(int pid, int uid) {
1586 // If we get this far we're good to go; all we need to do now is check
1587 // whether the app is allowed to persist its scheduled work.
1588 final boolean canPersist;
1589 synchronized (mPersistCache) {
1590 Boolean cached = mPersistCache.get(uid);
1591 if (cached != null) {
1592 canPersist = cached.booleanValue();
1593 } else {
1594 // Persisting jobs is tantamount to running at boot, so we permit
1595 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1596 // permission
1597 int result = getContext().checkPermission(
1598 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1599 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1600 mPersistCache.put(uid, canPersist);
1601 }
1602 }
1603 return canPersist;
1604 }
1605
1606 // IJobScheduler implementation
1607 @Override
1608 public int schedule(JobInfo job) throws RemoteException {
1609 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001610 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001611 }
1612 final int pid = Binder.getCallingPid();
1613 final int uid = Binder.getCallingUid();
1614
1615 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001616 if (job.isPersisted()) {
1617 if (!canPersistJobs(pid, uid)) {
1618 throw new IllegalArgumentException("Error: requested job be persisted without"
1619 + " holding RECEIVE_BOOT_COMPLETED permission.");
1620 }
1621 }
Christopher Tate7060b042014-06-09 19:50:00 -07001622
Jeff Sharkey785f4942016-07-14 10:31:15 -06001623 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1624 getContext().enforceCallingOrSelfPermission(
1625 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1626 }
1627
Christopher Tate7060b042014-06-09 19:50:00 -07001628 long ident = Binder.clearCallingIdentity();
1629 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001630 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001631 } finally {
1632 Binder.restoreCallingIdentity(ident);
1633 }
1634 }
1635
1636 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001637 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001638 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001639 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001640 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001641 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1642 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001643 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001644
1645 if (packageName == null) {
1646 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001647 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001648
1649 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1650 android.Manifest.permission.UPDATE_DEVICE_STATS);
1651 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1652 throw new SecurityException("Caller uid " + callerUid
1653 + " not permitted to schedule jobs for other apps");
1654 }
1655
Jeff Sharkey4f100402016-05-03 17:44:23 -06001656 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1657 getContext().enforceCallingOrSelfPermission(
1658 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1659 }
1660
Shreyas Basarge968ac752016-01-11 23:09:26 +00001661 long ident = Binder.clearCallingIdentity();
1662 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001663 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001664 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001665 } finally {
1666 Binder.restoreCallingIdentity(ident);
1667 }
1668 }
1669
1670 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001671 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1672 final int uid = Binder.getCallingUid();
1673
1674 long ident = Binder.clearCallingIdentity();
1675 try {
1676 return JobSchedulerService.this.getPendingJobs(uid);
1677 } finally {
1678 Binder.restoreCallingIdentity(ident);
1679 }
1680 }
1681
1682 @Override
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001683 public JobInfo getPendingJob(int jobId) throws RemoteException {
1684 final int uid = Binder.getCallingUid();
1685
1686 long ident = Binder.clearCallingIdentity();
1687 try {
1688 return JobSchedulerService.this.getPendingJob(uid, jobId);
1689 } finally {
1690 Binder.restoreCallingIdentity(ident);
1691 }
1692 }
1693
1694 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001695 public void cancelAll() throws RemoteException {
1696 final int uid = Binder.getCallingUid();
1697
1698 long ident = Binder.clearCallingIdentity();
1699 try {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001700 JobSchedulerService.this.cancelJobsForUid(uid, true);
Christopher Tate7060b042014-06-09 19:50:00 -07001701 } finally {
1702 Binder.restoreCallingIdentity(ident);
1703 }
1704 }
1705
1706 @Override
1707 public void cancel(int jobId) throws RemoteException {
1708 final int uid = Binder.getCallingUid();
1709
1710 long ident = Binder.clearCallingIdentity();
1711 try {
1712 JobSchedulerService.this.cancelJob(uid, jobId);
1713 } finally {
1714 Binder.restoreCallingIdentity(ident);
1715 }
1716 }
1717
1718 /**
1719 * "dumpsys" infrastructure
1720 */
1721 @Override
1722 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1723 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1724
1725 long identityToken = Binder.clearCallingIdentity();
1726 try {
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001727 JobSchedulerService.this.dumpInternal(pw, args);
Christopher Tate7060b042014-06-09 19:50:00 -07001728 } finally {
1729 Binder.restoreCallingIdentity(identityToken);
1730 }
1731 }
Christopher Tate5d346052016-03-08 12:56:08 -08001732
1733 @Override
1734 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
Dianne Hackborn354736e2016-08-22 17:00:05 -07001735 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
Christopher Tate5d346052016-03-08 12:56:08 -08001736 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
Dianne Hackborn354736e2016-08-22 17:00:05 -07001737 this, in, out, err, args, callback, resultReceiver);
Christopher Tate5d346052016-03-08 12:56:08 -08001738 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001739 };
1740
Christopher Tate5d346052016-03-08 12:56:08 -08001741 // Shell command infrastructure: run the given job immediately
1742 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1743 if (DEBUG) {
1744 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1745 + " " + jobId + " f=" + force);
1746 }
1747
1748 try {
1749 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1750 if (uid < 0) {
1751 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1752 }
1753
1754 synchronized (mLock) {
1755 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1756 if (js == null) {
1757 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1758 }
1759
1760 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1761 if (!js.isConstraintsSatisfied()) {
1762 js.overrideState = 0;
1763 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1764 }
1765
1766 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1767 }
1768 } catch (RemoteException e) {
1769 // can't happen
1770 }
1771 return 0;
1772 }
1773
Shreyas Basarge5db09082016-01-07 13:38:29 +00001774 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1775 StringBuilder s = new StringBuilder(initial + ": ");
1776 for (int i=0; i<map.length; i++) {
1777 s.append("(")
1778 .append(map[i] == null? -1: map[i].getJobId())
1779 .append(map[i] == null? -1: map[i].getUid())
1780 .append(")" );
1781 }
1782 return s.toString();
1783 }
1784
1785 private String printPendingQueue() {
1786 StringBuilder s = new StringBuilder("Pending queue: ");
1787 Iterator<JobStatus> it = mPendingJobs.iterator();
1788 while (it.hasNext()) {
1789 JobStatus js = it.next();
1790 s.append("(")
1791 .append(js.getJob().getId())
1792 .append(", ")
1793 .append(js.getUid())
1794 .append(") ");
1795 }
1796 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001797 }
Christopher Tate7060b042014-06-09 19:50:00 -07001798
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001799 static void dumpHelp(PrintWriter pw) {
1800 pw.println("Job Scheduler (jobscheduler) dump options:");
1801 pw.println(" [-h] [package] ...");
1802 pw.println(" -h: print this help");
1803 pw.println(" [package] is an optional package name to limit the output to.");
1804 }
1805
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001806 void dumpInternal(final PrintWriter pw, String[] args) {
1807 int filterUid = -1;
1808 if (!ArrayUtils.isEmpty(args)) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001809 int opti = 0;
1810 while (opti < args.length) {
1811 String arg = args[opti];
1812 if ("-h".equals(arg)) {
1813 dumpHelp(pw);
1814 return;
1815 } else if ("-a".equals(arg)) {
1816 // Ignore, we always dump all.
1817 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
1818 pw.println("Unknown option: " + arg);
1819 return;
1820 } else {
1821 break;
1822 }
1823 opti++;
1824 }
1825 if (opti < args.length) {
1826 String pkg = args[opti];
1827 try {
1828 filterUid = getContext().getPackageManager().getPackageUid(pkg,
1829 PackageManager.MATCH_UNINSTALLED_PACKAGES);
1830 } catch (NameNotFoundException ignored) {
1831 pw.println("Invalid package: " + pkg);
1832 return;
1833 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001834 }
1835 }
1836
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001837 final int filterUidFinal = UserHandle.getAppId(filterUid);
Christopher Tatef973a7b2014-08-29 12:54:08 -07001838 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001839 synchronized (mLock) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001840 mConstants.dump(pw);
1841 pw.println();
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001842 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001843 pw.print("Registered ");
1844 pw.print(mJobs.size());
1845 pw.println(" jobs:");
Christopher Tate7060b042014-06-09 19:50:00 -07001846 if (mJobs.size() > 0) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001847 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
1848 Collections.sort(jobs, new Comparator<JobStatus>() {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001849 @Override
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001850 public int compare(JobStatus o1, JobStatus o2) {
1851 int uid1 = o1.getUid();
1852 int uid2 = o2.getUid();
1853 int id1 = o1.getJobId();
1854 int id2 = o2.getJobId();
1855 if (uid1 != uid2) {
1856 return uid1 < uid2 ? -1 : 1;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001857 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001858 return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001859 }
1860 });
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001861 for (JobStatus job : jobs) {
1862 pw.print(" JOB #"); job.printUniqueId(pw); pw.print(": ");
1863 pw.println(job.toShortStringExceptUniqueId());
1864
1865 // Skip printing details if the caller requested a filter
1866 if (!job.shouldDump(filterUidFinal)) {
1867 continue;
1868 }
1869
1870 job.dump(pw, " ", true);
1871 pw.print(" Ready: ");
1872 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1873 pw.print(" (job=");
1874 pw.print(job.isReady());
1875 pw.print(" pending=");
1876 pw.print(mPendingJobs.contains(job));
1877 pw.print(" active=");
1878 pw.print(isCurrentlyActiveLocked(job));
1879 pw.print(" user=");
1880 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
1881 pw.println(")");
1882 }
Christopher Tate7060b042014-06-09 19:50:00 -07001883 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001884 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001885 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001886 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001887 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001888 mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal);
Christopher Tate7060b042014-06-09 19:50:00 -07001889 }
1890 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001891 pw.println("Uid priority overrides:");
1892 for (int i=0; i< mUidPriorityOverride.size(); i++) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001893 int uid = mUidPriorityOverride.keyAt(i);
1894 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
1895 pw.print(" "); pw.print(UserHandle.formatUid(uid));
1896 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
1897 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001898 }
1899 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001900 mJobPackageTracker.dump(pw, "", filterUidFinal);
Dianne Hackborn807de782016-04-07 17:54:41 -07001901 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001902 if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
1903 pw.println();
1904 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001905 pw.println("Pending queue:");
1906 for (int i=0; i<mPendingJobs.size(); i++) {
1907 JobStatus job = mPendingJobs.get(i);
1908 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1909 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001910 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001911 int priority = evaluateJobPriorityLocked(job);
1912 if (priority != JobInfo.PRIORITY_DEFAULT) {
1913 pw.print(" Evaluated priority: "); pw.println(priority);
1914 }
1915 pw.print(" Tag: "); pw.println(job.getTag());
1916 }
Christopher Tate7060b042014-06-09 19:50:00 -07001917 pw.println();
1918 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001919 for (int i=0; i<mActiveServices.size(); i++) {
1920 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001921 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001922 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001923 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001924 continue;
1925 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001926 pw.println(jsc.getRunningJob().toShortString());
1927 pw.print(" Running for: ");
1928 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1929 pw.print(", timeout at: ");
1930 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1931 pw.println();
1932 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001933 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1934 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001935 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001936 }
Christopher Tate7060b042014-06-09 19:50:00 -07001937 }
1938 }
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001939 if (filterUid == -1) {
1940 pw.println();
1941 pw.print("mReadyToRock="); pw.println(mReadyToRock);
1942 pw.print("mReportedActive="); pw.println(mReportedActive);
1943 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
1944 }
Christopher Tate7060b042014-06-09 19:50:00 -07001945 }
1946 pw.println();
1947 }
1948}