blob: e34fb9bef2babecb43fe3ffb82ed1a72bda09f2c [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;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070043import android.os.BatteryStats;
Christopher Tate7060b042014-06-09 19:50:00 -070044import android.os.Binder;
45import android.os.Handler;
46import android.os.Looper;
47import android.os.Message;
Shreyas Basargecbf5ae92016-03-08 16:13:06 +000048import android.os.Process;
Dianne Hackborn88e98df2015-03-23 13:29:14 -070049import android.os.PowerManager;
Christopher Tate7060b042014-06-09 19:50:00 -070050import android.os.RemoteException;
Christopher Tate5d346052016-03-08 12:56:08 -080051import android.os.ResultReceiver;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070052import android.os.ServiceManager;
Christopher Tate7060b042014-06-09 19:50:00 -070053import android.os.SystemClock;
54import android.os.UserHandle;
55import android.util.Slog;
56import android.util.SparseArray;
Dianne Hackborn970510b2016-02-24 16:56:42 -080057import android.util.SparseIntArray;
58import android.util.TimeUtils;
Christopher Tate5d346052016-03-08 12:56:08 -080059
Dianne Hackbornfdb19562014-07-11 16:03:36 -070060import com.android.internal.app.IBatteryStats;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070061import com.android.internal.util.ArrayUtils;
Dianne Hackborn970510b2016-02-24 16:56:42 -080062import com.android.internal.app.ProcessStats;
Dianne Hackborn627dfa12015-11-11 18:10:30 -080063import com.android.server.DeviceIdleController;
64import com.android.server.LocalServices;
Christopher Tate2f36fd62016-02-18 18:36:08 -080065import com.android.server.job.JobStore.JobStatusFunctor;
Amith Yamasanib0ff3222015-03-04 09:56:14 -080066import com.android.server.job.controllers.AppIdleController;
Christopher Tate7060b042014-06-09 19:50:00 -070067import com.android.server.job.controllers.BatteryController;
68import com.android.server.job.controllers.ConnectivityController;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080069import com.android.server.job.controllers.ContentObserverController;
Christopher Tate7060b042014-06-09 19:50:00 -070070import com.android.server.job.controllers.IdleController;
71import com.android.server.job.controllers.JobStatus;
72import com.android.server.job.controllers.StateController;
73import com.android.server.job.controllers.TimeController;
74
Jeff Sharkey822cbd12016-02-25 11:09:55 -070075import libcore.util.EmptyArray;
76
Christopher Tate7060b042014-06-09 19:50:00 -070077/**
78 * Responsible for taking jobs representing work to be performed by a client app, and determining
79 * based on the criteria specified when that job should be run against the client application's
80 * endpoint.
81 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
82 * about constraints, or the state of active jobs. It receives callbacks from the various
83 * controllers and completed jobs and operates accordingly.
84 *
85 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
86 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
87 * @hide
88 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -080089public final class JobSchedulerService extends com.android.server.SystemService
Matthew Williams01ac45b2014-07-22 20:44:12 -070090 implements StateChangedListener, JobCompletedListener {
Christopher Tate2f36fd62016-02-18 18:36:08 -080091 static final String TAG = "JobSchedulerService";
Matthew Williamsaa984312015-10-15 16:08:05 -070092 public static final boolean DEBUG = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -080093
Dianne Hackborn970510b2016-02-24 16:56:42 -080094 /** The maximum number of concurrent jobs we run at one time. */
95 private static final int MAX_JOB_CONTEXTS_COUNT = 8;
Christopher Tatedabdf6f2016-02-24 12:30:22 -080096 /** Enforce a per-app limit on scheduled jobs? */
Christopher Tate0213ace02016-02-24 14:18:35 -080097 private static final boolean ENFORCE_MAX_JOBS = true;
Christopher Tate2f36fd62016-02-18 18:36:08 -080098 /** The maximum number of jobs that we allow an unprivileged app to schedule */
99 private static final int MAX_JOBS_PER_APP = 100;
100
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800101 /** Global local for all job scheduler state. */
102 final Object mLock = new Object();
Christopher Tate7060b042014-06-09 19:50:00 -0700103 /** Master list of jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700104 final JobStore mJobs;
Christopher Tate7060b042014-06-09 19:50:00 -0700105
106 static final int MSG_JOB_EXPIRED = 0;
107 static final int MSG_CHECK_JOB = 1;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700108 static final int MSG_STOP_JOB = 2;
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000109 static final int MSG_CHECK_JOB_GREEDY = 3;
Christopher Tate7060b042014-06-09 19:50:00 -0700110
111 // Policy constants
112 /**
113 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
114 * early.
115 */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700116 static final int MIN_IDLE_COUNT = 1;
Christopher Tate7060b042014-06-09 19:50:00 -0700117 /**
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700118 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things
119 * early.
120 */
121 static final int MIN_CHARGING_COUNT = 1;
122 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700123 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
124 * things early.
125 */
Matthew Williamsaa984312015-10-15 16:08:05 -0700126 static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready.
Christopher Tate7060b042014-06-09 19:50:00 -0700127 /**
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800128 * Minimum # of content trigger jobs that must be ready in order to force the JMS to schedule
129 * things early.
130 */
131 static final int MIN_CONTENT_COUNT = 1;
132 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700133 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
134 * some work early.
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700135 * This is correlated with the amount of batching we'll be able to do.
Christopher Tate7060b042014-06-09 19:50:00 -0700136 */
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700137 static final int MIN_READY_JOBS_COUNT = 2;
Christopher Tate7060b042014-06-09 19:50:00 -0700138
139 /**
140 * Track Services that have currently active or pending jobs. The index is provided by
141 * {@link JobStatus#getServiceToken()}
142 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700143 final List<JobServiceContext> mActiveServices = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700144 /** List of controllers that will notify this service of updates to jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700145 List<StateController> mControllers;
Christopher Tate7060b042014-06-09 19:50:00 -0700146 /**
147 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
148 * when ready to execute them.
149 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700150 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700151
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700152 int[] mStartedUsers = EmptyArray.INT;
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700153
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700154 final JobHandler mHandler;
155 final JobSchedulerStub mJobSchedulerStub;
156
157 IBatteryStats mBatteryStats;
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700158 PowerManager mPowerManager;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800159 DeviceIdleController.LocalService mLocalDeviceIdleController;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700160
161 /**
162 * Set to true once we are allowed to run third party apps.
163 */
164 boolean mReadyToRock;
165
Christopher Tate7060b042014-06-09 19:50:00 -0700166 /**
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700167 * True when in device idle mode, so we don't want to schedule any jobs.
168 */
169 boolean mDeviceIdleMode;
170
171 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800172 * What we last reported to DeviceIdleController about whether we are active.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800173 */
174 boolean mReportedActive;
175
176 /**
Dianne Hackborn970510b2016-02-24 16:56:42 -0800177 * Current limit on the number of concurrent JobServiceContext entries we want to
178 * keep actively running a job.
179 */
180 int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
181
182 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800183 * Which uids are currently in the foreground.
184 */
Dianne Hackborn970510b2016-02-24 16:56:42 -0800185 final SparseIntArray mUidPriorityOverride = new SparseIntArray();
186
187 // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
188
189 /**
190 * This array essentially stores the state of mActiveServices array.
191 * The ith index stores the job present on the ith JobServiceContext.
192 * We manipulate this array until we arrive at what jobs should be running on
193 * what JobServiceContext.
194 */
195 JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
196 /**
197 * Indicates whether we need to act on this jobContext id
198 */
199 boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
200 /**
201 * The uid whose jobs we would like to assign to a context.
202 */
203 int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800204
205 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700206 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
207 * still clean up. On reinstall the package will have a new uid.
208 */
209 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
210 @Override
211 public void onReceive(Context context, Intent intent) {
Dianne Hackborn2fefbcf2016-03-18 15:34:54 -0700212 if (DEBUG) {
213 Slog.d(TAG, "Receieved: " + intent.getAction());
214 }
Shreyas Basarge5db09082016-01-07 13:38:29 +0000215 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700216 // If this is an outright uninstall rather than the first half of an
217 // app update sequence, cancel the jobs associated with the app.
218 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
219 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
220 if (DEBUG) {
221 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
222 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700223 cancelJobsForUid(uidRemoved, true);
Christopher Tate7060b042014-06-09 19:50:00 -0700224 }
Shreyas Basarge5db09082016-01-07 13:38:29 +0000225 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700226 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
227 if (DEBUG) {
228 Slog.d(TAG, "Removing jobs for user: " + userId);
229 }
230 cancelJobsForUser(userId);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000231 } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
232 || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700233 updateIdleMode(mPowerManager != null
234 ? (mPowerManager.isDeviceIdleMode()
Shreyas Basarge5db09082016-01-07 13:38:29 +0000235 || mPowerManager.isLightDeviceIdleMode())
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700236 : false);
Christopher Tate7060b042014-06-09 19:50:00 -0700237 }
238 }
239 };
240
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700241 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
242 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800243 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700244 }
245
246 @Override public void onUidGone(int uid) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800247 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700248 }
249
250 @Override public void onUidActive(int uid) throws RemoteException {
251 }
252
253 @Override public void onUidIdle(int uid) throws RemoteException {
254 cancelJobsForUid(uid, false);
255 }
256 };
257
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800258 public Object getLock() {
259 return mLock;
260 }
261
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700262 @Override
263 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700264 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
265 // Let's kick any outstanding jobs for this user.
266 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
267 }
268
269 @Override
270 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700271 // Let's kick any outstanding jobs for this user.
272 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
273 }
274
275 @Override
276 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700277 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700278 }
279
Christopher Tate7060b042014-06-09 19:50:00 -0700280 /**
281 * Entry point from client to schedule the provided job.
282 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
283 * @param job JobInfo object containing execution parameters
284 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700285 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
286 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700287 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800288 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000289 }
290
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800291 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
292 String tag) {
293 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700294 try {
295 if (ActivityManagerNative.getDefault().getAppStartMode(uId,
296 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
297 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
298 + " -- package not allowed to start");
299 return JobScheduler.RESULT_FAILURE;
300 }
301 } catch (RemoteException e) {
302 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800303 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
304 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800305 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800306 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800307 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800308 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
309 Slog.w(TAG, "Too many jobs for uid " + uId);
310 throw new IllegalStateException("Apps may not schedule more than "
311 + MAX_JOBS_PER_APP + " distinct jobs");
312 }
313 }
314
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800315 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
Christopher Tateb1c1f9a2016-03-17 13:29:25 -0700316 if (toCancel != null) {
317 cancelJobImpl(toCancel);
318 }
319 startTrackingJob(jobStatus, toCancel);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800320 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700321 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700322 return JobScheduler.RESULT_SUCCESS;
323 }
324
325 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800326 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800327 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
328 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
329 for (int i = jobs.size() - 1; i >= 0; i--) {
330 JobStatus job = jobs.get(i);
331 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700332 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800333 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700334 }
Christopher Tate7060b042014-06-09 19:50:00 -0700335 }
336
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700337 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700338 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800339 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700340 jobsForUser = mJobs.getJobsByUser(userHandle);
341 }
342 for (int i=0; i<jobsForUser.size(); i++) {
343 JobStatus toRemove = jobsForUser.get(i);
344 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700345 }
346 }
347
348 /**
349 * Entry point from client to cancel all jobs originating from their uid.
350 * This will remove the job from the master list, and cancel the job if it was staged for
351 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700352 * @param uid Uid to check against for removal of a job.
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700353 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
354 * whose apps are stopped.
Christopher Tate7060b042014-06-09 19:50:00 -0700355 */
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700356 public void cancelJobsForUid(int uid, boolean forceAll) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700357 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800358 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700359 jobsForUid = mJobs.getJobsByUid(uid);
360 }
361 for (int i=0; i<jobsForUid.size(); i++) {
362 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700363 if (!forceAll) {
364 String packageName = toRemove.getServiceComponent().getPackageName();
365 try {
366 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
367 != ActivityManager.APP_START_MODE_DISABLED) {
368 continue;
369 }
370 } catch (RemoteException e) {
371 }
372 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700373 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700374 }
375 }
376
377 /**
378 * Entry point from client to cancel the job corresponding to the jobId provided.
379 * This will remove the job from the master list, and cancel the job if it was staged for
380 * execution or being executed.
381 * @param uid Uid of the calling client.
382 * @param jobId Id of the job, provided at schedule-time.
383 */
384 public void cancelJob(int uid, int jobId) {
385 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800386 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700387 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700388 }
389 if (toCancel != null) {
390 cancelJobImpl(toCancel);
Christopher Tate7060b042014-06-09 19:50:00 -0700391 }
392 }
393
Matthew Williams48a30db2014-09-23 13:39:36 -0700394 private void cancelJobImpl(JobStatus cancelled) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800395 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Shreyas Basarge73f10252016-02-11 17:06:13 +0000396 stopTrackingJob(cancelled, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800397 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700398 // Remove from pending queue.
399 mPendingJobs.remove(cancelled);
400 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000401 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800402 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700403 }
Christopher Tate7060b042014-06-09 19:50:00 -0700404 }
405
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800406 void updateUidState(int uid, int procState) {
407 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800408 if (procState == ActivityManager.PROCESS_STATE_TOP) {
409 // Only use this if we are exactly the top app. All others can live
410 // with just the foreground priority. This means that persistent processes
411 // can never be the top app priority... that is fine.
412 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
413 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
414 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800415 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800416 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800417 }
418 }
419 }
420
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700421 void updateIdleMode(boolean enabled) {
422 boolean changed = false;
423 boolean rocking;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800424 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700425 if (mDeviceIdleMode != enabled) {
426 changed = true;
427 }
428 rocking = mReadyToRock;
429 }
430 if (changed) {
431 if (rocking) {
432 for (int i=0; i<mControllers.size(); i++) {
433 mControllers.get(i).deviceIdleModeChanged(enabled);
434 }
435 }
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800436 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700437 mDeviceIdleMode = enabled;
438 if (enabled) {
439 // When becoming idle, make sure no jobs are actively running.
440 for (int i=0; i<mActiveServices.size(); i++) {
441 JobServiceContext jsc = mActiveServices.get(i);
442 final JobStatus executing = jsc.getRunningJob();
443 if (executing != null) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000444 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700445 }
446 }
447 } else {
448 // When coming out of idle, allow thing to start back up.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800449 if (rocking) {
450 if (mLocalDeviceIdleController != null) {
451 if (!mReportedActive) {
452 mReportedActive = true;
453 mLocalDeviceIdleController.setJobsActive(true);
454 }
455 }
456 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700457 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
458 }
459 }
460 }
461 }
462
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800463 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000464 // active is true if pending queue contains jobs OR some job is running.
465 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800466 if (mPendingJobs.size() <= 0) {
467 for (int i=0; i<mActiveServices.size(); i++) {
468 JobServiceContext jsc = mActiveServices.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000469 if (jsc.getRunningJob() != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800470 active = true;
471 break;
472 }
473 }
474 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000475
476 if (mReportedActive != active) {
477 mReportedActive = active;
478 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800479 mLocalDeviceIdleController.setJobsActive(active);
480 }
481 }
482 }
483
Christopher Tate7060b042014-06-09 19:50:00 -0700484 /**
485 * Initializes the system service.
486 * <p>
487 * Subclasses must define a single argument constructor that accepts the context
488 * and passes it to super.
489 * </p>
490 *
491 * @param context The system server context.
492 */
493 public JobSchedulerService(Context context) {
494 super(context);
495 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700496 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700497 mControllers.add(ConnectivityController.get(this));
498 mControllers.add(TimeController.get(this));
499 mControllers.add(IdleController.get(this));
500 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800501 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800502 mControllers.add(ContentObserverController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700503
504 mHandler = new JobHandler(context.getMainLooper());
505 mJobSchedulerStub = new JobSchedulerStub();
Christopher Tate7060b042014-06-09 19:50:00 -0700506 mJobs = JobStore.initAndGet(this);
507 }
508
509 @Override
510 public void onStart() {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000511 publishLocalService(JobSchedulerInternal.class, new LocalService());
Christopher Tate7060b042014-06-09 19:50:00 -0700512 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
513 }
514
515 @Override
516 public void onBootPhase(int phase) {
517 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000518 // Register br for package removals and user removals.
Christopher Tate7060b042014-06-09 19:50:00 -0700519 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
520 filter.addDataScheme("package");
521 getContext().registerReceiverAsUser(
522 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
523 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700524 userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700525 userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
Christopher Tate7060b042014-06-09 19:50:00 -0700526 getContext().registerReceiverAsUser(
527 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000528 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700529 try {
530 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800531 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
532 | ActivityManager.UID_OBSERVER_IDLE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700533 } catch (RemoteException e) {
534 // ignored; both services live in system_server
535 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700536 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800537 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700538 // Let's go!
539 mReadyToRock = true;
540 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
541 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800542 mLocalDeviceIdleController
543 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700544 // Create the "runners".
545 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
546 mActiveServices.add(
547 new JobServiceContext(this, mBatteryStats,
548 getContext().getMainLooper()));
549 }
550 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800551 mJobs.forEachJob(new JobStatusFunctor() {
552 @Override
553 public void process(JobStatus job) {
554 for (int controller = 0; controller < mControllers.size(); controller++) {
555 final StateController sc = mControllers.get(controller);
556 sc.deviceIdleModeChanged(mDeviceIdleMode);
557 sc.maybeStartTrackingJobLocked(job, null);
558 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700559 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800560 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700561 // GO GO GO!
562 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
563 }
Christopher Tate7060b042014-06-09 19:50:00 -0700564 }
565 }
566
567 /**
568 * Called when we have a job status object that we need to insert in our
569 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
570 * about.
571 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800572 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800573 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800574 final boolean update = mJobs.add(jobStatus);
575 if (mReadyToRock) {
576 for (int i = 0; i < mControllers.size(); i++) {
577 StateController controller = mControllers.get(i);
578 if (update) {
579 controller.maybeStopTrackingJobLocked(jobStatus, true);
580 }
581 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700582 }
Christopher Tate7060b042014-06-09 19:50:00 -0700583 }
Christopher Tate7060b042014-06-09 19:50:00 -0700584 }
585 }
586
587 /**
588 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
589 * object removed.
590 */
Shreyas Basarge73f10252016-02-11 17:06:13 +0000591 private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800592 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700593 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800594 final boolean removed = mJobs.remove(jobStatus, writeBack);
595 if (removed && mReadyToRock) {
596 for (int i=0; i<mControllers.size(); i++) {
597 StateController controller = mControllers.get(i);
598 controller.maybeStopTrackingJobLocked(jobStatus, false);
599 }
Christopher Tate7060b042014-06-09 19:50:00 -0700600 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800601 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700602 }
Christopher Tate7060b042014-06-09 19:50:00 -0700603 }
604
Shreyas Basarge5db09082016-01-07 13:38:29 +0000605 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700606 for (int i=0; i<mActiveServices.size(); i++) {
607 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700608 final JobStatus executing = jsc.getRunningJob();
609 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000610 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700611 return true;
612 }
613 }
614 return false;
615 }
616
617 /**
618 * @param job JobStatus we are querying against.
619 * @return Whether or not the job represented by the status object is currently being run or
620 * is pending.
621 */
622 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700623 for (int i=0; i<mActiveServices.size(); i++) {
624 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700625 final JobStatus running = serviceContext.getRunningJob();
626 if (running != null && running.matches(job.getUid(), job.getJobId())) {
627 return true;
628 }
629 }
630 return false;
631 }
632
633 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700634 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
635 * specify an override deadline on a failed job (the failed job will run even though it's not
636 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
637 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
638 *
Christopher Tate7060b042014-06-09 19:50:00 -0700639 * @param failureToReschedule Provided job status that we will reschedule.
640 * @return A newly instantiated JobStatus with the same constraints as the last job except
641 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700642 *
643 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700644 */
645 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
646 final long elapsedNowMillis = SystemClock.elapsedRealtime();
647 final JobInfo job = failureToReschedule.getJob();
648
649 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700650 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
651 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700652
653 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700654 case JobInfo.BACKOFF_POLICY_LINEAR:
655 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700656 break;
657 default:
658 if (DEBUG) {
659 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
660 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700661 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
662 delayMillis =
663 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700664 break;
665 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700666 delayMillis =
667 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800668 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700669 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800670 for (int ic=0; ic<mControllers.size(); ic++) {
671 StateController controller = mControllers.get(ic);
672 controller.rescheduleForFailure(newJob, failureToReschedule);
673 }
674 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700675 }
676
677 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700678 * Called after a periodic has executed so we can reschedule it. We take the last execution
679 * time of the job to be the time of completion (i.e. the time at which this function is
680 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700681 * This could be inaccurate b/c the job can run for as long as
682 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
683 * to underscheduling at least, rather than if we had taken the last execution time to be the
684 * start of the execution.
685 * @return A new job representing the execution criteria for this instantiation of the
686 * recurring job.
687 */
688 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
689 final long elapsedNow = SystemClock.elapsedRealtime();
690 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700691 long runEarly = 0L;
692
693 // If this periodic was rescheduled it won't have a deadline.
694 if (periodicToReschedule.hasDeadlineConstraint()) {
695 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
696 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000697 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -0700698 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000699 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
700 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -0700701
702 if (DEBUG) {
703 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
704 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
705 }
706 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
707 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
708 }
709
710 // JobCompletedListener implementations.
711
712 /**
713 * A job just finished executing. We fetch the
714 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
715 * whether we want to reschedule we readd it to the controllers.
716 * @param jobStatus Completed job.
717 * @param needsReschedule Whether the implementing class should reschedule this job.
718 */
719 @Override
720 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
721 if (DEBUG) {
722 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
723 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000724 // Do not write back immediately if this is a periodic job. The job may get lost if system
725 // shuts down before it is added back.
726 if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700727 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700728 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -0700729 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800730 // We still want to check for jobs to execute, because this job may have
731 // scheduled a new job under the same job id, and now we can run it.
732 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700733 return;
734 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800735 // Note: there is a small window of time in here where, when rescheduling a job,
736 // we will stop monitoring its content providers. This should be fixed by stopping
737 // the old job after scheduling the new one, but since we have no lock held here
738 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -0700739 if (needsReschedule) {
740 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800741 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700742 } else if (jobStatus.getJob().isPeriodic()) {
743 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800744 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700745 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000746 reportActive();
747 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700748 }
749
750 // StateChangedListener implementations.
751
752 /**
Matthew Williams48a30db2014-09-23 13:39:36 -0700753 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
754 * some controller's state has changed, so as to run through the list of jobs and start/stop
755 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -0700756 */
757 @Override
758 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700759 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700760 }
761
762 @Override
763 public void onRunJobNow(JobStatus jobStatus) {
764 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
765 }
766
Christopher Tate7060b042014-06-09 19:50:00 -0700767 private class JobHandler extends Handler {
768
769 public JobHandler(Looper looper) {
770 super(looper);
771 }
772
773 @Override
774 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800775 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700776 if (!mReadyToRock) {
777 return;
778 }
779 }
Christopher Tate7060b042014-06-09 19:50:00 -0700780 switch (message.what) {
781 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800782 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700783 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700784 // runNow can be null, which is a controller's way of indicating that its
785 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -0700786 if (runNow != null && !mPendingJobs.contains(runNow)
787 && mJobs.containsJob(runNow)) {
Christopher Tate7060b042014-06-09 19:50:00 -0700788 mPendingJobs.add(runNow);
789 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700790 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -0700791 }
Christopher Tate7060b042014-06-09 19:50:00 -0700792 break;
793 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800794 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000795 if (mReportedActive) {
796 // if jobs are currently being run, queue all ready jobs for execution.
797 queueReadyJobsForExecutionLockedH();
798 } else {
799 // Check the list of jobs and run some of them if we feel inclined.
800 maybeQueueReadyJobsForExecutionLockedH();
801 }
802 }
803 break;
804 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800805 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000806 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -0700807 }
Christopher Tate7060b042014-06-09 19:50:00 -0700808 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700809 case MSG_STOP_JOB:
810 cancelJobImpl((JobStatus)message.obj);
811 break;
Christopher Tate7060b042014-06-09 19:50:00 -0700812 }
813 maybeRunPendingJobsH();
814 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
815 removeMessages(MSG_CHECK_JOB);
816 }
817
818 /**
819 * Run through list of jobs and execute all possible - at least one is expired so we do
820 * as many as we can.
821 */
Matthew Williams48a30db2014-09-23 13:39:36 -0700822 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700823 if (DEBUG) {
824 Slog.d(TAG, "queuing all ready jobs for execution:");
825 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800826 mPendingJobs.clear();
827 mJobs.forEachJob(mReadyQueueFunctor);
828 mReadyQueueFunctor.postProcess();
829
Matthew Williams48a30db2014-09-23 13:39:36 -0700830 if (DEBUG) {
831 final int queuedJobs = mPendingJobs.size();
832 if (queuedJobs == 0) {
833 Slog.d(TAG, "No jobs pending.");
834 } else {
835 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -0700836 }
Christopher Tate7060b042014-06-09 19:50:00 -0700837 }
838 }
839
Christopher Tate2f36fd62016-02-18 18:36:08 -0800840 class ReadyJobQueueFunctor implements JobStatusFunctor {
841 ArrayList<JobStatus> newReadyJobs;
842
843 @Override
844 public void process(JobStatus job) {
845 if (isReadyToBeExecutedLocked(job)) {
846 if (DEBUG) {
847 Slog.d(TAG, " queued " + job.toShortString());
848 }
849 if (newReadyJobs == null) {
850 newReadyJobs = new ArrayList<JobStatus>();
851 }
852 newReadyJobs.add(job);
853 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
854 stopJobOnServiceContextLocked(job,
855 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
856 }
857 }
858
859 public void postProcess() {
860 if (newReadyJobs != null) {
861 mPendingJobs.addAll(newReadyJobs);
862 }
863 newReadyJobs = null;
864 }
865 }
866 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
867
Christopher Tate7060b042014-06-09 19:50:00 -0700868 /**
869 * The state of at least one job has changed. Here is where we could enforce various
870 * policies on when we want to execute jobs.
871 * Right now the policy is such:
872 * If >1 of the ready jobs is idle mode we send all of them off
873 * if more than 2 network connectivity jobs are ready we send them all off.
874 * If more than 4 jobs total are ready we send them all off.
875 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
876 */
Christopher Tate2f36fd62016-02-18 18:36:08 -0800877 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
878 int chargingCount;
879 int idleCount;
880 int backoffCount;
881 int connectivityCount;
882 int contentCount;
883 List<JobStatus> runnableJobs;
884
885 public MaybeReadyJobQueueFunctor() {
886 reset();
887 }
888
889 // Functor method invoked for each job via JobStore.forEachJob()
890 @Override
891 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700892 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700893 try {
894 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
895 job.getJob().getService().getPackageName())
896 == ActivityManager.APP_START_MODE_DISABLED) {
897 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
898 + job.getJob().toString() + " -- package not allowed to start");
899 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800900 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700901 }
902 } catch (RemoteException e) {
903 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700904 if (job.getNumFailures() > 0) {
905 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -0700906 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700907 if (job.hasIdleConstraint()) {
908 idleCount++;
909 }
910 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
911 connectivityCount++;
912 }
913 if (job.hasChargingConstraint()) {
914 chargingCount++;
915 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800916 if (job.hasContentTriggerConstraint()) {
917 contentCount++;
918 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700919 if (runnableJobs == null) {
920 runnableJobs = new ArrayList<>();
921 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700922 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800923 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000924 stopJobOnServiceContextLocked(job,
925 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -0700926 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700927 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800928
929 public void postProcess() {
930 if (backoffCount > 0 ||
931 idleCount >= MIN_IDLE_COUNT ||
932 connectivityCount >= MIN_CONNECTIVITY_COUNT ||
933 chargingCount >= MIN_CHARGING_COUNT ||
934 contentCount >= MIN_CONTENT_COUNT ||
935 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
936 if (DEBUG) {
937 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
938 }
939 mPendingJobs.addAll(runnableJobs);
940 } else {
941 if (DEBUG) {
942 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
943 }
Christopher Tate7060b042014-06-09 19:50:00 -0700944 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800945
946 // Be ready for next time
947 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -0700948 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800949
950 private void reset() {
951 chargingCount = 0;
952 idleCount = 0;
953 backoffCount = 0;
954 connectivityCount = 0;
955 contentCount = 0;
956 runnableJobs = null;
957 }
958 }
959 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
960
961 private void maybeQueueReadyJobsForExecutionLockedH() {
962 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
963
964 mPendingJobs.clear();
965 mJobs.forEachJob(mMaybeQueueFunctor);
966 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -0700967 }
968
969 /**
970 * Criteria for moving a job into the pending queue:
971 * - It's ready.
972 * - It's not pending.
973 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700974 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700975 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -0700976 */
977 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700978 final boolean jobReady = job.isReady();
979 final boolean jobPending = mPendingJobs.contains(job);
980 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700981
982 final int userId = job.getUserId();
983 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
984 final boolean componentPresent;
985 try {
986 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
987 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
988 userId) != null);
989 } catch (RemoteException e) {
990 throw e.rethrowAsRuntimeException();
991 }
992
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700993 if (DEBUG) {
994 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
995 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700996 + " active=" + jobActive + " userStarted=" + userStarted
997 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700998 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700999 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -07001000 }
1001
1002 /**
1003 * Criteria for cancelling an active job:
1004 * - It's not ready
1005 * - It's running on a JSC.
1006 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001007 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001008 return !job.isReady() && isCurrentlyActiveLocked(job);
1009 }
1010
1011 /**
1012 * Reconcile jobs in the pending queue against available execution contexts.
1013 * A controller can force a job into the pending queue even if it's already running, but
1014 * here is where we decide whether to actually execute it.
1015 */
1016 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001017 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -07001018 if (mDeviceIdleMode) {
1019 // If device is idle, we will not schedule jobs to run.
1020 return;
1021 }
Matthew Williams75fc5252014-09-02 16:17:53 -07001022 if (DEBUG) {
1023 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1024 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001025 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001026 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001027 }
1028 }
1029 }
1030
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001031 private int evaluateJobPriorityLocked(JobStatus job) {
1032 int priority = job.getPriority();
1033 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
1034 return priority;
1035 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001036 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1037 if (override != 0) {
1038 return override;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001039 }
1040 return priority;
1041 }
1042
Christopher Tate7060b042014-06-09 19:50:00 -07001043 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001044 * Takes jobs from pending queue and runs them on available contexts.
1045 * If no contexts are available, preempts lower priority jobs to
1046 * run higher priority ones.
1047 * Lock on mJobs before calling this function.
1048 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001049 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001050 if (DEBUG) {
1051 Slog.d(TAG, printPendingQueue());
1052 }
1053
Dianne Hackborn970510b2016-02-24 16:56:42 -08001054 int memLevel;
1055 try {
1056 memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
1057 } catch (RemoteException e) {
1058 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1059 }
1060 switch (memLevel) {
1061 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
1062 mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - 2) * 2) / 3;
1063 break;
1064 case ProcessStats.ADJ_MEM_FACTOR_LOW:
1065 mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - 2) / 3;
1066 break;
1067 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
1068 mMaxActiveJobs = 1;
1069 break;
1070 default:
1071 mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
1072 break;
1073 }
1074
1075 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1076 boolean[] act = mTmpAssignAct;
1077 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1078 int numActive = 0;
1079 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1080 final JobServiceContext js = mActiveServices.get(i);
1081 if ((contextIdToJobMap[i] = js.getRunningJob()) != null) {
1082 numActive++;
1083 }
1084 act[i] = false;
1085 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001086 }
1087 if (DEBUG) {
1088 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1089 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001090 for (int i=0; i<mPendingJobs.size(); i++) {
1091 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001092
1093 // If job is already running, go to next job.
1094 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1095 if (jobRunningContext != -1) {
1096 continue;
1097 }
1098
Dianne Hackborn970510b2016-02-24 16:56:42 -08001099 final int priority = evaluateJobPriorityLocked(nextPending);
1100 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001101
Shreyas Basarge5db09082016-01-07 13:38:29 +00001102 // Find a context for nextPending. The context should be available OR
1103 // it should have lowest priority among all running jobs
1104 // (sharing the same Uid as nextPending)
1105 int minPriority = Integer.MAX_VALUE;
1106 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001107 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1108 JobStatus job = contextIdToJobMap[j];
1109 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001110 if (job == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001111 if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) &&
1112 (preferredUid == nextPending.getUid() ||
1113 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1114 // This slot is free, and we haven't yet hit the limit on
1115 // concurrent jobs... we can just throw the job in to here.
1116 minPriorityContextId = j;
1117 numActive++;
1118 break;
1119 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001120 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001121 // the context has a preferred Uid or we have reached the limit on
1122 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001123 continue;
1124 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001125 if (job.getUid() != nextPending.getUid()) {
1126 continue;
1127 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001128 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001129 continue;
1130 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001131 if (minPriority > nextPending.lastEvaluatedPriority) {
1132 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001133 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001134 }
1135 }
1136 if (minPriorityContextId != -1) {
1137 contextIdToJobMap[minPriorityContextId] = nextPending;
1138 act[minPriorityContextId] = true;
1139 }
1140 }
1141 if (DEBUG) {
1142 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1143 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001144 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001145 boolean preservePreferredUid = false;
1146 if (act[i]) {
1147 JobStatus js = mActiveServices.get(i).getRunningJob();
1148 if (js != null) {
1149 if (DEBUG) {
1150 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1151 }
1152 // preferredUid will be set to uid of currently running job.
1153 mActiveServices.get(i).preemptExecutingJob();
1154 preservePreferredUid = true;
1155 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001156 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001157 if (DEBUG) {
1158 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001159 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001160 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001161 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001162 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001163 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001164 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1165 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001166 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001167 mPendingJobs.remove(pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001168 }
1169 }
1170 if (!preservePreferredUid) {
1171 mActiveServices.get(i).clearPreferredUid();
1172 }
1173 }
1174 }
1175
1176 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1177 for (int i=0; i<map.length; i++) {
1178 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1179 return i;
1180 }
1181 }
1182 return -1;
1183 }
1184
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001185 final class LocalService implements JobSchedulerInternal {
1186
1187 /**
1188 * Returns a list of all pending jobs. A running job is not considered pending. Periodic
1189 * jobs are always considered pending.
1190 */
1191 public List<JobInfo> getSystemScheduledPendingJobs() {
1192 synchronized (mLock) {
1193 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
1194 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
1195 @Override
1196 public void process(JobStatus job) {
1197 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
1198 pendingJobs.add(job.getJob());
1199 }
1200 }
1201 });
1202 return pendingJobs;
1203 }
1204 }
1205 }
1206
Shreyas Basarge5db09082016-01-07 13:38:29 +00001207 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001208 * Binder stub trampoline implementation
1209 */
1210 final class JobSchedulerStub extends IJobScheduler.Stub {
1211 /** Cache determination of whether a given app can persist jobs
1212 * key is uid of the calling app; value is undetermined/true/false
1213 */
1214 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1215
1216 // Enforce that only the app itself (or shared uid participant) can schedule a
1217 // job that runs one of the app's services, as well as verifying that the
1218 // named service properly requires the BIND_JOB_SERVICE permission
1219 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001220 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001221 final ComponentName service = job.getService();
1222 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001223 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey8a372a02016-03-16 16:25:45 -06001224 PackageManager.MATCH_DIRECT_BOOT_AWARE
1225 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001226 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001227 if (si == null) {
1228 throw new IllegalArgumentException("No such service " + service);
1229 }
Christopher Tate7060b042014-06-09 19:50:00 -07001230 if (si.applicationInfo.uid != uid) {
1231 throw new IllegalArgumentException("uid " + uid +
1232 " cannot schedule job in " + service.getPackageName());
1233 }
1234 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1235 throw new IllegalArgumentException("Scheduled service " + service
1236 + " does not require android.permission.BIND_JOB_SERVICE permission");
1237 }
Christopher Tate5568f542014-06-18 13:53:31 -07001238 } catch (RemoteException e) {
1239 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001240 }
1241 }
1242
1243 private boolean canPersistJobs(int pid, int uid) {
1244 // If we get this far we're good to go; all we need to do now is check
1245 // whether the app is allowed to persist its scheduled work.
1246 final boolean canPersist;
1247 synchronized (mPersistCache) {
1248 Boolean cached = mPersistCache.get(uid);
1249 if (cached != null) {
1250 canPersist = cached.booleanValue();
1251 } else {
1252 // Persisting jobs is tantamount to running at boot, so we permit
1253 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1254 // permission
1255 int result = getContext().checkPermission(
1256 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1257 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1258 mPersistCache.put(uid, canPersist);
1259 }
1260 }
1261 return canPersist;
1262 }
1263
1264 // IJobScheduler implementation
1265 @Override
1266 public int schedule(JobInfo job) throws RemoteException {
1267 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001268 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001269 }
1270 final int pid = Binder.getCallingPid();
1271 final int uid = Binder.getCallingUid();
1272
1273 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001274 if (job.isPersisted()) {
1275 if (!canPersistJobs(pid, uid)) {
1276 throw new IllegalArgumentException("Error: requested job be persisted without"
1277 + " holding RECEIVE_BOOT_COMPLETED permission.");
1278 }
1279 }
Christopher Tate7060b042014-06-09 19:50:00 -07001280
1281 long ident = Binder.clearCallingIdentity();
1282 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001283 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001284 } finally {
1285 Binder.restoreCallingIdentity(ident);
1286 }
1287 }
1288
1289 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001290 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001291 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001292 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001293 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001294 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1295 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001296 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001297
1298 if (packageName == null) {
1299 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001300 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001301
1302 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1303 android.Manifest.permission.UPDATE_DEVICE_STATS);
1304 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1305 throw new SecurityException("Caller uid " + callerUid
1306 + " not permitted to schedule jobs for other apps");
1307 }
1308
Shreyas Basarge968ac752016-01-11 23:09:26 +00001309 long ident = Binder.clearCallingIdentity();
1310 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001311 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001312 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001313 } finally {
1314 Binder.restoreCallingIdentity(ident);
1315 }
1316 }
1317
1318 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001319 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1320 final int uid = Binder.getCallingUid();
1321
1322 long ident = Binder.clearCallingIdentity();
1323 try {
1324 return JobSchedulerService.this.getPendingJobs(uid);
1325 } finally {
1326 Binder.restoreCallingIdentity(ident);
1327 }
1328 }
1329
1330 @Override
1331 public void cancelAll() throws RemoteException {
1332 final int uid = Binder.getCallingUid();
1333
1334 long ident = Binder.clearCallingIdentity();
1335 try {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001336 JobSchedulerService.this.cancelJobsForUid(uid, true);
Christopher Tate7060b042014-06-09 19:50:00 -07001337 } finally {
1338 Binder.restoreCallingIdentity(ident);
1339 }
1340 }
1341
1342 @Override
1343 public void cancel(int jobId) throws RemoteException {
1344 final int uid = Binder.getCallingUid();
1345
1346 long ident = Binder.clearCallingIdentity();
1347 try {
1348 JobSchedulerService.this.cancelJob(uid, jobId);
1349 } finally {
1350 Binder.restoreCallingIdentity(ident);
1351 }
1352 }
1353
1354 /**
1355 * "dumpsys" infrastructure
1356 */
1357 @Override
1358 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1359 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1360
1361 long identityToken = Binder.clearCallingIdentity();
1362 try {
1363 JobSchedulerService.this.dumpInternal(pw);
1364 } finally {
1365 Binder.restoreCallingIdentity(identityToken);
1366 }
1367 }
Christopher Tate5d346052016-03-08 12:56:08 -08001368
1369 @Override
1370 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1371 String[] args, ResultReceiver resultReceiver) throws RemoteException {
1372 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
1373 this, in, out, err, args, resultReceiver);
1374 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001375 };
1376
Christopher Tate5d346052016-03-08 12:56:08 -08001377 // Shell command infrastructure: run the given job immediately
1378 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1379 if (DEBUG) {
1380 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1381 + " " + jobId + " f=" + force);
1382 }
1383
1384 try {
1385 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1386 if (uid < 0) {
1387 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1388 }
1389
1390 synchronized (mLock) {
1391 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1392 if (js == null) {
1393 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1394 }
1395
1396 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1397 if (!js.isConstraintsSatisfied()) {
1398 js.overrideState = 0;
1399 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1400 }
1401
1402 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1403 }
1404 } catch (RemoteException e) {
1405 // can't happen
1406 }
1407 return 0;
1408 }
1409
Shreyas Basarge5db09082016-01-07 13:38:29 +00001410 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1411 StringBuilder s = new StringBuilder(initial + ": ");
1412 for (int i=0; i<map.length; i++) {
1413 s.append("(")
1414 .append(map[i] == null? -1: map[i].getJobId())
1415 .append(map[i] == null? -1: map[i].getUid())
1416 .append(")" );
1417 }
1418 return s.toString();
1419 }
1420
1421 private String printPendingQueue() {
1422 StringBuilder s = new StringBuilder("Pending queue: ");
1423 Iterator<JobStatus> it = mPendingJobs.iterator();
1424 while (it.hasNext()) {
1425 JobStatus js = it.next();
1426 s.append("(")
1427 .append(js.getJob().getId())
1428 .append(", ")
1429 .append(js.getUid())
1430 .append(") ");
1431 }
1432 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001433 }
Christopher Tate7060b042014-06-09 19:50:00 -07001434
Christopher Tate2f36fd62016-02-18 18:36:08 -08001435 void dumpInternal(final PrintWriter pw) {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001436 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001437 synchronized (mLock) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001438 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Christopher Tate7060b042014-06-09 19:50:00 -07001439 pw.println("Registered jobs:");
1440 if (mJobs.size() > 0) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001441 mJobs.forEachJob(new JobStatusFunctor() {
1442 private int index = 0;
1443
1444 @Override
1445 public void process(JobStatus job) {
1446 pw.print(" Job #"); pw.print(index++); pw.print(": ");
1447 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001448 job.dump(pw, " ", true);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001449 pw.print(" Ready: ");
1450 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1451 pw.print(" (job=");
1452 pw.print(job.isReady());
1453 pw.print(" pending=");
1454 pw.print(mPendingJobs.contains(job));
1455 pw.print(" active=");
1456 pw.print(isCurrentlyActiveLocked(job));
1457 pw.print(" user=");
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001458 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
Christopher Tate2f36fd62016-02-18 18:36:08 -08001459 pw.println(")");
1460 }
1461 });
Christopher Tate7060b042014-06-09 19:50:00 -07001462 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001463 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001464 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001465 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001466 pw.println();
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001467 mControllers.get(i).dumpControllerStateLocked(pw);
Christopher Tate7060b042014-06-09 19:50:00 -07001468 }
1469 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001470 pw.println("Uid priority overrides:");
1471 for (int i=0; i< mUidPriorityOverride.size(); i++) {
1472 pw.print(" "); pw.print(UserHandle.formatUid(mUidPriorityOverride.keyAt(i)));
1473 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001474 }
1475 pw.println();
1476 pw.println("Pending queue:");
1477 for (int i=0; i<mPendingJobs.size(); i++) {
1478 JobStatus job = mPendingJobs.get(i);
1479 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1480 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001481 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001482 int priority = evaluateJobPriorityLocked(job);
1483 if (priority != JobInfo.PRIORITY_DEFAULT) {
1484 pw.print(" Evaluated priority: "); pw.println(priority);
1485 }
1486 pw.print(" Tag: "); pw.println(job.getTag());
1487 }
Christopher Tate7060b042014-06-09 19:50:00 -07001488 pw.println();
1489 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001490 for (int i=0; i<mActiveServices.size(); i++) {
1491 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001492 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001493 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001494 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001495 continue;
1496 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001497 pw.println(jsc.getRunningJob().toShortString());
1498 pw.print(" Running for: ");
1499 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1500 pw.print(", timeout at: ");
1501 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1502 pw.println();
1503 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001504 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1505 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001506 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001507 }
Christopher Tate7060b042014-06-09 19:50:00 -07001508 }
1509 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001510 pw.println();
1511 pw.print("mReadyToRock="); pw.println(mReadyToRock);
Dianne Hackborn88e98df2015-03-23 13:29:14 -07001512 pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001513 pw.print("mReportedActive="); pw.println(mReportedActive);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001514 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
Christopher Tate7060b042014-06-09 19:50:00 -07001515 }
1516 pw.println();
1517 }
1518}