blob: b8780992226edf2da3e15396640d9c46ff4d96d6 [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 }
Dianne Hackborne07641d2016-11-09 15:07:23 -0800429 cancelJobsForUid(pkgUid);
Christopher Tateb5c07882016-05-26 17:11:09 -0700430 }
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 Hackborne07641d2016-11-09 15:07:23 -0800458 cancelJobsForUid(uidRemoved);
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
Dianne Hackborne07641d2016-11-09 15:07:23 -0800512 @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800513 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackborne07641d2016-11-09 15:07:23 -0800514 if (disabled) {
515 cancelJobsForUid(uid);
516 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700517 }
518
519 @Override public void onUidActive(int uid) throws RemoteException {
520 }
521
Dianne Hackborne07641d2016-11-09 15:07:23 -0800522 @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
523 if (disabled) {
524 cancelJobsForUid(uid);
525 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700526 }
527 };
528
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800529 public Object getLock() {
530 return mLock;
531 }
532
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700533 public JobStore getJobStore() {
534 return mJobs;
535 }
536
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700537 @Override
538 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700539 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
540 // Let's kick any outstanding jobs for this user.
541 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
542 }
543
544 @Override
545 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700546 // Let's kick any outstanding jobs for this user.
547 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
548 }
549
550 @Override
551 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700552 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700553 }
554
Christopher Tate7060b042014-06-09 19:50:00 -0700555 /**
556 * Entry point from client to schedule the provided job.
557 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
558 * @param job JobInfo object containing execution parameters
559 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700560 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
561 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700562 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800563 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000564 }
565
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800566 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
567 String tag) {
568 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700569 try {
570 if (ActivityManagerNative.getDefault().getAppStartMode(uId,
571 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
572 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
573 + " -- package not allowed to start");
574 return JobScheduler.RESULT_FAILURE;
575 }
576 } catch (RemoteException e) {
577 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800578 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
579 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800580 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800581 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800582 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800583 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
584 Slog.w(TAG, "Too many jobs for uid " + uId);
585 throw new IllegalStateException("Apps may not schedule more than "
586 + MAX_JOBS_PER_APP + " distinct jobs");
587 }
588 }
589
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800590 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700591 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700592 cancelJobImpl(toCancel, jobStatus);
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700593 }
594 startTrackingJob(jobStatus, toCancel);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800595 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700596 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700597 return JobScheduler.RESULT_SUCCESS;
598 }
599
600 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800601 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800602 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
603 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
604 for (int i = jobs.size() - 1; i >= 0; i--) {
605 JobStatus job = jobs.get(i);
606 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700607 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800608 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700609 }
Christopher Tate7060b042014-06-09 19:50:00 -0700610 }
611
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600612 public JobInfo getPendingJob(int uid, int jobId) {
613 synchronized (mLock) {
614 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
615 for (int i = jobs.size() - 1; i >= 0; i--) {
616 JobStatus job = jobs.get(i);
617 if (job.getJobId() == jobId) {
618 return job.getJob();
619 }
620 }
621 return null;
622 }
623 }
624
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700625 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700626 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800627 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700628 jobsForUser = mJobs.getJobsByUser(userHandle);
629 }
630 for (int i=0; i<jobsForUser.size(); i++) {
631 JobStatus toRemove = jobsForUser.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700632 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700633 }
634 }
635
Christopher Tateee7805b2016-07-15 16:56:56 -0700636 void cancelJobsForPackageAndUid(String pkgName, int uid) {
637 List<JobStatus> jobsForUid;
638 synchronized (mLock) {
639 jobsForUid = mJobs.getJobsByUid(uid);
640 }
641 for (int i = jobsForUid.size() - 1; i >= 0; i--) {
642 final JobStatus job = jobsForUid.get(i);
643 if (job.getSourcePackageName().equals(pkgName)) {
644 cancelJobImpl(job, null);
645 }
646 }
647 }
648
Christopher Tate7060b042014-06-09 19:50:00 -0700649 /**
650 * Entry point from client to cancel all jobs originating from their uid.
651 * This will remove the job from the master list, and cancel the job if it was staged for
652 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700653 * @param uid Uid to check against for removal of a job.
Dianne Hackborne07641d2016-11-09 15:07:23 -0800654 *
Christopher Tate7060b042014-06-09 19:50:00 -0700655 */
Dianne Hackborne07641d2016-11-09 15:07:23 -0800656 public void cancelJobsForUid(int uid) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700657 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800658 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700659 jobsForUid = mJobs.getJobsByUid(uid);
660 }
661 for (int i=0; i<jobsForUid.size(); i++) {
662 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700663 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700664 }
665 }
666
667 /**
668 * Entry point from client to cancel the job corresponding to the jobId provided.
669 * This will remove the job from the master list, and cancel the job if it was staged for
670 * execution or being executed.
671 * @param uid Uid of the calling client.
672 * @param jobId Id of the job, provided at schedule-time.
673 */
674 public void cancelJob(int uid, int jobId) {
675 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800676 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700677 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700678 }
679 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700680 cancelJobImpl(toCancel, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700681 }
682 }
683
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700684 private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800685 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700686 stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800687 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700688 // Remove from pending queue.
Dianne Hackborn807de782016-04-07 17:54:41 -0700689 if (mPendingJobs.remove(cancelled)) {
690 mJobPackageTracker.noteNonpending(cancelled);
691 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700692 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000693 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800694 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700695 }
Christopher Tate7060b042014-06-09 19:50:00 -0700696 }
697
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800698 void updateUidState(int uid, int procState) {
699 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800700 if (procState == ActivityManager.PROCESS_STATE_TOP) {
701 // Only use this if we are exactly the top app. All others can live
702 // with just the foreground priority. This means that persistent processes
703 // can never be the top app priority... that is fine.
704 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
705 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
706 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800707 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800708 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800709 }
710 }
711 }
712
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700713 @Override
714 public void onDeviceIdleStateChanged(boolean deviceIdle) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800715 synchronized (mLock) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700716 if (deviceIdle) {
Jeff Sharkey34618b52016-06-01 15:51:19 -0600717 // When becoming idle, make sure no jobs are actively running,
718 // except those using the idle exemption flag.
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700719 for (int i=0; i<mActiveServices.size(); i++) {
720 JobServiceContext jsc = mActiveServices.get(i);
721 final JobStatus executing = jsc.getRunningJob();
Jeff Sharkey34618b52016-06-01 15:51:19 -0600722 if (executing != null
723 && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700724 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
725 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700726 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700727 } else {
728 // When coming out of idle, allow thing to start back up.
729 if (mReadyToRock) {
730 if (mLocalDeviceIdleController != null) {
731 if (!mReportedActive) {
732 mReportedActive = true;
733 mLocalDeviceIdleController.setJobsActive(true);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700734 }
735 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700736 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700737 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700738 }
739 }
740 }
741
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800742 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000743 // active is true if pending queue contains jobs OR some job is running.
744 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800745 if (mPendingJobs.size() <= 0) {
746 for (int i=0; i<mActiveServices.size(); i++) {
Dianne Hackborn7ab40252016-06-15 17:30:24 -0700747 final JobServiceContext jsc = mActiveServices.get(i);
748 final JobStatus job = jsc.getRunningJob();
749 if (job != null
750 && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
751 && !job.dozeWhitelisted) {
752 // We will report active if we have a job running and it is not an exception
753 // due to being in the foreground or whitelisted.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800754 active = true;
755 break;
756 }
757 }
758 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000759
760 if (mReportedActive != active) {
761 mReportedActive = active;
762 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800763 mLocalDeviceIdleController.setJobsActive(active);
764 }
765 }
766 }
767
Christopher Tate7060b042014-06-09 19:50:00 -0700768 /**
769 * Initializes the system service.
770 * <p>
771 * Subclasses must define a single argument constructor that accepts the context
772 * and passes it to super.
773 * </p>
774 *
775 * @param context The system server context.
776 */
777 public JobSchedulerService(Context context) {
778 super(context);
Dianne Hackborn970e3f42016-06-01 10:55:13 -0700779 mHandler = new JobHandler(context.getMainLooper());
780 mConstants = new Constants(mHandler);
781 mJobSchedulerStub = new JobSchedulerStub();
782 mJobs = JobStore.initAndGet(this);
783
Christopher Tate7060b042014-06-09 19:50:00 -0700784 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700785 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700786 mControllers.add(ConnectivityController.get(this));
787 mControllers.add(TimeController.get(this));
788 mControllers.add(IdleController.get(this));
789 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800790 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800791 mControllers.add(ContentObserverController.get(this));
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700792 mControllers.add(DeviceIdleJobsController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700793 }
794
795 @Override
796 public void onStart() {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000797 publishLocalService(JobSchedulerInternal.class, new LocalService());
Christopher Tate7060b042014-06-09 19:50:00 -0700798 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
799 }
800
801 @Override
802 public void onBootPhase(int phase) {
803 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700804 mConstants.start(getContext().getContentResolver());
Shreyas Basarge5db09082016-01-07 13:38:29 +0000805 // Register br for package removals and user removals.
Christopher Tateb5c07882016-05-26 17:11:09 -0700806 final IntentFilter filter = new IntentFilter();
807 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
808 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
Christopher Tateee7805b2016-07-15 16:56:56 -0700809 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
810 filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
Christopher Tate7060b042014-06-09 19:50:00 -0700811 filter.addDataScheme("package");
812 getContext().registerReceiverAsUser(
813 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
814 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
815 getContext().registerReceiverAsUser(
816 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000817 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700818 try {
819 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800820 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
Dianne Hackborn5614bf52016-11-07 17:26:41 -0800821 | ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN,
822 null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700823 } catch (RemoteException e) {
824 // ignored; both services live in system_server
825 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700826 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800827 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700828 // Let's go!
829 mReadyToRock = true;
830 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
831 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800832 mLocalDeviceIdleController
833 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700834 // Create the "runners".
835 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
836 mActiveServices.add(
Dianne Hackborn807de782016-04-07 17:54:41 -0700837 new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700838 getContext().getMainLooper()));
839 }
840 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800841 mJobs.forEachJob(new JobStatusFunctor() {
842 @Override
843 public void process(JobStatus job) {
844 for (int controller = 0; controller < mControllers.size(); controller++) {
845 final StateController sc = mControllers.get(controller);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800846 sc.maybeStartTrackingJobLocked(job, null);
847 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700848 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800849 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700850 // GO GO GO!
851 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
852 }
Christopher Tate7060b042014-06-09 19:50:00 -0700853 }
854 }
855
856 /**
857 * Called when we have a job status object that we need to insert in our
858 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
859 * about.
860 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800861 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800862 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800863 final boolean update = mJobs.add(jobStatus);
864 if (mReadyToRock) {
865 for (int i = 0; i < mControllers.size(); i++) {
866 StateController controller = mControllers.get(i);
867 if (update) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700868 controller.maybeStopTrackingJobLocked(jobStatus, null, true);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800869 }
870 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700871 }
Christopher Tate7060b042014-06-09 19:50:00 -0700872 }
Christopher Tate7060b042014-06-09 19:50:00 -0700873 }
874 }
875
876 /**
877 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
878 * object removed.
879 */
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700880 private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
881 boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800882 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700883 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800884 final boolean removed = mJobs.remove(jobStatus, writeBack);
885 if (removed && mReadyToRock) {
886 for (int i=0; i<mControllers.size(); i++) {
887 StateController controller = mControllers.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700888 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800889 }
Christopher Tate7060b042014-06-09 19:50:00 -0700890 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800891 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700892 }
Christopher Tate7060b042014-06-09 19:50:00 -0700893 }
894
Shreyas Basarge5db09082016-01-07 13:38:29 +0000895 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700896 for (int i=0; i<mActiveServices.size(); i++) {
897 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700898 final JobStatus executing = jsc.getRunningJob();
899 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000900 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700901 return true;
902 }
903 }
904 return false;
905 }
906
907 /**
908 * @param job JobStatus we are querying against.
909 * @return Whether or not the job represented by the status object is currently being run or
910 * is pending.
911 */
912 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700913 for (int i=0; i<mActiveServices.size(); i++) {
914 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tateeafb5352016-10-04 16:34:48 -0700915 // The 'unsafe' direct-internal-reference running-job inspector is okay to
916 // use here because we are already holding the necessary lock *and* we
917 // immediately discard the returned object reference, if any; we return
918 // only a boolean state indicator to the caller.
919 final JobStatus running = serviceContext.getRunningJobUnsafeLocked();
Christopher Tate7060b042014-06-09 19:50:00 -0700920 if (running != null && running.matches(job.getUid(), job.getJobId())) {
921 return true;
922 }
923 }
924 return false;
925 }
926
Dianne Hackborn807de782016-04-07 17:54:41 -0700927 void noteJobsPending(List<JobStatus> jobs) {
928 for (int i = jobs.size() - 1; i >= 0; i--) {
929 JobStatus job = jobs.get(i);
930 mJobPackageTracker.notePending(job);
931 }
932 }
933
934 void noteJobsNonpending(List<JobStatus> jobs) {
935 for (int i = jobs.size() - 1; i >= 0; i--) {
936 JobStatus job = jobs.get(i);
937 mJobPackageTracker.noteNonpending(job);
938 }
939 }
940
Christopher Tate7060b042014-06-09 19:50:00 -0700941 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700942 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
943 * specify an override deadline on a failed job (the failed job will run even though it's not
944 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
945 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
946 *
Christopher Tate7060b042014-06-09 19:50:00 -0700947 * @param failureToReschedule Provided job status that we will reschedule.
948 * @return A newly instantiated JobStatus with the same constraints as the last job except
949 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700950 *
951 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700952 */
953 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
954 final long elapsedNowMillis = SystemClock.elapsedRealtime();
955 final JobInfo job = failureToReschedule.getJob();
956
957 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700958 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
959 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700960
961 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700962 case JobInfo.BACKOFF_POLICY_LINEAR:
963 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700964 break;
965 default:
966 if (DEBUG) {
967 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
968 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700969 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
970 delayMillis =
971 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700972 break;
973 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700974 delayMillis =
975 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800976 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700977 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800978 for (int ic=0; ic<mControllers.size(); ic++) {
979 StateController controller = mControllers.get(ic);
980 controller.rescheduleForFailure(newJob, failureToReschedule);
981 }
982 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700983 }
984
985 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700986 * Called after a periodic has executed so we can reschedule it. We take the last execution
987 * time of the job to be the time of completion (i.e. the time at which this function is
988 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700989 * This could be inaccurate b/c the job can run for as long as
990 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
991 * to underscheduling at least, rather than if we had taken the last execution time to be the
992 * start of the execution.
993 * @return A new job representing the execution criteria for this instantiation of the
994 * recurring job.
995 */
996 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
997 final long elapsedNow = SystemClock.elapsedRealtime();
998 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700999 long runEarly = 0L;
1000
1001 // If this periodic was rescheduled it won't have a deadline.
1002 if (periodicToReschedule.hasDeadlineConstraint()) {
1003 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
1004 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001005 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -07001006 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001007 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
1008 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -07001009
1010 if (DEBUG) {
1011 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
1012 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
1013 }
1014 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
1015 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
1016 }
1017
1018 // JobCompletedListener implementations.
1019
1020 /**
1021 * A job just finished executing. We fetch the
1022 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
1023 * whether we want to reschedule we readd it to the controllers.
1024 * @param jobStatus Completed job.
1025 * @param needsReschedule Whether the implementing class should reschedule this job.
1026 */
1027 @Override
1028 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
1029 if (DEBUG) {
1030 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
1031 }
Shreyas Basarge73f10252016-02-11 17:06:13 +00001032 // Do not write back immediately if this is a periodic job. The job may get lost if system
1033 // shuts down before it is added back.
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001034 if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -07001035 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001036 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -07001037 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001038 // We still want to check for jobs to execute, because this job may have
1039 // scheduled a new job under the same job id, and now we can run it.
1040 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001041 return;
1042 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001043 // Note: there is a small window of time in here where, when rescheduling a job,
1044 // we will stop monitoring its content providers. This should be fixed by stopping
1045 // the old job after scheduling the new one, but since we have no lock held here
1046 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -07001047 if (needsReschedule) {
1048 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001049 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001050 } else if (jobStatus.getJob().isPeriodic()) {
1051 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001052 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001053 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001054 reportActive();
1055 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001056 }
1057
1058 // StateChangedListener implementations.
1059
1060 /**
Matthew Williams48a30db2014-09-23 13:39:36 -07001061 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
1062 * some controller's state has changed, so as to run through the list of jobs and start/stop
1063 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -07001064 */
1065 @Override
1066 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001067 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001068 }
1069
1070 @Override
1071 public void onRunJobNow(JobStatus jobStatus) {
1072 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
1073 }
1074
Christopher Tate7060b042014-06-09 19:50:00 -07001075 private class JobHandler extends Handler {
1076
1077 public JobHandler(Looper looper) {
1078 super(looper);
1079 }
1080
1081 @Override
1082 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001083 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001084 if (!mReadyToRock) {
1085 return;
1086 }
1087 }
Christopher Tate7060b042014-06-09 19:50:00 -07001088 switch (message.what) {
1089 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001090 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -07001091 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -07001092 // runNow can be null, which is a controller's way of indicating that its
1093 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -07001094 if (runNow != null && !mPendingJobs.contains(runNow)
1095 && mJobs.containsJob(runNow)) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001096 mJobPackageTracker.notePending(runNow);
Christopher Tate7060b042014-06-09 19:50:00 -07001097 mPendingJobs.add(runNow);
1098 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001099 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -07001100 }
Christopher Tate7060b042014-06-09 19:50:00 -07001101 break;
1102 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001103 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001104 if (mReportedActive) {
1105 // if jobs are currently being run, queue all ready jobs for execution.
1106 queueReadyJobsForExecutionLockedH();
1107 } else {
1108 // Check the list of jobs and run some of them if we feel inclined.
1109 maybeQueueReadyJobsForExecutionLockedH();
1110 }
1111 }
1112 break;
1113 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001114 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001115 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -07001116 }
Christopher Tate7060b042014-06-09 19:50:00 -07001117 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001118 case MSG_STOP_JOB:
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001119 cancelJobImpl((JobStatus)message.obj, null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001120 break;
Christopher Tate7060b042014-06-09 19:50:00 -07001121 }
1122 maybeRunPendingJobsH();
1123 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
1124 removeMessages(MSG_CHECK_JOB);
1125 }
1126
1127 /**
1128 * Run through list of jobs and execute all possible - at least one is expired so we do
1129 * as many as we can.
1130 */
Matthew Williams48a30db2014-09-23 13:39:36 -07001131 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001132 if (DEBUG) {
1133 Slog.d(TAG, "queuing all ready jobs for execution:");
1134 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001135 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001136 mPendingJobs.clear();
1137 mJobs.forEachJob(mReadyQueueFunctor);
1138 mReadyQueueFunctor.postProcess();
1139
Matthew Williams48a30db2014-09-23 13:39:36 -07001140 if (DEBUG) {
1141 final int queuedJobs = mPendingJobs.size();
1142 if (queuedJobs == 0) {
1143 Slog.d(TAG, "No jobs pending.");
1144 } else {
1145 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -07001146 }
Christopher Tate7060b042014-06-09 19:50:00 -07001147 }
1148 }
1149
Christopher Tate2f36fd62016-02-18 18:36:08 -08001150 class ReadyJobQueueFunctor implements JobStatusFunctor {
1151 ArrayList<JobStatus> newReadyJobs;
1152
1153 @Override
1154 public void process(JobStatus job) {
1155 if (isReadyToBeExecutedLocked(job)) {
1156 if (DEBUG) {
1157 Slog.d(TAG, " queued " + job.toShortString());
1158 }
1159 if (newReadyJobs == null) {
1160 newReadyJobs = new ArrayList<JobStatus>();
1161 }
1162 newReadyJobs.add(job);
1163 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
1164 stopJobOnServiceContextLocked(job,
1165 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
1166 }
1167 }
1168
1169 public void postProcess() {
1170 if (newReadyJobs != null) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001171 noteJobsPending(newReadyJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001172 mPendingJobs.addAll(newReadyJobs);
1173 }
1174 newReadyJobs = null;
1175 }
1176 }
1177 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
1178
Christopher Tate7060b042014-06-09 19:50:00 -07001179 /**
1180 * The state of at least one job has changed. Here is where we could enforce various
1181 * policies on when we want to execute jobs.
1182 * Right now the policy is such:
1183 * If >1 of the ready jobs is idle mode we send all of them off
1184 * if more than 2 network connectivity jobs are ready we send them all off.
1185 * If more than 4 jobs total are ready we send them all off.
1186 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
1187 */
Christopher Tate2f36fd62016-02-18 18:36:08 -08001188 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
1189 int chargingCount;
1190 int idleCount;
1191 int backoffCount;
1192 int connectivityCount;
1193 int contentCount;
1194 List<JobStatus> runnableJobs;
1195
1196 public MaybeReadyJobQueueFunctor() {
1197 reset();
1198 }
1199
1200 // Functor method invoked for each job via JobStore.forEachJob()
1201 @Override
1202 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001203 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001204 try {
1205 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
1206 job.getJob().getService().getPackageName())
1207 == ActivityManager.APP_START_MODE_DISABLED) {
1208 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
1209 + job.getJob().toString() + " -- package not allowed to start");
1210 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001211 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001212 }
1213 } catch (RemoteException e) {
1214 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001215 if (job.getNumFailures() > 0) {
1216 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -07001217 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001218 if (job.hasIdleConstraint()) {
1219 idleCount++;
1220 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001221 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()
1222 || job.hasNotRoamingConstraint()) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001223 connectivityCount++;
1224 }
1225 if (job.hasChargingConstraint()) {
1226 chargingCount++;
1227 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001228 if (job.hasContentTriggerConstraint()) {
1229 contentCount++;
1230 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001231 if (runnableJobs == null) {
1232 runnableJobs = new ArrayList<>();
1233 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001234 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001235 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001236 stopJobOnServiceContextLocked(job,
1237 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -07001238 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001239 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001240
1241 public void postProcess() {
1242 if (backoffCount > 0 ||
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001243 idleCount >= mConstants.MIN_IDLE_COUNT ||
1244 connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
1245 chargingCount >= mConstants.MIN_CHARGING_COUNT ||
1246 contentCount >= mConstants.MIN_CONTENT_COUNT ||
1247 (runnableJobs != null
1248 && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001249 if (DEBUG) {
1250 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
1251 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001252 noteJobsPending(runnableJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001253 mPendingJobs.addAll(runnableJobs);
1254 } else {
1255 if (DEBUG) {
1256 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
1257 }
Christopher Tate7060b042014-06-09 19:50:00 -07001258 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001259
1260 // Be ready for next time
1261 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -07001262 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001263
1264 private void reset() {
1265 chargingCount = 0;
1266 idleCount = 0;
1267 backoffCount = 0;
1268 connectivityCount = 0;
1269 contentCount = 0;
1270 runnableJobs = null;
1271 }
1272 }
1273 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
1274
1275 private void maybeQueueReadyJobsForExecutionLockedH() {
1276 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
1277
Dianne Hackborn807de782016-04-07 17:54:41 -07001278 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001279 mPendingJobs.clear();
1280 mJobs.forEachJob(mMaybeQueueFunctor);
1281 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -07001282 }
1283
1284 /**
1285 * Criteria for moving a job into the pending queue:
1286 * - It's ready.
1287 * - It's not pending.
1288 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001289 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001290 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -07001291 */
1292 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001293 final boolean jobReady = job.isReady();
1294 final boolean jobPending = mPendingJobs.contains(job);
1295 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001296
1297 final int userId = job.getUserId();
1298 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
1299 final boolean componentPresent;
1300 try {
1301 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
1302 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
1303 userId) != null);
1304 } catch (RemoteException e) {
1305 throw e.rethrowAsRuntimeException();
1306 }
1307
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001308 if (DEBUG) {
1309 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
1310 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001311 + " active=" + jobActive + " userStarted=" + userStarted
1312 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001313 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001314 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -07001315 }
1316
1317 /**
1318 * Criteria for cancelling an active job:
1319 * - It's not ready
1320 * - It's running on a JSC.
1321 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001322 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001323 return !job.isReady() && isCurrentlyActiveLocked(job);
1324 }
1325
1326 /**
1327 * Reconcile jobs in the pending queue against available execution contexts.
1328 * A controller can force a job into the pending queue even if it's already running, but
1329 * here is where we decide whether to actually execute it.
1330 */
1331 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001332 synchronized (mLock) {
Matthew Williams75fc5252014-09-02 16:17:53 -07001333 if (DEBUG) {
1334 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1335 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001336 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001337 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001338 }
1339 }
1340 }
1341
Dianne Hackborn807de782016-04-07 17:54:41 -07001342 private int adjustJobPriority(int curPriority, JobStatus job) {
1343 if (curPriority < JobInfo.PRIORITY_TOP_APP) {
1344 float factor = mJobPackageTracker.getLoadFactor(job);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001345 if (factor >= mConstants.HEAVY_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001346 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001347 } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001348 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
1349 }
1350 }
1351 return curPriority;
1352 }
1353
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001354 private int evaluateJobPriorityLocked(JobStatus job) {
1355 int priority = job.getPriority();
1356 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001357 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001358 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001359 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1360 if (override != 0) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001361 return adjustJobPriority(override, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001362 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001363 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001364 }
1365
Christopher Tate7060b042014-06-09 19:50:00 -07001366 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001367 * Takes jobs from pending queue and runs them on available contexts.
1368 * If no contexts are available, preempts lower priority jobs to
1369 * run higher priority ones.
1370 * Lock on mJobs before calling this function.
1371 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001372 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001373 if (DEBUG) {
1374 Slog.d(TAG, printPendingQueue());
1375 }
1376
Dianne Hackborn970510b2016-02-24 16:56:42 -08001377 int memLevel;
1378 try {
1379 memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
1380 } catch (RemoteException e) {
1381 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1382 }
1383 switch (memLevel) {
1384 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001385 mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001386 break;
1387 case ProcessStats.ADJ_MEM_FACTOR_LOW:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001388 mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001389 break;
1390 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001391 mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001392 break;
1393 default:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001394 mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001395 break;
1396 }
1397
1398 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1399 boolean[] act = mTmpAssignAct;
1400 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1401 int numActive = 0;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001402 int numForeground = 0;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001403 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1404 final JobServiceContext js = mActiveServices.get(i);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001405 final JobStatus status = js.getRunningJob();
1406 if ((contextIdToJobMap[i] = status) != null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001407 numActive++;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001408 if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
1409 numForeground++;
1410 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001411 }
1412 act[i] = false;
1413 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001414 }
1415 if (DEBUG) {
1416 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1417 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001418 for (int i=0; i<mPendingJobs.size(); i++) {
1419 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001420
1421 // If job is already running, go to next job.
1422 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1423 if (jobRunningContext != -1) {
1424 continue;
1425 }
1426
Dianne Hackborn970510b2016-02-24 16:56:42 -08001427 final int priority = evaluateJobPriorityLocked(nextPending);
1428 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001429
Shreyas Basarge5db09082016-01-07 13:38:29 +00001430 // Find a context for nextPending. The context should be available OR
1431 // it should have lowest priority among all running jobs
1432 // (sharing the same Uid as nextPending)
1433 int minPriority = Integer.MAX_VALUE;
1434 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001435 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1436 JobStatus job = contextIdToJobMap[j];
1437 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001438 if (job == null) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001439 if ((numActive < mMaxActiveJobs ||
1440 (priority >= JobInfo.PRIORITY_TOP_APP &&
1441 numForeground < mConstants.FG_JOB_COUNT)) &&
Dianne Hackborn970510b2016-02-24 16:56:42 -08001442 (preferredUid == nextPending.getUid() ||
1443 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1444 // This slot is free, and we haven't yet hit the limit on
1445 // concurrent jobs... we can just throw the job in to here.
1446 minPriorityContextId = j;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001447 break;
1448 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001449 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001450 // the context has a preferred Uid or we have reached the limit on
1451 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001452 continue;
1453 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001454 if (job.getUid() != nextPending.getUid()) {
1455 continue;
1456 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001457 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001458 continue;
1459 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001460 if (minPriority > nextPending.lastEvaluatedPriority) {
1461 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001462 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001463 }
1464 }
1465 if (minPriorityContextId != -1) {
1466 contextIdToJobMap[minPriorityContextId] = nextPending;
1467 act[minPriorityContextId] = true;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001468 numActive++;
1469 if (priority >= JobInfo.PRIORITY_TOP_APP) {
1470 numForeground++;
1471 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001472 }
1473 }
1474 if (DEBUG) {
1475 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1476 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001477 mJobPackageTracker.noteConcurrency(numActive, numForeground);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001478 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001479 boolean preservePreferredUid = false;
1480 if (act[i]) {
1481 JobStatus js = mActiveServices.get(i).getRunningJob();
1482 if (js != null) {
1483 if (DEBUG) {
1484 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1485 }
1486 // preferredUid will be set to uid of currently running job.
1487 mActiveServices.get(i).preemptExecutingJob();
1488 preservePreferredUid = true;
1489 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001490 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001491 if (DEBUG) {
1492 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001493 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001494 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001495 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001496 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001497 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001498 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1499 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001500 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001501 if (mPendingJobs.remove(pendingJob)) {
1502 mJobPackageTracker.noteNonpending(pendingJob);
1503 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001504 }
1505 }
1506 if (!preservePreferredUid) {
1507 mActiveServices.get(i).clearPreferredUid();
1508 }
1509 }
1510 }
1511
1512 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1513 for (int i=0; i<map.length; i++) {
1514 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1515 return i;
1516 }
1517 }
1518 return -1;
1519 }
1520
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001521 final class LocalService implements JobSchedulerInternal {
1522
1523 /**
1524 * Returns a list of all pending jobs. A running job is not considered pending. Periodic
1525 * jobs are always considered pending.
1526 */
Amith Yamasanicb926fc2016-03-14 17:15:20 -07001527 @Override
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001528 public List<JobInfo> getSystemScheduledPendingJobs() {
1529 synchronized (mLock) {
1530 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
1531 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
1532 @Override
1533 public void process(JobStatus job) {
1534 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
1535 pendingJobs.add(job.getJob());
1536 }
1537 }
1538 });
1539 return pendingJobs;
1540 }
1541 }
1542 }
1543
Shreyas Basarge5db09082016-01-07 13:38:29 +00001544 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001545 * Binder stub trampoline implementation
1546 */
1547 final class JobSchedulerStub extends IJobScheduler.Stub {
1548 /** Cache determination of whether a given app can persist jobs
1549 * key is uid of the calling app; value is undetermined/true/false
1550 */
1551 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1552
1553 // Enforce that only the app itself (or shared uid participant) can schedule a
1554 // job that runs one of the app's services, as well as verifying that the
1555 // named service properly requires the BIND_JOB_SERVICE permission
1556 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001557 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001558 final ComponentName service = job.getService();
1559 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001560 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey8a372a02016-03-16 16:25:45 -06001561 PackageManager.MATCH_DIRECT_BOOT_AWARE
1562 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001563 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001564 if (si == null) {
1565 throw new IllegalArgumentException("No such service " + service);
1566 }
Christopher Tate7060b042014-06-09 19:50:00 -07001567 if (si.applicationInfo.uid != uid) {
1568 throw new IllegalArgumentException("uid " + uid +
1569 " cannot schedule job in " + service.getPackageName());
1570 }
1571 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1572 throw new IllegalArgumentException("Scheduled service " + service
1573 + " does not require android.permission.BIND_JOB_SERVICE permission");
1574 }
Christopher Tate5568f542014-06-18 13:53:31 -07001575 } catch (RemoteException e) {
1576 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001577 }
1578 }
1579
1580 private boolean canPersistJobs(int pid, int uid) {
1581 // If we get this far we're good to go; all we need to do now is check
1582 // whether the app is allowed to persist its scheduled work.
1583 final boolean canPersist;
1584 synchronized (mPersistCache) {
1585 Boolean cached = mPersistCache.get(uid);
1586 if (cached != null) {
1587 canPersist = cached.booleanValue();
1588 } else {
1589 // Persisting jobs is tantamount to running at boot, so we permit
1590 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1591 // permission
1592 int result = getContext().checkPermission(
1593 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1594 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1595 mPersistCache.put(uid, canPersist);
1596 }
1597 }
1598 return canPersist;
1599 }
1600
1601 // IJobScheduler implementation
1602 @Override
1603 public int schedule(JobInfo job) throws RemoteException {
1604 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001605 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001606 }
1607 final int pid = Binder.getCallingPid();
1608 final int uid = Binder.getCallingUid();
1609
1610 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001611 if (job.isPersisted()) {
1612 if (!canPersistJobs(pid, uid)) {
1613 throw new IllegalArgumentException("Error: requested job be persisted without"
1614 + " holding RECEIVE_BOOT_COMPLETED permission.");
1615 }
1616 }
Christopher Tate7060b042014-06-09 19:50:00 -07001617
Jeff Sharkey785f4942016-07-14 10:31:15 -06001618 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1619 getContext().enforceCallingOrSelfPermission(
1620 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1621 }
1622
Christopher Tate7060b042014-06-09 19:50:00 -07001623 long ident = Binder.clearCallingIdentity();
1624 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001625 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001626 } finally {
1627 Binder.restoreCallingIdentity(ident);
1628 }
1629 }
1630
1631 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001632 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001633 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001634 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001635 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001636 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1637 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001638 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001639
1640 if (packageName == null) {
1641 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001642 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001643
1644 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1645 android.Manifest.permission.UPDATE_DEVICE_STATS);
1646 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1647 throw new SecurityException("Caller uid " + callerUid
1648 + " not permitted to schedule jobs for other apps");
1649 }
1650
Jeff Sharkey4f100402016-05-03 17:44:23 -06001651 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1652 getContext().enforceCallingOrSelfPermission(
1653 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1654 }
1655
Shreyas Basarge968ac752016-01-11 23:09:26 +00001656 long ident = Binder.clearCallingIdentity();
1657 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001658 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001659 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001660 } finally {
1661 Binder.restoreCallingIdentity(ident);
1662 }
1663 }
1664
1665 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001666 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1667 final int uid = Binder.getCallingUid();
1668
1669 long ident = Binder.clearCallingIdentity();
1670 try {
1671 return JobSchedulerService.this.getPendingJobs(uid);
1672 } finally {
1673 Binder.restoreCallingIdentity(ident);
1674 }
1675 }
1676
1677 @Override
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001678 public JobInfo getPendingJob(int jobId) throws RemoteException {
1679 final int uid = Binder.getCallingUid();
1680
1681 long ident = Binder.clearCallingIdentity();
1682 try {
1683 return JobSchedulerService.this.getPendingJob(uid, jobId);
1684 } finally {
1685 Binder.restoreCallingIdentity(ident);
1686 }
1687 }
1688
1689 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001690 public void cancelAll() throws RemoteException {
1691 final int uid = Binder.getCallingUid();
1692
1693 long ident = Binder.clearCallingIdentity();
1694 try {
Dianne Hackborne07641d2016-11-09 15:07:23 -08001695 JobSchedulerService.this.cancelJobsForUid(uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001696 } finally {
1697 Binder.restoreCallingIdentity(ident);
1698 }
1699 }
1700
1701 @Override
1702 public void cancel(int jobId) throws RemoteException {
1703 final int uid = Binder.getCallingUid();
1704
1705 long ident = Binder.clearCallingIdentity();
1706 try {
1707 JobSchedulerService.this.cancelJob(uid, jobId);
1708 } finally {
1709 Binder.restoreCallingIdentity(ident);
1710 }
1711 }
1712
1713 /**
1714 * "dumpsys" infrastructure
1715 */
1716 @Override
1717 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1718 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1719
1720 long identityToken = Binder.clearCallingIdentity();
1721 try {
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001722 JobSchedulerService.this.dumpInternal(pw, args);
Christopher Tate7060b042014-06-09 19:50:00 -07001723 } finally {
1724 Binder.restoreCallingIdentity(identityToken);
1725 }
1726 }
Christopher Tate5d346052016-03-08 12:56:08 -08001727
1728 @Override
1729 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
Dianne Hackborn354736e2016-08-22 17:00:05 -07001730 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
Christopher Tate5d346052016-03-08 12:56:08 -08001731 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
Dianne Hackborn354736e2016-08-22 17:00:05 -07001732 this, in, out, err, args, callback, resultReceiver);
Christopher Tate5d346052016-03-08 12:56:08 -08001733 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001734 };
1735
Christopher Tate5d346052016-03-08 12:56:08 -08001736 // Shell command infrastructure: run the given job immediately
1737 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1738 if (DEBUG) {
1739 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1740 + " " + jobId + " f=" + force);
1741 }
1742
1743 try {
1744 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1745 if (uid < 0) {
1746 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1747 }
1748
1749 synchronized (mLock) {
1750 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1751 if (js == null) {
1752 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1753 }
1754
1755 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1756 if (!js.isConstraintsSatisfied()) {
1757 js.overrideState = 0;
1758 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1759 }
1760
1761 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1762 }
1763 } catch (RemoteException e) {
1764 // can't happen
1765 }
1766 return 0;
1767 }
1768
Shreyas Basarge5db09082016-01-07 13:38:29 +00001769 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1770 StringBuilder s = new StringBuilder(initial + ": ");
1771 for (int i=0; i<map.length; i++) {
1772 s.append("(")
1773 .append(map[i] == null? -1: map[i].getJobId())
1774 .append(map[i] == null? -1: map[i].getUid())
1775 .append(")" );
1776 }
1777 return s.toString();
1778 }
1779
1780 private String printPendingQueue() {
1781 StringBuilder s = new StringBuilder("Pending queue: ");
1782 Iterator<JobStatus> it = mPendingJobs.iterator();
1783 while (it.hasNext()) {
1784 JobStatus js = it.next();
1785 s.append("(")
1786 .append(js.getJob().getId())
1787 .append(", ")
1788 .append(js.getUid())
1789 .append(") ");
1790 }
1791 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001792 }
Christopher Tate7060b042014-06-09 19:50:00 -07001793
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001794 static void dumpHelp(PrintWriter pw) {
1795 pw.println("Job Scheduler (jobscheduler) dump options:");
1796 pw.println(" [-h] [package] ...");
1797 pw.println(" -h: print this help");
1798 pw.println(" [package] is an optional package name to limit the output to.");
1799 }
1800
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001801 void dumpInternal(final PrintWriter pw, String[] args) {
1802 int filterUid = -1;
1803 if (!ArrayUtils.isEmpty(args)) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001804 int opti = 0;
1805 while (opti < args.length) {
1806 String arg = args[opti];
1807 if ("-h".equals(arg)) {
1808 dumpHelp(pw);
1809 return;
1810 } else if ("-a".equals(arg)) {
1811 // Ignore, we always dump all.
1812 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
1813 pw.println("Unknown option: " + arg);
1814 return;
1815 } else {
1816 break;
1817 }
1818 opti++;
1819 }
1820 if (opti < args.length) {
1821 String pkg = args[opti];
1822 try {
1823 filterUid = getContext().getPackageManager().getPackageUid(pkg,
1824 PackageManager.MATCH_UNINSTALLED_PACKAGES);
1825 } catch (NameNotFoundException ignored) {
1826 pw.println("Invalid package: " + pkg);
1827 return;
1828 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001829 }
1830 }
1831
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001832 final int filterUidFinal = UserHandle.getAppId(filterUid);
Christopher Tatef973a7b2014-08-29 12:54:08 -07001833 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001834 synchronized (mLock) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001835 mConstants.dump(pw);
1836 pw.println();
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001837 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001838 pw.print("Registered ");
1839 pw.print(mJobs.size());
1840 pw.println(" jobs:");
Christopher Tate7060b042014-06-09 19:50:00 -07001841 if (mJobs.size() > 0) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001842 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
1843 Collections.sort(jobs, new Comparator<JobStatus>() {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001844 @Override
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001845 public int compare(JobStatus o1, JobStatus o2) {
1846 int uid1 = o1.getUid();
1847 int uid2 = o2.getUid();
1848 int id1 = o1.getJobId();
1849 int id2 = o2.getJobId();
1850 if (uid1 != uid2) {
1851 return uid1 < uid2 ? -1 : 1;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001852 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001853 return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001854 }
1855 });
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001856 for (JobStatus job : jobs) {
1857 pw.print(" JOB #"); job.printUniqueId(pw); pw.print(": ");
1858 pw.println(job.toShortStringExceptUniqueId());
1859
1860 // Skip printing details if the caller requested a filter
1861 if (!job.shouldDump(filterUidFinal)) {
1862 continue;
1863 }
1864
1865 job.dump(pw, " ", true);
1866 pw.print(" Ready: ");
1867 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1868 pw.print(" (job=");
1869 pw.print(job.isReady());
1870 pw.print(" pending=");
1871 pw.print(mPendingJobs.contains(job));
1872 pw.print(" active=");
1873 pw.print(isCurrentlyActiveLocked(job));
1874 pw.print(" user=");
1875 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
1876 pw.println(")");
1877 }
Christopher Tate7060b042014-06-09 19:50:00 -07001878 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001879 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001880 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001881 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001882 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001883 mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal);
Christopher Tate7060b042014-06-09 19:50:00 -07001884 }
1885 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001886 pw.println("Uid priority overrides:");
1887 for (int i=0; i< mUidPriorityOverride.size(); i++) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001888 int uid = mUidPriorityOverride.keyAt(i);
1889 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
1890 pw.print(" "); pw.print(UserHandle.formatUid(uid));
1891 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
1892 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001893 }
1894 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001895 mJobPackageTracker.dump(pw, "", filterUidFinal);
Dianne Hackborn807de782016-04-07 17:54:41 -07001896 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001897 if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
1898 pw.println();
1899 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001900 pw.println("Pending queue:");
1901 for (int i=0; i<mPendingJobs.size(); i++) {
1902 JobStatus job = mPendingJobs.get(i);
1903 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1904 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001905 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001906 int priority = evaluateJobPriorityLocked(job);
1907 if (priority != JobInfo.PRIORITY_DEFAULT) {
1908 pw.print(" Evaluated priority: "); pw.println(priority);
1909 }
1910 pw.print(" Tag: "); pw.println(job.getTag());
1911 }
Christopher Tate7060b042014-06-09 19:50:00 -07001912 pw.println();
1913 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001914 for (int i=0; i<mActiveServices.size(); i++) {
1915 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001916 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001917 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001918 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001919 continue;
1920 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001921 pw.println(jsc.getRunningJob().toShortString());
1922 pw.print(" Running for: ");
1923 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1924 pw.print(", timeout at: ");
1925 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1926 pw.println();
1927 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001928 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1929 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001930 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001931 }
Christopher Tate7060b042014-06-09 19:50:00 -07001932 }
1933 }
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001934 if (filterUid == -1) {
1935 pw.println();
1936 pw.print("mReadyToRock="); pw.println(mReadyToRock);
1937 pw.print("mReportedActive="); pw.println(mReportedActive);
1938 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
1939 }
Christopher Tate7060b042014-06-09 19:50:00 -07001940 }
1941 pw.println();
1942 }
1943}