blob: 075008a1907873beaf01a7b241a4caa1355a72f4 [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) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000212 Slog.d(TAG, "Receieved: " + intent.getAction());
213 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700214 // If this is an outright uninstall rather than the first half of an
215 // app update sequence, cancel the jobs associated with the app.
216 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
217 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
218 if (DEBUG) {
219 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
220 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700221 cancelJobsForUid(uidRemoved, true);
Christopher Tate7060b042014-06-09 19:50:00 -0700222 }
Shreyas Basarge5db09082016-01-07 13:38:29 +0000223 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700224 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
225 if (DEBUG) {
226 Slog.d(TAG, "Removing jobs for user: " + userId);
227 }
228 cancelJobsForUser(userId);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000229 } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
230 || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700231 updateIdleMode(mPowerManager != null
232 ? (mPowerManager.isDeviceIdleMode()
Shreyas Basarge5db09082016-01-07 13:38:29 +0000233 || mPowerManager.isLightDeviceIdleMode())
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700234 : false);
Christopher Tate7060b042014-06-09 19:50:00 -0700235 }
236 }
237 };
238
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700239 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
240 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800241 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700242 }
243
244 @Override public void onUidGone(int uid) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800245 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700246 }
247
248 @Override public void onUidActive(int uid) throws RemoteException {
249 }
250
251 @Override public void onUidIdle(int uid) throws RemoteException {
252 cancelJobsForUid(uid, false);
253 }
254 };
255
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800256 public Object getLock() {
257 return mLock;
258 }
259
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700260 @Override
261 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700262 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
263 // Let's kick any outstanding jobs for this user.
264 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
265 }
266
267 @Override
268 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700269 // Let's kick any outstanding jobs for this user.
270 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
271 }
272
273 @Override
274 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700275 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700276 }
277
Christopher Tate7060b042014-06-09 19:50:00 -0700278 /**
279 * Entry point from client to schedule the provided job.
280 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
281 * @param job JobInfo object containing execution parameters
282 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700283 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
284 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700285 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800286 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000287 }
288
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800289 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
290 String tag) {
291 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700292 try {
293 if (ActivityManagerNative.getDefault().getAppStartMode(uId,
294 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
295 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
296 + " -- package not allowed to start");
297 return JobScheduler.RESULT_FAILURE;
298 }
299 } catch (RemoteException e) {
300 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800301 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
302 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800303 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800304 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800305 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800306 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
307 Slog.w(TAG, "Too many jobs for uid " + uId);
308 throw new IllegalStateException("Apps may not schedule more than "
309 + MAX_JOBS_PER_APP + " distinct jobs");
310 }
311 }
312
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800313 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
314 }
315 startTrackingJob(jobStatus, toCancel);
316 if (toCancel != null) {
317 cancelJobImpl(toCancel);
318 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700319 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700320 return JobScheduler.RESULT_SUCCESS;
321 }
322
323 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800324 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800325 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
326 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
327 for (int i = jobs.size() - 1; i >= 0; i--) {
328 JobStatus job = jobs.get(i);
329 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700330 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800331 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700332 }
Christopher Tate7060b042014-06-09 19:50:00 -0700333 }
334
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700335 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700336 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800337 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700338 jobsForUser = mJobs.getJobsByUser(userHandle);
339 }
340 for (int i=0; i<jobsForUser.size(); i++) {
341 JobStatus toRemove = jobsForUser.get(i);
342 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700343 }
344 }
345
346 /**
347 * Entry point from client to cancel all jobs originating from their uid.
348 * This will remove the job from the master list, and cancel the job if it was staged for
349 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700350 * @param uid Uid to check against for removal of a job.
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700351 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
352 * whose apps are stopped.
Christopher Tate7060b042014-06-09 19:50:00 -0700353 */
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700354 public void cancelJobsForUid(int uid, boolean forceAll) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700355 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800356 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700357 jobsForUid = mJobs.getJobsByUid(uid);
358 }
359 for (int i=0; i<jobsForUid.size(); i++) {
360 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700361 if (!forceAll) {
362 String packageName = toRemove.getServiceComponent().getPackageName();
363 try {
364 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
365 != ActivityManager.APP_START_MODE_DISABLED) {
366 continue;
367 }
368 } catch (RemoteException e) {
369 }
370 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700371 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700372 }
373 }
374
375 /**
376 * Entry point from client to cancel the job corresponding to the jobId provided.
377 * This will remove the job from the master list, and cancel the job if it was staged for
378 * execution or being executed.
379 * @param uid Uid of the calling client.
380 * @param jobId Id of the job, provided at schedule-time.
381 */
382 public void cancelJob(int uid, int jobId) {
383 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800384 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700385 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700386 }
387 if (toCancel != null) {
388 cancelJobImpl(toCancel);
Christopher Tate7060b042014-06-09 19:50:00 -0700389 }
390 }
391
Matthew Williams48a30db2014-09-23 13:39:36 -0700392 private void cancelJobImpl(JobStatus cancelled) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800393 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Shreyas Basarge73f10252016-02-11 17:06:13 +0000394 stopTrackingJob(cancelled, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800395 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700396 // Remove from pending queue.
397 mPendingJobs.remove(cancelled);
398 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000399 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800400 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700401 }
Christopher Tate7060b042014-06-09 19:50:00 -0700402 }
403
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800404 void updateUidState(int uid, int procState) {
405 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800406 if (procState == ActivityManager.PROCESS_STATE_TOP) {
407 // Only use this if we are exactly the top app. All others can live
408 // with just the foreground priority. This means that persistent processes
409 // can never be the top app priority... that is fine.
410 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
411 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
412 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800413 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800414 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800415 }
416 }
417 }
418
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700419 void updateIdleMode(boolean enabled) {
420 boolean changed = false;
421 boolean rocking;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800422 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700423 if (mDeviceIdleMode != enabled) {
424 changed = true;
425 }
426 rocking = mReadyToRock;
427 }
428 if (changed) {
429 if (rocking) {
430 for (int i=0; i<mControllers.size(); i++) {
431 mControllers.get(i).deviceIdleModeChanged(enabled);
432 }
433 }
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800434 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700435 mDeviceIdleMode = enabled;
436 if (enabled) {
437 // When becoming idle, make sure no jobs are actively running.
438 for (int i=0; i<mActiveServices.size(); i++) {
439 JobServiceContext jsc = mActiveServices.get(i);
440 final JobStatus executing = jsc.getRunningJob();
441 if (executing != null) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000442 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700443 }
444 }
445 } else {
446 // When coming out of idle, allow thing to start back up.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800447 if (rocking) {
448 if (mLocalDeviceIdleController != null) {
449 if (!mReportedActive) {
450 mReportedActive = true;
451 mLocalDeviceIdleController.setJobsActive(true);
452 }
453 }
454 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700455 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
456 }
457 }
458 }
459 }
460
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800461 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000462 // active is true if pending queue contains jobs OR some job is running.
463 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800464 if (mPendingJobs.size() <= 0) {
465 for (int i=0; i<mActiveServices.size(); i++) {
466 JobServiceContext jsc = mActiveServices.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000467 if (jsc.getRunningJob() != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800468 active = true;
469 break;
470 }
471 }
472 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000473
474 if (mReportedActive != active) {
475 mReportedActive = active;
476 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800477 mLocalDeviceIdleController.setJobsActive(active);
478 }
479 }
480 }
481
Christopher Tate7060b042014-06-09 19:50:00 -0700482 /**
483 * Initializes the system service.
484 * <p>
485 * Subclasses must define a single argument constructor that accepts the context
486 * and passes it to super.
487 * </p>
488 *
489 * @param context The system server context.
490 */
491 public JobSchedulerService(Context context) {
492 super(context);
493 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700494 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700495 mControllers.add(ConnectivityController.get(this));
496 mControllers.add(TimeController.get(this));
497 mControllers.add(IdleController.get(this));
498 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800499 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800500 mControllers.add(ContentObserverController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700501
502 mHandler = new JobHandler(context.getMainLooper());
503 mJobSchedulerStub = new JobSchedulerStub();
Christopher Tate7060b042014-06-09 19:50:00 -0700504 mJobs = JobStore.initAndGet(this);
505 }
506
507 @Override
508 public void onStart() {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000509 publishLocalService(JobSchedulerInternal.class, new LocalService());
Christopher Tate7060b042014-06-09 19:50:00 -0700510 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
511 }
512
513 @Override
514 public void onBootPhase(int phase) {
515 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000516 // Register br for package removals and user removals.
Christopher Tate7060b042014-06-09 19:50:00 -0700517 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
518 filter.addDataScheme("package");
519 getContext().registerReceiverAsUser(
520 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
521 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700522 userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700523 userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
Christopher Tate7060b042014-06-09 19:50:00 -0700524 getContext().registerReceiverAsUser(
525 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000526 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700527 try {
528 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800529 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
530 | ActivityManager.UID_OBSERVER_IDLE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700531 } catch (RemoteException e) {
532 // ignored; both services live in system_server
533 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700534 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800535 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700536 // Let's go!
537 mReadyToRock = true;
538 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
539 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800540 mLocalDeviceIdleController
541 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700542 // Create the "runners".
543 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
544 mActiveServices.add(
545 new JobServiceContext(this, mBatteryStats,
546 getContext().getMainLooper()));
547 }
548 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800549 mJobs.forEachJob(new JobStatusFunctor() {
550 @Override
551 public void process(JobStatus job) {
552 for (int controller = 0; controller < mControllers.size(); controller++) {
553 final StateController sc = mControllers.get(controller);
554 sc.deviceIdleModeChanged(mDeviceIdleMode);
555 sc.maybeStartTrackingJobLocked(job, null);
556 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700557 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800558 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700559 // GO GO GO!
560 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
561 }
Christopher Tate7060b042014-06-09 19:50:00 -0700562 }
563 }
564
565 /**
566 * Called when we have a job status object that we need to insert in our
567 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
568 * about.
569 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800570 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800571 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800572 final boolean update = mJobs.add(jobStatus);
573 if (mReadyToRock) {
574 for (int i = 0; i < mControllers.size(); i++) {
575 StateController controller = mControllers.get(i);
576 if (update) {
577 controller.maybeStopTrackingJobLocked(jobStatus, true);
578 }
579 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700580 }
Christopher Tate7060b042014-06-09 19:50:00 -0700581 }
Christopher Tate7060b042014-06-09 19:50:00 -0700582 }
583 }
584
585 /**
586 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
587 * object removed.
588 */
Shreyas Basarge73f10252016-02-11 17:06:13 +0000589 private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800590 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700591 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800592 final boolean removed = mJobs.remove(jobStatus, writeBack);
593 if (removed && mReadyToRock) {
594 for (int i=0; i<mControllers.size(); i++) {
595 StateController controller = mControllers.get(i);
596 controller.maybeStopTrackingJobLocked(jobStatus, false);
597 }
Christopher Tate7060b042014-06-09 19:50:00 -0700598 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800599 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700600 }
Christopher Tate7060b042014-06-09 19:50:00 -0700601 }
602
Shreyas Basarge5db09082016-01-07 13:38:29 +0000603 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700604 for (int i=0; i<mActiveServices.size(); i++) {
605 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700606 final JobStatus executing = jsc.getRunningJob();
607 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000608 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700609 return true;
610 }
611 }
612 return false;
613 }
614
615 /**
616 * @param job JobStatus we are querying against.
617 * @return Whether or not the job represented by the status object is currently being run or
618 * is pending.
619 */
620 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700621 for (int i=0; i<mActiveServices.size(); i++) {
622 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700623 final JobStatus running = serviceContext.getRunningJob();
624 if (running != null && running.matches(job.getUid(), job.getJobId())) {
625 return true;
626 }
627 }
628 return false;
629 }
630
631 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700632 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
633 * specify an override deadline on a failed job (the failed job will run even though it's not
634 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
635 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
636 *
Christopher Tate7060b042014-06-09 19:50:00 -0700637 * @param failureToReschedule Provided job status that we will reschedule.
638 * @return A newly instantiated JobStatus with the same constraints as the last job except
639 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700640 *
641 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700642 */
643 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
644 final long elapsedNowMillis = SystemClock.elapsedRealtime();
645 final JobInfo job = failureToReschedule.getJob();
646
647 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700648 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
649 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700650
651 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700652 case JobInfo.BACKOFF_POLICY_LINEAR:
653 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700654 break;
655 default:
656 if (DEBUG) {
657 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
658 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700659 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
660 delayMillis =
661 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700662 break;
663 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700664 delayMillis =
665 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800666 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700667 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800668 for (int ic=0; ic<mControllers.size(); ic++) {
669 StateController controller = mControllers.get(ic);
670 controller.rescheduleForFailure(newJob, failureToReschedule);
671 }
672 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700673 }
674
675 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700676 * Called after a periodic has executed so we can reschedule it. We take the last execution
677 * time of the job to be the time of completion (i.e. the time at which this function is
678 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700679 * This could be inaccurate b/c the job can run for as long as
680 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
681 * to underscheduling at least, rather than if we had taken the last execution time to be the
682 * start of the execution.
683 * @return A new job representing the execution criteria for this instantiation of the
684 * recurring job.
685 */
686 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
687 final long elapsedNow = SystemClock.elapsedRealtime();
688 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700689 long runEarly = 0L;
690
691 // If this periodic was rescheduled it won't have a deadline.
692 if (periodicToReschedule.hasDeadlineConstraint()) {
693 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
694 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000695 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -0700696 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000697 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
698 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -0700699
700 if (DEBUG) {
701 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
702 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
703 }
704 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
705 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
706 }
707
708 // JobCompletedListener implementations.
709
710 /**
711 * A job just finished executing. We fetch the
712 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
713 * whether we want to reschedule we readd it to the controllers.
714 * @param jobStatus Completed job.
715 * @param needsReschedule Whether the implementing class should reschedule this job.
716 */
717 @Override
718 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
719 if (DEBUG) {
720 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
721 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000722 // Do not write back immediately if this is a periodic job. The job may get lost if system
723 // shuts down before it is added back.
724 if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700725 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700726 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -0700727 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800728 // We still want to check for jobs to execute, because this job may have
729 // scheduled a new job under the same job id, and now we can run it.
730 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700731 return;
732 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800733 // Note: there is a small window of time in here where, when rescheduling a job,
734 // we will stop monitoring its content providers. This should be fixed by stopping
735 // the old job after scheduling the new one, but since we have no lock held here
736 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -0700737 if (needsReschedule) {
738 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800739 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700740 } else if (jobStatus.getJob().isPeriodic()) {
741 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800742 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700743 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000744 reportActive();
745 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700746 }
747
748 // StateChangedListener implementations.
749
750 /**
Matthew Williams48a30db2014-09-23 13:39:36 -0700751 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
752 * some controller's state has changed, so as to run through the list of jobs and start/stop
753 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -0700754 */
755 @Override
756 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700757 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700758 }
759
760 @Override
761 public void onRunJobNow(JobStatus jobStatus) {
762 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
763 }
764
Christopher Tate7060b042014-06-09 19:50:00 -0700765 private class JobHandler extends Handler {
766
767 public JobHandler(Looper looper) {
768 super(looper);
769 }
770
771 @Override
772 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800773 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700774 if (!mReadyToRock) {
775 return;
776 }
777 }
Christopher Tate7060b042014-06-09 19:50:00 -0700778 switch (message.what) {
779 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800780 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700781 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700782 // runNow can be null, which is a controller's way of indicating that its
783 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -0700784 if (runNow != null && !mPendingJobs.contains(runNow)
785 && mJobs.containsJob(runNow)) {
Christopher Tate7060b042014-06-09 19:50:00 -0700786 mPendingJobs.add(runNow);
787 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700788 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -0700789 }
Christopher Tate7060b042014-06-09 19:50:00 -0700790 break;
791 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800792 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000793 if (mReportedActive) {
794 // if jobs are currently being run, queue all ready jobs for execution.
795 queueReadyJobsForExecutionLockedH();
796 } else {
797 // Check the list of jobs and run some of them if we feel inclined.
798 maybeQueueReadyJobsForExecutionLockedH();
799 }
800 }
801 break;
802 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800803 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000804 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -0700805 }
Christopher Tate7060b042014-06-09 19:50:00 -0700806 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700807 case MSG_STOP_JOB:
808 cancelJobImpl((JobStatus)message.obj);
809 break;
Christopher Tate7060b042014-06-09 19:50:00 -0700810 }
811 maybeRunPendingJobsH();
812 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
813 removeMessages(MSG_CHECK_JOB);
814 }
815
816 /**
817 * Run through list of jobs and execute all possible - at least one is expired so we do
818 * as many as we can.
819 */
Matthew Williams48a30db2014-09-23 13:39:36 -0700820 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700821 if (DEBUG) {
822 Slog.d(TAG, "queuing all ready jobs for execution:");
823 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800824 mPendingJobs.clear();
825 mJobs.forEachJob(mReadyQueueFunctor);
826 mReadyQueueFunctor.postProcess();
827
Matthew Williams48a30db2014-09-23 13:39:36 -0700828 if (DEBUG) {
829 final int queuedJobs = mPendingJobs.size();
830 if (queuedJobs == 0) {
831 Slog.d(TAG, "No jobs pending.");
832 } else {
833 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -0700834 }
Christopher Tate7060b042014-06-09 19:50:00 -0700835 }
836 }
837
Christopher Tate2f36fd62016-02-18 18:36:08 -0800838 class ReadyJobQueueFunctor implements JobStatusFunctor {
839 ArrayList<JobStatus> newReadyJobs;
840
841 @Override
842 public void process(JobStatus job) {
843 if (isReadyToBeExecutedLocked(job)) {
844 if (DEBUG) {
845 Slog.d(TAG, " queued " + job.toShortString());
846 }
847 if (newReadyJobs == null) {
848 newReadyJobs = new ArrayList<JobStatus>();
849 }
850 newReadyJobs.add(job);
851 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
852 stopJobOnServiceContextLocked(job,
853 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
854 }
855 }
856
857 public void postProcess() {
858 if (newReadyJobs != null) {
859 mPendingJobs.addAll(newReadyJobs);
860 }
861 newReadyJobs = null;
862 }
863 }
864 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
865
Christopher Tate7060b042014-06-09 19:50:00 -0700866 /**
867 * The state of at least one job has changed. Here is where we could enforce various
868 * policies on when we want to execute jobs.
869 * Right now the policy is such:
870 * If >1 of the ready jobs is idle mode we send all of them off
871 * if more than 2 network connectivity jobs are ready we send them all off.
872 * If more than 4 jobs total are ready we send them all off.
873 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
874 */
Christopher Tate2f36fd62016-02-18 18:36:08 -0800875 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
876 int chargingCount;
877 int idleCount;
878 int backoffCount;
879 int connectivityCount;
880 int contentCount;
881 List<JobStatus> runnableJobs;
882
883 public MaybeReadyJobQueueFunctor() {
884 reset();
885 }
886
887 // Functor method invoked for each job via JobStore.forEachJob()
888 @Override
889 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700890 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700891 try {
892 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
893 job.getJob().getService().getPackageName())
894 == ActivityManager.APP_START_MODE_DISABLED) {
895 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
896 + job.getJob().toString() + " -- package not allowed to start");
897 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800898 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700899 }
900 } catch (RemoteException e) {
901 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700902 if (job.getNumFailures() > 0) {
903 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -0700904 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700905 if (job.hasIdleConstraint()) {
906 idleCount++;
907 }
908 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
909 connectivityCount++;
910 }
911 if (job.hasChargingConstraint()) {
912 chargingCount++;
913 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800914 if (job.hasContentTriggerConstraint()) {
915 contentCount++;
916 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700917 if (runnableJobs == null) {
918 runnableJobs = new ArrayList<>();
919 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700920 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800921 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000922 stopJobOnServiceContextLocked(job,
923 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -0700924 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700925 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800926
927 public void postProcess() {
928 if (backoffCount > 0 ||
929 idleCount >= MIN_IDLE_COUNT ||
930 connectivityCount >= MIN_CONNECTIVITY_COUNT ||
931 chargingCount >= MIN_CHARGING_COUNT ||
932 contentCount >= MIN_CONTENT_COUNT ||
933 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
934 if (DEBUG) {
935 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
936 }
937 mPendingJobs.addAll(runnableJobs);
938 } else {
939 if (DEBUG) {
940 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
941 }
Christopher Tate7060b042014-06-09 19:50:00 -0700942 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800943
944 // Be ready for next time
945 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -0700946 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800947
948 private void reset() {
949 chargingCount = 0;
950 idleCount = 0;
951 backoffCount = 0;
952 connectivityCount = 0;
953 contentCount = 0;
954 runnableJobs = null;
955 }
956 }
957 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
958
959 private void maybeQueueReadyJobsForExecutionLockedH() {
960 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
961
962 mPendingJobs.clear();
963 mJobs.forEachJob(mMaybeQueueFunctor);
964 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -0700965 }
966
967 /**
968 * Criteria for moving a job into the pending queue:
969 * - It's ready.
970 * - It's not pending.
971 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700972 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700973 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -0700974 */
975 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700976 final boolean jobReady = job.isReady();
977 final boolean jobPending = mPendingJobs.contains(job);
978 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700979
980 final int userId = job.getUserId();
981 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
982 final boolean componentPresent;
983 try {
984 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
985 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
986 userId) != null);
987 } catch (RemoteException e) {
988 throw e.rethrowAsRuntimeException();
989 }
990
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700991 if (DEBUG) {
992 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
993 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700994 + " active=" + jobActive + " userStarted=" + userStarted
995 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700996 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700997 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -0700998 }
999
1000 /**
1001 * Criteria for cancelling an active job:
1002 * - It's not ready
1003 * - It's running on a JSC.
1004 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001005 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001006 return !job.isReady() && isCurrentlyActiveLocked(job);
1007 }
1008
1009 /**
1010 * Reconcile jobs in the pending queue against available execution contexts.
1011 * A controller can force a job into the pending queue even if it's already running, but
1012 * here is where we decide whether to actually execute it.
1013 */
1014 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001015 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -07001016 if (mDeviceIdleMode) {
1017 // If device is idle, we will not schedule jobs to run.
1018 return;
1019 }
Matthew Williams75fc5252014-09-02 16:17:53 -07001020 if (DEBUG) {
1021 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1022 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001023 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001024 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001025 }
1026 }
1027 }
1028
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001029 private int evaluateJobPriorityLocked(JobStatus job) {
1030 int priority = job.getPriority();
1031 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
1032 return priority;
1033 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001034 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1035 if (override != 0) {
1036 return override;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001037 }
1038 return priority;
1039 }
1040
Christopher Tate7060b042014-06-09 19:50:00 -07001041 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001042 * Takes jobs from pending queue and runs them on available contexts.
1043 * If no contexts are available, preempts lower priority jobs to
1044 * run higher priority ones.
1045 * Lock on mJobs before calling this function.
1046 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001047 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001048 if (DEBUG) {
1049 Slog.d(TAG, printPendingQueue());
1050 }
1051
Dianne Hackborn970510b2016-02-24 16:56:42 -08001052 int memLevel;
1053 try {
1054 memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
1055 } catch (RemoteException e) {
1056 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1057 }
1058 switch (memLevel) {
1059 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
1060 mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - 2) * 2) / 3;
1061 break;
1062 case ProcessStats.ADJ_MEM_FACTOR_LOW:
1063 mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - 2) / 3;
1064 break;
1065 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
1066 mMaxActiveJobs = 1;
1067 break;
1068 default:
1069 mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
1070 break;
1071 }
1072
1073 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1074 boolean[] act = mTmpAssignAct;
1075 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1076 int numActive = 0;
1077 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1078 final JobServiceContext js = mActiveServices.get(i);
1079 if ((contextIdToJobMap[i] = js.getRunningJob()) != null) {
1080 numActive++;
1081 }
1082 act[i] = false;
1083 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001084 }
1085 if (DEBUG) {
1086 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1087 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001088 for (int i=0; i<mPendingJobs.size(); i++) {
1089 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001090
1091 // If job is already running, go to next job.
1092 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1093 if (jobRunningContext != -1) {
1094 continue;
1095 }
1096
Dianne Hackborn970510b2016-02-24 16:56:42 -08001097 final int priority = evaluateJobPriorityLocked(nextPending);
1098 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001099
Shreyas Basarge5db09082016-01-07 13:38:29 +00001100 // Find a context for nextPending. The context should be available OR
1101 // it should have lowest priority among all running jobs
1102 // (sharing the same Uid as nextPending)
1103 int minPriority = Integer.MAX_VALUE;
1104 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001105 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1106 JobStatus job = contextIdToJobMap[j];
1107 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001108 if (job == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001109 if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) &&
1110 (preferredUid == nextPending.getUid() ||
1111 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1112 // This slot is free, and we haven't yet hit the limit on
1113 // concurrent jobs... we can just throw the job in to here.
1114 minPriorityContextId = j;
1115 numActive++;
1116 break;
1117 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001118 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001119 // the context has a preferred Uid or we have reached the limit on
1120 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001121 continue;
1122 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001123 if (job.getUid() != nextPending.getUid()) {
1124 continue;
1125 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001126 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001127 continue;
1128 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001129 if (minPriority > nextPending.lastEvaluatedPriority) {
1130 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001131 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001132 }
1133 }
1134 if (minPriorityContextId != -1) {
1135 contextIdToJobMap[minPriorityContextId] = nextPending;
1136 act[minPriorityContextId] = true;
1137 }
1138 }
1139 if (DEBUG) {
1140 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1141 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001142 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001143 boolean preservePreferredUid = false;
1144 if (act[i]) {
1145 JobStatus js = mActiveServices.get(i).getRunningJob();
1146 if (js != null) {
1147 if (DEBUG) {
1148 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1149 }
1150 // preferredUid will be set to uid of currently running job.
1151 mActiveServices.get(i).preemptExecutingJob();
1152 preservePreferredUid = true;
1153 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001154 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001155 if (DEBUG) {
1156 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001157 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001158 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001159 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001160 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001161 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001162 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1163 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001164 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001165 mPendingJobs.remove(pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001166 }
1167 }
1168 if (!preservePreferredUid) {
1169 mActiveServices.get(i).clearPreferredUid();
1170 }
1171 }
1172 }
1173
1174 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1175 for (int i=0; i<map.length; i++) {
1176 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1177 return i;
1178 }
1179 }
1180 return -1;
1181 }
1182
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001183 final class LocalService implements JobSchedulerInternal {
1184
1185 /**
1186 * Returns a list of all pending jobs. A running job is not considered pending. Periodic
1187 * jobs are always considered pending.
1188 */
1189 public List<JobInfo> getSystemScheduledPendingJobs() {
1190 synchronized (mLock) {
1191 final List<JobInfo> pendingJobs = new ArrayList<JobInfo>();
1192 mJobs.forEachJob(Process.SYSTEM_UID, new JobStatusFunctor() {
1193 @Override
1194 public void process(JobStatus job) {
1195 if (job.getJob().isPeriodic() || !isCurrentlyActiveLocked(job)) {
1196 pendingJobs.add(job.getJob());
1197 }
1198 }
1199 });
1200 return pendingJobs;
1201 }
1202 }
1203 }
1204
Shreyas Basarge5db09082016-01-07 13:38:29 +00001205 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001206 * Binder stub trampoline implementation
1207 */
1208 final class JobSchedulerStub extends IJobScheduler.Stub {
1209 /** Cache determination of whether a given app can persist jobs
1210 * key is uid of the calling app; value is undetermined/true/false
1211 */
1212 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1213
1214 // Enforce that only the app itself (or shared uid participant) can schedule a
1215 // job that runs one of the app's services, as well as verifying that the
1216 // named service properly requires the BIND_JOB_SERVICE permission
1217 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001218 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001219 final ComponentName service = job.getService();
1220 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001221 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey8a372a02016-03-16 16:25:45 -06001222 PackageManager.MATCH_DIRECT_BOOT_AWARE
1223 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001224 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001225 if (si == null) {
1226 throw new IllegalArgumentException("No such service " + service);
1227 }
Christopher Tate7060b042014-06-09 19:50:00 -07001228 if (si.applicationInfo.uid != uid) {
1229 throw new IllegalArgumentException("uid " + uid +
1230 " cannot schedule job in " + service.getPackageName());
1231 }
1232 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1233 throw new IllegalArgumentException("Scheduled service " + service
1234 + " does not require android.permission.BIND_JOB_SERVICE permission");
1235 }
Christopher Tate5568f542014-06-18 13:53:31 -07001236 } catch (RemoteException e) {
1237 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001238 }
1239 }
1240
1241 private boolean canPersistJobs(int pid, int uid) {
1242 // If we get this far we're good to go; all we need to do now is check
1243 // whether the app is allowed to persist its scheduled work.
1244 final boolean canPersist;
1245 synchronized (mPersistCache) {
1246 Boolean cached = mPersistCache.get(uid);
1247 if (cached != null) {
1248 canPersist = cached.booleanValue();
1249 } else {
1250 // Persisting jobs is tantamount to running at boot, so we permit
1251 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1252 // permission
1253 int result = getContext().checkPermission(
1254 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1255 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1256 mPersistCache.put(uid, canPersist);
1257 }
1258 }
1259 return canPersist;
1260 }
1261
1262 // IJobScheduler implementation
1263 @Override
1264 public int schedule(JobInfo job) throws RemoteException {
1265 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001266 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001267 }
1268 final int pid = Binder.getCallingPid();
1269 final int uid = Binder.getCallingUid();
1270
1271 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001272 if (job.isPersisted()) {
1273 if (!canPersistJobs(pid, uid)) {
1274 throw new IllegalArgumentException("Error: requested job be persisted without"
1275 + " holding RECEIVE_BOOT_COMPLETED permission.");
1276 }
1277 }
Christopher Tate7060b042014-06-09 19:50:00 -07001278
1279 long ident = Binder.clearCallingIdentity();
1280 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001281 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001282 } finally {
1283 Binder.restoreCallingIdentity(ident);
1284 }
1285 }
1286
1287 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001288 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001289 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001290 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001291 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001292 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1293 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001294 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001295
1296 if (packageName == null) {
1297 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001298 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001299
1300 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1301 android.Manifest.permission.UPDATE_DEVICE_STATS);
1302 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1303 throw new SecurityException("Caller uid " + callerUid
1304 + " not permitted to schedule jobs for other apps");
1305 }
1306
Shreyas Basarge968ac752016-01-11 23:09:26 +00001307 long ident = Binder.clearCallingIdentity();
1308 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001309 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001310 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001311 } finally {
1312 Binder.restoreCallingIdentity(ident);
1313 }
1314 }
1315
1316 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001317 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1318 final int uid = Binder.getCallingUid();
1319
1320 long ident = Binder.clearCallingIdentity();
1321 try {
1322 return JobSchedulerService.this.getPendingJobs(uid);
1323 } finally {
1324 Binder.restoreCallingIdentity(ident);
1325 }
1326 }
1327
1328 @Override
1329 public void cancelAll() throws RemoteException {
1330 final int uid = Binder.getCallingUid();
1331
1332 long ident = Binder.clearCallingIdentity();
1333 try {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001334 JobSchedulerService.this.cancelJobsForUid(uid, true);
Christopher Tate7060b042014-06-09 19:50:00 -07001335 } finally {
1336 Binder.restoreCallingIdentity(ident);
1337 }
1338 }
1339
1340 @Override
1341 public void cancel(int jobId) throws RemoteException {
1342 final int uid = Binder.getCallingUid();
1343
1344 long ident = Binder.clearCallingIdentity();
1345 try {
1346 JobSchedulerService.this.cancelJob(uid, jobId);
1347 } finally {
1348 Binder.restoreCallingIdentity(ident);
1349 }
1350 }
1351
1352 /**
1353 * "dumpsys" infrastructure
1354 */
1355 @Override
1356 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1357 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1358
1359 long identityToken = Binder.clearCallingIdentity();
1360 try {
1361 JobSchedulerService.this.dumpInternal(pw);
1362 } finally {
1363 Binder.restoreCallingIdentity(identityToken);
1364 }
1365 }
Christopher Tate5d346052016-03-08 12:56:08 -08001366
1367 @Override
1368 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1369 String[] args, ResultReceiver resultReceiver) throws RemoteException {
1370 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
1371 this, in, out, err, args, resultReceiver);
1372 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001373 };
1374
Christopher Tate5d346052016-03-08 12:56:08 -08001375 // Shell command infrastructure: run the given job immediately
1376 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1377 if (DEBUG) {
1378 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1379 + " " + jobId + " f=" + force);
1380 }
1381
1382 try {
1383 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1384 if (uid < 0) {
1385 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1386 }
1387
1388 synchronized (mLock) {
1389 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1390 if (js == null) {
1391 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1392 }
1393
1394 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1395 if (!js.isConstraintsSatisfied()) {
1396 js.overrideState = 0;
1397 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1398 }
1399
1400 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1401 }
1402 } catch (RemoteException e) {
1403 // can't happen
1404 }
1405 return 0;
1406 }
1407
Shreyas Basarge5db09082016-01-07 13:38:29 +00001408 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1409 StringBuilder s = new StringBuilder(initial + ": ");
1410 for (int i=0; i<map.length; i++) {
1411 s.append("(")
1412 .append(map[i] == null? -1: map[i].getJobId())
1413 .append(map[i] == null? -1: map[i].getUid())
1414 .append(")" );
1415 }
1416 return s.toString();
1417 }
1418
1419 private String printPendingQueue() {
1420 StringBuilder s = new StringBuilder("Pending queue: ");
1421 Iterator<JobStatus> it = mPendingJobs.iterator();
1422 while (it.hasNext()) {
1423 JobStatus js = it.next();
1424 s.append("(")
1425 .append(js.getJob().getId())
1426 .append(", ")
1427 .append(js.getUid())
1428 .append(") ");
1429 }
1430 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001431 }
Christopher Tate7060b042014-06-09 19:50:00 -07001432
Christopher Tate2f36fd62016-02-18 18:36:08 -08001433 void dumpInternal(final PrintWriter pw) {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001434 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001435 synchronized (mLock) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001436 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Christopher Tate7060b042014-06-09 19:50:00 -07001437 pw.println("Registered jobs:");
1438 if (mJobs.size() > 0) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001439 mJobs.forEachJob(new JobStatusFunctor() {
1440 private int index = 0;
1441
1442 @Override
1443 public void process(JobStatus job) {
1444 pw.print(" Job #"); pw.print(index++); pw.print(": ");
1445 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001446 job.dump(pw, " ", true);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001447 pw.print(" Ready: ");
1448 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1449 pw.print(" (job=");
1450 pw.print(job.isReady());
1451 pw.print(" pending=");
1452 pw.print(mPendingJobs.contains(job));
1453 pw.print(" active=");
1454 pw.print(isCurrentlyActiveLocked(job));
1455 pw.print(" user=");
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001456 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
Christopher Tate2f36fd62016-02-18 18:36:08 -08001457 pw.println(")");
1458 }
1459 });
Christopher Tate7060b042014-06-09 19:50:00 -07001460 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001461 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001462 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001463 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001464 pw.println();
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001465 mControllers.get(i).dumpControllerStateLocked(pw);
Christopher Tate7060b042014-06-09 19:50:00 -07001466 }
1467 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001468 pw.println("Uid priority overrides:");
1469 for (int i=0; i< mUidPriorityOverride.size(); i++) {
1470 pw.print(" "); pw.print(UserHandle.formatUid(mUidPriorityOverride.keyAt(i)));
1471 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001472 }
1473 pw.println();
1474 pw.println("Pending queue:");
1475 for (int i=0; i<mPendingJobs.size(); i++) {
1476 JobStatus job = mPendingJobs.get(i);
1477 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1478 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001479 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001480 int priority = evaluateJobPriorityLocked(job);
1481 if (priority != JobInfo.PRIORITY_DEFAULT) {
1482 pw.print(" Evaluated priority: "); pw.println(priority);
1483 }
1484 pw.print(" Tag: "); pw.println(job.getTag());
1485 }
Christopher Tate7060b042014-06-09 19:50:00 -07001486 pw.println();
1487 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001488 for (int i=0; i<mActiveServices.size(); i++) {
1489 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001490 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001491 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001492 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001493 continue;
1494 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001495 pw.println(jsc.getRunningJob().toShortString());
1496 pw.print(" Running for: ");
1497 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1498 pw.print(", timeout at: ");
1499 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1500 pw.println();
1501 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001502 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1503 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001504 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001505 }
Christopher Tate7060b042014-06-09 19:50:00 -07001506 }
1507 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001508 pw.println();
1509 pw.print("mReadyToRock="); pw.println(mReadyToRock);
Dianne Hackborn88e98df2015-03-23 13:29:14 -07001510 pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001511 pw.print("mReportedActive="); pw.println(mReportedActive);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001512 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
Christopher Tate7060b042014-06-09 19:50:00 -07001513 }
1514 pw.println();
1515 }
1516}