blob: 4bf77d3e150f67b76fad7c157855acad336f8cec [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;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070050import android.os.ServiceManager;
Christopher Tate7060b042014-06-09 19:50:00 -070051import android.os.SystemClock;
52import android.os.UserHandle;
53import android.util.Slog;
54import android.util.SparseArray;
55
Dianne Hackborn1085ff62016-02-23 17:04:58 -080056import android.util.SparseBooleanArray;
Dianne Hackbornfdb19562014-07-11 16:03:36 -070057import com.android.internal.app.IBatteryStats;
Jeff Sharkey822cbd12016-02-25 11:09:55 -070058import com.android.internal.util.ArrayUtils;
Dianne Hackborn627dfa12015-11-11 18:10:30 -080059import com.android.server.DeviceIdleController;
60import com.android.server.LocalServices;
Christopher Tate2f36fd62016-02-18 18:36:08 -080061import com.android.server.job.JobStore.JobStatusFunctor;
Amith Yamasanib0ff3222015-03-04 09:56:14 -080062import com.android.server.job.controllers.AppIdleController;
Christopher Tate7060b042014-06-09 19:50:00 -070063import com.android.server.job.controllers.BatteryController;
64import com.android.server.job.controllers.ConnectivityController;
Dianne Hackborn1a30bd92016-01-11 11:05:00 -080065import com.android.server.job.controllers.ContentObserverController;
Christopher Tate7060b042014-06-09 19:50:00 -070066import com.android.server.job.controllers.IdleController;
67import com.android.server.job.controllers.JobStatus;
68import com.android.server.job.controllers.StateController;
69import com.android.server.job.controllers.TimeController;
70
Jeff Sharkey822cbd12016-02-25 11:09:55 -070071import libcore.util.EmptyArray;
72
Christopher Tate7060b042014-06-09 19:50:00 -070073/**
74 * Responsible for taking jobs representing work to be performed by a client app, and determining
75 * based on the criteria specified when that job should be run against the client application's
76 * endpoint.
77 * Implements logic for scheduling, and rescheduling jobs. The JobSchedulerService knows nothing
78 * about constraints, or the state of active jobs. It receives callbacks from the various
79 * controllers and completed jobs and operates accordingly.
80 *
81 * Note on locking: Any operations that manipulate {@link #mJobs} need to lock on that object.
82 * Any function with the suffix 'Locked' also needs to lock on {@link #mJobs}.
83 * @hide
84 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -080085public final class JobSchedulerService extends com.android.server.SystemService
Matthew Williams01ac45b2014-07-22 20:44:12 -070086 implements StateChangedListener, JobCompletedListener {
Christopher Tate2f36fd62016-02-18 18:36:08 -080087 static final String TAG = "JobSchedulerService";
Matthew Williamsaa984312015-10-15 16:08:05 -070088 public static final boolean DEBUG = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -080089
Christopher Tate7060b042014-06-09 19:50:00 -070090 /** The number of concurrent jobs we run at one time. */
Dianne Hackborn8ad2af72015-03-17 17:00:24 -070091 private static final int MAX_JOB_CONTEXTS_COUNT
Shreyas Basarge8c834c02016-01-07 13:53:16 +000092 = ActivityManager.isLowRamDeviceStatic() ? 3 : 6;
Christopher Tatedabdf6f2016-02-24 12:30:22 -080093 /** Enforce a per-app limit on scheduled jobs? */
94 private static final boolean ENFORCE_MAX_JOBS = false;
Christopher Tate2f36fd62016-02-18 18:36:08 -080095 /** The maximum number of jobs that we allow an unprivileged app to schedule */
96 private static final int MAX_JOBS_PER_APP = 100;
97
Dianne Hackborn33d31c52016-02-16 10:30:33 -080098 /** Global local for all job scheduler state. */
99 final Object mLock = new Object();
Christopher Tate7060b042014-06-09 19:50:00 -0700100 /** Master list of jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700101 final JobStore mJobs;
Christopher Tate7060b042014-06-09 19:50:00 -0700102
103 static final int MSG_JOB_EXPIRED = 0;
104 static final int MSG_CHECK_JOB = 1;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700105 static final int MSG_STOP_JOB = 2;
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000106 static final int MSG_CHECK_JOB_GREEDY = 3;
Christopher Tate7060b042014-06-09 19:50:00 -0700107
108 // Policy constants
109 /**
110 * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
111 * early.
112 */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700113 static final int MIN_IDLE_COUNT = 1;
Christopher Tate7060b042014-06-09 19:50:00 -0700114 /**
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700115 * Minimum # of charging jobs that must be ready in order to force the JMS to schedule things
116 * early.
117 */
118 static final int MIN_CHARGING_COUNT = 1;
119 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700120 * Minimum # of connectivity jobs that must be ready in order to force the JMS to schedule
121 * things early.
122 */
Matthew Williamsaa984312015-10-15 16:08:05 -0700123 static final int MIN_CONNECTIVITY_COUNT = 1; // Run connectivity jobs as soon as ready.
Christopher Tate7060b042014-06-09 19:50:00 -0700124 /**
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800125 * Minimum # of content trigger jobs that must be ready in order to force the JMS to schedule
126 * things early.
127 */
128 static final int MIN_CONTENT_COUNT = 1;
129 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700130 * Minimum # of jobs (with no particular constraints) for which the JMS will be happy running
131 * some work early.
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700132 * This is correlated with the amount of batching we'll be able to do.
Christopher Tate7060b042014-06-09 19:50:00 -0700133 */
Matthew Williamsbe0c4172014-08-06 18:14:16 -0700134 static final int MIN_READY_JOBS_COUNT = 2;
Christopher Tate7060b042014-06-09 19:50:00 -0700135
136 /**
137 * Track Services that have currently active or pending jobs. The index is provided by
138 * {@link JobStatus#getServiceToken()}
139 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700140 final List<JobServiceContext> mActiveServices = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700141 /** List of controllers that will notify this service of updates to jobs. */
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700142 List<StateController> mControllers;
Christopher Tate7060b042014-06-09 19:50:00 -0700143 /**
144 * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
145 * when ready to execute them.
146 */
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700147 final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
Christopher Tate7060b042014-06-09 19:50:00 -0700148
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700149 int[] mStartedUsers = EmptyArray.INT;
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700150
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700151 final JobHandler mHandler;
152 final JobSchedulerStub mJobSchedulerStub;
153
154 IBatteryStats mBatteryStats;
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700155 PowerManager mPowerManager;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800156 DeviceIdleController.LocalService mLocalDeviceIdleController;
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700157
158 /**
159 * Set to true once we are allowed to run third party apps.
160 */
161 boolean mReadyToRock;
162
Christopher Tate7060b042014-06-09 19:50:00 -0700163 /**
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700164 * True when in device idle mode, so we don't want to schedule any jobs.
165 */
166 boolean mDeviceIdleMode;
167
168 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800169 * What we last reported to DeviceIdleController about whether we are active.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800170 */
171 boolean mReportedActive;
172
173 /**
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800174 * Which uids are currently in the foreground.
175 */
176 final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
177
178 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700179 * Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
180 * still clean up. On reinstall the package will have a new uid.
181 */
182 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
183 @Override
184 public void onReceive(Context context, Intent intent) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000185 Slog.d(TAG, "Receieved: " + intent.getAction());
186 if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
Christopher Tateaad67a32014-10-20 16:29:20 -0700187 // If this is an outright uninstall rather than the first half of an
188 // app update sequence, cancel the jobs associated with the app.
189 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
190 int uidRemoved = intent.getIntExtra(Intent.EXTRA_UID, -1);
191 if (DEBUG) {
192 Slog.d(TAG, "Removing jobs for uid: " + uidRemoved);
193 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700194 cancelJobsForUid(uidRemoved, true);
Christopher Tate7060b042014-06-09 19:50:00 -0700195 }
Shreyas Basarge5db09082016-01-07 13:38:29 +0000196 } else if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700197 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
198 if (DEBUG) {
199 Slog.d(TAG, "Removing jobs for user: " + userId);
200 }
201 cancelJobsForUser(userId);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000202 } else if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())
203 || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700204 updateIdleMode(mPowerManager != null
205 ? (mPowerManager.isDeviceIdleMode()
Shreyas Basarge5db09082016-01-07 13:38:29 +0000206 || mPowerManager.isLightDeviceIdleMode())
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700207 : false);
Christopher Tate7060b042014-06-09 19:50:00 -0700208 }
209 }
210 };
211
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700212 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
213 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800214 updateUidState(uid, procState);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700215 }
216
217 @Override public void onUidGone(int uid) throws RemoteException {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800218 updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700219 }
220
221 @Override public void onUidActive(int uid) throws RemoteException {
222 }
223
224 @Override public void onUidIdle(int uid) throws RemoteException {
225 cancelJobsForUid(uid, false);
226 }
227 };
228
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800229 public Object getLock() {
230 return mLock;
231 }
232
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700233 @Override
234 public void onStartUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700235 mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
236 // Let's kick any outstanding jobs for this user.
237 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
238 }
239
240 @Override
241 public void onUnlockUser(int userHandle) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700242 // Let's kick any outstanding jobs for this user.
243 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
244 }
245
246 @Override
247 public void onStopUser(int userHandle) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700248 mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700249 }
250
Christopher Tate7060b042014-06-09 19:50:00 -0700251 /**
252 * Entry point from client to schedule the provided job.
253 * This cancels the job if it's already been scheduled, and replaces it with the one provided.
254 * @param job JobInfo object containing execution parameters
255 * @param uId The package identifier of the application this job is for.
Christopher Tate7060b042014-06-09 19:50:00 -0700256 * @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
257 */
Matthew Williams900c67f2014-07-09 12:46:53 -0700258 public int schedule(JobInfo job, int uId) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800259 return scheduleAsPackage(job, uId, null, -1, null);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000260 }
261
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800262 public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId,
263 String tag) {
264 JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700265 try {
266 if (ActivityManagerNative.getDefault().getAppStartMode(uId,
267 job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) {
268 Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
269 + " -- package not allowed to start");
270 return JobScheduler.RESULT_FAILURE;
271 }
272 } catch (RemoteException e) {
273 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800274 if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
275 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800276 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800277 // Jobs on behalf of others don't apply to the per-app job cap
Christopher Tatedabdf6f2016-02-24 12:30:22 -0800278 if (ENFORCE_MAX_JOBS && packageName == null) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800279 if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
280 Slog.w(TAG, "Too many jobs for uid " + uId);
281 throw new IllegalStateException("Apps may not schedule more than "
282 + MAX_JOBS_PER_APP + " distinct jobs");
283 }
284 }
285
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800286 toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
287 }
288 startTrackingJob(jobStatus, toCancel);
289 if (toCancel != null) {
290 cancelJobImpl(toCancel);
291 }
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700292 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700293 return JobScheduler.RESULT_SUCCESS;
294 }
295
296 public List<JobInfo> getPendingJobs(int uid) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800297 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800298 List<JobStatus> jobs = mJobs.getJobsByUid(uid);
299 ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
300 for (int i = jobs.size() - 1; i >= 0; i--) {
301 JobStatus job = jobs.get(i);
302 outList.add(job.getJob());
Christopher Tate7060b042014-06-09 19:50:00 -0700303 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800304 return outList;
Christopher Tate7060b042014-06-09 19:50:00 -0700305 }
Christopher Tate7060b042014-06-09 19:50:00 -0700306 }
307
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700308 void cancelJobsForUser(int userHandle) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700309 List<JobStatus> jobsForUser;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800310 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700311 jobsForUser = mJobs.getJobsByUser(userHandle);
312 }
313 for (int i=0; i<jobsForUser.size(); i++) {
314 JobStatus toRemove = jobsForUser.get(i);
315 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700316 }
317 }
318
319 /**
320 * Entry point from client to cancel all jobs originating from their uid.
321 * This will remove the job from the master list, and cancel the job if it was staged for
322 * execution or being executed.
Matthew Williams48a30db2014-09-23 13:39:36 -0700323 * @param uid Uid to check against for removal of a job.
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700324 * @param forceAll If true, all jobs for the uid will be canceled; if false, only those
325 * whose apps are stopped.
Christopher Tate7060b042014-06-09 19:50:00 -0700326 */
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700327 public void cancelJobsForUid(int uid, boolean forceAll) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700328 List<JobStatus> jobsForUid;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800329 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700330 jobsForUid = mJobs.getJobsByUid(uid);
331 }
332 for (int i=0; i<jobsForUid.size(); i++) {
333 JobStatus toRemove = jobsForUid.get(i);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700334 if (!forceAll) {
335 String packageName = toRemove.getServiceComponent().getPackageName();
336 try {
337 if (ActivityManagerNative.getDefault().getAppStartMode(uid, packageName)
338 != ActivityManager.APP_START_MODE_DISABLED) {
339 continue;
340 }
341 } catch (RemoteException e) {
342 }
343 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700344 cancelJobImpl(toRemove);
Christopher Tate7060b042014-06-09 19:50:00 -0700345 }
346 }
347
348 /**
349 * Entry point from client to cancel the job corresponding to the jobId provided.
350 * This will remove the job from the master list, and cancel the job if it was staged for
351 * execution or being executed.
352 * @param uid Uid of the calling client.
353 * @param jobId Id of the job, provided at schedule-time.
354 */
355 public void cancelJob(int uid, int jobId) {
356 JobStatus toCancel;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800357 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700358 toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
Matthew Williams48a30db2014-09-23 13:39:36 -0700359 }
360 if (toCancel != null) {
361 cancelJobImpl(toCancel);
Christopher Tate7060b042014-06-09 19:50:00 -0700362 }
363 }
364
Matthew Williams48a30db2014-09-23 13:39:36 -0700365 private void cancelJobImpl(JobStatus cancelled) {
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800366 if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
Shreyas Basarge73f10252016-02-11 17:06:13 +0000367 stopTrackingJob(cancelled, true /* writeBack */);
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800368 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700369 // Remove from pending queue.
370 mPendingJobs.remove(cancelled);
371 // Cancel if running.
Shreyas Basarge5db09082016-01-07 13:38:29 +0000372 stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800373 reportActive();
Matthew Williams48a30db2014-09-23 13:39:36 -0700374 }
Christopher Tate7060b042014-06-09 19:50:00 -0700375 }
376
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800377 void updateUidState(int uid, int procState) {
378 synchronized (mLock) {
379 boolean foreground = procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
380 boolean changed = false;
381 if (foreground) {
382 if (!mForegroundUids.get(uid)) {
383 changed = true;
384 mForegroundUids.put(uid, true);
385 }
386 } else {
387 int index = mForegroundUids.indexOfKey(uid);
388 if (index >= 0) {
389 mForegroundUids.removeAt(index);
390 changed = true;
391 }
392 }
393 }
394 }
395
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700396 void updateIdleMode(boolean enabled) {
397 boolean changed = false;
398 boolean rocking;
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800399 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700400 if (mDeviceIdleMode != enabled) {
401 changed = true;
402 }
403 rocking = mReadyToRock;
404 }
405 if (changed) {
406 if (rocking) {
407 for (int i=0; i<mControllers.size(); i++) {
408 mControllers.get(i).deviceIdleModeChanged(enabled);
409 }
410 }
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800411 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700412 mDeviceIdleMode = enabled;
413 if (enabled) {
414 // When becoming idle, make sure no jobs are actively running.
415 for (int i=0; i<mActiveServices.size(); i++) {
416 JobServiceContext jsc = mActiveServices.get(i);
417 final JobStatus executing = jsc.getRunningJob();
418 if (executing != null) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000419 jsc.cancelExecutingJob(JobParameters.REASON_DEVICE_IDLE);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700420 }
421 }
422 } else {
423 // When coming out of idle, allow thing to start back up.
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800424 if (rocking) {
425 if (mLocalDeviceIdleController != null) {
426 if (!mReportedActive) {
427 mReportedActive = true;
428 mLocalDeviceIdleController.setJobsActive(true);
429 }
430 }
431 }
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700432 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
433 }
434 }
435 }
436 }
437
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800438 void reportActive() {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000439 // active is true if pending queue contains jobs OR some job is running.
440 boolean active = mPendingJobs.size() > 0;
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800441 if (mPendingJobs.size() <= 0) {
442 for (int i=0; i<mActiveServices.size(); i++) {
443 JobServiceContext jsc = mActiveServices.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000444 if (jsc.getRunningJob() != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800445 active = true;
446 break;
447 }
448 }
449 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000450
451 if (mReportedActive != active) {
452 mReportedActive = active;
453 if (mLocalDeviceIdleController != null) {
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800454 mLocalDeviceIdleController.setJobsActive(active);
455 }
456 }
457 }
458
Christopher Tate7060b042014-06-09 19:50:00 -0700459 /**
460 * Initializes the system service.
461 * <p>
462 * Subclasses must define a single argument constructor that accepts the context
463 * and passes it to super.
464 * </p>
465 *
466 * @param context The system server context.
467 */
468 public JobSchedulerService(Context context) {
469 super(context);
470 // Create the controllers.
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700471 mControllers = new ArrayList<StateController>();
Christopher Tate7060b042014-06-09 19:50:00 -0700472 mControllers.add(ConnectivityController.get(this));
473 mControllers.add(TimeController.get(this));
474 mControllers.add(IdleController.get(this));
475 mControllers.add(BatteryController.get(this));
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800476 mControllers.add(AppIdleController.get(this));
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800477 mControllers.add(ContentObserverController.get(this));
Christopher Tate7060b042014-06-09 19:50:00 -0700478
479 mHandler = new JobHandler(context.getMainLooper());
480 mJobSchedulerStub = new JobSchedulerStub();
Christopher Tate7060b042014-06-09 19:50:00 -0700481 mJobs = JobStore.initAndGet(this);
482 }
483
484 @Override
485 public void onStart() {
486 publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
487 }
488
489 @Override
490 public void onBootPhase(int phase) {
491 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000492 // Register br for package removals and user removals.
Christopher Tate7060b042014-06-09 19:50:00 -0700493 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
494 filter.addDataScheme("package");
495 getContext().registerReceiverAsUser(
496 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
497 final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700498 userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
Dianne Hackborn08c47a52015-10-15 12:38:14 -0700499 userFilter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
Christopher Tate7060b042014-06-09 19:50:00 -0700500 getContext().registerReceiverAsUser(
501 mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
Shreyas Basarge5db09082016-01-07 13:38:29 +0000502 mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700503 try {
504 ActivityManagerNative.getDefault().registerUidObserver(mUidObserver,
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800505 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
506 | ActivityManager.UID_OBSERVER_IDLE);
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700507 } catch (RemoteException e) {
508 // ignored; both services live in system_server
509 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700510 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800511 synchronized (mLock) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700512 // Let's go!
513 mReadyToRock = true;
514 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
515 BatteryStats.SERVICE_NAME));
Dianne Hackborn627dfa12015-11-11 18:10:30 -0800516 mLocalDeviceIdleController
517 = LocalServices.getService(DeviceIdleController.LocalService.class);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700518 // Create the "runners".
519 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
520 mActiveServices.add(
521 new JobServiceContext(this, mBatteryStats,
522 getContext().getMainLooper()));
523 }
524 // Attach jobs to their controllers.
Christopher Tate2f36fd62016-02-18 18:36:08 -0800525 mJobs.forEachJob(new JobStatusFunctor() {
526 @Override
527 public void process(JobStatus job) {
528 for (int controller = 0; controller < mControllers.size(); controller++) {
529 final StateController sc = mControllers.get(controller);
530 sc.deviceIdleModeChanged(mDeviceIdleMode);
531 sc.maybeStartTrackingJobLocked(job, null);
532 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700533 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800534 });
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700535 // GO GO GO!
536 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
537 }
Christopher Tate7060b042014-06-09 19:50:00 -0700538 }
539 }
540
541 /**
542 * Called when we have a job status object that we need to insert in our
543 * {@link com.android.server.job.JobStore}, and make sure all the relevant controllers know
544 * about.
545 */
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800546 private void startTrackingJob(JobStatus jobStatus, JobStatus lastJob) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800547 synchronized (mLock) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800548 final boolean update = mJobs.add(jobStatus);
549 if (mReadyToRock) {
550 for (int i = 0; i < mControllers.size(); i++) {
551 StateController controller = mControllers.get(i);
552 if (update) {
553 controller.maybeStopTrackingJobLocked(jobStatus, true);
554 }
555 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700556 }
Christopher Tate7060b042014-06-09 19:50:00 -0700557 }
Christopher Tate7060b042014-06-09 19:50:00 -0700558 }
559 }
560
561 /**
562 * Called when we want to remove a JobStatus object that we've finished executing. Returns the
563 * object removed.
564 */
Shreyas Basarge73f10252016-02-11 17:06:13 +0000565 private boolean stopTrackingJob(JobStatus jobStatus, boolean writeBack) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800566 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700567 // Remove from store as well as controllers.
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800568 final boolean removed = mJobs.remove(jobStatus, writeBack);
569 if (removed && mReadyToRock) {
570 for (int i=0; i<mControllers.size(); i++) {
571 StateController controller = mControllers.get(i);
572 controller.maybeStopTrackingJobLocked(jobStatus, false);
573 }
Christopher Tate7060b042014-06-09 19:50:00 -0700574 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800575 return removed;
Christopher Tate7060b042014-06-09 19:50:00 -0700576 }
Christopher Tate7060b042014-06-09 19:50:00 -0700577 }
578
Shreyas Basarge5db09082016-01-07 13:38:29 +0000579 private boolean stopJobOnServiceContextLocked(JobStatus job, int reason) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700580 for (int i=0; i<mActiveServices.size(); i++) {
581 JobServiceContext jsc = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700582 final JobStatus executing = jsc.getRunningJob();
583 if (executing != null && executing.matches(job.getUid(), job.getJobId())) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000584 jsc.cancelExecutingJob(reason);
Christopher Tate7060b042014-06-09 19:50:00 -0700585 return true;
586 }
587 }
588 return false;
589 }
590
591 /**
592 * @param job JobStatus we are querying against.
593 * @return Whether or not the job represented by the status object is currently being run or
594 * is pending.
595 */
596 private boolean isCurrentlyActiveLocked(JobStatus job) {
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700597 for (int i=0; i<mActiveServices.size(); i++) {
598 JobServiceContext serviceContext = mActiveServices.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700599 final JobStatus running = serviceContext.getRunningJob();
600 if (running != null && running.matches(job.getUid(), job.getJobId())) {
601 return true;
602 }
603 }
604 return false;
605 }
606
607 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700608 * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
609 * specify an override deadline on a failed job (the failed job will run even though it's not
610 * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
611 * ready job with {@link JobStatus#numFailures} > 0 will be executed.
612 *
Christopher Tate7060b042014-06-09 19:50:00 -0700613 * @param failureToReschedule Provided job status that we will reschedule.
614 * @return A newly instantiated JobStatus with the same constraints as the last job except
615 * with adjusted timing constraints.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700616 *
617 * @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
Christopher Tate7060b042014-06-09 19:50:00 -0700618 */
619 private JobStatus getRescheduleJobForFailure(JobStatus failureToReschedule) {
620 final long elapsedNowMillis = SystemClock.elapsedRealtime();
621 final JobInfo job = failureToReschedule.getJob();
622
623 final long initialBackoffMillis = job.getInitialBackoffMillis();
Matthew Williamsd1c06752014-08-22 14:15:28 -0700624 final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
625 long delayMillis;
Christopher Tate7060b042014-06-09 19:50:00 -0700626
627 switch (job.getBackoffPolicy()) {
Matthew Williamsd1c06752014-08-22 14:15:28 -0700628 case JobInfo.BACKOFF_POLICY_LINEAR:
629 delayMillis = initialBackoffMillis * backoffAttempts;
Christopher Tate7060b042014-06-09 19:50:00 -0700630 break;
631 default:
632 if (DEBUG) {
633 Slog.v(TAG, "Unrecognised back-off policy, defaulting to exponential.");
634 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700635 case JobInfo.BACKOFF_POLICY_EXPONENTIAL:
636 delayMillis =
637 (long) Math.scalb(initialBackoffMillis, backoffAttempts - 1);
Christopher Tate7060b042014-06-09 19:50:00 -0700638 break;
639 }
Matthew Williamsd1c06752014-08-22 14:15:28 -0700640 delayMillis =
641 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800642 JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
Matthew Williamsd1c06752014-08-22 14:15:28 -0700643 JobStatus.NO_LATEST_RUNTIME, backoffAttempts);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800644 for (int ic=0; ic<mControllers.size(); ic++) {
645 StateController controller = mControllers.get(ic);
646 controller.rescheduleForFailure(newJob, failureToReschedule);
647 }
648 return newJob;
Christopher Tate7060b042014-06-09 19:50:00 -0700649 }
650
651 /**
Matthew Williams1bde39a2015-10-07 14:29:30 -0700652 * Called after a periodic has executed so we can reschedule it. We take the last execution
653 * time of the job to be the time of completion (i.e. the time at which this function is
654 * called).
Christopher Tate7060b042014-06-09 19:50:00 -0700655 * This could be inaccurate b/c the job can run for as long as
656 * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
657 * to underscheduling at least, rather than if we had taken the last execution time to be the
658 * start of the execution.
659 * @return A new job representing the execution criteria for this instantiation of the
660 * recurring job.
661 */
662 private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
663 final long elapsedNow = SystemClock.elapsedRealtime();
664 // Compute how much of the period is remaining.
Matthew Williams1bde39a2015-10-07 14:29:30 -0700665 long runEarly = 0L;
666
667 // If this periodic was rescheduled it won't have a deadline.
668 if (periodicToReschedule.hasDeadlineConstraint()) {
669 runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
670 }
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000671 long flex = periodicToReschedule.getJob().getFlexMillis();
Christopher Tate7060b042014-06-09 19:50:00 -0700672 long period = periodicToReschedule.getJob().getIntervalMillis();
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000673 long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
674 long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
Christopher Tate7060b042014-06-09 19:50:00 -0700675
676 if (DEBUG) {
677 Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
678 newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
679 }
680 return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
681 newLatestRuntimeElapsed, 0 /* backoffAttempt */);
682 }
683
684 // JobCompletedListener implementations.
685
686 /**
687 * A job just finished executing. We fetch the
688 * {@link com.android.server.job.controllers.JobStatus} from the store and depending on
689 * whether we want to reschedule we readd it to the controllers.
690 * @param jobStatus Completed job.
691 * @param needsReschedule Whether the implementing class should reschedule this job.
692 */
693 @Override
694 public void onJobCompleted(JobStatus jobStatus, boolean needsReschedule) {
695 if (DEBUG) {
696 Slog.d(TAG, "Completed " + jobStatus + ", reschedule=" + needsReschedule);
697 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000698 // Do not write back immediately if this is a periodic job. The job may get lost if system
699 // shuts down before it is added back.
700 if (!stopTrackingJob(jobStatus, !jobStatus.getJob().isPeriodic())) {
Christopher Tate7060b042014-06-09 19:50:00 -0700701 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -0700702 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
Christopher Tate7060b042014-06-09 19:50:00 -0700703 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800704 // We still want to check for jobs to execute, because this job may have
705 // scheduled a new job under the same job id, and now we can run it.
706 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700707 return;
708 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800709 // Note: there is a small window of time in here where, when rescheduling a job,
710 // we will stop monitoring its content providers. This should be fixed by stopping
711 // the old job after scheduling the new one, but since we have no lock held here
712 // that may cause ordering problems if the app removes jobStatus while in here.
Christopher Tate7060b042014-06-09 19:50:00 -0700713 if (needsReschedule) {
714 JobStatus rescheduled = getRescheduleJobForFailure(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800715 startTrackingJob(rescheduled, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700716 } else if (jobStatus.getJob().isPeriodic()) {
717 JobStatus rescheduledPeriodic = getRescheduleJobForPeriodic(jobStatus);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800718 startTrackingJob(rescheduledPeriodic, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700719 }
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000720 reportActive();
721 mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700722 }
723
724 // StateChangedListener implementations.
725
726 /**
Matthew Williams48a30db2014-09-23 13:39:36 -0700727 * Posts a message to the {@link com.android.server.job.JobSchedulerService.JobHandler} that
728 * some controller's state has changed, so as to run through the list of jobs and start/stop
729 * any that are eligible.
Christopher Tate7060b042014-06-09 19:50:00 -0700730 */
731 @Override
732 public void onControllerStateChanged() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700733 mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
Christopher Tate7060b042014-06-09 19:50:00 -0700734 }
735
736 @Override
737 public void onRunJobNow(JobStatus jobStatus) {
738 mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
739 }
740
Christopher Tate7060b042014-06-09 19:50:00 -0700741 private class JobHandler extends Handler {
742
743 public JobHandler(Looper looper) {
744 super(looper);
745 }
746
747 @Override
748 public void handleMessage(Message message) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800749 synchronized (mLock) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700750 if (!mReadyToRock) {
751 return;
752 }
753 }
Christopher Tate7060b042014-06-09 19:50:00 -0700754 switch (message.what) {
755 case MSG_JOB_EXPIRED:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800756 synchronized (mLock) {
Christopher Tate7060b042014-06-09 19:50:00 -0700757 JobStatus runNow = (JobStatus) message.obj;
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700758 // runNow can be null, which is a controller's way of indicating that its
759 // state is such that all ready jobs should be run immediately.
Matthew Williams48a30db2014-09-23 13:39:36 -0700760 if (runNow != null && !mPendingJobs.contains(runNow)
761 && mJobs.containsJob(runNow)) {
Christopher Tate7060b042014-06-09 19:50:00 -0700762 mPendingJobs.add(runNow);
763 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700764 queueReadyJobsForExecutionLockedH();
Christopher Tate7060b042014-06-09 19:50:00 -0700765 }
Christopher Tate7060b042014-06-09 19:50:00 -0700766 break;
767 case MSG_CHECK_JOB:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800768 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000769 if (mReportedActive) {
770 // if jobs are currently being run, queue all ready jobs for execution.
771 queueReadyJobsForExecutionLockedH();
772 } else {
773 // Check the list of jobs and run some of them if we feel inclined.
774 maybeQueueReadyJobsForExecutionLockedH();
775 }
776 }
777 break;
778 case MSG_CHECK_JOB_GREEDY:
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800779 synchronized (mLock) {
Shreyas Basarge4cff8ac2015-12-10 21:32:52 +0000780 queueReadyJobsForExecutionLockedH();
Matthew Williams48a30db2014-09-23 13:39:36 -0700781 }
Christopher Tate7060b042014-06-09 19:50:00 -0700782 break;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700783 case MSG_STOP_JOB:
784 cancelJobImpl((JobStatus)message.obj);
785 break;
Christopher Tate7060b042014-06-09 19:50:00 -0700786 }
787 maybeRunPendingJobsH();
788 // Don't remove JOB_EXPIRED in case one came along while processing the queue.
789 removeMessages(MSG_CHECK_JOB);
790 }
791
792 /**
793 * Run through list of jobs and execute all possible - at least one is expired so we do
794 * as many as we can.
795 */
Matthew Williams48a30db2014-09-23 13:39:36 -0700796 private void queueReadyJobsForExecutionLockedH() {
Matthew Williams48a30db2014-09-23 13:39:36 -0700797 if (DEBUG) {
798 Slog.d(TAG, "queuing all ready jobs for execution:");
799 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800800 mPendingJobs.clear();
801 mJobs.forEachJob(mReadyQueueFunctor);
802 mReadyQueueFunctor.postProcess();
803
Matthew Williams48a30db2014-09-23 13:39:36 -0700804 if (DEBUG) {
805 final int queuedJobs = mPendingJobs.size();
806 if (queuedJobs == 0) {
807 Slog.d(TAG, "No jobs pending.");
808 } else {
809 Slog.d(TAG, queuedJobs + " jobs queued.");
Matthew Williams75fc5252014-09-02 16:17:53 -0700810 }
Christopher Tate7060b042014-06-09 19:50:00 -0700811 }
812 }
813
Christopher Tate2f36fd62016-02-18 18:36:08 -0800814 class ReadyJobQueueFunctor implements JobStatusFunctor {
815 ArrayList<JobStatus> newReadyJobs;
816
817 @Override
818 public void process(JobStatus job) {
819 if (isReadyToBeExecutedLocked(job)) {
820 if (DEBUG) {
821 Slog.d(TAG, " queued " + job.toShortString());
822 }
823 if (newReadyJobs == null) {
824 newReadyJobs = new ArrayList<JobStatus>();
825 }
826 newReadyJobs.add(job);
827 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
828 stopJobOnServiceContextLocked(job,
829 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
830 }
831 }
832
833 public void postProcess() {
834 if (newReadyJobs != null) {
835 mPendingJobs.addAll(newReadyJobs);
836 }
837 newReadyJobs = null;
838 }
839 }
840 private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
841
Christopher Tate7060b042014-06-09 19:50:00 -0700842 /**
843 * The state of at least one job has changed. Here is where we could enforce various
844 * policies on when we want to execute jobs.
845 * Right now the policy is such:
846 * If >1 of the ready jobs is idle mode we send all of them off
847 * if more than 2 network connectivity jobs are ready we send them all off.
848 * If more than 4 jobs total are ready we send them all off.
849 * TODO: It would be nice to consolidate these sort of high-level policies somewhere.
850 */
Christopher Tate2f36fd62016-02-18 18:36:08 -0800851 class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
852 int chargingCount;
853 int idleCount;
854 int backoffCount;
855 int connectivityCount;
856 int contentCount;
857 List<JobStatus> runnableJobs;
858
859 public MaybeReadyJobQueueFunctor() {
860 reset();
861 }
862
863 // Functor method invoked for each job via JobStore.forEachJob()
864 @Override
865 public void process(JobStatus job) {
Matthew Williams48a30db2014-09-23 13:39:36 -0700866 if (isReadyToBeExecutedLocked(job)) {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700867 try {
868 if (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
869 job.getJob().getService().getPackageName())
870 == ActivityManager.APP_START_MODE_DISABLED) {
871 Slog.w(TAG, "Aborting job " + job.getUid() + ":"
872 + job.getJob().toString() + " -- package not allowed to start");
873 mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800874 return;
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700875 }
876 } catch (RemoteException e) {
877 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700878 if (job.getNumFailures() > 0) {
879 backoffCount++;
Christopher Tate7060b042014-06-09 19:50:00 -0700880 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700881 if (job.hasIdleConstraint()) {
882 idleCount++;
883 }
884 if (job.hasConnectivityConstraint() || job.hasUnmeteredConstraint()) {
885 connectivityCount++;
886 }
887 if (job.hasChargingConstraint()) {
888 chargingCount++;
889 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -0800890 if (job.hasContentTriggerConstraint()) {
891 contentCount++;
892 }
Dianne Hackbornbef28fe2015-10-29 17:57:11 -0700893 if (runnableJobs == null) {
894 runnableJobs = new ArrayList<>();
895 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700896 runnableJobs.add(job);
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800897 } else if (areJobConstraintsNotSatisfiedLocked(job)) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000898 stopJobOnServiceContextLocked(job,
899 JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
Christopher Tate7060b042014-06-09 19:50:00 -0700900 }
Matthew Williams48a30db2014-09-23 13:39:36 -0700901 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800902
903 public void postProcess() {
904 if (backoffCount > 0 ||
905 idleCount >= MIN_IDLE_COUNT ||
906 connectivityCount >= MIN_CONNECTIVITY_COUNT ||
907 chargingCount >= MIN_CHARGING_COUNT ||
908 contentCount >= MIN_CONTENT_COUNT ||
909 (runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
910 if (DEBUG) {
911 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
912 }
913 mPendingJobs.addAll(runnableJobs);
914 } else {
915 if (DEBUG) {
916 Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
917 }
Christopher Tate7060b042014-06-09 19:50:00 -0700918 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800919
920 // Be ready for next time
921 reset();
Matthew Williams48a30db2014-09-23 13:39:36 -0700922 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800923
924 private void reset() {
925 chargingCount = 0;
926 idleCount = 0;
927 backoffCount = 0;
928 connectivityCount = 0;
929 contentCount = 0;
930 runnableJobs = null;
931 }
932 }
933 private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
934
935 private void maybeQueueReadyJobsForExecutionLockedH() {
936 if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
937
938 mPendingJobs.clear();
939 mJobs.forEachJob(mMaybeQueueFunctor);
940 mMaybeQueueFunctor.postProcess();
Christopher Tate7060b042014-06-09 19:50:00 -0700941 }
942
943 /**
944 * Criteria for moving a job into the pending queue:
945 * - It's ready.
946 * - It's not pending.
947 * - It's not already running on a JSC.
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700948 * - The user that requested the job is running.
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700949 * - The component is enabled and runnable.
Christopher Tate7060b042014-06-09 19:50:00 -0700950 */
951 private boolean isReadyToBeExecutedLocked(JobStatus job) {
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700952 final boolean jobReady = job.isReady();
953 final boolean jobPending = mPendingJobs.contains(job);
954 final boolean jobActive = isCurrentlyActiveLocked(job);
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700955
956 final int userId = job.getUserId();
957 final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
958 final boolean componentPresent;
959 try {
960 componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
961 job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
962 userId) != null);
963 } catch (RemoteException e) {
964 throw e.rethrowAsRuntimeException();
965 }
966
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700967 if (DEBUG) {
968 Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
969 + " ready=" + jobReady + " pending=" + jobPending
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700970 + " active=" + jobActive + " userStarted=" + userStarted
971 + " componentPresent=" + componentPresent);
Matthew Williams9ae3dbe2014-08-21 13:47:47 -0700972 }
Jeff Sharkey822cbd12016-02-25 11:09:55 -0700973 return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
Christopher Tate7060b042014-06-09 19:50:00 -0700974 }
975
976 /**
977 * Criteria for cancelling an active job:
978 * - It's not ready
979 * - It's running on a JSC.
980 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800981 private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
Christopher Tate7060b042014-06-09 19:50:00 -0700982 return !job.isReady() && isCurrentlyActiveLocked(job);
983 }
984
985 /**
986 * Reconcile jobs in the pending queue against available execution contexts.
987 * A controller can force a job into the pending queue even if it's already running, but
988 * here is where we decide whether to actually execute it.
989 */
990 private void maybeRunPendingJobsH() {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800991 synchronized (mLock) {
Dianne Hackborn88e98df2015-03-23 13:29:14 -0700992 if (mDeviceIdleMode) {
993 // If device is idle, we will not schedule jobs to run.
994 return;
995 }
Matthew Williams75fc5252014-09-02 16:17:53 -0700996 if (DEBUG) {
997 Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
998 }
Dianne Hackbornb0001f62016-02-16 10:30:33 -0800999 assignJobsToContextsLocked();
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001000 reportActive();
Christopher Tate7060b042014-06-09 19:50:00 -07001001 }
1002 }
1003 }
1004
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001005 private int evaluateJobPriorityLocked(JobStatus job) {
1006 int priority = job.getPriority();
1007 if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
1008 return priority;
1009 }
1010 if (mForegroundUids.get(job.getSourceUid())) {
1011 return JobInfo.PRIORITY_FOREGROUND_APP;
1012 }
1013 return priority;
1014 }
1015
Christopher Tate7060b042014-06-09 19:50:00 -07001016 /**
Shreyas Basarge5db09082016-01-07 13:38:29 +00001017 * Takes jobs from pending queue and runs them on available contexts.
1018 * If no contexts are available, preempts lower priority jobs to
1019 * run higher priority ones.
1020 * Lock on mJobs before calling this function.
1021 */
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001022 private void assignJobsToContextsLocked() {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001023 if (DEBUG) {
1024 Slog.d(TAG, printPendingQueue());
1025 }
1026
1027 // This array essentially stores the state of mActiveServices array.
1028 // ith index stores the job present on the ith JobServiceContext.
1029 // We manipulate this array until we arrive at what jobs should be running on
1030 // what JobServiceContext.
1031 JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
1032 // Indicates whether we need to act on this jobContext id
1033 boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT];
1034 int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
1035 for (int i=0; i<mActiveServices.size(); i++) {
1036 contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob();
1037 preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid();
1038 }
1039 if (DEBUG) {
1040 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
1041 }
1042 Iterator<JobStatus> it = mPendingJobs.iterator();
1043 while (it.hasNext()) {
1044 JobStatus nextPending = it.next();
1045
1046 // If job is already running, go to next job.
1047 int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
1048 if (jobRunningContext != -1) {
1049 continue;
1050 }
1051
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001052 nextPending.lastEvaluatedPriority = evaluateJobPriorityLocked(nextPending);
1053
Shreyas Basarge5db09082016-01-07 13:38:29 +00001054 // Find a context for nextPending. The context should be available OR
1055 // it should have lowest priority among all running jobs
1056 // (sharing the same Uid as nextPending)
1057 int minPriority = Integer.MAX_VALUE;
1058 int minPriorityContextId = -1;
1059 for (int i=0; i<mActiveServices.size(); i++) {
1060 JobStatus job = contextIdToJobMap[i];
1061 int preferredUid = preferredUidForContext[i];
1062 if (job == null && (preferredUid == nextPending.getUid() ||
1063 preferredUid == JobServiceContext.NO_PREFERRED_UID) ) {
1064 minPriorityContextId = i;
1065 break;
1066 }
Shreyas Basarge347c2782016-01-15 18:24:36 +00001067 if (job == null) {
1068 // No job on this context, but nextPending can't run here because
1069 // the context has a preferred Uid.
1070 continue;
1071 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001072 if (job.getUid() != nextPending.getUid()) {
1073 continue;
1074 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001075 if (evaluateJobPriorityLocked(job) >= nextPending.lastEvaluatedPriority) {
Shreyas Basarge5db09082016-01-07 13:38:29 +00001076 continue;
1077 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001078 if (minPriority > nextPending.lastEvaluatedPriority) {
1079 minPriority = nextPending.lastEvaluatedPriority;
Shreyas Basarge5db09082016-01-07 13:38:29 +00001080 minPriorityContextId = i;
1081 }
1082 }
1083 if (minPriorityContextId != -1) {
1084 contextIdToJobMap[minPriorityContextId] = nextPending;
1085 act[minPriorityContextId] = true;
1086 }
1087 }
1088 if (DEBUG) {
1089 Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
1090 }
1091 for (int i=0; i<mActiveServices.size(); i++) {
1092 boolean preservePreferredUid = false;
1093 if (act[i]) {
1094 JobStatus js = mActiveServices.get(i).getRunningJob();
1095 if (js != null) {
1096 if (DEBUG) {
1097 Slog.d(TAG, "preempting job: " + mActiveServices.get(i).getRunningJob());
1098 }
1099 // preferredUid will be set to uid of currently running job.
1100 mActiveServices.get(i).preemptExecutingJob();
1101 preservePreferredUid = true;
1102 } else {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001103 final JobStatus pendingJob = contextIdToJobMap[i];
Shreyas Basarge5db09082016-01-07 13:38:29 +00001104 if (DEBUG) {
1105 Slog.d(TAG, "About to run job on context "
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001106 + String.valueOf(i) + ", job: " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001107 }
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001108 for (int ic=0; ic<mControllers.size(); ic++) {
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001109 mControllers.get(ic).prepareForExecutionLocked(pendingJob);
Dianne Hackborn1a30bd92016-01-11 11:05:00 -08001110 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001111 if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
1112 Slog.d(TAG, "Error executing " + pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001113 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001114 mPendingJobs.remove(pendingJob);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001115 }
1116 }
1117 if (!preservePreferredUid) {
1118 mActiveServices.get(i).clearPreferredUid();
1119 }
1120 }
1121 }
1122
1123 int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
1124 for (int i=0; i<map.length; i++) {
1125 if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
1126 return i;
1127 }
1128 }
1129 return -1;
1130 }
1131
1132 /**
Christopher Tate7060b042014-06-09 19:50:00 -07001133 * Binder stub trampoline implementation
1134 */
1135 final class JobSchedulerStub extends IJobScheduler.Stub {
1136 /** Cache determination of whether a given app can persist jobs
1137 * key is uid of the calling app; value is undetermined/true/false
1138 */
1139 private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>();
1140
1141 // Enforce that only the app itself (or shared uid participant) can schedule a
1142 // job that runs one of the app's services, as well as verifying that the
1143 // named service properly requires the BIND_JOB_SERVICE permission
1144 private void enforceValidJobRequest(int uid, JobInfo job) {
Christopher Tate5568f542014-06-18 13:53:31 -07001145 final IPackageManager pm = AppGlobals.getPackageManager();
Christopher Tate7060b042014-06-09 19:50:00 -07001146 final ComponentName service = job.getService();
1147 try {
Jeff Sharkeyc7bacab2016-02-09 15:56:11 -07001148 ServiceInfo si = pm.getServiceInfo(service,
Jeff Sharkey12c0da42016-02-25 17:10:50 -07001149 PackageManager.MATCH_ENCRYPTION_AWARE_AND_UNAWARE,
1150 UserHandle.getUserId(uid));
Christopher Tate5568f542014-06-18 13:53:31 -07001151 if (si == null) {
1152 throw new IllegalArgumentException("No such service " + service);
1153 }
Christopher Tate7060b042014-06-09 19:50:00 -07001154 if (si.applicationInfo.uid != uid) {
1155 throw new IllegalArgumentException("uid " + uid +
1156 " cannot schedule job in " + service.getPackageName());
1157 }
1158 if (!JobService.PERMISSION_BIND.equals(si.permission)) {
1159 throw new IllegalArgumentException("Scheduled service " + service
1160 + " does not require android.permission.BIND_JOB_SERVICE permission");
1161 }
Christopher Tate5568f542014-06-18 13:53:31 -07001162 } catch (RemoteException e) {
1163 // Can't happen; the Package Manager is in this same process
Christopher Tate7060b042014-06-09 19:50:00 -07001164 }
1165 }
1166
1167 private boolean canPersistJobs(int pid, int uid) {
1168 // If we get this far we're good to go; all we need to do now is check
1169 // whether the app is allowed to persist its scheduled work.
1170 final boolean canPersist;
1171 synchronized (mPersistCache) {
1172 Boolean cached = mPersistCache.get(uid);
1173 if (cached != null) {
1174 canPersist = cached.booleanValue();
1175 } else {
1176 // Persisting jobs is tantamount to running at boot, so we permit
1177 // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED
1178 // permission
1179 int result = getContext().checkPermission(
1180 android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid);
1181 canPersist = (result == PackageManager.PERMISSION_GRANTED);
1182 mPersistCache.put(uid, canPersist);
1183 }
1184 }
1185 return canPersist;
1186 }
1187
1188 // IJobScheduler implementation
1189 @Override
1190 public int schedule(JobInfo job) throws RemoteException {
1191 if (DEBUG) {
Matthew Williamsee410da2014-07-25 11:30:40 -07001192 Slog.d(TAG, "Scheduling job: " + job.toString());
Christopher Tate7060b042014-06-09 19:50:00 -07001193 }
1194 final int pid = Binder.getCallingPid();
1195 final int uid = Binder.getCallingUid();
1196
1197 enforceValidJobRequest(uid, job);
Matthew Williams900c67f2014-07-09 12:46:53 -07001198 if (job.isPersisted()) {
1199 if (!canPersistJobs(pid, uid)) {
1200 throw new IllegalArgumentException("Error: requested job be persisted without"
1201 + " holding RECEIVE_BOOT_COMPLETED permission.");
1202 }
1203 }
Christopher Tate7060b042014-06-09 19:50:00 -07001204
1205 long ident = Binder.clearCallingIdentity();
1206 try {
Matthew Williams900c67f2014-07-09 12:46:53 -07001207 return JobSchedulerService.this.schedule(job, uid);
Christopher Tate7060b042014-06-09 19:50:00 -07001208 } finally {
1209 Binder.restoreCallingIdentity(ident);
1210 }
1211 }
1212
1213 @Override
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001214 public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
Shreyas Basarge968ac752016-01-11 23:09:26 +00001215 throws RemoteException {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001216 final int callerUid = Binder.getCallingUid();
Shreyas Basarge968ac752016-01-11 23:09:26 +00001217 if (DEBUG) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001218 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
1219 + " on behalf of " + packageName);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001220 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001221
1222 if (packageName == null) {
1223 throw new NullPointerException("Must specify a package for scheduleAsPackage()");
Shreyas Basarge968ac752016-01-11 23:09:26 +00001224 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001225
1226 int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
1227 android.Manifest.permission.UPDATE_DEVICE_STATS);
1228 if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
1229 throw new SecurityException("Caller uid " + callerUid
1230 + " not permitted to schedule jobs for other apps");
1231 }
1232
Shreyas Basarge968ac752016-01-11 23:09:26 +00001233 long ident = Binder.clearCallingIdentity();
1234 try {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001235 return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001236 packageName, userId, tag);
Shreyas Basarge968ac752016-01-11 23:09:26 +00001237 } finally {
1238 Binder.restoreCallingIdentity(ident);
1239 }
1240 }
1241
1242 @Override
Christopher Tate7060b042014-06-09 19:50:00 -07001243 public List<JobInfo> getAllPendingJobs() throws RemoteException {
1244 final int uid = Binder.getCallingUid();
1245
1246 long ident = Binder.clearCallingIdentity();
1247 try {
1248 return JobSchedulerService.this.getPendingJobs(uid);
1249 } finally {
1250 Binder.restoreCallingIdentity(ident);
1251 }
1252 }
1253
1254 @Override
1255 public void cancelAll() throws RemoteException {
1256 final int uid = Binder.getCallingUid();
1257
1258 long ident = Binder.clearCallingIdentity();
1259 try {
Dianne Hackbornbef28fe2015-10-29 17:57:11 -07001260 JobSchedulerService.this.cancelJobsForUid(uid, true);
Christopher Tate7060b042014-06-09 19:50:00 -07001261 } finally {
1262 Binder.restoreCallingIdentity(ident);
1263 }
1264 }
1265
1266 @Override
1267 public void cancel(int jobId) throws RemoteException {
1268 final int uid = Binder.getCallingUid();
1269
1270 long ident = Binder.clearCallingIdentity();
1271 try {
1272 JobSchedulerService.this.cancelJob(uid, jobId);
1273 } finally {
1274 Binder.restoreCallingIdentity(ident);
1275 }
1276 }
1277
1278 /**
1279 * "dumpsys" infrastructure
1280 */
1281 @Override
1282 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1283 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1284
1285 long identityToken = Binder.clearCallingIdentity();
1286 try {
1287 JobSchedulerService.this.dumpInternal(pw);
1288 } finally {
1289 Binder.restoreCallingIdentity(identityToken);
1290 }
1291 }
Shreyas Basarge5db09082016-01-07 13:38:29 +00001292 };
1293
1294 private String printContextIdToJobMap(JobStatus[] map, String initial) {
1295 StringBuilder s = new StringBuilder(initial + ": ");
1296 for (int i=0; i<map.length; i++) {
1297 s.append("(")
1298 .append(map[i] == null? -1: map[i].getJobId())
1299 .append(map[i] == null? -1: map[i].getUid())
1300 .append(")" );
1301 }
1302 return s.toString();
1303 }
1304
1305 private String printPendingQueue() {
1306 StringBuilder s = new StringBuilder("Pending queue: ");
1307 Iterator<JobStatus> it = mPendingJobs.iterator();
1308 while (it.hasNext()) {
1309 JobStatus js = it.next();
1310 s.append("(")
1311 .append(js.getJob().getId())
1312 .append(", ")
1313 .append(js.getUid())
1314 .append(") ");
1315 }
1316 return s.toString();
Jeff Sharkey5217cac2015-12-20 15:34:01 -07001317 }
Christopher Tate7060b042014-06-09 19:50:00 -07001318
Christopher Tate2f36fd62016-02-18 18:36:08 -08001319 void dumpInternal(final PrintWriter pw) {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001320 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn33d31c52016-02-16 10:30:33 -08001321 synchronized (mLock) {
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001322 pw.println("Started users: " + Arrays.toString(mStartedUsers));
Christopher Tate7060b042014-06-09 19:50:00 -07001323 pw.println("Registered jobs:");
1324 if (mJobs.size() > 0) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001325 mJobs.forEachJob(new JobStatusFunctor() {
1326 private int index = 0;
1327
1328 @Override
1329 public void process(JobStatus job) {
1330 pw.print(" Job #"); pw.print(index++); pw.print(": ");
1331 pw.println(job.toShortString());
1332 job.dump(pw, " ");
1333 pw.print(" Ready: ");
1334 pw.print(mHandler.isReadyToBeExecutedLocked(job));
1335 pw.print(" (job=");
1336 pw.print(job.isReady());
1337 pw.print(" pending=");
1338 pw.print(mPendingJobs.contains(job));
1339 pw.print(" active=");
1340 pw.print(isCurrentlyActiveLocked(job));
1341 pw.print(" user=");
Jeff Sharkey822cbd12016-02-25 11:09:55 -07001342 pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
Christopher Tate2f36fd62016-02-18 18:36:08 -08001343 pw.println(")");
1344 }
1345 });
Christopher Tate7060b042014-06-09 19:50:00 -07001346 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001347 pw.println(" None.");
Christopher Tate7060b042014-06-09 19:50:00 -07001348 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001349 for (int i=0; i<mControllers.size(); i++) {
Christopher Tate7060b042014-06-09 19:50:00 -07001350 pw.println();
Dianne Hackbornb0001f62016-02-16 10:30:33 -08001351 mControllers.get(i).dumpControllerStateLocked(pw);
Christopher Tate7060b042014-06-09 19:50:00 -07001352 }
1353 pw.println();
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001354 pw.println("Foreground uids:");
1355 for (int i=0; i<mForegroundUids.size(); i++) {
1356 pw.print(" "); pw.println(UserHandle.formatUid(mForegroundUids.keyAt(i)));
1357 }
1358 pw.println();
1359 pw.println("Pending queue:");
1360 for (int i=0; i<mPendingJobs.size(); i++) {
1361 JobStatus job = mPendingJobs.get(i);
1362 pw.print(" Pending #"); pw.print(i); pw.print(": ");
1363 pw.println(job.toShortString());
1364 int priority = evaluateJobPriorityLocked(job);
1365 if (priority != JobInfo.PRIORITY_DEFAULT) {
1366 pw.print(" Evaluated priority: "); pw.println(priority);
1367 }
1368 pw.print(" Tag: "); pw.println(job.getTag());
1369 }
Christopher Tate7060b042014-06-09 19:50:00 -07001370 pw.println();
1371 pw.println("Active jobs:");
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001372 for (int i=0; i<mActiveServices.size(); i++) {
1373 JobServiceContext jsc = mActiveServices.get(i);
Shreyas Basarge5db09082016-01-07 13:38:29 +00001374 if (jsc.getRunningJob() == null) {
Christopher Tate7060b042014-06-09 19:50:00 -07001375 continue;
1376 } else {
Christopher Tatef973a7b2014-08-29 12:54:08 -07001377 final long timeout = jsc.getTimeoutElapsed();
1378 pw.print("Running for: ");
1379 pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
1380 pw.print("s timeout=");
1381 pw.print(timeout);
1382 pw.print(" fromnow=");
1383 pw.println(timeout-now);
1384 jsc.getRunningJob().dump(pw, " ");
Dianne Hackborn1085ff62016-02-23 17:04:58 -08001385 int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
1386 if (priority != JobInfo.PRIORITY_DEFAULT) {
1387 pw.print(" Evaluated priority: "); pw.println(priority);
1388 }
Christopher Tate7060b042014-06-09 19:50:00 -07001389 }
1390 }
Dianne Hackbornfdb19562014-07-11 16:03:36 -07001391 pw.println();
1392 pw.print("mReadyToRock="); pw.println(mReadyToRock);
Dianne Hackborn88e98df2015-03-23 13:29:14 -07001393 pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
Dianne Hackborn627dfa12015-11-11 18:10:30 -08001394 pw.print("mReportedActive="); pw.println(mReportedActive);
Christopher Tate7060b042014-06-09 19:50:00 -07001395 }
1396 pw.println();
1397 }
1398}