blob: a7480133972adf60fed7b53964866ad74414e4c5 [file] [log] [blame]
Christopher Tate7060b042014-06-09 19:50:00 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.job;
18
Christopher Tateb5c07882016-05-26 17:11:09 -070019import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
20import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
21
Shreyas Basarge5db09082016-01-07 13:38:29 +000022import java.io.FileDescriptor;
23import java.io.PrintWriter;
24import java.util.ArrayList;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070025import java.util.Arrays;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070026import java.util.Collections;
27import java.util.Comparator;
Shreyas Basarge5db09082016-01-07 13:38:29 +000028import java.util.Iterator;
29import java.util.List;
30
Christopher Tateee7805b2016-07-15 16:56:56 -070031import android.app.Activity;
Dianne Hackborn8ad2af72015-03-17 17:00:24 -070032import android.app.ActivityManager;
Christopher Tate5568f542014-06-18 13:53:31 -070033import android.app.AppGlobals;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -070034import android.app.IUidObserver;
Christopher Tate7060b042014-06-09 19:50:00 -070035import android.app.job.JobInfo;
Shreyas Basarge5db09082016-01-07 13:38:29 +000036import android.app.job.JobParameters;
Christopher Tate7060b042014-06-09 19:50:00 -070037import android.app.job.JobScheduler;
38import android.app.job.JobService;
Shreyas Basarge5db09082016-01-07 13:38:29 +000039import android.app.job.IJobScheduler;
Christopher Tate7060b042014-06-09 19:50:00 -070040import android.content.BroadcastReceiver;
41import android.content.ComponentName;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070042import android.content.ContentResolver;
Christopher Tate7060b042014-06-09 19:50:00 -070043import android.content.Context;
44import android.content.Intent;
45import android.content.IntentFilter;
Christopher Tate5568f542014-06-18 13:53:31 -070046import android.content.pm.IPackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070047import android.content.pm.PackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070048import android.content.pm.ServiceInfo;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060049import android.content.pm.PackageManager.NameNotFoundException;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070050import android.database.ContentObserver;
Christopher Tateb5c07882016-05-26 17:11:09 -070051import android.net.Uri;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070052import android.os.BatteryStats;
Christopher Tate7060b042014-06-09 19:50:00 -070053import android.os.Binder;
54import android.os.Handler;
55import android.os.Looper;
56import android.os.Message;
Shreyas Basargecbf5ae92016-03-08 16:13:06 +000057import android.os.Process;
Dianne Hackborn88e98df2015-03-23 13:29:14 -070058import android.os.PowerManager;
Christopher Tate7060b042014-06-09 19:50:00 -070059import android.os.RemoteException;
Christopher Tate5d346052016-03-08 12:56:08 -080060import android.os.ResultReceiver;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070061import android.os.ServiceManager;
Dianne Hackborn354736e2016-08-22 17:00:05 -070062import android.os.ShellCallback;
Christopher Tate7060b042014-06-09 19:50:00 -070063import android.os.SystemClock;
64import android.os.UserHandle;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070065import android.provider.Settings;
66import android.util.KeyValueListParser;
Christopher Tate7060b042014-06-09 19:50:00 -070067import android.util.Slog;
68import android.util.SparseArray;
Dianne Hackborn970510b2016-02-24 16:56:42 -080069import android.util.SparseIntArray;
70import android.util.TimeUtils;
Christopher Tate5d346052016-03-08 12:56:08 -080071
Dianne Hackbornfdb19562014-07-11 16:03:36 -070072import com.android.internal.app.IBatteryStats;
Joe Onorato4eb64fd2016-03-21 15:30:09 -070073import com.android.internal.app.procstats.ProcessStats;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070074import com.android.internal.util.ArrayUtils;
Dianne Hackborn627dfa12015-11-11 18:10:30 -080075import com.android.server.DeviceIdleController;
76import com.android.server.LocalServices;
Christopher Tate2f36fd62016-02-18 18:36:08 -080077import com.android.server.job.JobStore.JobStatusFunctor;
Amith Yamasanib0ff3222015-03-04 09:56:14 -080078import com.android.server.job.controllers.AppIdleController;
Christopher Tate7060b042014-06-09 19:50:00 -070079import com.android.server.job.controllers.BatteryController;
80import com.android.server.job.controllers.ConnectivityController;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080081import com.android.server.job.controllers.ContentObserverController;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070082import com.android.server.job.controllers.DeviceIdleJobsController;
Christopher Tate7060b042014-06-09 19:50:00 -070083import com.android.server.job.controllers.IdleController;
84import com.android.server.job.controllers.JobStatus;
85import com.android.server.job.controllers.StateController;
86import com.android.server.job.controllers.TimeController;
87
Jeff Sharkey822cbd12016-02-25 11:09:55 -070088import libcore.util.EmptyArray;
89
Christopher Tate7060b042014-06-09 19:50:00 -070090/**
91 * Responsible for taking jobs representing work to be performed by a client app, and determining
92 * based on the criteria specified when that job should be run against the client application's
93 * endpoint.
94 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
95 * about constraints, or the state of active jobs. It receives callbacks from the various
96 * controllers and completed jobs and operates accordingly.
97 *
98 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
99 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
100 * @hide
101 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800102public final class JobSchedulerService extends com.android.server.SystemService
Matthew Williams01ac45b2014-07-22 20:44:12 -0700103 implements StateChangedListener, JobCompletedListener {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800104 static final String TAG = "JobSchedulerService";
Matthew Williamsaa984312015-10-15 16:08:05 -0700105 public static final boolean DEBUG = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800106
Dianne Hackborn970510b2016-02-24 16:56:42 -0800107 /** The maximum number of concurrent jobs we run at one time. */
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700108 private static final int MAX_JOB_CONTEXTS_COUNT = 16;
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800109 /** Enforce a per-app limit on scheduled jobs? */
Christopher Tate0213ace02016-02-24 14:18:35 -0800110 private static final boolean ENFORCE_MAX_JOBS = true;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800111 /** The maximum number of jobs that we allow an unprivileged app to schedule */
112 private static final int MAX_JOBS_PER_APP = 100;
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700113
Christopher Tate2f36fd62016-02-18 18:36:08 -0800114
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800115 /** Global local for all job scheduler state. */
116 final Object mLock = new Object();
Christopher Tate7060b042014-06-09 19:50:00 -0700117 /** Master list of jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700118 final JobStore mJobs;
Dianne Hackborn807de782016-04-07 17:54:41 -0700119 /** Tracking amount of time each package runs for. */
120 final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
Christopher Tate7060b042014-06-09 19:50:00 -0700121
122 static final int MSG_JOB_EXPIRED = 0;
123 static final int MSG_CHECK_JOB = 1;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700124 static final int MSG_STOP_JOB = 2;
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000125 static final int MSG_CHECK_JOB_GREEDY = 3;
Christopher Tate7060b042014-06-09 19:50:00 -0700126
Christopher Tate7060b042014-06-09 19:50:00 -0700127 /**
128 * Track Services that have currently active or pending jobs. The index is provided by
129 * {@link JobStatus#getServiceToken()}
130 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700131 final List<JobServiceContext> mActiveServices = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700132 /** List of controllers that will notify this service of updates to jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700133 List<StateController> mControllers;
Christopher Tate7060b042014-06-09 19:50:00 -0700134 /**
135 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
136 * when ready to execute them.
137 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700138 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700139
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700140 int[] mStartedUsers = EmptyArray.INT;
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700141
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700142 final JobHandler mHandler;
143 final JobSchedulerStub mJobSchedulerStub;
144
145 IBatteryStats mBatteryStats;
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700146 PowerManager mPowerManager;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800147 DeviceIdleController.LocalService mLocalDeviceIdleController;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700148
149 /**
150 * Set to true once we are allowed to run third party apps.
151 */
152 boolean mReadyToRock;
153
Christopher Tate7060b042014-06-09 19:50:00 -0700154 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800155 * What we last reported to DeviceIdleController about whether we are active.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800156 */
157 boolean mReportedActive;
158
159 /**
Dianne Hackborn970510b2016-02-24 16:56:42 -0800160 * Current limit on the number of concurrent JobServiceContext entries we want to
161 * keep actively running a job.
162 */
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700163 int mMaxActiveJobs = 1;
Dianne Hackborn970510b2016-02-24 16:56:42 -0800164
165 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800166 * Which uids are currently in the foreground.
167 */
Dianne Hackborn970510b2016-02-24 16:56:42 -0800168 final SparseIntArray mUidPriorityOverride = new SparseIntArray();
169
170 // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
171
172 /**
173 * This array essentially stores the state of mActiveServices array.
174 * The ith index stores the job present on the ith JobServiceContext.
175 * We manipulate this array until we arrive at what jobs should be running on
176 * what JobServiceContext.
177 */
178 JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
179 /**
180 * Indicates whether we need to act on this jobContext id
181 */
182 boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
183 /**
184 * The uid whose jobs we would like to assign to a context.
185 */
186 int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800187
188 /**
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700189 * All times are in milliseconds. These constants are kept synchronized with the system
190 * global Settings. Any access to this class or its fields should be done while
191 * holding the JobSchedulerService.mLock lock.
192 */
193 private final class Constants extends ContentObserver {
194 // Key names stored in the settings value.
195 private static final String KEY_MIN_IDLE_COUNT = "min_idle_count";
196 private static final String KEY_MIN_CHARGING_COUNT = "min_charging_count";
197 private static final String KEY_MIN_CONNECTIVITY_COUNT = "min_connectivity_count";
198 private static final String KEY_MIN_CONTENT_COUNT = "min_content_count";
199 private static final String KEY_MIN_READY_JOBS_COUNT = "min_ready_jobs_count";
200 private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
201 private static final String KEY_MODERATE_USE_FACTOR = "moderate_use_factor";
202 private static final String KEY_FG_JOB_COUNT = "fg_job_count";
203 private static final String KEY_BG_NORMAL_JOB_COUNT = "bg_normal_job_count";
204 private static final String KEY_BG_MODERATE_JOB_COUNT = "bg_moderate_job_count";
205 private static final String KEY_BG_LOW_JOB_COUNT = "bg_low_job_count";
206 private static final String KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count";
207
208 private static final int DEFAULT_MIN_IDLE_COUNT = 1;
209 private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
210 private static final int DEFAULT_MIN_CONNECTIVITY_COUNT = 1;
211 private static final int DEFAULT_MIN_CONTENT_COUNT = 1;
212 private static final int DEFAULT_MIN_READY_JOBS_COUNT = 1;
213 private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
214 private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
215 private static final int DEFAULT_FG_JOB_COUNT = 4;
216 private static final int DEFAULT_BG_NORMAL_JOB_COUNT = 6;
217 private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4;
Nancy Zhenge39a8a42016-10-05 16:27:14 -0700218 private static final int DEFAULT_BG_LOW_JOB_COUNT = 1;
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700219 private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1;
220
221 /**
222 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
223 * early.
224 */
225 int MIN_IDLE_COUNT = DEFAULT_MIN_IDLE_COUNT;
226 /**
227 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule
228 * things early.
229 */
230 int MIN_CHARGING_COUNT = DEFAULT_MIN_CHARGING_COUNT;
231 /**
232 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
233 * things early. 1 == Run connectivity jobs as soon as ready.
234 */
235 int MIN_CONNECTIVITY_COUNT = DEFAULT_MIN_CONNECTIVITY_COUNT;
236 /**
237 * Minimum # of content trigger jobs that must be ready in order to force the JMS to
238 * schedule things early.
239 */
240 int MIN_CONTENT_COUNT = DEFAULT_MIN_CONTENT_COUNT;
241 /**
242 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy
243 * running some work early. This (and thus the other min counts) is now set to 1, to
244 * prevent any batching at this level. Since we now do batching through doze, that is
245 * a much better mechanism.
246 */
247 int MIN_READY_JOBS_COUNT = DEFAULT_MIN_READY_JOBS_COUNT;
248 /**
249 * This is the job execution factor that is considered to be heavy use of the system.
250 */
251 float HEAVY_USE_FACTOR = DEFAULT_HEAVY_USE_FACTOR;
252 /**
253 * This is the job execution factor that is considered to be moderate use of the system.
254 */
255 float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
256 /**
257 * The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app.
258 */
259 int FG_JOB_COUNT = DEFAULT_FG_JOB_COUNT;
260 /**
261 * The maximum number of background jobs we allow when the system is in a normal
262 * memory state.
263 */
264 int BG_NORMAL_JOB_COUNT = DEFAULT_BG_NORMAL_JOB_COUNT;
265 /**
266 * The maximum number of background jobs we allow when the system is in a moderate
267 * memory state.
268 */
269 int BG_MODERATE_JOB_COUNT = DEFAULT_BG_MODERATE_JOB_COUNT;
270 /**
271 * The maximum number of background jobs we allow when the system is in a low
272 * memory state.
273 */
274 int BG_LOW_JOB_COUNT = DEFAULT_BG_LOW_JOB_COUNT;
275 /**
276 * The maximum number of background jobs we allow when the system is in a critical
277 * memory state.
278 */
279 int BG_CRITICAL_JOB_COUNT = DEFAULT_BG_CRITICAL_JOB_COUNT;
280
281 private ContentResolver mResolver;
282 private final KeyValueListParser mParser = new KeyValueListParser(',');
283
284 public Constants(Handler handler) {
285 super(handler);
286 }
287
288 public void start(ContentResolver resolver) {
289 mResolver = resolver;
290 mResolver.registerContentObserver(Settings.Global.getUriFor(
291 Settings.Global.JOB_SCHEDULER_CONSTANTS), false, this);
292 updateConstants();
293 }
294
295 @Override
296 public void onChange(boolean selfChange, Uri uri) {
297 updateConstants();
298 }
299
300 private void updateConstants() {
301 synchronized (mLock) {
302 try {
303 mParser.setString(Settings.Global.getString(mResolver,
304 Settings.Global.ALARM_MANAGER_CONSTANTS));
305 } catch (IllegalArgumentException e) {
306 // Failed to parse the settings string, log this and move on
307 // with defaults.
308 Slog.e(TAG, "Bad device idle settings", e);
309 }
310
311 MIN_IDLE_COUNT = mParser.getInt(KEY_MIN_IDLE_COUNT,
312 DEFAULT_MIN_IDLE_COUNT);
313 MIN_CHARGING_COUNT = mParser.getInt(KEY_MIN_CHARGING_COUNT,
314 DEFAULT_MIN_CHARGING_COUNT);
315 MIN_CONNECTIVITY_COUNT = mParser.getInt(KEY_MIN_CONNECTIVITY_COUNT,
316 DEFAULT_MIN_CONNECTIVITY_COUNT);
317 MIN_CONTENT_COUNT = mParser.getInt(KEY_MIN_CONTENT_COUNT,
318 DEFAULT_MIN_CONTENT_COUNT);
319 MIN_READY_JOBS_COUNT = mParser.getInt(KEY_MIN_READY_JOBS_COUNT,
320 DEFAULT_MIN_READY_JOBS_COUNT);
321 HEAVY_USE_FACTOR = mParser.getFloat(KEY_HEAVY_USE_FACTOR,
322 DEFAULT_HEAVY_USE_FACTOR);
323 MODERATE_USE_FACTOR = mParser.getFloat(KEY_MODERATE_USE_FACTOR,
324 DEFAULT_MODERATE_USE_FACTOR);
325 FG_JOB_COUNT = mParser.getInt(KEY_FG_JOB_COUNT,
326 DEFAULT_FG_JOB_COUNT);
327 BG_NORMAL_JOB_COUNT = mParser.getInt(KEY_BG_NORMAL_JOB_COUNT,
328 DEFAULT_BG_NORMAL_JOB_COUNT);
329 if ((FG_JOB_COUNT+BG_NORMAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
330 BG_NORMAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
331 }
332 BG_MODERATE_JOB_COUNT = mParser.getInt(KEY_BG_MODERATE_JOB_COUNT,
333 DEFAULT_BG_MODERATE_JOB_COUNT);
334 if ((FG_JOB_COUNT+BG_MODERATE_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
335 BG_MODERATE_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
336 }
337 BG_LOW_JOB_COUNT = mParser.getInt(KEY_BG_LOW_JOB_COUNT,
338 DEFAULT_BG_LOW_JOB_COUNT);
339 if ((FG_JOB_COUNT+BG_LOW_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
340 BG_LOW_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
341 }
342 BG_CRITICAL_JOB_COUNT = mParser.getInt(KEY_BG_CRITICAL_JOB_COUNT,
343 DEFAULT_BG_CRITICAL_JOB_COUNT);
344 if ((FG_JOB_COUNT+BG_CRITICAL_JOB_COUNT) > MAX_JOB_CONTEXTS_COUNT) {
345 BG_CRITICAL_JOB_COUNT = MAX_JOB_CONTEXTS_COUNT - FG_JOB_COUNT;
346 }
347 }
348 }
349
350 void dump(PrintWriter pw) {
351 pw.println(" Settings:");
352
353 pw.print(" "); pw.print(KEY_MIN_IDLE_COUNT); pw.print("=");
354 pw.print(MIN_IDLE_COUNT); pw.println();
355
356 pw.print(" "); pw.print(KEY_MIN_CHARGING_COUNT); pw.print("=");
357 pw.print(MIN_CHARGING_COUNT); pw.println();
358
359 pw.print(" "); pw.print(KEY_MIN_CONNECTIVITY_COUNT); pw.print("=");
360 pw.print(MIN_CONNECTIVITY_COUNT); pw.println();
361
362 pw.print(" "); pw.print(KEY_MIN_CONTENT_COUNT); pw.print("=");
363 pw.print(MIN_CONTENT_COUNT); pw.println();
364
365 pw.print(" "); pw.print(KEY_MIN_READY_JOBS_COUNT); pw.print("=");
366 pw.print(MIN_READY_JOBS_COUNT); pw.println();
367
368 pw.print(" "); pw.print(KEY_HEAVY_USE_FACTOR); pw.print("=");
369 pw.print(HEAVY_USE_FACTOR); pw.println();
370
371 pw.print(" "); pw.print(KEY_MODERATE_USE_FACTOR); pw.print("=");
372 pw.print(MODERATE_USE_FACTOR); pw.println();
373
374 pw.print(" "); pw.print(KEY_FG_JOB_COUNT); pw.print("=");
375 pw.print(FG_JOB_COUNT); pw.println();
376
377 pw.print(" "); pw.print(KEY_BG_NORMAL_JOB_COUNT); pw.print("=");
378 pw.print(BG_NORMAL_JOB_COUNT); pw.println();
379
380 pw.print(" "); pw.print(KEY_BG_MODERATE_JOB_COUNT); pw.print("=");
381 pw.print(BG_MODERATE_JOB_COUNT); pw.println();
382
383 pw.print(" "); pw.print(KEY_BG_LOW_JOB_COUNT); pw.print("=");
384 pw.print(BG_LOW_JOB_COUNT); pw.println();
385
386 pw.print(" "); pw.print(KEY_BG_CRITICAL_JOB_COUNT); pw.print("=");
387 pw.print(BG_CRITICAL_JOB_COUNT); pw.println();
388 }
389 }
390
391 final Constants mConstants;
392
393 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700394 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
395 * still clean up. On reinstall the package will have a new uid.
396 */
397 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
398 @Override
399 public void onReceive(Context context, Intent intent) {
Christopher Tateee7805b2016-07-15 16:56:56 -0700400 final String action = intent.getAction();
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700401 if (DEBUG) {
Christopher Tateee7805b2016-07-15 16:56:56 -0700402 Slog.d(TAG, "Receieved: " + action);
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700403 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700404 if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
Christopher Tateb5c07882016-05-26 17:11:09 -0700405 // Purge the app's jobs if the whole package was just disabled. When this is
406 // the case the component name will be a bare package name.
407 final String pkgName = getPackageName(intent);
408 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
409 if (pkgName != null && pkgUid != -1) {
410 final String[] changedComponents = intent.getStringArrayExtra(
411 Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
412 if (changedComponents != null) {
413 for (String component : changedComponents) {
414 if (component.equals(pkgName)) {
415 if (DEBUG) {
416 Slog.d(TAG, "Package state change: " + pkgName);
417 }
418 try {
419 final int userId = UserHandle.getUserId(pkgUid);
420 IPackageManager pm = AppGlobals.getPackageManager();
421 final int state = pm.getApplicationEnabledSetting(pkgName, userId);
422 if (state == COMPONENT_ENABLED_STATE_DISABLED
423 || state == COMPONENT_ENABLED_STATE_DISABLED_USER) {
424 if (DEBUG) {
425 Slog.d(TAG, "Removing jobs for package " + pkgName
426 + " in user " + userId);
427 }
Dianne Hackborne07641d2016-11-09 15:07:23 -0800428 cancelJobsForUid(pkgUid);
Christopher Tateb5c07882016-05-26 17:11:09 -0700429 }
Christopher Tate652c5ad2016-10-05 14:45:46 -0700430 } catch (RemoteException|IllegalArgumentException e) {
431 /*
432 * IllegalArgumentException means that the package doesn't exist.
433 * This arises when PACKAGE_CHANGED broadcast delivery has lagged
434 * behind outright uninstall, so by the time we try to act it's gone.
435 * We don't need to act on this PACKAGE_CHANGED when this happens;
436 * we'll get a PACKAGE_REMOVED later and clean up then.
437 *
438 * RemoteException can't actually happen; the package manager is
439 * running in this same process.
440 */
441 }
Christopher Tateb5c07882016-05-26 17:11:09 -0700442 break;
443 }
444 }
445 }
446 } else {
447 Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid);
448 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700449 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700450 // If this is an outright uninstall rather than the first half of an
451 // app update sequence, cancel the jobs associated with the app.
452 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
453 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
454 if (DEBUG) {
455 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
456 }
Dianne Hackborne07641d2016-11-09 15:07:23 -0800457 cancelJobsForUid(uidRemoved);
Christopher Tate7060b042014-06-09 19:50:00 -0700458 }
Christopher Tateee7805b2016-07-15 16:56:56 -0700459 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
Christopher Tate7060b042014-06-09 19:50:00 -0700460 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
461 if (DEBUG) {
462 Slog.d(TAG, "Removing jobs for user: " + userId);
463 }
464 cancelJobsForUser(userId);
Christopher Tateee7805b2016-07-15 16:56:56 -0700465 } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
466 // Has this package scheduled any jobs, such that we will take action
467 // if it were to be force-stopped?
468 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
469 final String pkgName = intent.getData().getSchemeSpecificPart();
470 if (pkgUid != -1) {
471 List<JobStatus> jobsForUid;
472 synchronized (mLock) {
473 jobsForUid = mJobs.getJobsByUid(pkgUid);
474 }
475 for (int i = jobsForUid.size() - 1; i >= 0; i--) {
476 if (jobsForUid.get(i).getSourcePackageName().equals(pkgName)) {
477 if (DEBUG) {
478 Slog.d(TAG, "Restart query: package " + pkgName + " at uid "
479 + pkgUid + " has jobs");
480 }
481 setResultCode(Activity.RESULT_OK);
482 break;
483 }
484 }
485 }
486 } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
487 // possible force-stop
488 final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
489 final String pkgName = intent.getData().getSchemeSpecificPart();
490 if (pkgUid != -1) {
491 if (DEBUG) {
492 Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
493 }
494 cancelJobsForPackageAndUid(pkgName, pkgUid);
495 }
Christopher Tate7060b042014-06-09 19:50:00 -0700496 }
497 }
498 };
499
Christopher Tateb5c07882016-05-26 17:11:09 -0700500 private String getPackageName(Intent intent) {
501 Uri uri = intent.getData();
502 String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
503 return pkg;
504 }
505
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700506 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
507 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800508 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700509 }
510
Dianne Hackborne07641d2016-11-09 15:07:23 -0800511 @Override public void onUidGone(int uid, boolean disabled) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800512 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackborne07641d2016-11-09 15:07:23 -0800513 if (disabled) {
514 cancelJobsForUid(uid);
515 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700516 }
517
518 @Override public void onUidActive(int uid) throws RemoteException {
519 }
520
Dianne Hackborne07641d2016-11-09 15:07:23 -0800521 @Override public void onUidIdle(int uid, boolean disabled) throws RemoteException {
522 if (disabled) {
523 cancelJobsForUid(uid);
524 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700525 }
526 };
527
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800528 public Object getLock() {
529 return mLock;
530 }
531
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700532 public JobStore getJobStore() {
533 return mJobs;
534 }
535
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700536 @Override
537 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700538 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
539 // Let's kick any outstanding jobs for this user.
540 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
541 }
542
543 @Override
544 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700545 // Let's kick any outstanding jobs for this user.
546 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
547 }
548
549 @Override
550 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700551 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700552 }
553
Christopher Tate7060b042014-06-09 19:50:00 -0700554 /**
555 * Entry point from client to schedule the provided job.
556 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
557 * @param job JobInfo object containing execution parameters
558 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700559 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
560 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700561 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800562 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000563 }
564
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800565 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
566 String tag) {
567 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700568 try {
Dianne Hackbornc3af19a2017-01-20 17:00:44 -0800569 if (ActivityManager.getService().isAppStartModeDisabled(uId,
570 job.getService().getPackageName())) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700571 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
572 + " -- package not allowed to start");
573 return JobScheduler.RESULT_FAILURE;
574 }
575 } catch (RemoteException e) {
576 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800577 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
578 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800579 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800580 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800581 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800582 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
583 Slog.w(TAG, "Too many jobs for uid " + uId);
584 throw new IllegalStateException("Apps may not schedule more than "
585 + MAX_JOBS_PER_APP + " distinct jobs");
586 }
587 }
588
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800589 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700590 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700591 cancelJobImpl(toCancel, jobStatus);
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700592 }
593 startTrackingJob(jobStatus, toCancel);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800594 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700595 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700596 return JobScheduler.RESULT_SUCCESS;
597 }
598
599 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800600 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800601 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
602 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
603 for (int i = jobs.size() - 1; i >= 0; i--) {
604 JobStatus job = jobs.get(i);
605 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700606 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800607 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700608 }
Christopher Tate7060b042014-06-09 19:50:00 -0700609 }
610
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600611 public JobInfo getPendingJob(int uid, int jobId) {
612 synchronized (mLock) {
613 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
614 for (int i = jobs.size() - 1; i >= 0; i--) {
615 JobStatus job = jobs.get(i);
616 if (job.getJobId() == jobId) {
617 return job.getJob();
618 }
619 }
620 return null;
621 }
622 }
623
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700624 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700625 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800626 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700627 jobsForUser = mJobs.getJobsByUser(userHandle);
628 }
629 for (int i=0; i<jobsForUser.size(); i++) {
630 JobStatus toRemove = jobsForUser.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700631 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700632 }
633 }
634
Christopher Tateee7805b2016-07-15 16:56:56 -0700635 void cancelJobsForPackageAndUid(String pkgName, int uid) {
636 List<JobStatus> jobsForUid;
637 synchronized (mLock) {
638 jobsForUid = mJobs.getJobsByUid(uid);
639 }
640 for (int i = jobsForUid.size() - 1; i >= 0; i--) {
641 final JobStatus job = jobsForUid.get(i);
642 if (job.getSourcePackageName().equals(pkgName)) {
643 cancelJobImpl(job, null);
644 }
645 }
646 }
647
Christopher Tate7060b042014-06-09 19:50:00 -0700648 /**
649 * Entry point from client to cancel all jobs originating from their uid.
650 * This will remove the job from the master list, and cancel the job if it was staged for
651 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700652 * @param uid Uid to check against for removal of a job.
Dianne Hackborne07641d2016-11-09 15:07:23 -0800653 *
Christopher Tate7060b042014-06-09 19:50:00 -0700654 */
Dianne Hackborne07641d2016-11-09 15:07:23 -0800655 public void cancelJobsForUid(int uid) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700656 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800657 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700658 jobsForUid = mJobs.getJobsByUid(uid);
659 }
660 for (int i=0; i<jobsForUid.size(); i++) {
661 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700662 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700663 }
664 }
665
666 /**
667 * Entry point from client to cancel the job corresponding to the jobId provided.
668 * This will remove the job from the master list, and cancel the job if it was staged for
669 * execution or being executed.
670 * @param uid Uid of the calling client.
671 * @param jobId Id of the job, provided at schedule-time.
672 */
673 public void cancelJob(int uid, int jobId) {
674 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800675 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700676 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700677 }
678 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700679 cancelJobImpl(toCancel, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700680 }
681 }
682
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700683 private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800684 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700685 stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800686 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700687 // Remove from pending queue.
Dianne Hackborn807de782016-04-07 17:54:41 -0700688 if (mPendingJobs.remove(cancelled)) {
689 mJobPackageTracker.noteNonpending(cancelled);
690 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700691 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000692 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800693 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700694 }
Christopher Tate7060b042014-06-09 19:50:00 -0700695 }
696
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800697 void updateUidState(int uid, int procState) {
698 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800699 if (procState == ActivityManager.PROCESS_STATE_TOP) {
700 // Only use this if we are exactly the top app. All others can live
701 // with just the foreground priority. This means that persistent processes
702 // can never be the top app priority... that is fine.
703 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
704 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
705 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800706 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800707 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800708 }
709 }
710 }
711
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700712 @Override
713 public void onDeviceIdleStateChanged(boolean deviceIdle) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800714 synchronized (mLock) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700715 if (deviceIdle) {
Jeff Sharkey34618b52016-06-01 15:51:19 -0600716 // When becoming idle, make sure no jobs are actively running,
717 // except those using the idle exemption flag.
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700718 for (int i=0; i<mActiveServices.size(); i++) {
719 JobServiceContext jsc = mActiveServices.get(i);
720 final JobStatus executing = jsc.getRunningJob();
Jeff Sharkey34618b52016-06-01 15:51:19 -0600721 if (executing != null
722 && (executing.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700723 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
724 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700725 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700726 } else {
727 // When coming out of idle, allow thing to start back up.
728 if (mReadyToRock) {
729 if (mLocalDeviceIdleController != null) {
730 if (!mReportedActive) {
731 mReportedActive = true;
732 mLocalDeviceIdleController.setJobsActive(true);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700733 }
734 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700735 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700736 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700737 }
738 }
739 }
740
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800741 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000742 // active is true if pending queue contains jobs OR some job is running.
743 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800744 if (mPendingJobs.size() <= 0) {
745 for (int i=0; i<mActiveServices.size(); i++) {
Dianne Hackborn7ab40252016-06-15 17:30:24 -0700746 final JobServiceContext jsc = mActiveServices.get(i);
747 final JobStatus job = jsc.getRunningJob();
748 if (job != null
749 && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
750 && !job.dozeWhitelisted) {
751 // We will report active if we have a job running and it is not an exception
752 // due to being in the foreground or whitelisted.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800753 active = true;
754 break;
755 }
756 }
757 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000758
759 if (mReportedActive != active) {
760 mReportedActive = active;
761 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800762 mLocalDeviceIdleController.setJobsActive(active);
763 }
764 }
765 }
766
Christopher Tate7060b042014-06-09 19:50:00 -0700767 /**
768 * Initializes the system service.
769 * <p>
770 * Subclasses must define a single argument constructor that accepts the context
771 * and passes it to super.
772 * </p>
773 *
774 * @param context The system server context.
775 */
776 public JobSchedulerService(Context context) {
777 super(context);
Dianne Hackborn970e3f42016-06-01 10:55:13 -0700778 mHandler = new JobHandler(context.getMainLooper());
779 mConstants = new Constants(mHandler);
780 mJobSchedulerStub = new JobSchedulerStub();
781 mJobs = JobStore.initAndGet(this);
782
Christopher Tate7060b042014-06-09 19:50:00 -0700783 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700784 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700785 mControllers.add(ConnectivityController.get(this));
786 mControllers.add(TimeController.get(this));
787 mControllers.add(IdleController.get(this));
788 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800789 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800790 mControllers.add(ContentObserverController.get(this));
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700791 mControllers.add(DeviceIdleJobsController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700792 }
793
794 @Override
795 public void onStart() {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000796 publishLocalService(JobSchedulerInternal.class, new LocalService());
Christopher Tate7060b042014-06-09 19:50:00 -0700797 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
798 }
799
800 @Override
801 public void onBootPhase(int phase) {
802 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700803 mConstants.start(getContext().getContentResolver());
Shreyas Basarge5db09082016-01-07 13:38:29 +0000804 // Register br for package removals and user removals.
Christopher Tateb5c07882016-05-26 17:11:09 -0700805 final IntentFilter filter = new IntentFilter();
806 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
807 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
Christopher Tateee7805b2016-07-15 16:56:56 -0700808 filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
809 filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
Christopher Tate7060b042014-06-09 19:50:00 -0700810 filter.addDataScheme("package");
811 getContext().registerReceiverAsUser(
812 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
813 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
814 getContext().registerReceiverAsUser(
815 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000816 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700817 try {
Sudheer Shankadc589ac2016-11-10 15:30:17 -0800818 ActivityManager.getService().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800819 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
Dianne Hackborn5614bf52016-11-07 17:26:41 -0800820 | ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN,
821 null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700822 } catch (RemoteException e) {
823 // ignored; both services live in system_server
824 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700825 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800826 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700827 // Let's go!
828 mReadyToRock = true;
829 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
830 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800831 mLocalDeviceIdleController
832 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700833 // Create the "runners".
834 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
835 mActiveServices.add(
Dianne Hackborn807de782016-04-07 17:54:41 -0700836 new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700837 getContext().getMainLooper()));
838 }
839 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800840 mJobs.forEachJob(new JobStatusFunctor() {
841 @Override
842 public void process(JobStatus job) {
843 for (int controller = 0; controller < mControllers.size(); controller++) {
844 final StateController sc = mControllers.get(controller);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800845 sc.maybeStartTrackingJobLocked(job, null);
846 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700847 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800848 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700849 // GO GO GO!
850 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
851 }
Christopher Tate7060b042014-06-09 19:50:00 -0700852 }
853 }
854
855 /**
856 * Called when we have a job status object that we need to insert in our
857 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
858 * about.
859 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800860 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800861 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800862 final boolean update = mJobs.add(jobStatus);
863 if (mReadyToRock) {
864 for (int i = 0; i < mControllers.size(); i++) {
865 StateController controller = mControllers.get(i);
866 if (update) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700867 controller.maybeStopTrackingJobLocked(jobStatus, null, true);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800868 }
869 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700870 }
Christopher Tate7060b042014-06-09 19:50:00 -0700871 }
Christopher Tate7060b042014-06-09 19:50:00 -0700872 }
873 }
874
875 /**
876 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
877 * object removed.
878 */
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700879 private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
880 boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800881 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700882 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800883 final boolean removed = mJobs.remove(jobStatus, writeBack);
884 if (removed && mReadyToRock) {
885 for (int i=0; i<mControllers.size(); i++) {
886 StateController controller = mControllers.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700887 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800888 }
Christopher Tate7060b042014-06-09 19:50:00 -0700889 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800890 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700891 }
Christopher Tate7060b042014-06-09 19:50:00 -0700892 }
893
Shreyas Basarge5db09082016-01-07 13:38:29 +0000894 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700895 for (int i=0; i<mActiveServices.size(); i++) {
896 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700897 final JobStatus executing = jsc.getRunningJob();
898 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000899 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700900 return true;
901 }
902 }
903 return false;
904 }
905
906 /**
907 * @param job JobStatus we are querying against.
908 * @return Whether or not the job represented by the status object is currently being run or
909 * is pending.
910 */
911 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700912 for (int i=0; i<mActiveServices.size(); i++) {
913 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tateeafb5352016-10-04 16:34:48 -0700914 // The 'unsafe' direct-internal-reference running-job inspector is okay to
915 // use here because we are already holding the necessary lock *and* we
916 // immediately discard the returned object reference, if any; we return
917 // only a boolean state indicator to the caller.
918 final JobStatus running = serviceContext.getRunningJobUnsafeLocked();
Christopher Tate7060b042014-06-09 19:50:00 -0700919 if (running != null && running.matches(job.getUid(), job.getJobId())) {
920 return true;
921 }
922 }
923 return false;
924 }
925
Dianne Hackborn807de782016-04-07 17:54:41 -0700926 void noteJobsPending(List<JobStatus> jobs) {
927 for (int i = jobs.size() - 1; i >= 0; i--) {
928 JobStatus job = jobs.get(i);
929 mJobPackageTracker.notePending(job);
930 }
931 }
932
933 void noteJobsNonpending(List<JobStatus> jobs) {
934 for (int i = jobs.size() - 1; i >= 0; i--) {
935 JobStatus job = jobs.get(i);
936 mJobPackageTracker.noteNonpending(job);
937 }
938 }
939
Christopher Tate7060b042014-06-09 19:50:00 -0700940 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700941 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
942 * specify an override deadline on a failed job (the failed job will run even though it's not
943 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
944 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
945 *
Christopher Tate7060b042014-06-09 19:50:00 -0700946 * @param failureToReschedule Provided job status that we will reschedule.
947 * @return A newly instantiated JobStatus with the same constraints as the last job except
948 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700949 *
950 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700951 */
952 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
953 final long elapsedNowMillis = SystemClock.elapsedRealtime();
954 final JobInfo job = failureToReschedule.getJob();
955
956 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700957 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
958 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700959
960 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700961 case JobInfo.BACKOFF_POLICY_LINEAR:
962 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700963 break;
964 default:
965 if (DEBUG) {
966 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
967 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700968 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
969 delayMillis =
970 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700971 break;
972 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700973 delayMillis =
974 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800975 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700976 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800977 for (int ic=0; ic<mControllers.size(); ic++) {
978 StateController controller = mControllers.get(ic);
979 controller.rescheduleForFailure(newJob, failureToReschedule);
980 }
981 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700982 }
983
984 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700985 * Called after a periodic has executed so we can reschedule it. We take the last execution
986 * time of the job to be the time of completion (i.e. the time at which this function is
987 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700988 * This could be inaccurate b/c the job can run for as long as
989 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
990 * to underscheduling at least, rather than if we had taken the last execution time to be the
991 * start of the execution.
992 * @return A new job representing the execution criteria for this instantiation of the
993 * recurring job.
994 */
995 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
996 final long elapsedNow = SystemClock.elapsedRealtime();
997 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700998 long runEarly = 0L;
999
1000 // If this periodic was rescheduled it won't have a deadline.
1001 if (periodicToReschedule.hasDeadlineConstraint()) {
1002 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
1003 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001004 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -07001005 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +00001006 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
1007 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -07001008
1009 if (DEBUG) {
1010 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
1011 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
1012 }
1013 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
1014 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
1015 }
1016
1017 // JobCompletedListener implementations.
1018
1019 /**
1020 * A job just finished executing. We fetch the
1021 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
1022 * whether we want to reschedule we readd it to the controllers.
1023 * @param jobStatus Completed job.
1024 * @param needsReschedule Whether the implementing class should reschedule this job.
1025 */
1026 @Override
1027 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
1028 if (DEBUG) {
1029 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
1030 }
Shreyas Basarge73f10252016-02-11 17:06:13 +00001031 // Do not write back immediately if this is a periodic job. The job may get lost if system
1032 // shuts down before it is added back.
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001033 if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -07001034 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001035 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -07001036 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001037 // We still want to check for jobs to execute, because this job may have
1038 // scheduled a new job under the same job id, and now we can run it.
1039 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001040 return;
1041 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001042 // Note: there is a small window of time in here where, when rescheduling a job,
1043 // we will stop monitoring its content providers. This should be fixed by stopping
1044 // the old job after scheduling the new one, but since we have no lock held here
1045 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -07001046 if (needsReschedule) {
1047 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001048 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001049 } else if (jobStatus.getJob().isPeriodic()) {
1050 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001051 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -07001052 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001053 reportActive();
1054 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001055 }
1056
1057 // StateChangedListener implementations.
1058
1059 /**
Matthew Williams48a30db2014-09-23 13:39:36 -07001060 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
1061 * some controller's state has changed, so as to run through the list of jobs and start/stop
1062 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -07001063 */
1064 @Override
1065 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001066 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -07001067 }
1068
1069 @Override
1070 public void onRunJobNow(JobStatus jobStatus) {
1071 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
1072 }
1073
Christopher Tate7060b042014-06-09 19:50:00 -07001074 private class JobHandler extends Handler {
1075
1076 public JobHandler(Looper looper) {
1077 super(looper);
1078 }
1079
1080 @Override
1081 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001082 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001083 if (!mReadyToRock) {
1084 return;
1085 }
1086 }
Christopher Tate7060b042014-06-09 19:50:00 -07001087 switch (message.what) {
1088 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001089 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -07001090 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -07001091 // runNow can be null, which is a controller's way of indicating that its
1092 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -07001093 if (runNow != null && !mPendingJobs.contains(runNow)
1094 && mJobs.containsJob(runNow)) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001095 mJobPackageTracker.notePending(runNow);
Christopher Tate7060b042014-06-09 19:50:00 -07001096 mPendingJobs.add(runNow);
1097 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001098 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -07001099 }
Christopher Tate7060b042014-06-09 19:50:00 -07001100 break;
1101 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001102 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001103 if (mReportedActive) {
1104 // if jobs are currently being run, queue all ready jobs for execution.
1105 queueReadyJobsForExecutionLockedH();
1106 } else {
1107 // Check the list of jobs and run some of them if we feel inclined.
1108 maybeQueueReadyJobsForExecutionLockedH();
1109 }
1110 }
1111 break;
1112 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001113 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +00001114 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -07001115 }
Christopher Tate7060b042014-06-09 19:50:00 -07001116 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001117 case MSG_STOP_JOB:
Dianne Hackborn141f11c2016-04-05 15:46:12 -07001118 cancelJobImpl((JobStatus)message.obj, null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001119 break;
Christopher Tate7060b042014-06-09 19:50:00 -07001120 }
1121 maybeRunPendingJobsH();
1122 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
1123 removeMessages(MSG_CHECK_JOB);
1124 }
1125
1126 /**
1127 * Run through list of jobs and execute all possible - at least one is expired so we do
1128 * as many as we can.
1129 */
Matthew Williams48a30db2014-09-23 13:39:36 -07001130 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -07001131 if (DEBUG) {
1132 Slog.d(TAG, "queuing all ready jobs for execution:");
1133 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001134 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001135 mPendingJobs.clear();
1136 mJobs.forEachJob(mReadyQueueFunctor);
1137 mReadyQueueFunctor.postProcess();
1138
Matthew Williams48a30db2014-09-23 13:39:36 -07001139 if (DEBUG) {
1140 final int queuedJobs = mPendingJobs.size();
1141 if (queuedJobs == 0) {
1142 Slog.d(TAG, "No jobs pending.");
1143 } else {
1144 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -07001145 }
Christopher Tate7060b042014-06-09 19:50:00 -07001146 }
1147 }
1148
Christopher Tate2f36fd62016-02-18 18:36:08 -08001149 class ReadyJobQueueFunctor implements JobStatusFunctor {
1150 ArrayList<JobStatus> newReadyJobs;
1151
1152 @Override
1153 public void process(JobStatus job) {
1154 if (isReadyToBeExecutedLocked(job)) {
1155 if (DEBUG) {
1156 Slog.d(TAG, " queued " + job.toShortString());
1157 }
1158 if (newReadyJobs == null) {
1159 newReadyJobs = new ArrayList<JobStatus>();
1160 }
1161 newReadyJobs.add(job);
1162 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
1163 stopJobOnServiceContextLocked(job,
1164 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
1165 }
1166 }
1167
1168 public void postProcess() {
1169 if (newReadyJobs != null) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001170 noteJobsPending(newReadyJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001171 mPendingJobs.addAll(newReadyJobs);
1172 }
1173 newReadyJobs = null;
1174 }
1175 }
1176 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
1177
Christopher Tate7060b042014-06-09 19:50:00 -07001178 /**
1179 * The state of at least one job has changed. Here is where we could enforce various
1180 * policies on when we want to execute jobs.
1181 * Right now the policy is such:
1182 * If >1 of the ready jobs is idle mode we send all of them off
1183 * if more than 2 network connectivity jobs are ready we send them all off.
1184 * If more than 4 jobs total are ready we send them all off.
1185 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
1186 */
Christopher Tate2f36fd62016-02-18 18:36:08 -08001187 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
1188 int chargingCount;
1189 int idleCount;
1190 int backoffCount;
1191 int connectivityCount;
1192 int contentCount;
1193 List<JobStatus> runnableJobs;
1194
1195 public MaybeReadyJobQueueFunctor() {
1196 reset();
1197 }
1198
1199 // Functor method invoked for each job via JobStore.forEachJob()
1200 @Override
1201 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001202 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001203 try {
Dianne Hackbornc3af19a2017-01-20 17:00:44 -08001204 if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
1205 job.getJob().getService().getPackageName())) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001206 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
1207 + job.getJob().toString() + " -- package not allowed to start");
1208 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001209 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001210 }
1211 } catch (RemoteException e) {
1212 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001213 if (job.getNumFailures() > 0) {
1214 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -07001215 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001216 if (job.hasIdleConstraint()) {
1217 idleCount++;
1218 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001219 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()
1220 || job.hasNotRoamingConstraint()) {
Matthew Williams48a30db2014-09-23 13:39:36 -07001221 connectivityCount++;
1222 }
1223 if (job.hasChargingConstraint()) {
1224 chargingCount++;
1225 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001226 if (job.hasContentTriggerConstraint()) {
1227 contentCount++;
1228 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001229 if (runnableJobs == null) {
1230 runnableJobs = new ArrayList<>();
1231 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001232 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001233 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001234 stopJobOnServiceContextLocked(job,
1235 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -07001236 }
Matthew Williams48a30db2014-09-23 13:39:36 -07001237 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001238
1239 public void postProcess() {
1240 if (backoffCount > 0 ||
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001241 idleCount >= mConstants.MIN_IDLE_COUNT ||
1242 connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
1243 chargingCount >= mConstants.MIN_CHARGING_COUNT ||
1244 contentCount >= mConstants.MIN_CONTENT_COUNT ||
1245 (runnableJobs != null
1246 && runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001247 if (DEBUG) {
1248 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
1249 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001250 noteJobsPending(runnableJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001251 mPendingJobs.addAll(runnableJobs);
1252 } else {
1253 if (DEBUG) {
1254 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
1255 }
Christopher Tate7060b042014-06-09 19:50:00 -07001256 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001257
1258 // Be ready for next time
1259 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -07001260 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001261
1262 private void reset() {
1263 chargingCount = 0;
1264 idleCount = 0;
1265 backoffCount = 0;
1266 connectivityCount = 0;
1267 contentCount = 0;
1268 runnableJobs = null;
1269 }
1270 }
1271 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
1272
1273 private void maybeQueueReadyJobsForExecutionLockedH() {
1274 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
1275
Dianne Hackborn807de782016-04-07 17:54:41 -07001276 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001277 mPendingJobs.clear();
1278 mJobs.forEachJob(mMaybeQueueFunctor);
1279 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -07001280 }
1281
1282 /**
1283 * Criteria for moving a job into the pending queue:
1284 * - It's ready.
1285 * - It's not pending.
1286 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001287 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001288 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -07001289 */
1290 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001291 final boolean jobReady = job.isReady();
1292 final boolean jobPending = mPendingJobs.contains(job);
1293 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001294
1295 final int userId = job.getUserId();
1296 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
1297 final boolean componentPresent;
1298 try {
1299 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
1300 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
1301 userId) != null);
1302 } catch (RemoteException e) {
1303 throw e.rethrowAsRuntimeException();
1304 }
1305
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001306 if (DEBUG) {
1307 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
1308 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001309 + " active=" + jobActive + " userStarted=" + userStarted
1310 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001311 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001312 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -07001313 }
1314
1315 /**
1316 * Criteria for cancelling an active job:
1317 * - It's not ready
1318 * - It's running on a JSC.
1319 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001320 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001321 return !job.isReady() && isCurrentlyActiveLocked(job);
1322 }
1323
1324 /**
1325 * Reconcile jobs in the pending queue against available execution contexts.
1326 * A controller can force a job into the pending queue even if it's already running, but
1327 * here is where we decide whether to actually execute it.
1328 */
1329 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001330 synchronized (mLock) {
Matthew Williams75fc5252014-09-02 16:17:53 -07001331 if (DEBUG) {
1332 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1333 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001334 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001335 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001336 }
1337 }
1338 }
1339
Dianne Hackborn807de782016-04-07 17:54:41 -07001340 private int adjustJobPriority(int curPriority, JobStatus job) {
1341 if (curPriority < JobInfo.PRIORITY_TOP_APP) {
1342 float factor = mJobPackageTracker.getLoadFactor(job);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001343 if (factor >= mConstants.HEAVY_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001344 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001345 } else if (factor >= mConstants.MODERATE_USE_FACTOR) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001346 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
1347 }
1348 }
1349 return curPriority;
1350 }
1351
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001352 private int evaluateJobPriorityLocked(JobStatus job) {
1353 int priority = job.getPriority();
1354 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001355 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001356 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001357 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1358 if (override != 0) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001359 return adjustJobPriority(override, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001360 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001361 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001362 }
1363
Christopher Tate7060b042014-06-09 19:50:00 -07001364 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001365 * Takes jobs from pending queue and runs them on available contexts.
1366 * If no contexts are available, preempts lower priority jobs to
1367 * run higher priority ones.
1368 * Lock on mJobs before calling this function.
1369 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001370 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001371 if (DEBUG) {
1372 Slog.d(TAG, printPendingQueue());
1373 }
1374
Dianne Hackborn970510b2016-02-24 16:56:42 -08001375 int memLevel;
1376 try {
Sudheer Shankadc589ac2016-11-10 15:30:17 -08001377 memLevel = ActivityManager.getService().getMemoryTrimLevel();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001378 } catch (RemoteException e) {
1379 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1380 }
1381 switch (memLevel) {
1382 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001383 mMaxActiveJobs = mConstants.BG_MODERATE_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001384 break;
1385 case ProcessStats.ADJ_MEM_FACTOR_LOW:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001386 mMaxActiveJobs = mConstants.BG_LOW_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001387 break;
1388 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001389 mMaxActiveJobs = mConstants.BG_CRITICAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001390 break;
1391 default:
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001392 mMaxActiveJobs = mConstants.BG_NORMAL_JOB_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001393 break;
1394 }
1395
1396 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1397 boolean[] act = mTmpAssignAct;
1398 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1399 int numActive = 0;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001400 int numForeground = 0;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001401 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1402 final JobServiceContext js = mActiveServices.get(i);
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001403 final JobStatus status = js.getRunningJob();
1404 if ((contextIdToJobMap[i] = status) != null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001405 numActive++;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001406 if (status.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
1407 numForeground++;
1408 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001409 }
1410 act[i] = false;
1411 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001412 }
1413 if (DEBUG) {
1414 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1415 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001416 for (int i=0; i<mPendingJobs.size(); i++) {
1417 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001418
1419 // If job is already running, go to next job.
1420 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1421 if (jobRunningContext != -1) {
1422 continue;
1423 }
1424
Dianne Hackborn970510b2016-02-24 16:56:42 -08001425 final int priority = evaluateJobPriorityLocked(nextPending);
1426 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001427
Shreyas Basarge5db09082016-01-07 13:38:29 +00001428 // Find a context for nextPending. The context should be available OR
1429 // it should have lowest priority among all running jobs
1430 // (sharing the same Uid as nextPending)
1431 int minPriority = Integer.MAX_VALUE;
1432 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001433 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1434 JobStatus job = contextIdToJobMap[j];
1435 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001436 if (job == null) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001437 if ((numActive < mMaxActiveJobs ||
1438 (priority >= JobInfo.PRIORITY_TOP_APP &&
1439 numForeground < mConstants.FG_JOB_COUNT)) &&
Dianne Hackborn970510b2016-02-24 16:56:42 -08001440 (preferredUid == nextPending.getUid() ||
1441 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1442 // This slot is free, and we haven't yet hit the limit on
1443 // concurrent jobs... we can just throw the job in to here.
1444 minPriorityContextId = j;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001445 break;
1446 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001447 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001448 // the context has a preferred Uid or we have reached the limit on
1449 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001450 continue;
1451 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001452 if (job.getUid() != nextPending.getUid()) {
1453 continue;
1454 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001455 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001456 continue;
1457 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001458 if (minPriority > nextPending.lastEvaluatedPriority) {
1459 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001460 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001461 }
1462 }
1463 if (minPriorityContextId != -1) {
1464 contextIdToJobMap[minPriorityContextId] = nextPending;
1465 act[minPriorityContextId] = true;
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001466 numActive++;
1467 if (priority >= JobInfo.PRIORITY_TOP_APP) {
1468 numForeground++;
1469 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001470 }
1471 }
1472 if (DEBUG) {
1473 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1474 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001475 mJobPackageTracker.noteConcurrency(numActive, numForeground);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001476 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001477 boolean preservePreferredUid = false;
1478 if (act[i]) {
1479 JobStatus js = mActiveServices.get(i).getRunningJob();
1480 if (js != null) {
1481 if (DEBUG) {
1482 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1483 }
1484 // preferredUid will be set to uid of currently running job.
1485 mActiveServices.get(i).preemptExecutingJob();
1486 preservePreferredUid = true;
1487 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001488 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001489 if (DEBUG) {
1490 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001491 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001492 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001493 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001494 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001495 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001496 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1497 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001498 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001499 if (mPendingJobs.remove(pendingJob)) {
1500 mJobPackageTracker.noteNonpending(pendingJob);
1501 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001502 }
1503 }
1504 if (!preservePreferredUid) {
1505 mActiveServices.get(i).clearPreferredUid();
1506 }
1507 }
1508 }
1509
1510 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1511 for (int i=0; i<map.length; i++) {
1512 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1513 return i;
1514 }
1515 }
1516 return -1;
1517 }
1518
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001519 final class LocalService implements JobSchedulerInternal {
1520
1521 /**
1522 * Returns a list of all pending jobs. A running job is not considered pending. Periodic
1523 * jobs are always considered pending.
1524 */
Amith Yamasanicb926fc2016-03-14 17:15:20 -07001525 @Override
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001526 public List<JobInfo> getSystemScheduledPendingJobs() {
1527 synchronized (mLock) {
1528 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
1529 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
1530 @Override
1531 public void process(JobStatus job) {
1532 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
1533 pendingJobs.add(job.getJob());
1534 }
1535 }
1536 });
1537 return pendingJobs;
1538 }
1539 }
1540 }
1541
Shreyas Basarge5db09082016-01-07 13:38:29 +00001542 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001543 * Binder stub trampoline implementation
1544 */
1545 final class JobSchedulerStub extends IJobScheduler.Stub {
1546 /** Cache determination of whether a given app can persist jobs
1547 * key is uid of the calling app; value is undetermined/true/false
1548 */
1549 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1550
1551 // Enforce that only the app itself (or shared uid participant) can schedule a
1552 // job that runs one of the app's services, as well as verifying that the
1553 // named service properly requires the BIND_JOB_SERVICE permission
1554 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001555 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001556 final ComponentName service = job.getService();
1557 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001558 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey8a372a02016-03-16 16:25:45 -06001559 PackageManager.MATCH_DIRECT_BOOT_AWARE
1560 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001561 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001562 if (si == null) {
1563 throw new IllegalArgumentException("No such service " + service);
1564 }
Christopher Tate7060b042014-06-09 19:50:00 -07001565 if (si.applicationInfo.uid != uid) {
1566 throw new IllegalArgumentException("uid " + uid +
1567 " cannot schedule job in " + service.getPackageName());
1568 }
1569 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1570 throw new IllegalArgumentException("Scheduled service " + service
1571 + " does not require android.permission.BIND_JOB_SERVICE permission");
1572 }
Christopher Tate5568f542014-06-18 13:53:31 -07001573 } catch (RemoteException e) {
1574 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001575 }
1576 }
1577
1578 private boolean canPersistJobs(int pid, int uid) {
1579 // If we get this far we're good to go; all we need to do now is check
1580 // whether the app is allowed to persist its scheduled work.
1581 final boolean canPersist;
1582 synchronized (mPersistCache) {
1583 Boolean cached = mPersistCache.get(uid);
1584 if (cached != null) {
1585 canPersist = cached.booleanValue();
1586 } else {
1587 // Persisting jobs is tantamount to running at boot, so we permit
1588 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1589 // permission
1590 int result = getContext().checkPermission(
1591 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1592 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1593 mPersistCache.put(uid, canPersist);
1594 }
1595 }
1596 return canPersist;
1597 }
1598
1599 // IJobScheduler implementation
1600 @Override
1601 public int schedule(JobInfo job) throws RemoteException {
1602 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001603 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001604 }
1605 final int pid = Binder.getCallingPid();
1606 final int uid = Binder.getCallingUid();
1607
1608 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001609 if (job.isPersisted()) {
1610 if (!canPersistJobs(pid, uid)) {
1611 throw new IllegalArgumentException("Error: requested job be persisted without"
1612 + " holding RECEIVE_BOOT_COMPLETED permission.");
1613 }
1614 }
Christopher Tate7060b042014-06-09 19:50:00 -07001615
Jeff Sharkey785f4942016-07-14 10:31:15 -06001616 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1617 getContext().enforceCallingOrSelfPermission(
1618 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1619 }
1620
Christopher Tate7060b042014-06-09 19:50:00 -07001621 long ident = Binder.clearCallingIdentity();
1622 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001623 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001624 } finally {
1625 Binder.restoreCallingIdentity(ident);
1626 }
1627 }
1628
1629 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001630 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001631 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001632 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001633 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001634 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1635 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001636 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001637
1638 if (packageName == null) {
1639 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001640 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001641
1642 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1643 android.Manifest.permission.UPDATE_DEVICE_STATS);
1644 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1645 throw new SecurityException("Caller uid " + callerUid
1646 + " not permitted to schedule jobs for other apps");
1647 }
1648
Jeff Sharkey4f100402016-05-03 17:44:23 -06001649 if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
1650 getContext().enforceCallingOrSelfPermission(
1651 android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
1652 }
1653
Shreyas Basarge968ac752016-01-11 23:09:26 +00001654 long ident = Binder.clearCallingIdentity();
1655 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001656 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001657 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001658 } finally {
1659 Binder.restoreCallingIdentity(ident);
1660 }
1661 }
1662
1663 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001664 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1665 final int uid = Binder.getCallingUid();
1666
1667 long ident = Binder.clearCallingIdentity();
1668 try {
1669 return JobSchedulerService.this.getPendingJobs(uid);
1670 } finally {
1671 Binder.restoreCallingIdentity(ident);
1672 }
1673 }
1674
1675 @Override
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001676 public JobInfo getPendingJob(int jobId) throws RemoteException {
1677 final int uid = Binder.getCallingUid();
1678
1679 long ident = Binder.clearCallingIdentity();
1680 try {
1681 return JobSchedulerService.this.getPendingJob(uid, jobId);
1682 } finally {
1683 Binder.restoreCallingIdentity(ident);
1684 }
1685 }
1686
1687 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001688 public void cancelAll() throws RemoteException {
1689 final int uid = Binder.getCallingUid();
1690
1691 long ident = Binder.clearCallingIdentity();
1692 try {
Dianne Hackborne07641d2016-11-09 15:07:23 -08001693 JobSchedulerService.this.cancelJobsForUid(uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001694 } finally {
1695 Binder.restoreCallingIdentity(ident);
1696 }
1697 }
1698
1699 @Override
1700 public void cancel(int jobId) throws RemoteException {
1701 final int uid = Binder.getCallingUid();
1702
1703 long ident = Binder.clearCallingIdentity();
1704 try {
1705 JobSchedulerService.this.cancelJob(uid, jobId);
1706 } finally {
1707 Binder.restoreCallingIdentity(ident);
1708 }
1709 }
1710
1711 /**
1712 * "dumpsys" infrastructure
1713 */
1714 @Override
1715 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1716 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1717
1718 long identityToken = Binder.clearCallingIdentity();
1719 try {
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001720 JobSchedulerService.this.dumpInternal(pw, args);
Christopher Tate7060b042014-06-09 19:50:00 -07001721 } finally {
1722 Binder.restoreCallingIdentity(identityToken);
1723 }
1724 }
Christopher Tate5d346052016-03-08 12:56:08 -08001725
1726 @Override
1727 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
Dianne Hackborn354736e2016-08-22 17:00:05 -07001728 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
Christopher Tate5d346052016-03-08 12:56:08 -08001729 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
Dianne Hackborn354736e2016-08-22 17:00:05 -07001730 this, in, out, err, args, callback, resultReceiver);
Christopher Tate5d346052016-03-08 12:56:08 -08001731 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001732 };
1733
Christopher Tate5d346052016-03-08 12:56:08 -08001734 // Shell command infrastructure: run the given job immediately
1735 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1736 if (DEBUG) {
1737 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1738 + " " + jobId + " f=" + force);
1739 }
1740
1741 try {
1742 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1743 if (uid < 0) {
1744 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1745 }
1746
1747 synchronized (mLock) {
1748 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1749 if (js == null) {
1750 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1751 }
1752
1753 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1754 if (!js.isConstraintsSatisfied()) {
1755 js.overrideState = 0;
1756 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1757 }
1758
1759 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1760 }
1761 } catch (RemoteException e) {
1762 // can't happen
1763 }
1764 return 0;
1765 }
1766
Shreyas Basarge5db09082016-01-07 13:38:29 +00001767 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1768 StringBuilder s = new StringBuilder(initial + ": ");
1769 for (int i=0; i<map.length; i++) {
1770 s.append("(")
1771 .append(map[i] == null? -1: map[i].getJobId())
1772 .append(map[i] == null? -1: map[i].getUid())
1773 .append(")" );
1774 }
1775 return s.toString();
1776 }
1777
1778 private String printPendingQueue() {
1779 StringBuilder s = new StringBuilder("Pending queue: ");
1780 Iterator<JobStatus> it = mPendingJobs.iterator();
1781 while (it.hasNext()) {
1782 JobStatus js = it.next();
1783 s.append("(")
1784 .append(js.getJob().getId())
1785 .append(", ")
1786 .append(js.getUid())
1787 .append(") ");
1788 }
1789 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001790 }
Christopher Tate7060b042014-06-09 19:50:00 -07001791
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001792 static void dumpHelp(PrintWriter pw) {
1793 pw.println("Job Scheduler (jobscheduler) dump options:");
1794 pw.println(" [-h] [package] ...");
1795 pw.println(" -h: print this help");
1796 pw.println(" [package] is an optional package name to limit the output to.");
1797 }
1798
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001799 void dumpInternal(final PrintWriter pw, String[] args) {
1800 int filterUid = -1;
1801 if (!ArrayUtils.isEmpty(args)) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001802 int opti = 0;
1803 while (opti < args.length) {
1804 String arg = args[opti];
1805 if ("-h".equals(arg)) {
1806 dumpHelp(pw);
1807 return;
1808 } else if ("-a".equals(arg)) {
1809 // Ignore, we always dump all.
1810 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
1811 pw.println("Unknown option: " + arg);
1812 return;
1813 } else {
1814 break;
1815 }
1816 opti++;
1817 }
1818 if (opti < args.length) {
1819 String pkg = args[opti];
1820 try {
1821 filterUid = getContext().getPackageManager().getPackageUid(pkg,
Amith Yamasani0d1fd8d2016-10-12 14:21:51 -07001822 PackageManager.MATCH_ANY_USER);
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001823 } catch (NameNotFoundException ignored) {
1824 pw.println("Invalid package: " + pkg);
1825 return;
1826 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001827 }
1828 }
1829
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001830 final int filterUidFinal = UserHandle.getAppId(filterUid);
Christopher Tatef973a7b2014-08-29 12:54:08 -07001831 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001832 synchronized (mLock) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001833 mConstants.dump(pw);
1834 pw.println();
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001835 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001836 pw.print("Registered ");
1837 pw.print(mJobs.size());
1838 pw.println(" jobs:");
Christopher Tate7060b042014-06-09 19:50:00 -07001839 if (mJobs.size() > 0) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001840 final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
1841 Collections.sort(jobs, new Comparator<JobStatus>() {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001842 @Override
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001843 public int compare(JobStatus o1, JobStatus o2) {
1844 int uid1 = o1.getUid();
1845 int uid2 = o2.getUid();
1846 int id1 = o1.getJobId();
1847 int id2 = o2.getJobId();
1848 if (uid1 != uid2) {
1849 return uid1 < uid2 ? -1 : 1;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001850 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001851 return id1 < id2 ? -1 : (id1 > id2 ? 1 : 0);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001852 }
1853 });
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001854 for (JobStatus job : jobs) {
1855 pw.print(" JOB #"); job.printUniqueId(pw); pw.print(": ");
1856 pw.println(job.toShortStringExceptUniqueId());
1857
1858 // Skip printing details if the caller requested a filter
1859 if (!job.shouldDump(filterUidFinal)) {
1860 continue;
1861 }
1862
1863 job.dump(pw, " ", true);
1864 pw.print(" Ready: ");
1865 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1866 pw.print(" (job=");
1867 pw.print(job.isReady());
1868 pw.print(" pending=");
1869 pw.print(mPendingJobs.contains(job));
1870 pw.print(" active=");
1871 pw.print(isCurrentlyActiveLocked(job));
1872 pw.print(" user=");
1873 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
1874 pw.println(")");
1875 }
Christopher Tate7060b042014-06-09 19:50:00 -07001876 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001877 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001878 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001879 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001880 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001881 mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal);
Christopher Tate7060b042014-06-09 19:50:00 -07001882 }
1883 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001884 pw.println("Uid priority overrides:");
1885 for (int i=0; i< mUidPriorityOverride.size(); i++) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001886 int uid = mUidPriorityOverride.keyAt(i);
1887 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
1888 pw.print(" "); pw.print(UserHandle.formatUid(uid));
1889 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
1890 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001891 }
1892 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001893 mJobPackageTracker.dump(pw, "", filterUidFinal);
Dianne Hackborn807de782016-04-07 17:54:41 -07001894 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001895 if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
1896 pw.println();
1897 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001898 pw.println("Pending queue:");
1899 for (int i=0; i<mPendingJobs.size(); i++) {
1900 JobStatus job = mPendingJobs.get(i);
1901 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1902 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001903 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001904 int priority = evaluateJobPriorityLocked(job);
1905 if (priority != JobInfo.PRIORITY_DEFAULT) {
1906 pw.print(" Evaluated priority: "); pw.println(priority);
1907 }
1908 pw.print(" Tag: "); pw.println(job.getTag());
1909 }
Christopher Tate7060b042014-06-09 19:50:00 -07001910 pw.println();
1911 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001912 for (int i=0; i<mActiveServices.size(); i++) {
1913 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001914 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001915 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001916 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001917 continue;
1918 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001919 pw.println(jsc.getRunningJob().toShortString());
1920 pw.print(" Running for: ");
1921 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1922 pw.print(", timeout at: ");
1923 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1924 pw.println();
1925 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001926 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1927 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001928 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001929 }
Christopher Tate7060b042014-06-09 19:50:00 -07001930 }
1931 }
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001932 if (filterUid == -1) {
1933 pw.println();
1934 pw.print("mReadyToRock="); pw.println(mReadyToRock);
1935 pw.print("mReportedActive="); pw.println(mReportedActive);
1936 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
1937 }
Christopher Tate7060b042014-06-09 19:50:00 -07001938 }
1939 pw.println();
1940 }
1941}