blob: d2b77aab70b892bffbdc78128d0c059c89f7eab6 [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;
Dianne Hackborn88e98df2015-03-23 13:29:14 -070048import android.os.PowerManager;
Christopher Tate7060b042014-06-09 19:50:00 -070049import android.os.RemoteException;
Christopher Tate5d346052016-03-08 12:56:08 -080050import android.os.ResultReceiver;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070051import android.os.ServiceManager;
Christopher Tate7060b042014-06-09 19:50:00 -070052import android.os.SystemClock;
53import android.os.UserHandle;
54import android.util.Slog;
55import android.util.SparseArray;
Dianne Hackborn970510b2016-02-24 16:56:42 -080056import android.util.SparseIntArray;
57import android.util.TimeUtils;
Christopher Tate5d346052016-03-08 12:56:08 -080058
Dianne Hackbornfdb19562014-07-11 16:03:36 -070059import com.android.internal.app.IBatteryStats;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070060import com.android.internal.util.ArrayUtils;
Dianne Hackborn970510b2016-02-24 16:56:42 -080061import com.android.internal.app.ProcessStats;
Dianne Hackborn627dfa12015-11-11 18:10:30 -080062import com.android.server.DeviceIdleController;
63import com.android.server.LocalServices;
Christopher Tate2f36fd62016-02-18 18:36:08 -080064import com.android.server.job.JobStore.JobStatusFunctor;
Amith Yamasanib0ff3222015-03-04 09:56:14 -080065import com.android.server.job.controllers.AppIdleController;
Christopher Tate7060b042014-06-09 19:50:00 -070066import com.android.server.job.controllers.BatteryController;
67import com.android.server.job.controllers.ConnectivityController;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080068import com.android.server.job.controllers.ContentObserverController;
Christopher Tate7060b042014-06-09 19:50:00 -070069import com.android.server.job.controllers.IdleController;
70import com.android.server.job.controllers.JobStatus;
71import com.android.server.job.controllers.StateController;
72import com.android.server.job.controllers.TimeController;
73
Jeff Sharkey822cbd12016-02-25 11:09:55 -070074import libcore.util.EmptyArray;
75
Christopher Tate7060b042014-06-09 19:50:00 -070076/**
77 * Responsible for taking jobs representing work to be performed by a client app, and determining
78 * based on the criteria specified when that job should be run against the client application's
79 * endpoint.
80 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
81 * about constraints, or the state of active jobs. It receives callbacks from the various
82 * controllers and completed jobs and operates accordingly.
83 *
84 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
85 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
86 * @hide
87 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -080088public final class JobSchedulerService extends com.android.server.SystemService
Matthew Williams01ac45b2014-07-22 20:44:12 -070089 implements StateChangedListener, JobCompletedListener {
Christopher Tate2f36fd62016-02-18 18:36:08 -080090 static final String TAG = "JobSchedulerService";
Matthew Williamsaa984312015-10-15 16:08:05 -070091 public static final boolean DEBUG = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -080092
Dianne Hackborn970510b2016-02-24 16:56:42 -080093 /** The maximum number of concurrent jobs we run at one time. */
94 private static final int MAX_JOB_CONTEXTS_COUNT = 8;
Christopher Tatedabdf6f2016-02-24 12:30:22 -080095 /** Enforce a per-app limit on scheduled jobs? */
Christopher Tate0213ace02016-02-24 14:18:35 -080096 private static final boolean ENFORCE_MAX_JOBS = true;
Christopher Tate2f36fd62016-02-18 18:36:08 -080097 /** The maximum number of jobs that we allow an unprivileged app to schedule */
98 private static final int MAX_JOBS_PER_APP = 100;
99
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800100 /** Global local for all job scheduler state. */
101 final Object mLock = new Object();
Christopher Tate7060b042014-06-09 19:50:00 -0700102 /** Master list of jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700103 final JobStore mJobs;
Christopher Tate7060b042014-06-09 19:50:00 -0700104
105 static final int MSG_JOB_EXPIRED = 0;
106 static final int MSG_CHECK_JOB = 1;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700107 static final int MSG_STOP_JOB = 2;
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000108 static final int MSG_CHECK_JOB_GREEDY = 3;
Christopher Tate7060b042014-06-09 19:50:00 -0700109
110 // Policy constants
111 /**
112 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
113 * early.
114 */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700115 static final int MIN_IDLE_COUNT = 1;
Christopher Tate7060b042014-06-09 19:50:00 -0700116 /**
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700117 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things
118 * early.
119 */
120 static final int MIN_CHARGING_COUNT = 1;
121 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700122 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
123 * things early.
124 */
Matthew Williamsaa984312015-10-15 16:08:05 -0700125 static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready.
Christopher Tate7060b042014-06-09 19:50:00 -0700126 /**
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800127 * Minimum # of content trigger jobs that must be ready in order to force the JMS to schedule
128 * things early.
129 */
130 static final int MIN_CONTENT_COUNT = 1;
131 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700132 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
133 * some work early.
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700134 * This is correlated with the amount of batching we'll be able to do.
Christopher Tate7060b042014-06-09 19:50:00 -0700135 */
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700136 static final int MIN_READY_JOBS_COUNT = 2;
Christopher Tate7060b042014-06-09 19:50:00 -0700137
138 /**
139 * Track Services that have currently active or pending jobs. The index is provided by
140 * {@link JobStatus#getServiceToken()}
141 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700142 final List<JobServiceContext> mActiveServices = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700143 /** List of controllers that will notify this service of updates to jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700144 List<StateController> mControllers;
Christopher Tate7060b042014-06-09 19:50:00 -0700145 /**
146 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
147 * when ready to execute them.
148 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700149 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700150
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700151 int[] mStartedUsers = EmptyArray.INT;
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700152
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700153 final JobHandler mHandler;
154 final JobSchedulerStub mJobSchedulerStub;
155
156 IBatteryStats mBatteryStats;
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700157 PowerManager mPowerManager;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800158 DeviceIdleController.LocalService mLocalDeviceIdleController;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700159
160 /**
161 * Set to true once we are allowed to run third party apps.
162 */
163 boolean mReadyToRock;
164
Christopher Tate7060b042014-06-09 19:50:00 -0700165 /**
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700166 * True when in device idle mode, so we don't want to schedule any jobs.
167 */
168 boolean mDeviceIdleMode;
169
170 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800171 * What we last reported to DeviceIdleController about whether we are active.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800172 */
173 boolean mReportedActive;
174
175 /**
Dianne Hackborn970510b2016-02-24 16:56:42 -0800176 * Current limit on the number of concurrent JobServiceContext entries we want to
177 * keep actively running a job.
178 */
179 int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
180
181 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800182 * Which uids are currently in the foreground.
183 */
Dianne Hackborn970510b2016-02-24 16:56:42 -0800184 final SparseIntArray mUidPriorityOverride = new SparseIntArray();
185
186 // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
187
188 /**
189 * This array essentially stores the state of mActiveServices array.
190 * The ith index stores the job present on the ith JobServiceContext.
191 * We manipulate this array until we arrive at what jobs should be running on
192 * what JobServiceContext.
193 */
194 JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
195 /**
196 * Indicates whether we need to act on this jobContext id
197 */
198 boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
199 /**
200 * The uid whose jobs we would like to assign to a context.
201 */
202 int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800203
204 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700205 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
206 * still clean up. On reinstall the package will have a new uid.
207 */
208 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
209 @Override
210 public void onReceive(Context context, Intent intent) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000211 Slog.d(TAG, "Receieved: " + intent.getAction());
212 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700213 // If this is an outright uninstall rather than the first half of an
214 // app update sequence, cancel the jobs associated with the app.
215 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
216 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
217 if (DEBUG) {
218 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
219 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700220 cancelJobsForUid(uidRemoved, true);
Christopher Tate7060b042014-06-09 19:50:00 -0700221 }
Shreyas Basarge5db09082016-01-07 13:38:29 +0000222 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700223 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
224 if (DEBUG) {
225 Slog.d(TAG, "Removing jobs for user: " + userId);
226 }
227 cancelJobsForUser(userId);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000228 } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
229 || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700230 updateIdleMode(mPowerManager != null
231 ? (mPowerManager.isDeviceIdleMode()
Shreyas Basarge5db09082016-01-07 13:38:29 +0000232 || mPowerManager.isLightDeviceIdleMode())
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700233 : false);
Christopher Tate7060b042014-06-09 19:50:00 -0700234 }
235 }
236 };
237
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700238 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
239 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800240 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700241 }
242
243 @Override public void onUidGone(int uid) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800244 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700245 }
246
247 @Override public void onUidActive(int uid) throws RemoteException {
248 }
249
250 @Override public void onUidIdle(int uid) throws RemoteException {
251 cancelJobsForUid(uid, false);
252 }
253 };
254
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800255 public Object getLock() {
256 return mLock;
257 }
258
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700259 @Override
260 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700261 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
262 // Let's kick any outstanding jobs for this user.
263 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
264 }
265
266 @Override
267 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700268 // Let's kick any outstanding jobs for this user.
269 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
270 }
271
272 @Override
273 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700274 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700275 }
276
Christopher Tate7060b042014-06-09 19:50:00 -0700277 /**
278 * Entry point from client to schedule the provided job.
279 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
280 * @param job JobInfo object containing execution parameters
281 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700282 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
283 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700284 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800285 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000286 }
287
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800288 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
289 String tag) {
290 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700291 try {
292 if (ActivityManagerNative.getDefault().getAppStartMode(uId,
293 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
294 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
295 + " -- package not allowed to start");
296 return JobScheduler.RESULT_FAILURE;
297 }
298 } catch (RemoteException e) {
299 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800300 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
301 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800302 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800303 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800304 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800305 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
306 Slog.w(TAG, "Too many jobs for uid " + uId);
307 throw new IllegalStateException("Apps may not schedule more than "
308 + MAX_JOBS_PER_APP + " distinct jobs");
309 }
310 }
311
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800312 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
313 }
314 startTrackingJob(jobStatus, toCancel);
315 if (toCancel != null) {
316 cancelJobImpl(toCancel);
317 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700318 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700319 return JobScheduler.RESULT_SUCCESS;
320 }
321
322 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800323 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800324 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
325 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
326 for (int i = jobs.size() - 1; i >= 0; i--) {
327 JobStatus job = jobs.get(i);
328 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700329 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800330 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700331 }
Christopher Tate7060b042014-06-09 19:50:00 -0700332 }
333
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700334 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700335 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800336 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700337 jobsForUser = mJobs.getJobsByUser(userHandle);
338 }
339 for (int i=0; i<jobsForUser.size(); i++) {
340 JobStatus toRemove = jobsForUser.get(i);
341 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700342 }
343 }
344
345 /**
346 * Entry point from client to cancel all jobs originating from their uid.
347 * This will remove the job from the master list, and cancel the job if it was staged for
348 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700349 * @param uid Uid to check against for removal of a job.
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700350 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
351 * whose apps are stopped.
Christopher Tate7060b042014-06-09 19:50:00 -0700352 */
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700353 public void cancelJobsForUid(int uid, boolean forceAll) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700354 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800355 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700356 jobsForUid = mJobs.getJobsByUid(uid);
357 }
358 for (int i=0; i<jobsForUid.size(); i++) {
359 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700360 if (!forceAll) {
361 String packageName = toRemove.getServiceComponent().getPackageName();
362 try {
363 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
364 != ActivityManager.APP_START_MODE_DISABLED) {
365 continue;
366 }
367 } catch (RemoteException e) {
368 }
369 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700370 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700371 }
372 }
373
374 /**
375 * Entry point from client to cancel the job corresponding to the jobId provided.
376 * This will remove the job from the master list, and cancel the job if it was staged for
377 * execution or being executed.
378 * @param uid Uid of the calling client.
379 * @param jobId Id of the job, provided at schedule-time.
380 */
381 public void cancelJob(int uid, int jobId) {
382 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800383 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700384 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700385 }
386 if (toCancel != null) {
387 cancelJobImpl(toCancel);
Christopher Tate7060b042014-06-09 19:50:00 -0700388 }
389 }
390
Matthew Williams48a30db2014-09-23 13:39:36 -0700391 private void cancelJobImpl(JobStatus cancelled) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800392 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Shreyas Basarge73f10252016-02-11 17:06:13 +0000393 stopTrackingJob(cancelled, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800394 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700395 // Remove from pending queue.
396 mPendingJobs.remove(cancelled);
397 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000398 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800399 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700400 }
Christopher Tate7060b042014-06-09 19:50:00 -0700401 }
402
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800403 void updateUidState(int uid, int procState) {
404 synchronized (mLock) {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800405 if (procState == ActivityManager.PROCESS_STATE_TOP) {
406 // Only use this if we are exactly the top app. All others can live
407 // with just the foreground priority. This means that persistent processes
408 // can never be the top app priority... that is fine.
409 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
410 } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
411 mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800412 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -0800413 mUidPriorityOverride.delete(uid);
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800414 }
415 }
416 }
417
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700418 void updateIdleMode(boolean enabled) {
419 boolean changed = false;
420 boolean rocking;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800421 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700422 if (mDeviceIdleMode != enabled) {
423 changed = true;
424 }
425 rocking = mReadyToRock;
426 }
427 if (changed) {
428 if (rocking) {
429 for (int i=0; i<mControllers.size(); i++) {
430 mControllers.get(i).deviceIdleModeChanged(enabled);
431 }
432 }
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800433 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700434 mDeviceIdleMode = enabled;
435 if (enabled) {
436 // When becoming idle, make sure no jobs are actively running.
437 for (int i=0; i<mActiveServices.size(); i++) {
438 JobServiceContext jsc = mActiveServices.get(i);
439 final JobStatus executing = jsc.getRunningJob();
440 if (executing != null) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000441 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700442 }
443 }
444 } else {
445 // When coming out of idle, allow thing to start back up.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800446 if (rocking) {
447 if (mLocalDeviceIdleController != null) {
448 if (!mReportedActive) {
449 mReportedActive = true;
450 mLocalDeviceIdleController.setJobsActive(true);
451 }
452 }
453 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700454 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
455 }
456 }
457 }
458 }
459
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800460 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000461 // active is true if pending queue contains jobs OR some job is running.
462 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800463 if (mPendingJobs.size() <= 0) {
464 for (int i=0; i<mActiveServices.size(); i++) {
465 JobServiceContext jsc = mActiveServices.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000466 if (jsc.getRunningJob() != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800467 active = true;
468 break;
469 }
470 }
471 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000472
473 if (mReportedActive != active) {
474 mReportedActive = active;
475 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800476 mLocalDeviceIdleController.setJobsActive(active);
477 }
478 }
479 }
480
Christopher Tate7060b042014-06-09 19:50:00 -0700481 /**
482 * Initializes the system service.
483 * <p>
484 * Subclasses must define a single argument constructor that accepts the context
485 * and passes it to super.
486 * </p>
487 *
488 * @param context The system server context.
489 */
490 public JobSchedulerService(Context context) {
491 super(context);
492 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700493 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700494 mControllers.add(ConnectivityController.get(this));
495 mControllers.add(TimeController.get(this));
496 mControllers.add(IdleController.get(this));
497 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800498 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800499 mControllers.add(ContentObserverController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700500
501 mHandler = new JobHandler(context.getMainLooper());
502 mJobSchedulerStub = new JobSchedulerStub();
Christopher Tate7060b042014-06-09 19:50:00 -0700503 mJobs = JobStore.initAndGet(this);
504 }
505
506 @Override
507 public void onStart() {
508 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
509 }
510
511 @Override
512 public void onBootPhase(int phase) {
513 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000514 // Register br for package removals and user removals.
Christopher Tate7060b042014-06-09 19:50:00 -0700515 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
516 filter.addDataScheme("package");
517 getContext().registerReceiverAsUser(
518 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
519 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700520 userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700521 userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
Christopher Tate7060b042014-06-09 19:50:00 -0700522 getContext().registerReceiverAsUser(
523 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000524 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700525 try {
526 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800527 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
528 | ActivityManager.UID_OBSERVER_IDLE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700529 } catch (RemoteException e) {
530 // ignored; both services live in system_server
531 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700532 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800533 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700534 // Let's go!
535 mReadyToRock = true;
536 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
537 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800538 mLocalDeviceIdleController
539 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700540 // Create the "runners".
541 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
542 mActiveServices.add(
543 new JobServiceContext(this, mBatteryStats,
544 getContext().getMainLooper()));
545 }
546 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800547 mJobs.forEachJob(new JobStatusFunctor() {
548 @Override
549 public void process(JobStatus job) {
550 for (int controller = 0; controller < mControllers.size(); controller++) {
551 final StateController sc = mControllers.get(controller);
552 sc.deviceIdleModeChanged(mDeviceIdleMode);
553 sc.maybeStartTrackingJobLocked(job, null);
554 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700555 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800556 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700557 // GO GO GO!
558 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
559 }
Christopher Tate7060b042014-06-09 19:50:00 -0700560 }
561 }
562
563 /**
564 * Called when we have a job status object that we need to insert in our
565 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
566 * about.
567 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800568 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800569 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800570 final boolean update = mJobs.add(jobStatus);
571 if (mReadyToRock) {
572 for (int i = 0; i < mControllers.size(); i++) {
573 StateController controller = mControllers.get(i);
574 if (update) {
575 controller.maybeStopTrackingJobLocked(jobStatus, true);
576 }
577 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700578 }
Christopher Tate7060b042014-06-09 19:50:00 -0700579 }
Christopher Tate7060b042014-06-09 19:50:00 -0700580 }
581 }
582
583 /**
584 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
585 * object removed.
586 */
Shreyas Basarge73f10252016-02-11 17:06:13 +0000587 private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800588 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700589 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800590 final boolean removed = mJobs.remove(jobStatus, writeBack);
591 if (removed && mReadyToRock) {
592 for (int i=0; i<mControllers.size(); i++) {
593 StateController controller = mControllers.get(i);
594 controller.maybeStopTrackingJobLocked(jobStatus, false);
595 }
Christopher Tate7060b042014-06-09 19:50:00 -0700596 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800597 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700598 }
Christopher Tate7060b042014-06-09 19:50:00 -0700599 }
600
Shreyas Basarge5db09082016-01-07 13:38:29 +0000601 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700602 for (int i=0; i<mActiveServices.size(); i++) {
603 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700604 final JobStatus executing = jsc.getRunningJob();
605 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000606 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700607 return true;
608 }
609 }
610 return false;
611 }
612
613 /**
614 * @param job JobStatus we are querying against.
615 * @return Whether or not the job represented by the status object is currently being run or
616 * is pending.
617 */
618 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700619 for (int i=0; i<mActiveServices.size(); i++) {
620 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700621 final JobStatus running = serviceContext.getRunningJob();
622 if (running != null && running.matches(job.getUid(), job.getJobId())) {
623 return true;
624 }
625 }
626 return false;
627 }
628
629 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700630 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
631 * specify an override deadline on a failed job (the failed job will run even though it's not
632 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
633 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
634 *
Christopher Tate7060b042014-06-09 19:50:00 -0700635 * @param failureToReschedule Provided job status that we will reschedule.
636 * @return A newly instantiated JobStatus with the same constraints as the last job except
637 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700638 *
639 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700640 */
641 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
642 final long elapsedNowMillis = SystemClock.elapsedRealtime();
643 final JobInfo job = failureToReschedule.getJob();
644
645 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700646 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
647 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700648
649 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700650 case JobInfo.BACKOFF_POLICY_LINEAR:
651 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700652 break;
653 default:
654 if (DEBUG) {
655 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
656 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700657 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
658 delayMillis =
659 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700660 break;
661 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700662 delayMillis =
663 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800664 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700665 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800666 for (int ic=0; ic<mControllers.size(); ic++) {
667 StateController controller = mControllers.get(ic);
668 controller.rescheduleForFailure(newJob, failureToReschedule);
669 }
670 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700671 }
672
673 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700674 * Called after a periodic has executed so we can reschedule it. We take the last execution
675 * time of the job to be the time of completion (i.e. the time at which this function is
676 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700677 * This could be inaccurate b/c the job can run for as long as
678 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
679 * to underscheduling at least, rather than if we had taken the last execution time to be the
680 * start of the execution.
681 * @return A new job representing the execution criteria for this instantiation of the
682 * recurring job.
683 */
684 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
685 final long elapsedNow = SystemClock.elapsedRealtime();
686 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700687 long runEarly = 0L;
688
689 // If this periodic was rescheduled it won't have a deadline.
690 if (periodicToReschedule.hasDeadlineConstraint()) {
691 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
692 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000693 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -0700694 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000695 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
696 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -0700697
698 if (DEBUG) {
699 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
700 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
701 }
702 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
703 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
704 }
705
706 // JobCompletedListener implementations.
707
708 /**
709 * A job just finished executing. We fetch the
710 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
711 * whether we want to reschedule we readd it to the controllers.
712 * @param jobStatus Completed job.
713 * @param needsReschedule Whether the implementing class should reschedule this job.
714 */
715 @Override
716 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
717 if (DEBUG) {
718 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
719 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000720 // Do not write back immediately if this is a periodic job. The job may get lost if system
721 // shuts down before it is added back.
722 if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700723 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700724 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -0700725 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800726 // We still want to check for jobs to execute, because this job may have
727 // scheduled a new job under the same job id, and now we can run it.
728 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700729 return;
730 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800731 // Note: there is a small window of time in here where, when rescheduling a job,
732 // we will stop monitoring its content providers. This should be fixed by stopping
733 // the old job after scheduling the new one, but since we have no lock held here
734 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -0700735 if (needsReschedule) {
736 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800737 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700738 } else if (jobStatus.getJob().isPeriodic()) {
739 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800740 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700741 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000742 reportActive();
743 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700744 }
745
746 // StateChangedListener implementations.
747
748 /**
Matthew Williams48a30db2014-09-23 13:39:36 -0700749 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
750 * some controller's state has changed, so as to run through the list of jobs and start/stop
751 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -0700752 */
753 @Override
754 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700755 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700756 }
757
758 @Override
759 public void onRunJobNow(JobStatus jobStatus) {
760 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
761 }
762
Christopher Tate7060b042014-06-09 19:50:00 -0700763 private class JobHandler extends Handler {
764
765 public JobHandler(Looper looper) {
766 super(looper);
767 }
768
769 @Override
770 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800771 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700772 if (!mReadyToRock) {
773 return;
774 }
775 }
Christopher Tate7060b042014-06-09 19:50:00 -0700776 switch (message.what) {
777 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800778 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700779 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700780 // runNow can be null, which is a controller's way of indicating that its
781 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -0700782 if (runNow != null && !mPendingJobs.contains(runNow)
783 && mJobs.containsJob(runNow)) {
Christopher Tate7060b042014-06-09 19:50:00 -0700784 mPendingJobs.add(runNow);
785 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700786 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -0700787 }
Christopher Tate7060b042014-06-09 19:50:00 -0700788 break;
789 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800790 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000791 if (mReportedActive) {
792 // if jobs are currently being run, queue all ready jobs for execution.
793 queueReadyJobsForExecutionLockedH();
794 } else {
795 // Check the list of jobs and run some of them if we feel inclined.
796 maybeQueueReadyJobsForExecutionLockedH();
797 }
798 }
799 break;
800 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800801 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000802 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -0700803 }
Christopher Tate7060b042014-06-09 19:50:00 -0700804 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700805 case MSG_STOP_JOB:
806 cancelJobImpl((JobStatus)message.obj);
807 break;
Christopher Tate7060b042014-06-09 19:50:00 -0700808 }
809 maybeRunPendingJobsH();
810 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
811 removeMessages(MSG_CHECK_JOB);
812 }
813
814 /**
815 * Run through list of jobs and execute all possible - at least one is expired so we do
816 * as many as we can.
817 */
Matthew Williams48a30db2014-09-23 13:39:36 -0700818 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700819 if (DEBUG) {
820 Slog.d(TAG, "queuing all ready jobs for execution:");
821 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800822 mPendingJobs.clear();
823 mJobs.forEachJob(mReadyQueueFunctor);
824 mReadyQueueFunctor.postProcess();
825
Matthew Williams48a30db2014-09-23 13:39:36 -0700826 if (DEBUG) {
827 final int queuedJobs = mPendingJobs.size();
828 if (queuedJobs == 0) {
829 Slog.d(TAG, "No jobs pending.");
830 } else {
831 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -0700832 }
Christopher Tate7060b042014-06-09 19:50:00 -0700833 }
834 }
835
Christopher Tate2f36fd62016-02-18 18:36:08 -0800836 class ReadyJobQueueFunctor implements JobStatusFunctor {
837 ArrayList<JobStatus> newReadyJobs;
838
839 @Override
840 public void process(JobStatus job) {
841 if (isReadyToBeExecutedLocked(job)) {
842 if (DEBUG) {
843 Slog.d(TAG, " queued " + job.toShortString());
844 }
845 if (newReadyJobs == null) {
846 newReadyJobs = new ArrayList<JobStatus>();
847 }
848 newReadyJobs.add(job);
849 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
850 stopJobOnServiceContextLocked(job,
851 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
852 }
853 }
854
855 public void postProcess() {
856 if (newReadyJobs != null) {
857 mPendingJobs.addAll(newReadyJobs);
858 }
859 newReadyJobs = null;
860 }
861 }
862 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
863
Christopher Tate7060b042014-06-09 19:50:00 -0700864 /**
865 * The state of at least one job has changed. Here is where we could enforce various
866 * policies on when we want to execute jobs.
867 * Right now the policy is such:
868 * If >1 of the ready jobs is idle mode we send all of them off
869 * if more than 2 network connectivity jobs are ready we send them all off.
870 * If more than 4 jobs total are ready we send them all off.
871 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
872 */
Christopher Tate2f36fd62016-02-18 18:36:08 -0800873 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
874 int chargingCount;
875 int idleCount;
876 int backoffCount;
877 int connectivityCount;
878 int contentCount;
879 List<JobStatus> runnableJobs;
880
881 public MaybeReadyJobQueueFunctor() {
882 reset();
883 }
884
885 // Functor method invoked for each job via JobStore.forEachJob()
886 @Override
887 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700888 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700889 try {
890 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
891 job.getJob().getService().getPackageName())
892 == ActivityManager.APP_START_MODE_DISABLED) {
893 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
894 + job.getJob().toString() + " -- package not allowed to start");
895 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800896 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700897 }
898 } catch (RemoteException e) {
899 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700900 if (job.getNumFailures() > 0) {
901 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -0700902 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700903 if (job.hasIdleConstraint()) {
904 idleCount++;
905 }
906 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
907 connectivityCount++;
908 }
909 if (job.hasChargingConstraint()) {
910 chargingCount++;
911 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800912 if (job.hasContentTriggerConstraint()) {
913 contentCount++;
914 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700915 if (runnableJobs == null) {
916 runnableJobs = new ArrayList<>();
917 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700918 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800919 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000920 stopJobOnServiceContextLocked(job,
921 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -0700922 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700923 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800924
925 public void postProcess() {
926 if (backoffCount > 0 ||
927 idleCount >= MIN_IDLE_COUNT ||
928 connectivityCount >= MIN_CONNECTIVITY_COUNT ||
929 chargingCount >= MIN_CHARGING_COUNT ||
930 contentCount >= MIN_CONTENT_COUNT ||
931 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
932 if (DEBUG) {
933 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
934 }
935 mPendingJobs.addAll(runnableJobs);
936 } else {
937 if (DEBUG) {
938 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
939 }
Christopher Tate7060b042014-06-09 19:50:00 -0700940 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800941
942 // Be ready for next time
943 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -0700944 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800945
946 private void reset() {
947 chargingCount = 0;
948 idleCount = 0;
949 backoffCount = 0;
950 connectivityCount = 0;
951 contentCount = 0;
952 runnableJobs = null;
953 }
954 }
955 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
956
957 private void maybeQueueReadyJobsForExecutionLockedH() {
958 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
959
960 mPendingJobs.clear();
961 mJobs.forEachJob(mMaybeQueueFunctor);
962 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -0700963 }
964
965 /**
966 * Criteria for moving a job into the pending queue:
967 * - It's ready.
968 * - It's not pending.
969 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700970 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700971 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -0700972 */
973 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700974 final boolean jobReady = job.isReady();
975 final boolean jobPending = mPendingJobs.contains(job);
976 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700977
978 final int userId = job.getUserId();
979 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
980 final boolean componentPresent;
981 try {
982 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
983 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
984 userId) != null);
985 } catch (RemoteException e) {
986 throw e.rethrowAsRuntimeException();
987 }
988
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700989 if (DEBUG) {
990 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
991 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700992 + " active=" + jobActive + " userStarted=" + userStarted
993 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700994 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700995 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -0700996 }
997
998 /**
999 * Criteria for cancelling an active job:
1000 * - It's not ready
1001 * - It's running on a JSC.
1002 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001003 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -07001004 return !job.isReady() && isCurrentlyActiveLocked(job);
1005 }
1006
1007 /**
1008 * Reconcile jobs in the pending queue against available execution contexts.
1009 * A controller can force a job into the pending queue even if it's already running, but
1010 * here is where we decide whether to actually execute it.
1011 */
1012 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001013 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -07001014 if (mDeviceIdleMode) {
1015 // If device is idle, we will not schedule jobs to run.
1016 return;
1017 }
Matthew Williams75fc5252014-09-02 16:17:53 -07001018 if (DEBUG) {
1019 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
1020 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001021 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001022 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001023 }
1024 }
1025 }
1026
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001027 private int evaluateJobPriorityLocked(JobStatus job) {
1028 int priority = job.getPriority();
1029 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
1030 return priority;
1031 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001032 int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
1033 if (override != 0) {
1034 return override;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001035 }
1036 return priority;
1037 }
1038
Christopher Tate7060b042014-06-09 19:50:00 -07001039 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001040 * Takes jobs from pending queue and runs them on available contexts.
1041 * If no contexts are available, preempts lower priority jobs to
1042 * run higher priority ones.
1043 * Lock on mJobs before calling this function.
1044 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001045 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001046 if (DEBUG) {
1047 Slog.d(TAG, printPendingQueue());
1048 }
1049
Dianne Hackborn970510b2016-02-24 16:56:42 -08001050 int memLevel;
1051 try {
1052 memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
1053 } catch (RemoteException e) {
1054 memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
1055 }
1056 switch (memLevel) {
1057 case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
1058 mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - 2) * 2) / 3;
1059 break;
1060 case ProcessStats.ADJ_MEM_FACTOR_LOW:
1061 mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - 2) / 3;
1062 break;
1063 case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
1064 mMaxActiveJobs = 1;
1065 break;
1066 default:
1067 mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
1068 break;
1069 }
1070
1071 JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
1072 boolean[] act = mTmpAssignAct;
1073 int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
1074 int numActive = 0;
1075 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
1076 final JobServiceContext js = mActiveServices.get(i);
1077 if ((contextIdToJobMap[i] = js.getRunningJob()) != null) {
1078 numActive++;
1079 }
1080 act[i] = false;
1081 preferredUidForContext[i] = js.getPreferredUid();
Shreyas Basarge5db09082016-01-07 13:38:29 +00001082 }
1083 if (DEBUG) {
1084 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1085 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001086 for (int i=0; i<mPendingJobs.size(); i++) {
1087 JobStatus nextPending = mPendingJobs.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001088
1089 // If job is already running, go to next job.
1090 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1091 if (jobRunningContext != -1) {
1092 continue;
1093 }
1094
Dianne Hackborn970510b2016-02-24 16:56:42 -08001095 final int priority = evaluateJobPriorityLocked(nextPending);
1096 nextPending.lastEvaluatedPriority = priority;
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001097
Shreyas Basarge5db09082016-01-07 13:38:29 +00001098 // Find a context for nextPending. The context should be available OR
1099 // it should have lowest priority among all running jobs
1100 // (sharing the same Uid as nextPending)
1101 int minPriority = Integer.MAX_VALUE;
1102 int minPriorityContextId = -1;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001103 for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
1104 JobStatus job = contextIdToJobMap[j];
1105 int preferredUid = preferredUidForContext[j];
Shreyas Basarge347c2782016-01-15 18:24:36 +00001106 if (job == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001107 if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) &&
1108 (preferredUid == nextPending.getUid() ||
1109 preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
1110 // This slot is free, and we haven't yet hit the limit on
1111 // concurrent jobs... we can just throw the job in to here.
1112 minPriorityContextId = j;
1113 numActive++;
1114 break;
1115 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001116 // No job on this context, but nextPending can't run here because
Dianne Hackborn970510b2016-02-24 16:56:42 -08001117 // the context has a preferred Uid or we have reached the limit on
1118 // concurrent jobs.
Shreyas Basarge347c2782016-01-15 18:24:36 +00001119 continue;
1120 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001121 if (job.getUid() != nextPending.getUid()) {
1122 continue;
1123 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001124 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001125 continue;
1126 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001127 if (minPriority > nextPending.lastEvaluatedPriority) {
1128 minPriority = nextPending.lastEvaluatedPriority;
Dianne Hackborn970510b2016-02-24 16:56:42 -08001129 minPriorityContextId = j;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001130 }
1131 }
1132 if (minPriorityContextId != -1) {
1133 contextIdToJobMap[minPriorityContextId] = nextPending;
1134 act[minPriorityContextId] = true;
1135 }
1136 }
1137 if (DEBUG) {
1138 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1139 }
Dianne Hackborn970510b2016-02-24 16:56:42 -08001140 for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001141 boolean preservePreferredUid = false;
1142 if (act[i]) {
1143 JobStatus js = mActiveServices.get(i).getRunningJob();
1144 if (js != null) {
1145 if (DEBUG) {
1146 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1147 }
1148 // preferredUid will be set to uid of currently running job.
1149 mActiveServices.get(i).preemptExecutingJob();
1150 preservePreferredUid = true;
1151 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001152 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001153 if (DEBUG) {
1154 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001155 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001156 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001157 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001158 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001159 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001160 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1161 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001162 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001163 mPendingJobs.remove(pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001164 }
1165 }
1166 if (!preservePreferredUid) {
1167 mActiveServices.get(i).clearPreferredUid();
1168 }
1169 }
1170 }
1171
1172 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1173 for (int i=0; i<map.length; i++) {
1174 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1175 return i;
1176 }
1177 }
1178 return -1;
1179 }
1180
1181 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001182 * Binder stub trampoline implementation
1183 */
1184 final class JobSchedulerStub extends IJobScheduler.Stub {
1185 /** Cache determination of whether a given app can persist jobs
1186 * key is uid of the calling app; value is undetermined/true/false
1187 */
1188 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1189
1190 // Enforce that only the app itself (or shared uid participant) can schedule a
1191 // job that runs one of the app's services, as well as verifying that the
1192 // named service properly requires the BIND_JOB_SERVICE permission
1193 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001194 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001195 final ComponentName service = job.getService();
1196 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001197 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001198 PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE,
1199 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001200 if (si == null) {
1201 throw new IllegalArgumentException("No such service " + service);
1202 }
Christopher Tate7060b042014-06-09 19:50:00 -07001203 if (si.applicationInfo.uid != uid) {
1204 throw new IllegalArgumentException("uid " + uid +
1205 " cannot schedule job in " + service.getPackageName());
1206 }
1207 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1208 throw new IllegalArgumentException("Scheduled service " + service
1209 + " does not require android.permission.BIND_JOB_SERVICE permission");
1210 }
Christopher Tate5568f542014-06-18 13:53:31 -07001211 } catch (RemoteException e) {
1212 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001213 }
1214 }
1215
1216 private boolean canPersistJobs(int pid, int uid) {
1217 // If we get this far we're good to go; all we need to do now is check
1218 // whether the app is allowed to persist its scheduled work.
1219 final boolean canPersist;
1220 synchronized (mPersistCache) {
1221 Boolean cached = mPersistCache.get(uid);
1222 if (cached != null) {
1223 canPersist = cached.booleanValue();
1224 } else {
1225 // Persisting jobs is tantamount to running at boot, so we permit
1226 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1227 // permission
1228 int result = getContext().checkPermission(
1229 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1230 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1231 mPersistCache.put(uid, canPersist);
1232 }
1233 }
1234 return canPersist;
1235 }
1236
1237 // IJobScheduler implementation
1238 @Override
1239 public int schedule(JobInfo job) throws RemoteException {
1240 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001241 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001242 }
1243 final int pid = Binder.getCallingPid();
1244 final int uid = Binder.getCallingUid();
1245
1246 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001247 if (job.isPersisted()) {
1248 if (!canPersistJobs(pid, uid)) {
1249 throw new IllegalArgumentException("Error: requested job be persisted without"
1250 + " holding RECEIVE_BOOT_COMPLETED permission.");
1251 }
1252 }
Christopher Tate7060b042014-06-09 19:50:00 -07001253
1254 long ident = Binder.clearCallingIdentity();
1255 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001256 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001257 } finally {
1258 Binder.restoreCallingIdentity(ident);
1259 }
1260 }
1261
1262 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001263 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001264 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001265 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001266 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001267 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1268 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001269 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001270
1271 if (packageName == null) {
1272 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001273 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001274
1275 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1276 android.Manifest.permission.UPDATE_DEVICE_STATS);
1277 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1278 throw new SecurityException("Caller uid " + callerUid
1279 + " not permitted to schedule jobs for other apps");
1280 }
1281
Shreyas Basarge968ac752016-01-11 23:09:26 +00001282 long ident = Binder.clearCallingIdentity();
1283 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001284 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001285 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001286 } finally {
1287 Binder.restoreCallingIdentity(ident);
1288 }
1289 }
1290
1291 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001292 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1293 final int uid = Binder.getCallingUid();
1294
1295 long ident = Binder.clearCallingIdentity();
1296 try {
1297 return JobSchedulerService.this.getPendingJobs(uid);
1298 } finally {
1299 Binder.restoreCallingIdentity(ident);
1300 }
1301 }
1302
1303 @Override
1304 public void cancelAll() throws RemoteException {
1305 final int uid = Binder.getCallingUid();
1306
1307 long ident = Binder.clearCallingIdentity();
1308 try {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001309 JobSchedulerService.this.cancelJobsForUid(uid, true);
Christopher Tate7060b042014-06-09 19:50:00 -07001310 } finally {
1311 Binder.restoreCallingIdentity(ident);
1312 }
1313 }
1314
1315 @Override
1316 public void cancel(int jobId) throws RemoteException {
1317 final int uid = Binder.getCallingUid();
1318
1319 long ident = Binder.clearCallingIdentity();
1320 try {
1321 JobSchedulerService.this.cancelJob(uid, jobId);
1322 } finally {
1323 Binder.restoreCallingIdentity(ident);
1324 }
1325 }
1326
1327 /**
1328 * "dumpsys" infrastructure
1329 */
1330 @Override
1331 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1332 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1333
1334 long identityToken = Binder.clearCallingIdentity();
1335 try {
1336 JobSchedulerService.this.dumpInternal(pw);
1337 } finally {
1338 Binder.restoreCallingIdentity(identityToken);
1339 }
1340 }
Christopher Tate5d346052016-03-08 12:56:08 -08001341
1342 @Override
1343 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1344 String[] args, ResultReceiver resultReceiver) throws RemoteException {
1345 (new JobSchedulerShellCommand(JobSchedulerService.this)).exec(
1346 this, in, out, err, args, resultReceiver);
1347 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001348 };
1349
Christopher Tate5d346052016-03-08 12:56:08 -08001350 // Shell command infrastructure: run the given job immediately
1351 int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
1352 if (DEBUG) {
1353 Slog.v(TAG, "executeRunCommand(): " + pkgName + "/" + userId
1354 + " " + jobId + " f=" + force);
1355 }
1356
1357 try {
1358 final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0, userId);
1359 if (uid < 0) {
1360 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
1361 }
1362
1363 synchronized (mLock) {
1364 final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
1365 if (js == null) {
1366 return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
1367 }
1368
1369 js.overrideState = (force) ? JobStatus.OVERRIDE_FULL : JobStatus.OVERRIDE_SOFT;
1370 if (!js.isConstraintsSatisfied()) {
1371 js.overrideState = 0;
1372 return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
1373 }
1374
1375 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
1376 }
1377 } catch (RemoteException e) {
1378 // can't happen
1379 }
1380 return 0;
1381 }
1382
Shreyas Basarge5db09082016-01-07 13:38:29 +00001383 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1384 StringBuilder s = new StringBuilder(initial + ": ");
1385 for (int i=0; i<map.length; i++) {
1386 s.append("(")
1387 .append(map[i] == null? -1: map[i].getJobId())
1388 .append(map[i] == null? -1: map[i].getUid())
1389 .append(")" );
1390 }
1391 return s.toString();
1392 }
1393
1394 private String printPendingQueue() {
1395 StringBuilder s = new StringBuilder("Pending queue: ");
1396 Iterator<JobStatus> it = mPendingJobs.iterator();
1397 while (it.hasNext()) {
1398 JobStatus js = it.next();
1399 s.append("(")
1400 .append(js.getJob().getId())
1401 .append(", ")
1402 .append(js.getUid())
1403 .append(") ");
1404 }
1405 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001406 }
Christopher Tate7060b042014-06-09 19:50:00 -07001407
Christopher Tate2f36fd62016-02-18 18:36:08 -08001408 void dumpInternal(final PrintWriter pw) {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001409 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001410 synchronized (mLock) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001411 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Christopher Tate7060b042014-06-09 19:50:00 -07001412 pw.println("Registered jobs:");
1413 if (mJobs.size() > 0) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001414 mJobs.forEachJob(new JobStatusFunctor() {
1415 private int index = 0;
1416
1417 @Override
1418 public void process(JobStatus job) {
1419 pw.print(" Job #"); pw.print(index++); pw.print(": ");
1420 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001421 job.dump(pw, " ", true);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001422 pw.print(" Ready: ");
1423 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1424 pw.print(" (job=");
1425 pw.print(job.isReady());
1426 pw.print(" pending=");
1427 pw.print(mPendingJobs.contains(job));
1428 pw.print(" active=");
1429 pw.print(isCurrentlyActiveLocked(job));
1430 pw.print(" user=");
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001431 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
Christopher Tate2f36fd62016-02-18 18:36:08 -08001432 pw.println(")");
1433 }
1434 });
Christopher Tate7060b042014-06-09 19:50:00 -07001435 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001436 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001437 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001438 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001439 pw.println();
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001440 mControllers.get(i).dumpControllerStateLocked(pw);
Christopher Tate7060b042014-06-09 19:50:00 -07001441 }
1442 pw.println();
Dianne Hackborn970510b2016-02-24 16:56:42 -08001443 pw.println("Uid priority overrides:");
1444 for (int i=0; i< mUidPriorityOverride.size(); i++) {
1445 pw.print(" "); pw.print(UserHandle.formatUid(mUidPriorityOverride.keyAt(i)));
1446 pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001447 }
1448 pw.println();
1449 pw.println("Pending queue:");
1450 for (int i=0; i<mPendingJobs.size(); i++) {
1451 JobStatus job = mPendingJobs.get(i);
1452 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1453 pw.println(job.toShortString());
Dianne Hackborn970510b2016-02-24 16:56:42 -08001454 job.dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001455 int priority = evaluateJobPriorityLocked(job);
1456 if (priority != JobInfo.PRIORITY_DEFAULT) {
1457 pw.print(" Evaluated priority: "); pw.println(priority);
1458 }
1459 pw.print(" Tag: "); pw.println(job.getTag());
1460 }
Christopher Tate7060b042014-06-09 19:50:00 -07001461 pw.println();
1462 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001463 for (int i=0; i<mActiveServices.size(); i++) {
1464 JobServiceContext jsc = mActiveServices.get(i);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001465 pw.print(" Slot #"); pw.print(i); pw.print(": ");
Shreyas Basarge5db09082016-01-07 13:38:29 +00001466 if (jsc.getRunningJob() == null) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001467 pw.println("inactive");
Christopher Tate7060b042014-06-09 19:50:00 -07001468 continue;
1469 } else {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001470 pw.println(jsc.getRunningJob().toShortString());
1471 pw.print(" Running for: ");
1472 TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
1473 pw.print(", timeout at: ");
1474 TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
1475 pw.println();
1476 jsc.getRunningJob().dump(pw, " ", false);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001477 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1478 if (priority != JobInfo.PRIORITY_DEFAULT) {
Dianne Hackborn970510b2016-02-24 16:56:42 -08001479 pw.print(" Evaluated priority: "); pw.println(priority);
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001480 }
Christopher Tate7060b042014-06-09 19:50:00 -07001481 }
1482 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001483 pw.println();
1484 pw.print("mReadyToRock="); pw.println(mReadyToRock);
Dianne Hackborn88e98df2015-03-23 13:29:14 -07001485 pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001486 pw.print("mReportedActive="); pw.println(mReportedActive);
Dianne Hackborn970510b2016-02-24 16:56:42 -08001487 pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
Christopher Tate7060b042014-06-09 19:50:00 -07001488 }
1489 pw.println();
1490 }
1491}