blob: 7e26d4b5c4ca53573cc3c8f3b0dc420dcc23320d [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
Shreyas Basarge5db09082016-01-07 13:38:29 +000019import java.io.FileDescriptor;
20import java.io.PrintWriter;
21import java.util.ArrayList;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070022import java.util.Arrays;
Shreyas Basarge5db09082016-01-07 13:38:29 +000023import java.util.Iterator;
24import java.util.List;
25
Dianne Hackborn8ad2af72015-03-17 17:00:24 -070026import android.app.ActivityManager;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -070027import android.app.ActivityManagerNative;
Christopher Tate5568f542014-06-18 13:53:31 -070028import android.app.AppGlobals;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -070029import android.app.IUidObserver;
Christopher Tate7060b042014-06-09 19:50:00 -070030import android.app.job.JobInfo;
Shreyas Basarge5db09082016-01-07 13:38:29 +000031import android.app.job.JobParameters;
Christopher Tate7060b042014-06-09 19:50:00 -070032import android.app.job.JobScheduler;
33import android.app.job.JobService;
Shreyas Basarge5db09082016-01-07 13:38:29 +000034import android.app.job.IJobScheduler;
Christopher Tate7060b042014-06-09 19:50:00 -070035import android.content.BroadcastReceiver;
36import android.content.ComponentName;
37import android.content.Context;
38import android.content.Intent;
39import android.content.IntentFilter;
Christopher Tate5568f542014-06-18 13:53:31 -070040import android.content.pm.IPackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070041import android.content.pm.PackageManager;
Christopher Tate7060b042014-06-09 19:50:00 -070042import android.content.pm.ServiceInfo;
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -060043import android.content.pm.PackageManager.NameNotFoundException;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070044import android.os.BatteryStats;
Christopher Tate7060b042014-06-09 19:50:00 -070045import android.os.Binder;
46import android.os.Handler;
47import android.os.Looper;
48import android.os.Message;
Shreyas Basargecbf5ae92016-03-08 16:13:06 +000049import android.os.Process;
Dianne Hackborn88e98df2015-03-23 13:29:14 -070050import android.os.PowerManager;
Christopher Tate7060b042014-06-09 19:50:00 -070051import android.os.RemoteException;
Christopher Tate5d346052016-03-08 12:56:08 -080052import android.os.ResultReceiver;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070053import android.os.ServiceManager;
Christopher Tate7060b042014-06-09 19:50:00 -070054import android.os.SystemClock;
55import android.os.UserHandle;
56import android.util.Slog;
57import android.util.SparseArray;
Dianne Hackborn970510b2016-02-24 16:56:42 -080058import android.util.SparseIntArray;
59import android.util.TimeUtils;
Christopher Tate5d346052016-03-08 12:56:08 -080060
Dianne Hackbornfdb19562014-07-11 16:03:36 -070061import com.android.internal.app.IBatteryStats;
Joe Onorato4eb64fd2016-03-21 15:30:09 -070062import com.android.internal.app.procstats.ProcessStats;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070063import com.android.internal.util.ArrayUtils;
Dianne Hackborn627dfa12015-11-11 18:10:30 -080064import com.android.server.DeviceIdleController;
65import com.android.server.LocalServices;
Christopher Tate2f36fd62016-02-18 18:36:08 -080066import com.android.server.job.JobStore.JobStatusFunctor;
Amith Yamasanib0ff3222015-03-04 09:56:14 -080067import com.android.server.job.controllers.AppIdleController;
Christopher Tate7060b042014-06-09 19:50:00 -070068import com.android.server.job.controllers.BatteryController;
69import com.android.server.job.controllers.ConnectivityController;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080070import com.android.server.job.controllers.ContentObserverController;
Amith Yamasanicb926fc2016-03-14 17:15:20 -070071import com.android.server.job.controllers.DeviceIdleJobsController;
Christopher Tate7060b042014-06-09 19:50:00 -070072import com.android.server.job.controllers.IdleController;
73import com.android.server.job.controllers.JobStatus;
74import com.android.server.job.controllers.StateController;
75import com.android.server.job.controllers.TimeController;
76
Jeff Sharkey822cbd12016-02-25 11:09:55 -070077import libcore.util.EmptyArray;
78
Christopher Tate7060b042014-06-09 19:50:00 -070079/**
80 * Responsible for taking jobs representing work to be performed by a client app, and determining
81 * based on the criteria specified when that job should be run against the client application's
82 * endpoint.
83 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
84 * about constraints, or the state of active jobs. It receives callbacks from the various
85 * controllers and completed jobs and operates accordingly.
86 *
87 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
88 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
89 * @hide
90 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -080091public final class JobSchedulerService extends com.android.server.SystemService
Matthew Williams01ac45b2014-07-22 20:44:12 -070092 implements StateChangedListener, JobCompletedListener {
Christopher Tate2f36fd62016-02-18 18:36:08 -080093 static final String TAG = "JobSchedulerService";
Matthew Williamsaa984312015-10-15 16:08:05 -070094 public static final boolean DEBUG = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -080095
Dianne Hackborn970510b2016-02-24 16:56:42 -080096 /** The maximum number of concurrent jobs we run at one time. */
Dianne Hackborn807de782016-04-07 17:54:41 -070097 private static final int MAX_JOB_CONTEXTS_COUNT = 12;
98 /** The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. */
99 private static final int FG_JOB_CONTEXTS_COUNT = 4;
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800100 /** Enforce a per-app limit on scheduled jobs? */
Christopher Tate0213ace02016-02-24 14:18:35 -0800101 private static final boolean ENFORCE_MAX_JOBS = true;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800102 /** The maximum number of jobs that we allow an unprivileged app to schedule */
103 private static final int MAX_JOBS_PER_APP = 100;
Dianne Hackborn807de782016-04-07 17:54:41 -0700104 /** This is the job execution factor that is considered to be heavy use of the system. */
105 private static final float HEAVY_USE_FACTOR = .9f;
106 /** This is the job execution factor that is considered to be moderate use of the system. */
107 private static final float MODERATE_USE_FACTOR = .5f;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800108
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800109 /** Global local for all job scheduler state. */
110 final Object mLock = new Object();
Christopher Tate7060b042014-06-09 19:50:00 -0700111 /** Master list of jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700112 final JobStore mJobs;
Dianne Hackborn807de782016-04-07 17:54:41 -0700113 /** Tracking amount of time each package runs for. */
114 final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
Christopher Tate7060b042014-06-09 19:50:00 -0700115
116 static final int MSG_JOB_EXPIRED = 0;
117 static final int MSG_CHECK_JOB = 1;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700118 static final int MSG_STOP_JOB = 2;
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000119 static final int MSG_CHECK_JOB_GREEDY = 3;
Christopher Tate7060b042014-06-09 19:50:00 -0700120
121 // Policy constants
122 /**
123 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
124 * early.
125 */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700126 static final int MIN_IDLE_COUNT = 1;
Christopher Tate7060b042014-06-09 19:50:00 -0700127 /**
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700128 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things
129 * early.
130 */
131 static final int MIN_CHARGING_COUNT = 1;
132 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700133 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
134 * things early.
135 */
Matthew Williamsaa984312015-10-15 16:08:05 -0700136 static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready.
Christopher Tate7060b042014-06-09 19:50:00 -0700137 /**
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800138 * Minimum # of content trigger jobs that must be ready in order to force the JMS to schedule
139 * things early.
140 */
141 static final int MIN_CONTENT_COUNT = 1;
142 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700143 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
144 * some work early.
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700145 * This is correlated with the amount of batching we'll be able to do.
Christopher Tate7060b042014-06-09 19:50:00 -0700146 */
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700147 static final int MIN_READY_JOBS_COUNT = 2;
Christopher Tate7060b042014-06-09 19:50:00 -0700148
149 /**
150 * Track Services that have currently active or pending jobs. The index is provided by
151 * {@link JobStatus#getServiceToken()}
152 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700153 final List<JobServiceContext> mActiveServices = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700154 /** List of controllers that will notify this service of updates to jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700155 List<StateController> mControllers;
Christopher Tate7060b042014-06-09 19:50:00 -0700156 /**
157 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
158 * when ready to execute them.
159 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700160 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700161
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700162 int[] mStartedUsers = EmptyArray.INT;
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700163
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700164 final JobHandler mHandler;
165 final JobSchedulerStub mJobSchedulerStub;
166
167 IBatteryStats mBatteryStats;
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700168 PowerManager mPowerManager;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800169 DeviceIdleController.LocalService mLocalDeviceIdleController;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700170
171 /**
172 * Set to true once we are allowed to run third party apps.
173 */
174 boolean mReadyToRock;
175
Christopher Tate7060b042014-06-09 19:50:00 -0700176 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800177 * What we last reported to DeviceIdleController about whether we are active.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800178 */
179 boolean mReportedActive;
180
181 /**
Dianne Hackborn970510b2016-02-24 16:56:42 -0800182 * Current limit on the number of concurrent JobServiceContext entries we want to
183 * keep actively running a job.
184 */
Dianne Hackborn807de782016-04-07 17:54:41 -0700185 int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -0800186
187 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800188 * Which uids are currently in the foreground.
189 */
Dianne Hackborn970510b2016-02-24 16:56:42 -0800190 final SparseIntArray mUidPriorityOverride = new SparseIntArray();
191
192 // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
193
194 /**
195 * This array essentially stores the state of mActiveServices array.
196 * The ith index stores the job present on the ith JobServiceContext.
197 * We manipulate this array until we arrive at what jobs should be running on
198 * what JobServiceContext.
199 */
200 JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
201 /**
202 * Indicates whether we need to act on this jobContext id
203 */
204 boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
205 /**
206 * The uid whose jobs we would like to assign to a context.
207 */
208 int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800209
210 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700211 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
212 * still clean up. On reinstall the package will have a new uid.
213 */
214 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
215 @Override
216 public void onReceive(Context context, Intent intent) {
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700217 if (DEBUG) {
218 Slog.d(TAG, "Receieved: " + intent.getAction());
219 }
Shreyas Basarge5db09082016-01-07 13:38:29 +0000220 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700221 // If this is an outright uninstall rather than the first half of an
222 // app update sequence, cancel the jobs associated with the app.
223 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
224 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
225 if (DEBUG) {
226 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
227 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700228 cancelJobsForUid(uidRemoved, true);
Christopher Tate7060b042014-06-09 19:50:00 -0700229 }
Shreyas Basarge5db09082016-01-07 13:38:29 +0000230 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700231 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
232 if (DEBUG) {
233 Slog.d(TAG, "Removing jobs for user: " + userId);
234 }
235 cancelJobsForUser(userId);
236 }
237 }
238 };
239
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700240 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
241 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800242 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700243 }
244
245 @Override public void onUidGone(int uid) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800246 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700247 }
248
249 @Override public void onUidActive(int uid) throws RemoteException {
250 }
251
252 @Override public void onUidIdle(int uid) throws RemoteException {
253 cancelJobsForUid(uid, false);
254 }
255 };
256
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800257 public Object getLock() {
258 return mLock;
259 }
260
Dianne Hackborn8db0fc12016-04-12 13:48:25 -0700261 public JobStore getJobStore() {
262 return mJobs;
263 }
264
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700265 @Override
266 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700267 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
268 // Let's kick any outstanding jobs for this user.
269 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
270 }
271
272 @Override
273 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700274 // Let's kick any outstanding jobs for this user.
275 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
276 }
277
278 @Override
279 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700280 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700281 }
282
Christopher Tate7060b042014-06-09 19:50:00 -0700283 /**
284 * Entry point from client to schedule the provided job.
285 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
286 * @param job JobInfo object containing execution parameters
287 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700288 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
289 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700290 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800291 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000292 }
293
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800294 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
295 String tag) {
296 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700297 try {
298 if (ActivityManagerNative.getDefault().getAppStartMode(uId,
299 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
300 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
301 + " -- package not allowed to start");
302 return JobScheduler.RESULT_FAILURE;
303 }
304 } catch (RemoteException e) {
305 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800306 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
307 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800308 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800309 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800310 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800311 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
312 Slog.w(TAG, "Too many jobs for uid " + uId);
313 throw new IllegalStateException("Apps may not schedule more than "
314 + MAX_JOBS_PER_APP + " distinct jobs");
315 }
316 }
317
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800318 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700319 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700320 cancelJobImpl(toCancel, jobStatus);
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700321 }
322 startTrackingJob(jobStatus, toCancel);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800323 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700324 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700325 return JobScheduler.RESULT_SUCCESS;
326 }
327
328 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800329 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800330 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
331 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
332 for (int i = jobs.size() - 1; i >= 0; i--) {
333 JobStatus job = jobs.get(i);
334 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700335 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800336 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700337 }
Christopher Tate7060b042014-06-09 19:50:00 -0700338 }
339
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600340 public JobInfo getPendingJob(int uid, int jobId) {
341 synchronized (mLock) {
342 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
343 for (int i = jobs.size() - 1; i >= 0; i--) {
344 JobStatus job = jobs.get(i);
345 if (job.getJobId() == jobId) {
346 return job.getJob();
347 }
348 }
349 return null;
350 }
351 }
352
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700353 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700354 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800355 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700356 jobsForUser = mJobs.getJobsByUser(userHandle);
357 }
358 for (int i=0; i<jobsForUser.size(); i++) {
359 JobStatus toRemove = jobsForUser.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700360 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700361 }
362 }
363
364 /**
365 * Entry point from client to cancel all jobs originating from their uid.
366 * This will remove the job from the master list, and cancel the job if it was staged for
367 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700368 * @param uid Uid to check against for removal of a job.
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700369 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
370 * whose apps are stopped.
Christopher Tate7060b042014-06-09 19:50:00 -0700371 */
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700372 public void cancelJobsForUid(int uid, boolean forceAll) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700373 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800374 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700375 jobsForUid = mJobs.getJobsByUid(uid);
376 }
377 for (int i=0; i<jobsForUid.size(); i++) {
378 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700379 if (!forceAll) {
380 String packageName = toRemove.getServiceComponent().getPackageName();
381 try {
382 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
383 != ActivityManager.APP_START_MODE_DISABLED) {
384 continue;
385 }
386 } catch (RemoteException e) {
387 }
388 }
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700389 cancelJobImpl(toRemove, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700390 }
391 }
392
393 /**
394 * Entry point from client to cancel the job corresponding to the jobId provided.
395 * This will remove the job from the master list, and cancel the job if it was staged for
396 * execution or being executed.
397 * @param uid Uid of the calling client.
398 * @param jobId Id of the job, provided at schedule-time.
399 */
400 public void cancelJob(int uid, int jobId) {
401 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800402 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700403 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700404 }
405 if (toCancel != null) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700406 cancelJobImpl(toCancel, null);
Christopher Tate7060b042014-06-09 19:50:00 -0700407 }
408 }
409
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700410 private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800411 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700412 stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800413 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700414 // Remove from pending queue.
Dianne Hackborn807de782016-04-07 17:54:41 -0700415 if (mPendingJobs.remove(cancelled)) {
416 mJobPackageTracker.noteNonpending(cancelled);
417 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700418 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000419 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800420 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700421 }
Christopher Tate7060b042014-06-09 19:50:00 -0700422 }
423
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800424 void updateUidState(int uid, int procState) {
425 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800426 if (procState == ActivityManager.PROCESS_STATE_TOP) {
427 // Only use this if we are exactly the top app. All others can live
428 // with just the foreground priority. This means that persistent processes
429 // can never be the top app priority... that is fine.
430 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
431 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
432 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800433 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800434 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800435 }
436 }
437 }
438
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700439 @Override
440 public void onDeviceIdleStateChanged(boolean deviceIdle) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800441 synchronized (mLock) {
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700442 if (deviceIdle) {
443 // When becoming idle, make sure no jobs are actively running.
444 for (int i=0; i<mActiveServices.size(); i++) {
445 JobServiceContext jsc = mActiveServices.get(i);
446 final JobStatus executing = jsc.getRunningJob();
447 if (executing != null) {
448 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
449 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700450 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700451 } else {
452 // When coming out of idle, allow thing to start back up.
453 if (mReadyToRock) {
454 if (mLocalDeviceIdleController != null) {
455 if (!mReportedActive) {
456 mReportedActive = true;
457 mLocalDeviceIdleController.setJobsActive(true);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700458 }
459 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700460 }
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700461 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700462 }
463 }
464 }
465
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800466 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000467 // active is true if pending queue contains jobs OR some job is running.
468 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800469 if (mPendingJobs.size() <= 0) {
470 for (int i=0; i<mActiveServices.size(); i++) {
471 JobServiceContext jsc = mActiveServices.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000472 if (jsc.getRunningJob() != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800473 active = true;
474 break;
475 }
476 }
477 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000478
479 if (mReportedActive != active) {
480 mReportedActive = active;
481 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800482 mLocalDeviceIdleController.setJobsActive(active);
483 }
484 }
485 }
486
Christopher Tate7060b042014-06-09 19:50:00 -0700487 /**
488 * Initializes the system service.
489 * <p>
490 * Subclasses must define a single argument constructor that accepts the context
491 * and passes it to super.
492 * </p>
493 *
494 * @param context The system server context.
495 */
496 public JobSchedulerService(Context context) {
497 super(context);
498 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700499 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700500 mControllers.add(ConnectivityController.get(this));
501 mControllers.add(TimeController.get(this));
502 mControllers.add(IdleController.get(this));
503 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800504 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800505 mControllers.add(ContentObserverController.get(this));
Amith Yamasanicb926fc2016-03-14 17:15:20 -0700506 mControllers.add(DeviceIdleJobsController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700507
508 mHandler = new JobHandler(context.getMainLooper());
509 mJobSchedulerStub = new JobSchedulerStub();
Christopher Tate7060b042014-06-09 19:50:00 -0700510 mJobs = JobStore.initAndGet(this);
511 }
512
513 @Override
514 public void onStart() {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000515 publishLocalService(JobSchedulerInternal.class, new LocalService());
Christopher Tate7060b042014-06-09 19:50:00 -0700516 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
517 }
518
519 @Override
520 public void onBootPhase(int phase) {
521 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000522 // Register br for package removals and user removals.
Christopher Tate7060b042014-06-09 19:50:00 -0700523 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
524 filter.addDataScheme("package");
525 getContext().registerReceiverAsUser(
526 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
527 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
528 getContext().registerReceiverAsUser(
529 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000530 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700531 try {
532 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800533 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
534 | ActivityManager.UID_OBSERVER_IDLE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700535 } catch (RemoteException e) {
536 // ignored; both services live in system_server
537 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700538 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800539 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700540 // Let's go!
541 mReadyToRock = true;
542 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
543 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800544 mLocalDeviceIdleController
545 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700546 // Create the "runners".
547 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
548 mActiveServices.add(
Dianne Hackborn807de782016-04-07 17:54:41 -0700549 new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700550 getContext().getMainLooper()));
551 }
552 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800553 mJobs.forEachJob(new JobStatusFunctor() {
554 @Override
555 public void process(JobStatus job) {
556 for (int controller = 0; controller < mControllers.size(); controller++) {
557 final StateController sc = mControllers.get(controller);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800558 sc.maybeStartTrackingJobLocked(job, null);
559 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700560 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800561 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700562 // GO GO GO!
563 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
564 }
Christopher Tate7060b042014-06-09 19:50:00 -0700565 }
566 }
567
568 /**
569 * Called when we have a job status object that we need to insert in our
570 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
571 * about.
572 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800573 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800574 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800575 final boolean update = mJobs.add(jobStatus);
576 if (mReadyToRock) {
577 for (int i = 0; i < mControllers.size(); i++) {
578 StateController controller = mControllers.get(i);
579 if (update) {
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700580 controller.maybeStopTrackingJobLocked(jobStatus, null, true);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800581 }
582 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700583 }
Christopher Tate7060b042014-06-09 19:50:00 -0700584 }
Christopher Tate7060b042014-06-09 19:50:00 -0700585 }
586 }
587
588 /**
589 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
590 * object removed.
591 */
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700592 private boolean stopTrackingJob(JobStatus jobStatus, JobStatus incomingJob,
593 boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800594 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700595 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800596 final boolean removed = mJobs.remove(jobStatus, writeBack);
597 if (removed && mReadyToRock) {
598 for (int i=0; i<mControllers.size(); i++) {
599 StateController controller = mControllers.get(i);
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700600 controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800601 }
Christopher Tate7060b042014-06-09 19:50:00 -0700602 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800603 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700604 }
Christopher Tate7060b042014-06-09 19:50:00 -0700605 }
606
Shreyas Basarge5db09082016-01-07 13:38:29 +0000607 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700608 for (int i=0; i<mActiveServices.size(); i++) {
609 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700610 final JobStatus executing = jsc.getRunningJob();
611 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000612 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700613 return true;
614 }
615 }
616 return false;
617 }
618
619 /**
620 * @param job JobStatus we are querying against.
621 * @return Whether or not the job represented by the status object is currently being run or
622 * is pending.
623 */
624 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700625 for (int i=0; i<mActiveServices.size(); i++) {
626 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700627 final JobStatus running = serviceContext.getRunningJob();
628 if (running != null && running.matches(job.getUid(), job.getJobId())) {
629 return true;
630 }
631 }
632 return false;
633 }
634
Dianne Hackborn807de782016-04-07 17:54:41 -0700635 void noteJobsPending(List<JobStatus> jobs) {
636 for (int i = jobs.size() - 1; i >= 0; i--) {
637 JobStatus job = jobs.get(i);
638 mJobPackageTracker.notePending(job);
639 }
640 }
641
642 void noteJobsNonpending(List<JobStatus> jobs) {
643 for (int i = jobs.size() - 1; i >= 0; i--) {
644 JobStatus job = jobs.get(i);
645 mJobPackageTracker.noteNonpending(job);
646 }
647 }
648
Christopher Tate7060b042014-06-09 19:50:00 -0700649 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700650 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
651 * specify an override deadline on a failed job (the failed job will run even though it's not
652 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
653 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
654 *
Christopher Tate7060b042014-06-09 19:50:00 -0700655 * @param failureToReschedule Provided job status that we will reschedule.
656 * @return A newly instantiated JobStatus with the same constraints as the last job except
657 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700658 *
659 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700660 */
661 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
662 final long elapsedNowMillis = SystemClock.elapsedRealtime();
663 final JobInfo job = failureToReschedule.getJob();
664
665 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700666 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
667 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700668
669 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700670 case JobInfo.BACKOFF_POLICY_LINEAR:
671 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700672 break;
673 default:
674 if (DEBUG) {
675 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
676 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700677 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
678 delayMillis =
679 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700680 break;
681 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700682 delayMillis =
683 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800684 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700685 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800686 for (int ic=0; ic<mControllers.size(); ic++) {
687 StateController controller = mControllers.get(ic);
688 controller.rescheduleForFailure(newJob, failureToReschedule);
689 }
690 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700691 }
692
693 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700694 * Called after a periodic has executed so we can reschedule it. We take the last execution
695 * time of the job to be the time of completion (i.e. the time at which this function is
696 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700697 * This could be inaccurate b/c the job can run for as long as
698 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
699 * to underscheduling at least, rather than if we had taken the last execution time to be the
700 * start of the execution.
701 * @return A new job representing the execution criteria for this instantiation of the
702 * recurring job.
703 */
704 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
705 final long elapsedNow = SystemClock.elapsedRealtime();
706 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700707 long runEarly = 0L;
708
709 // If this periodic was rescheduled it won't have a deadline.
710 if (periodicToReschedule.hasDeadlineConstraint()) {
711 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
712 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000713 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -0700714 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000715 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
716 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -0700717
718 if (DEBUG) {
719 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
720 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
721 }
722 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
723 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
724 }
725
726 // JobCompletedListener implementations.
727
728 /**
729 * A job just finished executing. We fetch the
730 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
731 * whether we want to reschedule we readd it to the controllers.
732 * @param jobStatus Completed job.
733 * @param needsReschedule Whether the implementing class should reschedule this job.
734 */
735 @Override
736 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
737 if (DEBUG) {
738 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
739 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000740 // Do not write back immediately if this is a periodic job. The job may get lost if system
741 // shuts down before it is added back.
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700742 if (!stopTrackingJob(jobStatus, null, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700743 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700744 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -0700745 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800746 // We still want to check for jobs to execute, because this job may have
747 // scheduled a new job under the same job id, and now we can run it.
748 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700749 return;
750 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800751 // Note: there is a small window of time in here where, when rescheduling a job,
752 // we will stop monitoring its content providers. This should be fixed by stopping
753 // the old job after scheduling the new one, but since we have no lock held here
754 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -0700755 if (needsReschedule) {
756 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800757 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700758 } else if (jobStatus.getJob().isPeriodic()) {
759 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800760 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700761 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000762 reportActive();
763 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700764 }
765
766 // StateChangedListener implementations.
767
768 /**
Matthew Williams48a30db2014-09-23 13:39:36 -0700769 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
770 * some controller's state has changed, so as to run through the list of jobs and start/stop
771 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -0700772 */
773 @Override
774 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700775 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700776 }
777
778 @Override
779 public void onRunJobNow(JobStatus jobStatus) {
780 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
781 }
782
Christopher Tate7060b042014-06-09 19:50:00 -0700783 private class JobHandler extends Handler {
784
785 public JobHandler(Looper looper) {
786 super(looper);
787 }
788
789 @Override
790 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800791 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700792 if (!mReadyToRock) {
793 return;
794 }
795 }
Christopher Tate7060b042014-06-09 19:50:00 -0700796 switch (message.what) {
797 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800798 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700799 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700800 // runNow can be null, which is a controller's way of indicating that its
801 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -0700802 if (runNow != null && !mPendingJobs.contains(runNow)
803 && mJobs.containsJob(runNow)) {
Dianne Hackborn807de782016-04-07 17:54:41 -0700804 mJobPackageTracker.notePending(runNow);
Christopher Tate7060b042014-06-09 19:50:00 -0700805 mPendingJobs.add(runNow);
806 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700807 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -0700808 }
Christopher Tate7060b042014-06-09 19:50:00 -0700809 break;
810 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800811 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000812 if (mReportedActive) {
813 // if jobs are currently being run, queue all ready jobs for execution.
814 queueReadyJobsForExecutionLockedH();
815 } else {
816 // Check the list of jobs and run some of them if we feel inclined.
817 maybeQueueReadyJobsForExecutionLockedH();
818 }
819 }
820 break;
821 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800822 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000823 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -0700824 }
Christopher Tate7060b042014-06-09 19:50:00 -0700825 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700826 case MSG_STOP_JOB:
Dianne Hackborn141f11c2016-04-05 15:46:12 -0700827 cancelJobImpl((JobStatus)message.obj, null);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700828 break;
Christopher Tate7060b042014-06-09 19:50:00 -0700829 }
830 maybeRunPendingJobsH();
831 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
832 removeMessages(MSG_CHECK_JOB);
833 }
834
835 /**
836 * Run through list of jobs and execute all possible - at least one is expired so we do
837 * as many as we can.
838 */
Matthew Williams48a30db2014-09-23 13:39:36 -0700839 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700840 if (DEBUG) {
841 Slog.d(TAG, "queuing all ready jobs for execution:");
842 }
Dianne Hackborn807de782016-04-07 17:54:41 -0700843 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800844 mPendingJobs.clear();
845 mJobs.forEachJob(mReadyQueueFunctor);
846 mReadyQueueFunctor.postProcess();
847
Matthew Williams48a30db2014-09-23 13:39:36 -0700848 if (DEBUG) {
849 final int queuedJobs = mPendingJobs.size();
850 if (queuedJobs == 0) {
851 Slog.d(TAG, "No jobs pending.");
852 } else {
853 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -0700854 }
Christopher Tate7060b042014-06-09 19:50:00 -0700855 }
856 }
857
Christopher Tate2f36fd62016-02-18 18:36:08 -0800858 class ReadyJobQueueFunctor implements JobStatusFunctor {
859 ArrayList<JobStatus> newReadyJobs;
860
861 @Override
862 public void process(JobStatus job) {
863 if (isReadyToBeExecutedLocked(job)) {
864 if (DEBUG) {
865 Slog.d(TAG, " queued " + job.toShortString());
866 }
867 if (newReadyJobs == null) {
868 newReadyJobs = new ArrayList<JobStatus>();
869 }
870 newReadyJobs.add(job);
871 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
872 stopJobOnServiceContextLocked(job,
873 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
874 }
875 }
876
877 public void postProcess() {
878 if (newReadyJobs != null) {
Dianne Hackborn807de782016-04-07 17:54:41 -0700879 noteJobsPending(newReadyJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800880 mPendingJobs.addAll(newReadyJobs);
881 }
882 newReadyJobs = null;
883 }
884 }
885 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
886
Christopher Tate7060b042014-06-09 19:50:00 -0700887 /**
888 * The state of at least one job has changed. Here is where we could enforce various
889 * policies on when we want to execute jobs.
890 * Right now the policy is such:
891 * If >1 of the ready jobs is idle mode we send all of them off
892 * if more than 2 network connectivity jobs are ready we send them all off.
893 * If more than 4 jobs total are ready we send them all off.
894 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
895 */
Christopher Tate2f36fd62016-02-18 18:36:08 -0800896 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
897 int chargingCount;
898 int idleCount;
899 int backoffCount;
900 int connectivityCount;
901 int contentCount;
902 List<JobStatus> runnableJobs;
903
904 public MaybeReadyJobQueueFunctor() {
905 reset();
906 }
907
908 // Functor method invoked for each job via JobStore.forEachJob()
909 @Override
910 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700911 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700912 try {
913 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
914 job.getJob().getService().getPackageName())
915 == ActivityManager.APP_START_MODE_DISABLED) {
916 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
917 + job.getJob().toString() + " -- package not allowed to start");
918 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800919 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700920 }
921 } catch (RemoteException e) {
922 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700923 if (job.getNumFailures() > 0) {
924 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -0700925 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700926 if (job.hasIdleConstraint()) {
927 idleCount++;
928 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600929 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()
930 || job.hasNotRoamingConstraint()) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700931 connectivityCount++;
932 }
933 if (job.hasChargingConstraint()) {
934 chargingCount++;
935 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800936 if (job.hasContentTriggerConstraint()) {
937 contentCount++;
938 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700939 if (runnableJobs == null) {
940 runnableJobs = new ArrayList<>();
941 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700942 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800943 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000944 stopJobOnServiceContextLocked(job,
945 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -0700946 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700947 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800948
949 public void postProcess() {
950 if (backoffCount > 0 ||
951 idleCount >= MIN_IDLE_COUNT ||
952 connectivityCount >= MIN_CONNECTIVITY_COUNT ||
953 chargingCount >= MIN_CHARGING_COUNT ||
954 contentCount >= MIN_CONTENT_COUNT ||
955 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
956 if (DEBUG) {
957 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
958 }
Dianne Hackborn807de782016-04-07 17:54:41 -0700959 noteJobsPending(runnableJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800960 mPendingJobs.addAll(runnableJobs);
961 } else {
962 if (DEBUG) {
963 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
964 }
Christopher Tate7060b042014-06-09 19:50:00 -0700965 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800966
967 // Be ready for next time
968 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -0700969 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800970
971 private void reset() {
972 chargingCount = 0;
973 idleCount = 0;
974 backoffCount = 0;
975 connectivityCount = 0;
976 contentCount = 0;
977 runnableJobs = null;
978 }
979 }
980 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
981
982 private void maybeQueueReadyJobsForExecutionLockedH() {
983 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
984
Dianne Hackborn807de782016-04-07 17:54:41 -0700985 noteJobsNonpending(mPendingJobs);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800986 mPendingJobs.clear();
987 mJobs.forEachJob(mMaybeQueueFunctor);
988 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -0700989 }
990
991 /**
992 * Criteria for moving a job into the pending queue:
993 * - It's ready.
994 * - It's not pending.
995 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700996 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700997 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -0700998 */
999 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001000 final boolean jobReady = job.isReady();
1001 final boolean jobPending = mPendingJobs.contains(job);
1002 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001003
1004 final int userId = job.getUserId();
1005 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
1006 final boolean componentPresent;
1007 try {
1008 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
1009 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
1010 userId) != null);
1011 } catch (RemoteException e) {
1012 throw e.rethrowAsRuntimeException();
1013 }
1014
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001015 if (DEBUG) {
1016 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
1017 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001018 + " active=" + jobActive + " userStarted=" + userStarted
1019 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -07001020 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001021 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -07001022 }
1023
1024 /**
1025 * Criteria for cancelling an active job:
1026 * - It's not ready
1027 * - It's running on a JSC.
1028 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001029 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001030 return !job.isReady() && isCurrentlyActiveLocked(job);
1031 }
1032
1033 /**
1034 * Reconcile jobs in the pending queue against available execution contexts.
1035 * A controller can force a job into the pending queue even if it's already running, but
1036 * here is where we decide whether to actually execute it.
1037 */
1038 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001039 synchronized (mLock) {
Matthew Williams75fc5252014-09-02 16:17:53 -07001040 if (DEBUG) {
1041 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1042 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001043 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001044 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001045 }
1046 }
1047 }
1048
Dianne Hackborn807de782016-04-07 17:54:41 -07001049 private int adjustJobPriority(int curPriority, JobStatus job) {
1050 if (curPriority < JobInfo.PRIORITY_TOP_APP) {
1051 float factor = mJobPackageTracker.getLoadFactor(job);
1052 if (factor >= HEAVY_USE_FACTOR) {
1053 curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
1054 } else if (factor >= MODERATE_USE_FACTOR) {
1055 curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
1056 }
1057 }
1058 return curPriority;
1059 }
1060
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001061 private int evaluateJobPriorityLocked(JobStatus job) {
1062 int priority = job.getPriority();
1063 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001064 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001065 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001066 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1067 if (override != 0) {
Dianne Hackborn807de782016-04-07 17:54:41 -07001068 return adjustJobPriority(override, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001069 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001070 return adjustJobPriority(priority, job);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001071 }
1072
Christopher Tate7060b042014-06-09 19:50:00 -07001073 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001074 * Takes jobs from pending queue and runs them on available contexts.
1075 * If no contexts are available, preempts lower priority jobs to
1076 * run higher priority ones.
1077 * Lock on mJobs before calling this function.
1078 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001079 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001080 if (DEBUG) {
1081 Slog.d(TAG, printPendingQueue());
1082 }
1083
Dianne Hackborn970510b2016-02-24 16:56:42 -08001084 int memLevel;
1085 try {
1086 memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
1087 } catch (RemoteException e) {
1088 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1089 }
1090 switch (memLevel) {
1091 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
Dianne Hackborn807de782016-04-07 17:54:41 -07001092 mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) * 2) / 3;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001093 break;
1094 case ProcessStats.ADJ_MEM_FACTOR_LOW:
Dianne Hackborn807de782016-04-07 17:54:41 -07001095 mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) / 3;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001096 break;
1097 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
1098 mMaxActiveJobs = 1;
1099 break;
1100 default:
Dianne Hackborn807de782016-04-07 17:54:41 -07001101 mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001102 break;
1103 }
1104
1105 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1106 boolean[] act = mTmpAssignAct;
1107 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1108 int numActive = 0;
1109 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1110 final JobServiceContext js = mActiveServices.get(i);
1111 if ((contextIdToJobMap[i] = js.getRunningJob()) != null) {
1112 numActive++;
1113 }
1114 act[i] = false;
1115 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001116 }
1117 if (DEBUG) {
1118 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1119 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001120 for (int i=0; i<mPendingJobs.size(); i++) {
1121 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001122
1123 // If job is already running, go to next job.
1124 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1125 if (jobRunningContext != -1) {
1126 continue;
1127 }
1128
Dianne Hackborn970510b2016-02-24 16:56:42 -08001129 final int priority = evaluateJobPriorityLocked(nextPending);
1130 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001131
Shreyas Basarge5db09082016-01-07 13:38:29 +00001132 // Find a context for nextPending. The context should be available OR
1133 // it should have lowest priority among all running jobs
1134 // (sharing the same Uid as nextPending)
1135 int minPriority = Integer.MAX_VALUE;
1136 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001137 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1138 JobStatus job = contextIdToJobMap[j];
1139 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001140 if (job == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001141 if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) &&
1142 (preferredUid == nextPending.getUid() ||
1143 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1144 // This slot is free, and we haven't yet hit the limit on
1145 // concurrent jobs... we can just throw the job in to here.
1146 minPriorityContextId = j;
1147 numActive++;
1148 break;
1149 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001150 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001151 // the context has a preferred Uid or we have reached the limit on
1152 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001153 continue;
1154 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001155 if (job.getUid() != nextPending.getUid()) {
1156 continue;
1157 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001158 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001159 continue;
1160 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001161 if (minPriority > nextPending.lastEvaluatedPriority) {
1162 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001163 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001164 }
1165 }
1166 if (minPriorityContextId != -1) {
1167 contextIdToJobMap[minPriorityContextId] = nextPending;
1168 act[minPriorityContextId] = true;
1169 }
1170 }
1171 if (DEBUG) {
1172 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1173 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001174 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001175 boolean preservePreferredUid = false;
1176 if (act[i]) {
1177 JobStatus js = mActiveServices.get(i).getRunningJob();
1178 if (js != null) {
1179 if (DEBUG) {
1180 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1181 }
1182 // preferredUid will be set to uid of currently running job.
1183 mActiveServices.get(i).preemptExecutingJob();
1184 preservePreferredUid = true;
1185 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001186 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001187 if (DEBUG) {
1188 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001189 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001190 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001191 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001192 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001193 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001194 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1195 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001196 }
Dianne Hackborn807de782016-04-07 17:54:41 -07001197 if (mPendingJobs.remove(pendingJob)) {
1198 mJobPackageTracker.noteNonpending(pendingJob);
1199 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001200 }
1201 }
1202 if (!preservePreferredUid) {
1203 mActiveServices.get(i).clearPreferredUid();
1204 }
1205 }
1206 }
1207
1208 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1209 for (int i=0; i<map.length; i++) {
1210 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1211 return i;
1212 }
1213 }
1214 return -1;
1215 }
1216
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001217 final class LocalService implements JobSchedulerInternal {
1218
1219 /**
1220 * Returns a list of all pending jobs. A running job is not considered pending. Periodic
1221 * jobs are always considered pending.
1222 */
Amith Yamasanicb926fc2016-03-14 17:15:20 -07001223 @Override
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001224 public List<JobInfo> getSystemScheduledPendingJobs() {
1225 synchronized (mLock) {
1226 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
1227 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
1228 @Override
1229 public void process(JobStatus job) {
1230 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
1231 pendingJobs.add(job.getJob());
1232 }
1233 }
1234 });
1235 return pendingJobs;
1236 }
1237 }
1238 }
1239
Shreyas Basarge5db09082016-01-07 13:38:29 +00001240 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001241 * Binder stub trampoline implementation
1242 */
1243 final class JobSchedulerStub extends IJobScheduler.Stub {
1244 /** Cache determination of whether a given app can persist jobs
1245 * key is uid of the calling app; value is undetermined/true/false
1246 */
1247 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1248
1249 // Enforce that only the app itself (or shared uid participant) can schedule a
1250 // job that runs one of the app's services, as well as verifying that the
1251 // named service properly requires the BIND_JOB_SERVICE permission
1252 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001253 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001254 final ComponentName service = job.getService();
1255 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001256 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey8a372a02016-03-16 16:25:45 -06001257 PackageManager.MATCH_DIRECT_BOOT_AWARE
1258 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001259 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001260 if (si == null) {
1261 throw new IllegalArgumentException("No such service " + service);
1262 }
Christopher Tate7060b042014-06-09 19:50:00 -07001263 if (si.applicationInfo.uid != uid) {
1264 throw new IllegalArgumentException("uid " + uid +
1265 " cannot schedule job in " + service.getPackageName());
1266 }
1267 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1268 throw new IllegalArgumentException("Scheduled service " + service
1269 + " does not require android.permission.BIND_JOB_SERVICE permission");
1270 }
Christopher Tate5568f542014-06-18 13:53:31 -07001271 } catch (RemoteException e) {
1272 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001273 }
1274 }
1275
1276 private boolean canPersistJobs(int pid, int uid) {
1277 // If we get this far we're good to go; all we need to do now is check
1278 // whether the app is allowed to persist its scheduled work.
1279 final boolean canPersist;
1280 synchronized (mPersistCache) {
1281 Boolean cached = mPersistCache.get(uid);
1282 if (cached != null) {
1283 canPersist = cached.booleanValue();
1284 } else {
1285 // Persisting jobs is tantamount to running at boot, so we permit
1286 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1287 // permission
1288 int result = getContext().checkPermission(
1289 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1290 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1291 mPersistCache.put(uid, canPersist);
1292 }
1293 }
1294 return canPersist;
1295 }
1296
1297 // IJobScheduler implementation
1298 @Override
1299 public int schedule(JobInfo job) throws RemoteException {
1300 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001301 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001302 }
1303 final int pid = Binder.getCallingPid();
1304 final int uid = Binder.getCallingUid();
1305
1306 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001307 if (job.isPersisted()) {
1308 if (!canPersistJobs(pid, uid)) {
1309 throw new IllegalArgumentException("Error: requested job be persisted without"
1310 + " holding RECEIVE_BOOT_COMPLETED permission.");
1311 }
1312 }
Christopher Tate7060b042014-06-09 19:50:00 -07001313
1314 long ident = Binder.clearCallingIdentity();
1315 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001316 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001317 } finally {
1318 Binder.restoreCallingIdentity(ident);
1319 }
1320 }
1321
1322 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001323 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001324 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001325 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001326 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001327 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1328 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001329 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001330
1331 if (packageName == null) {
1332 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001333 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001334
1335 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1336 android.Manifest.permission.UPDATE_DEVICE_STATS);
1337 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1338 throw new SecurityException("Caller uid " + callerUid
1339 + " not permitted to schedule jobs for other apps");
1340 }
1341
Shreyas Basarge968ac752016-01-11 23:09:26 +00001342 long ident = Binder.clearCallingIdentity();
1343 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001344 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001345 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001346 } finally {
1347 Binder.restoreCallingIdentity(ident);
1348 }
1349 }
1350
1351 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001352 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1353 final int uid = Binder.getCallingUid();
1354
1355 long ident = Binder.clearCallingIdentity();
1356 try {
1357 return JobSchedulerService.this.getPendingJobs(uid);
1358 } finally {
1359 Binder.restoreCallingIdentity(ident);
1360 }
1361 }
1362
1363 @Override
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001364 public JobInfo getPendingJob(int jobId) throws RemoteException {
1365 final int uid = Binder.getCallingUid();
1366
1367 long ident = Binder.clearCallingIdentity();
1368 try {
1369 return JobSchedulerService.this.getPendingJob(uid, jobId);
1370 } finally {
1371 Binder.restoreCallingIdentity(ident);
1372 }
1373 }
1374
1375 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001376 public void cancelAll() throws RemoteException {
1377 final int uid = Binder.getCallingUid();
1378
1379 long ident = Binder.clearCallingIdentity();
1380 try {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001381 JobSchedulerService.this.cancelJobsForUid(uid, true);
Christopher Tate7060b042014-06-09 19:50:00 -07001382 } finally {
1383 Binder.restoreCallingIdentity(ident);
1384 }
1385 }
1386
1387 @Override
1388 public void cancel(int jobId) throws RemoteException {
1389 final int uid = Binder.getCallingUid();
1390
1391 long ident = Binder.clearCallingIdentity();
1392 try {
1393 JobSchedulerService.this.cancelJob(uid, jobId);
1394 } finally {
1395 Binder.restoreCallingIdentity(ident);
1396 }
1397 }
1398
1399 /**
1400 * "dumpsys" infrastructure
1401 */
1402 @Override
1403 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1404 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1405
1406 long identityToken = Binder.clearCallingIdentity();
1407 try {
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001408 JobSchedulerService.this.dumpInternal(pw, args);
Christopher Tate7060b042014-06-09 19:50:00 -07001409 } finally {
1410 Binder.restoreCallingIdentity(identityToken);
1411 }
1412 }
Christopher Tate5d346052016-03-08 12:56:08 -08001413
1414 @Override
1415 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1416 String[] args, ResultReceiver resultReceiver) throws RemoteException {
1417 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
1418 this, in, out, err, args, resultReceiver);
1419 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001420 };
1421
Christopher Tate5d346052016-03-08 12:56:08 -08001422 // Shell command infrastructure: run the given job immediately
1423 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1424 if (DEBUG) {
1425 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1426 + " " + jobId + " f=" + force);
1427 }
1428
1429 try {
1430 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1431 if (uid < 0) {
1432 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1433 }
1434
1435 synchronized (mLock) {
1436 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1437 if (js == null) {
1438 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1439 }
1440
1441 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1442 if (!js.isConstraintsSatisfied()) {
1443 js.overrideState = 0;
1444 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1445 }
1446
1447 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1448 }
1449 } catch (RemoteException e) {
1450 // can't happen
1451 }
1452 return 0;
1453 }
1454
Shreyas Basarge5db09082016-01-07 13:38:29 +00001455 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1456 StringBuilder s = new StringBuilder(initial + ": ");
1457 for (int i=0; i<map.length; i++) {
1458 s.append("(")
1459 .append(map[i] == null? -1: map[i].getJobId())
1460 .append(map[i] == null? -1: map[i].getUid())
1461 .append(")" );
1462 }
1463 return s.toString();
1464 }
1465
1466 private String printPendingQueue() {
1467 StringBuilder s = new StringBuilder("Pending queue: ");
1468 Iterator<JobStatus> it = mPendingJobs.iterator();
1469 while (it.hasNext()) {
1470 JobStatus js = it.next();
1471 s.append("(")
1472 .append(js.getJob().getId())
1473 .append(", ")
1474 .append(js.getUid())
1475 .append(") ");
1476 }
1477 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001478 }
Christopher Tate7060b042014-06-09 19:50:00 -07001479
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001480 static void dumpHelp(PrintWriter pw) {
1481 pw.println("Job Scheduler (jobscheduler) dump options:");
1482 pw.println(" [-h] [package] ...");
1483 pw.println(" -h: print this help");
1484 pw.println(" [package] is an optional package name to limit the output to.");
1485 }
1486
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001487 void dumpInternal(final PrintWriter pw, String[] args) {
1488 int filterUid = -1;
1489 if (!ArrayUtils.isEmpty(args)) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001490 int opti = 0;
1491 while (opti < args.length) {
1492 String arg = args[opti];
1493 if ("-h".equals(arg)) {
1494 dumpHelp(pw);
1495 return;
1496 } else if ("-a".equals(arg)) {
1497 // Ignore, we always dump all.
1498 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
1499 pw.println("Unknown option: " + arg);
1500 return;
1501 } else {
1502 break;
1503 }
1504 opti++;
1505 }
1506 if (opti < args.length) {
1507 String pkg = args[opti];
1508 try {
1509 filterUid = getContext().getPackageManager().getPackageUid(pkg,
1510 PackageManager.MATCH_UNINSTALLED_PACKAGES);
1511 } catch (NameNotFoundException ignored) {
1512 pw.println("Invalid package: " + pkg);
1513 return;
1514 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001515 }
1516 }
1517
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001518 final int filterUidFinal = UserHandle.getAppId(filterUid);
Christopher Tatef973a7b2014-08-29 12:54:08 -07001519 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001520 synchronized (mLock) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001521 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Christopher Tate7060b042014-06-09 19:50:00 -07001522 pw.println("Registered jobs:");
1523 if (mJobs.size() > 0) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001524 mJobs.forEachJob(new JobStatusFunctor() {
1525 private int index = 0;
1526
1527 @Override
1528 public void process(JobStatus job) {
1529 pw.print(" Job #"); pw.print(index++); pw.print(": ");
1530 pw.println(job.toShortString());
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001531
1532 // Skip printing details if the caller requested a filter
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001533 if (!job.shouldDump(filterUidFinal)) {
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -06001534 return;
1535 }
1536
Dianne Hackborn970510b2016-02-24 16:56:42 -08001537 job.dump(pw, " ", true);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001538 pw.print(" Ready: ");
1539 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1540 pw.print(" (job=");
1541 pw.print(job.isReady());
1542 pw.print(" pending=");
1543 pw.print(mPendingJobs.contains(job));
1544 pw.print(" active=");
1545 pw.print(isCurrentlyActiveLocked(job));
1546 pw.print(" user=");
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001547 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
Christopher Tate2f36fd62016-02-18 18:36:08 -08001548 pw.println(")");
1549 }
1550 });
Christopher Tate7060b042014-06-09 19:50:00 -07001551 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001552 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001553 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001554 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001555 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001556 mControllers.get(i).dumpControllerStateLocked(pw, filterUidFinal);
Christopher Tate7060b042014-06-09 19:50:00 -07001557 }
1558 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001559 pw.println("Uid priority overrides:");
1560 for (int i=0; i< mUidPriorityOverride.size(); i++) {
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001561 int uid = mUidPriorityOverride.keyAt(i);
1562 if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
1563 pw.print(" "); pw.print(UserHandle.formatUid(uid));
1564 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
1565 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001566 }
1567 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001568 mJobPackageTracker.dump(pw, "", filterUidFinal);
Dianne Hackborn807de782016-04-07 17:54:41 -07001569 pw.println();
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001570 if (mJobPackageTracker.dumpHistory(pw, "", filterUidFinal)) {
1571 pw.println();
1572 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001573 pw.println("Pending queue:");
1574 for (int i=0; i<mPendingJobs.size(); i++) {
1575 JobStatus job = mPendingJobs.get(i);
1576 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1577 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001578 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001579 int priority = evaluateJobPriorityLocked(job);
1580 if (priority != JobInfo.PRIORITY_DEFAULT) {
1581 pw.print(" Evaluated priority: "); pw.println(priority);
1582 }
1583 pw.print(" Tag: "); pw.println(job.getTag());
1584 }
Christopher Tate7060b042014-06-09 19:50:00 -07001585 pw.println();
1586 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001587 for (int i=0; i<mActiveServices.size(); i++) {
1588 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001589 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001590 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001591 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001592 continue;
1593 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001594 pw.println(jsc.getRunningJob().toShortString());
1595 pw.print(" Running for: ");
1596 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1597 pw.print(", timeout at: ");
1598 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1599 pw.println();
1600 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001601 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1602 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001603 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001604 }
Christopher Tate7060b042014-06-09 19:50:00 -07001605 }
1606 }
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -07001607 if (filterUid == -1) {
1608 pw.println();
1609 pw.print("mReadyToRock="); pw.println(mReadyToRock);
1610 pw.print("mReportedActive="); pw.println(mReportedActive);
1611 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
1612 }
Christopher Tate7060b042014-06-09 19:50:00 -07001613 }
1614 pw.println();
1615 }
1616}