blob: 36cacd7a7d96686835e150bbbc156943c1488c18 [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
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070019import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
20import static com.android.server.job.JobSchedulerService.sSystemClock;
21
Dianne Hackborna47223f2017-03-30 13:49:13 -070022import android.app.ActivityManager;
23import android.app.IActivityManager;
Christopher Tate7060b042014-06-09 19:50:00 -070024import android.app.job.JobInfo;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070025import android.content.ComponentName;
Christopher Tate7060b042014-06-09 19:50:00 -070026import android.content.Context;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070027import android.net.NetworkRequest;
Christopher Tate7060b042014-06-09 19:50:00 -070028import android.os.Environment;
29import android.os.Handler;
30import android.os.PersistableBundle;
Makoto Onukie7b02982017-08-24 14:23:36 -070031import android.os.Process;
Christopher Tate7060b042014-06-09 19:50:00 -070032import android.os.UserHandle;
Matthew Williamsfa8e5082015-10-15 15:59:12 -070033import android.text.format.DateUtils;
Christopher Tate7060b042014-06-09 19:50:00 -070034import android.util.ArraySet;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070035import android.util.AtomicFile;
Christopher Tate7060b042014-06-09 19:50:00 -070036import android.util.Pair;
37import android.util.Slog;
Christopher Tate2f36fd62016-02-18 18:36:08 -080038import android.util.SparseArray;
Christopher Tate7060b042014-06-09 19:50:00 -070039import android.util.Xml;
40
41import com.android.internal.annotations.VisibleForTesting;
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -070042import com.android.internal.util.ArrayUtils;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070043import com.android.internal.util.BitUtils;
Christopher Tate7060b042014-06-09 19:50:00 -070044import com.android.internal.util.FastXmlSerializer;
45import com.android.server.IoThread;
Christopher Tatea732f012017-10-26 17:26:53 -070046import com.android.server.LocalServices;
Makoto Onukie7b02982017-08-24 14:23:36 -070047import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
Christopher Tate7060b042014-06-09 19:50:00 -070048import com.android.server.job.controllers.JobStatus;
49
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070050import org.xmlpull.v1.XmlPullParser;
51import org.xmlpull.v1.XmlPullParserException;
52import org.xmlpull.v1.XmlSerializer;
53
Christopher Tate7060b042014-06-09 19:50:00 -070054import java.io.ByteArrayOutputStream;
55import java.io.File;
56import java.io.FileInputStream;
57import java.io.FileNotFoundException;
58import java.io.FileOutputStream;
59import java.io.IOException;
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +010060import java.nio.charset.StandardCharsets;
Christopher Tate7060b042014-06-09 19:50:00 -070061import java.util.ArrayList;
Christopher Tate7060b042014-06-09 19:50:00 -070062import java.util.List;
Shreyas Basarged09973b2015-12-16 18:10:05 +000063import java.util.Set;
Christopher Tate7060b042014-06-09 19:50:00 -070064
Christopher Tate7060b042014-06-09 19:50:00 -070065/**
Matthew Williams48a30db2014-09-23 13:39:36 -070066 * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
67 * reference, so none of the functions in this class should make a copy.
68 * Also handles read/write of persisted jobs.
Christopher Tate7060b042014-06-09 19:50:00 -070069 *
70 * Note on locking:
71 * All callers to this class must <strong>lock on the class object they are calling</strong>.
72 * This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable}
73 * and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
74 * object.
75 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -070076public final class JobStore {
Christopher Tate7060b042014-06-09 19:50:00 -070077 private static final String TAG = "JobStore";
78 private static final boolean DEBUG = JobSchedulerService.DEBUG;
79
80 /** Threshold to adjust how often we want to write to the db. */
81 private static final int MAX_OPS_BEFORE_WRITE = 1;
Christopher Tate616541d2017-07-26 14:27:38 -070082
Dianne Hackborn33d31c52016-02-16 10:30:33 -080083 final Object mLock;
Christopher Tate2f36fd62016-02-18 18:36:08 -080084 final JobSet mJobSet; // per-caller-uid tracking
Christopher Tate7060b042014-06-09 19:50:00 -070085 final Context mContext;
86
Christopher Tate616541d2017-07-26 14:27:38 -070087 // Bookkeeping around incorrect boot-time system clock
88 private final long mXmlTimestamp;
89 private boolean mRtcGood;
90
Christopher Tate7060b042014-06-09 19:50:00 -070091 private int mDirtyOperations;
92
93 private static final Object sSingletonLock = new Object();
94 private final AtomicFile mJobsFile;
95 /** Handler backed by IoThread for writing to disk. */
96 private final Handler mIoHandler = IoThread.getHandler();
97 private static JobStore sSingleton;
98
Makoto Onukie7b02982017-08-24 14:23:36 -070099 private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
100
Christopher Tate7060b042014-06-09 19:50:00 -0700101 /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
102 static JobStore initAndGet(JobSchedulerService jobManagerService) {
103 synchronized (sSingletonLock) {
104 if (sSingleton == null) {
105 sSingleton = new JobStore(jobManagerService.getContext(),
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800106 jobManagerService.getLock(), Environment.getDataDirectory());
Christopher Tate7060b042014-06-09 19:50:00 -0700107 }
108 return sSingleton;
109 }
110 }
111
Matthew Williams01ac45b2014-07-22 20:44:12 -0700112 /**
113 * @return A freshly initialized job store object, with no loaded jobs.
114 */
Christopher Tate7060b042014-06-09 19:50:00 -0700115 @VisibleForTesting
Matthew Williams01ac45b2014-07-22 20:44:12 -0700116 public static JobStore initAndGetForTesting(Context context, File dataDir) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800117 JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir);
Matthew Williams01ac45b2014-07-22 20:44:12 -0700118 jobStoreUnderTest.clear();
119 return jobStoreUnderTest;
Christopher Tate7060b042014-06-09 19:50:00 -0700120 }
121
Matthew Williams01ac45b2014-07-22 20:44:12 -0700122 /**
123 * Construct the instance of the job store. This results in a blocking read from disk.
124 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800125 private JobStore(Context context, Object lock, File dataDir) {
126 mLock = lock;
Christopher Tate7060b042014-06-09 19:50:00 -0700127 mContext = context;
128 mDirtyOperations = 0;
129
130 File systemDir = new File(dataDir, "system");
131 File jobDir = new File(systemDir, "job");
132 jobDir.mkdirs();
133 mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
134
Christopher Tate2f36fd62016-02-18 18:36:08 -0800135 mJobSet = new JobSet();
Christopher Tate7060b042014-06-09 19:50:00 -0700136
Christopher Tate616541d2017-07-26 14:27:38 -0700137 // If the current RTC is earlier than the timestamp on our persisted jobs file,
138 // we suspect that the RTC is uninitialized and so we cannot draw conclusions
139 // about persisted job scheduling.
140 //
141 // Note that if the persisted jobs file does not exist, we proceed with the
142 // assumption that the RTC is good. This is less work and is safe: if the
143 // clock updates to sanity then we'll be saving the persisted jobs file in that
144 // correct state, which is normal; or we'll wind up writing the jobs file with
145 // an incorrect historical timestamp. That's fine; at worst we'll reboot with
146 // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
147 // settle into normal operation.
148 mXmlTimestamp = mJobsFile.getLastModifiedTime();
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700149 mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
Christopher Tate616541d2017-07-26 14:27:38 -0700150
151 readJobMapFromDisk(mJobSet, mRtcGood);
152 }
153
154 public boolean jobTimesInflatedValid() {
155 return mRtcGood;
156 }
157
158 public boolean clockNowValidToInflate(long now) {
159 return now >= mXmlTimestamp;
160 }
161
162 /**
163 * Find all the jobs that were affected by RTC clock uncertainty at boot time. Returns
164 * parallel lists of the existing JobStatus objects and of new, equivalent JobStatus instances
165 * with now-corrected time bounds.
166 */
167 public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
168 final ArrayList<JobStatus> toRemove) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700169 final long elapsedNow = sElapsedRealtimeClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700170
171 // Find the jobs that need to be fixed up, collecting them for post-iteration
172 // replacement with their new versions
173 forEachJob(job -> {
174 final Pair<Long, Long> utcTimes = job.getPersistedUtcTimes();
175 if (utcTimes != null) {
176 Pair<Long, Long> elapsedRuntimes =
177 convertRtcBoundsToElapsed(utcTimes, elapsedNow);
Christopher Tatea732f012017-10-26 17:26:53 -0700178 toAdd.add(new JobStatus(job, job.getBaseHeartbeat(),
179 elapsedRuntimes.first, elapsedRuntimes.second,
Christopher Tate616541d2017-07-26 14:27:38 -0700180 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()));
181 toRemove.add(job);
182 }
183 });
Christopher Tate7060b042014-06-09 19:50:00 -0700184 }
185
186 /**
187 * Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
188 * it will be replaced.
189 * @param jobStatus Job to add.
190 * @return Whether or not an equivalent JobStatus was replaced by this operation.
191 */
192 public boolean add(JobStatus jobStatus) {
193 boolean replaced = mJobSet.remove(jobStatus);
194 mJobSet.add(jobStatus);
195 if (jobStatus.isPersisted()) {
196 maybeWriteStatusToDiskAsync();
197 }
198 if (DEBUG) {
199 Slog.d(TAG, "Added job status to store: " + jobStatus);
200 }
201 return replaced;
202 }
203
Matthew Williams48a30db2014-09-23 13:39:36 -0700204 boolean containsJob(JobStatus jobStatus) {
205 return mJobSet.contains(jobStatus);
206 }
207
Christopher Tate7060b042014-06-09 19:50:00 -0700208 public int size() {
209 return mJobSet.size();
210 }
211
Makoto Onukie7b02982017-08-24 14:23:36 -0700212 public JobStorePersistStats getPersistStats() {
213 return mPersistInfo;
214 }
215
Christopher Tate2f36fd62016-02-18 18:36:08 -0800216 public int countJobsForUid(int uid) {
217 return mJobSet.countJobsForUid(uid);
218 }
219
Christopher Tate7060b042014-06-09 19:50:00 -0700220 /**
221 * Remove the provided job. Will also delete the job if it was persisted.
Shreyas Basarge73f10252016-02-11 17:06:13 +0000222 * @param writeBack If true, the job will be deleted (if it was persisted) immediately.
Christopher Tate7060b042014-06-09 19:50:00 -0700223 * @return Whether or not the job existed to be removed.
224 */
Shreyas Basarge73f10252016-02-11 17:06:13 +0000225 public boolean remove(JobStatus jobStatus, boolean writeBack) {
Christopher Tate7060b042014-06-09 19:50:00 -0700226 boolean removed = mJobSet.remove(jobStatus);
227 if (!removed) {
228 if (DEBUG) {
229 Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus);
230 }
231 return false;
232 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000233 if (writeBack && jobStatus.isPersisted()) {
Matthew Williams900c67f2014-07-09 12:46:53 -0700234 maybeWriteStatusToDiskAsync();
235 }
Christopher Tate7060b042014-06-09 19:50:00 -0700236 return removed;
237 }
238
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -0700239 /**
240 * Remove the jobs of users not specified in the whitelist.
241 * @param whitelist Array of User IDs whose jobs are not to be removed.
242 */
243 public void removeJobsOfNonUsers(int[] whitelist) {
244 mJobSet.removeJobsOfNonUsers(whitelist);
245 }
246
Christopher Tate7060b042014-06-09 19:50:00 -0700247 @VisibleForTesting
248 public void clear() {
249 mJobSet.clear();
250 maybeWriteStatusToDiskAsync();
251 }
252
Matthew Williams48a30db2014-09-23 13:39:36 -0700253 /**
254 * @param userHandle User for whom we are querying the list of jobs.
Suprabh Shukla106203b2017-11-02 21:23:44 -0700255 * @return A list of all the jobs scheduled for the provided user. Never null.
Matthew Williams48a30db2014-09-23 13:39:36 -0700256 */
Christopher Tate7060b042014-06-09 19:50:00 -0700257 public List<JobStatus> getJobsByUser(int userHandle) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800258 return mJobSet.getJobsByUser(userHandle);
Christopher Tate7060b042014-06-09 19:50:00 -0700259 }
260
261 /**
262 * @param uid Uid of the requesting app.
Matthew Williams48a30db2014-09-23 13:39:36 -0700263 * @return All JobStatus objects for a given uid from the master list. Never null.
Christopher Tate7060b042014-06-09 19:50:00 -0700264 */
265 public List<JobStatus> getJobsByUid(int uid) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800266 return mJobSet.getJobsByUid(uid);
Christopher Tate7060b042014-06-09 19:50:00 -0700267 }
268
269 /**
270 * @param uid Uid of the requesting app.
271 * @param jobId Job id, specified at schedule-time.
272 * @return the JobStatus that matches the provided uId and jobId, or null if none found.
273 */
274 public JobStatus getJobByUidAndJobId(int uid, int jobId) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800275 return mJobSet.get(uid, jobId);
Christopher Tate7060b042014-06-09 19:50:00 -0700276 }
277
278 /**
Christopher Tate2f36fd62016-02-18 18:36:08 -0800279 * Iterate over the set of all jobs, invoking the supplied functor on each. This is for
280 * customers who need to examine each job; we'd much rather not have to generate
281 * transient unified collections for them to iterate over and then discard, or creating
282 * iterators every time a client needs to perform a sweep.
Christopher Tate7060b042014-06-09 19:50:00 -0700283 */
Christopher Tate2f36fd62016-02-18 18:36:08 -0800284 public void forEachJob(JobStatusFunctor functor) {
285 mJobSet.forEachJob(functor);
286 }
287
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000288 public void forEachJob(int uid, JobStatusFunctor functor) {
289 mJobSet.forEachJob(uid, functor);
290 }
291
Suprabh Shukla106203b2017-11-02 21:23:44 -0700292 public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
293 mJobSet.forEachJobForSourceUid(sourceUid, functor);
294 }
295
Christopher Tate2f36fd62016-02-18 18:36:08 -0800296 public interface JobStatusFunctor {
297 public void process(JobStatus jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700298 }
299
300 /** Version of the db schema. */
301 private static final int JOBS_FILE_VERSION = 0;
302 /** Tag corresponds to constraints this job needs. */
303 private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
304 /** Tag corresponds to execution parameters. */
305 private static final String XML_TAG_PERIODIC = "periodic";
306 private static final String XML_TAG_ONEOFF = "one-off";
307 private static final String XML_TAG_EXTRAS = "extras";
308
309 /**
310 * Every time the state changes we write all the jobs in one swath, instead of trying to
311 * track incremental changes.
Christopher Tate7060b042014-06-09 19:50:00 -0700312 */
313 private void maybeWriteStatusToDiskAsync() {
314 mDirtyOperations++;
315 if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) {
316 if (DEBUG) {
317 Slog.v(TAG, "Writing jobs to disk.");
318 }
Christopher Tate616541d2017-07-26 14:27:38 -0700319 mIoHandler.removeCallbacks(mWriteRunnable);
320 mIoHandler.post(mWriteRunnable);
Christopher Tate7060b042014-06-09 19:50:00 -0700321 }
322 }
323
Matthew Williams01ac45b2014-07-22 20:44:12 -0700324 @VisibleForTesting
Christopher Tate616541d2017-07-26 14:27:38 -0700325 public void readJobMapFromDisk(JobSet jobSet, boolean rtcGood) {
326 new ReadJobMapFromDiskRunnable(jobSet, rtcGood).run();
Christopher Tate7060b042014-06-09 19:50:00 -0700327 }
328
329 /**
330 * Runnable that writes {@link #mJobSet} out to xml.
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800331 * NOTE: This Runnable locks on mLock
Christopher Tate7060b042014-06-09 19:50:00 -0700332 */
Christopher Tate616541d2017-07-26 14:27:38 -0700333 private final Runnable mWriteRunnable = new Runnable() {
Christopher Tate7060b042014-06-09 19:50:00 -0700334 @Override
335 public void run() {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700336 final long startElapsed = sElapsedRealtimeClock.millis();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800337 final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800338 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800339 // Clone the jobs so we can release the lock before writing.
340 mJobSet.forEachJob(new JobStatusFunctor() {
341 @Override
342 public void process(JobStatus job) {
343 if (job.isPersisted()) {
344 storeCopy.add(new JobStatus(job));
345 }
Shreyas Basarge7ef490f2015-12-03 16:45:22 +0000346 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800347 });
Christopher Tate7060b042014-06-09 19:50:00 -0700348 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800349 writeJobsMapImpl(storeCopy);
Christopher Tate616541d2017-07-26 14:27:38 -0700350 if (DEBUG) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700351 Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
Christopher Tate7060b042014-06-09 19:50:00 -0700352 - startElapsed) + "ms");
353 }
354 }
355
Matthew Williams49a85b62014-06-12 11:02:34 -0700356 private void writeJobsMapImpl(List<JobStatus> jobList) {
Makoto Onukie7b02982017-08-24 14:23:36 -0700357 int numJobs = 0;
358 int numSystemJobs = 0;
359 int numSyncJobs = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700360 try {
361 ByteArrayOutputStream baos = new ByteArrayOutputStream();
362 XmlSerializer out = new FastXmlSerializer();
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +0100363 out.setOutput(baos, StandardCharsets.UTF_8.name());
Christopher Tate7060b042014-06-09 19:50:00 -0700364 out.startDocument(null, true);
365 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
366
367 out.startTag(null, "job-info");
368 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700369 for (int i=0; i<jobList.size(); i++) {
370 JobStatus jobStatus = jobList.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700371 if (DEBUG) {
372 Slog.d(TAG, "Saving job " + jobStatus.getJobId());
373 }
374 out.startTag(null, "job");
Shreyas Basarge5db09082016-01-07 13:38:29 +0000375 addAttributesToJobTag(out, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700376 writeConstraintsToXml(out, jobStatus);
377 writeExecutionCriteriaToXml(out, jobStatus);
Dianne Hackborna47223f2017-03-30 13:49:13 -0700378 writeBundleToXml(jobStatus.getJob().getExtras(), out);
Christopher Tate7060b042014-06-09 19:50:00 -0700379 out.endTag(null, "job");
Makoto Onukie7b02982017-08-24 14:23:36 -0700380
381 numJobs++;
382 if (jobStatus.getUid() == Process.SYSTEM_UID) {
383 numSystemJobs++;
384 if (isSyncJob(jobStatus)) {
385 numSyncJobs++;
386 }
387 }
Christopher Tate7060b042014-06-09 19:50:00 -0700388 }
389 out.endTag(null, "job-info");
390 out.endDocument();
391
Christopher Tate616541d2017-07-26 14:27:38 -0700392 // Write out to disk in one fell swoop.
Christopher Tate7060b042014-06-09 19:50:00 -0700393 FileOutputStream fos = mJobsFile.startWrite();
394 fos.write(baos.toByteArray());
395 mJobsFile.finishWrite(fos);
396 mDirtyOperations = 0;
397 } catch (IOException e) {
398 if (DEBUG) {
399 Slog.v(TAG, "Error writing out job data.", e);
400 }
401 } catch (XmlPullParserException e) {
402 if (DEBUG) {
403 Slog.d(TAG, "Error persisting bundle.", e);
404 }
Makoto Onukie7b02982017-08-24 14:23:36 -0700405 } finally {
406 mPersistInfo.countAllJobsSaved = numJobs;
407 mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
408 mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
Christopher Tate7060b042014-06-09 19:50:00 -0700409 }
410 }
411
Shreyas Basarge5db09082016-01-07 13:38:29 +0000412 /** Write out a tag with data comprising the required fields and priority of this job and
413 * its client.
414 */
415 private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
Christopher Tate7060b042014-06-09 19:50:00 -0700416 throws IOException {
417 out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
418 out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
419 out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
Shreyas Basarge968ac752016-01-11 23:09:26 +0000420 if (jobStatus.getSourcePackageName() != null) {
421 out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
422 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800423 if (jobStatus.getSourceTag() != null) {
424 out.attribute(null, "sourceTag", jobStatus.getSourceTag());
425 }
Shreyas Basarge968ac752016-01-11 23:09:26 +0000426 out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
Christopher Tate7060b042014-06-09 19:50:00 -0700427 out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000428 out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
Jeff Sharkey1b6519b2016-04-28 15:33:18 -0600429 out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700430
431 out.attribute(null, "lastSuccessfulRunTime",
432 String.valueOf(jobStatus.getLastSuccessfulRunTime()));
433 out.attribute(null, "lastFailedRunTime",
434 String.valueOf(jobStatus.getLastFailedRunTime()));
Christopher Tate7060b042014-06-09 19:50:00 -0700435 }
436
437 private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
438 throws IOException, XmlPullParserException {
439 out.startTag(null, XML_TAG_EXTRAS);
Shreyas Basarged09973b2015-12-16 18:10:05 +0000440 PersistableBundle extrasCopy = deepCopyBundle(extras, 10);
441 extrasCopy.saveToXml(out);
Christopher Tate7060b042014-06-09 19:50:00 -0700442 out.endTag(null, XML_TAG_EXTRAS);
443 }
Shreyas Basarged09973b2015-12-16 18:10:05 +0000444
445 private PersistableBundle deepCopyBundle(PersistableBundle bundle, int maxDepth) {
446 if (maxDepth <= 0) {
447 return null;
448 }
449 PersistableBundle copy = (PersistableBundle) bundle.clone();
450 Set<String> keySet = bundle.keySet();
451 for (String key: keySet) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000452 Object o = copy.get(key);
453 if (o instanceof PersistableBundle) {
454 PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1);
Shreyas Basarged09973b2015-12-16 18:10:05 +0000455 copy.putPersistableBundle(key, bCopy);
456 }
457 }
458 return copy;
459 }
460
Christopher Tate7060b042014-06-09 19:50:00 -0700461 /**
462 * Write out a tag with data identifying this job's constraints. If the constraint isn't here
463 * it doesn't apply.
464 */
465 private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
466 out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700467 if (jobStatus.hasConnectivityConstraint()) {
468 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
469 out.attribute(null, "net-capabilities", Long.toString(
470 BitUtils.packBits(network.networkCapabilities.getCapabilities())));
471 out.attribute(null, "net-transport-types", Long.toString(
472 BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
Christopher Tate7060b042014-06-09 19:50:00 -0700473 }
474 if (jobStatus.hasIdleConstraint()) {
475 out.attribute(null, "idle", Boolean.toString(true));
476 }
477 if (jobStatus.hasChargingConstraint()) {
478 out.attribute(null, "charging", Boolean.toString(true));
479 }
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800480 if (jobStatus.hasBatteryNotLowConstraint()) {
481 out.attribute(null, "battery-not-low", Boolean.toString(true));
482 }
Christopher Tate7060b042014-06-09 19:50:00 -0700483 out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
484 }
485
486 private void writeExecutionCriteriaToXml(XmlSerializer out, JobStatus jobStatus)
487 throws IOException {
488 final JobInfo job = jobStatus.getJob();
489 if (jobStatus.getJob().isPeriodic()) {
490 out.startTag(null, XML_TAG_PERIODIC);
491 out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000492 out.attribute(null, "flex", Long.toString(job.getFlexMillis()));
Christopher Tate7060b042014-06-09 19:50:00 -0700493 } else {
494 out.startTag(null, XML_TAG_ONEOFF);
495 }
496
Christopher Tate616541d2017-07-26 14:27:38 -0700497 // If we still have the persisted times, we need to record those directly because
498 // we haven't yet been able to calculate the usual elapsed-timebase bounds
499 // correctly due to wall-clock uncertainty.
500 Pair <Long, Long> utcJobTimes = jobStatus.getPersistedUtcTimes();
501 if (DEBUG && utcJobTimes != null) {
502 Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
503 }
504
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700505 final long nowRTC = sSystemClock.millis();
506 final long nowElapsed = sElapsedRealtimeClock.millis();
Christopher Tate7060b042014-06-09 19:50:00 -0700507 if (jobStatus.hasDeadlineConstraint()) {
508 // Wall clock deadline.
Christopher Tate616541d2017-07-26 14:27:38 -0700509 final long deadlineWallclock = (utcJobTimes == null)
510 ? nowRTC + (jobStatus.getLatestRunTimeElapsed() - nowElapsed)
511 : utcJobTimes.second;
Christopher Tate7060b042014-06-09 19:50:00 -0700512 out.attribute(null, "deadline", Long.toString(deadlineWallclock));
513 }
514 if (jobStatus.hasTimingDelayConstraint()) {
Christopher Tate616541d2017-07-26 14:27:38 -0700515 final long delayWallclock = (utcJobTimes == null)
516 ? nowRTC + (jobStatus.getEarliestRunTime() - nowElapsed)
517 : utcJobTimes.first;
Christopher Tate7060b042014-06-09 19:50:00 -0700518 out.attribute(null, "delay", Long.toString(delayWallclock));
519 }
520
521 // Only write out back-off policy if it differs from the default.
522 // This also helps the case where the job is idle -> these aren't allowed to specify
523 // back-off.
524 if (jobStatus.getJob().getInitialBackoffMillis() != JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS
525 || jobStatus.getJob().getBackoffPolicy() != JobInfo.DEFAULT_BACKOFF_POLICY) {
526 out.attribute(null, "backoff-policy", Integer.toString(job.getBackoffPolicy()));
527 out.attribute(null, "initial-backoff", Long.toString(job.getInitialBackoffMillis()));
528 }
529 if (job.isPeriodic()) {
530 out.endTag(null, XML_TAG_PERIODIC);
531 } else {
532 out.endTag(null, XML_TAG_ONEOFF);
533 }
534 }
Christopher Tate616541d2017-07-26 14:27:38 -0700535 };
536
537 /**
538 * Translate the supplied RTC times to the elapsed timebase, with clamping appropriate
539 * to interpreting them as a job's delay + deadline times for alarm-setting purposes.
540 * @param rtcTimes a Pair<Long, Long> in which {@code first} is the "delay" earliest
541 * allowable runtime for the job, and {@code second} is the "deadline" time at which
542 * the job becomes overdue.
543 */
544 private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
545 long nowElapsed) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700546 final long nowWallclock = sSystemClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700547 final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
548 ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
549 : JobStatus.NO_EARLIEST_RUNTIME;
550 final long latest = (rtcTimes.second < JobStatus.NO_LATEST_RUNTIME)
551 ? nowElapsed + Math.max(rtcTimes.second - nowWallclock, 0)
552 : JobStatus.NO_LATEST_RUNTIME;
553 return Pair.create(earliest, latest);
Christopher Tate7060b042014-06-09 19:50:00 -0700554 }
555
Makoto Onukie7b02982017-08-24 14:23:36 -0700556 private static boolean isSyncJob(JobStatus status) {
557 return com.android.server.content.SyncJobService.class.getName()
558 .equals(status.getServiceComponent().getClassName());
559 }
560
Christopher Tate7060b042014-06-09 19:50:00 -0700561 /**
Matthew Williams01ac45b2014-07-22 20:44:12 -0700562 * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
563 * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
Christopher Tate7060b042014-06-09 19:50:00 -0700564 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -0700565 private final class ReadJobMapFromDiskRunnable implements Runnable {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800566 private final JobSet jobSet;
Christopher Tate616541d2017-07-26 14:27:38 -0700567 private final boolean rtcGood;
Matthew Williams01ac45b2014-07-22 20:44:12 -0700568
569 /**
570 * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
571 * so that after disk read we can populate it directly.
572 */
Christopher Tate616541d2017-07-26 14:27:38 -0700573 ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood) {
Matthew Williams01ac45b2014-07-22 20:44:12 -0700574 this.jobSet = jobSet;
Christopher Tate616541d2017-07-26 14:27:38 -0700575 this.rtcGood = rtcIsGood;
Christopher Tate7060b042014-06-09 19:50:00 -0700576 }
577
578 @Override
579 public void run() {
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700580 int numJobs = 0;
Makoto Onukie7b02982017-08-24 14:23:36 -0700581 int numSystemJobs = 0;
582 int numSyncJobs = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700583 try {
584 List<JobStatus> jobs;
585 FileInputStream fis = mJobsFile.openRead();
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800586 synchronized (mLock) {
Christopher Tate616541d2017-07-26 14:27:38 -0700587 jobs = readJobMapImpl(fis, rtcGood);
Matthew Williams01ac45b2014-07-22 20:44:12 -0700588 if (jobs != null) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700589 long now = sElapsedRealtimeClock.millis();
Dianne Hackborna47223f2017-03-30 13:49:13 -0700590 IActivityManager am = ActivityManager.getService();
Matthew Williams01ac45b2014-07-22 20:44:12 -0700591 for (int i=0; i<jobs.size(); i++) {
Dianne Hackborna47223f2017-03-30 13:49:13 -0700592 JobStatus js = jobs.get(i);
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700593 js.prepareLocked(am);
Dianne Hackbornbfc23312017-05-11 11:53:24 -0700594 js.enqueueTime = now;
Dianne Hackborna47223f2017-03-30 13:49:13 -0700595 this.jobSet.add(js);
Makoto Onukie7b02982017-08-24 14:23:36 -0700596
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700597 numJobs++;
Makoto Onukie7b02982017-08-24 14:23:36 -0700598 if (js.getUid() == Process.SYSTEM_UID) {
599 numSystemJobs++;
600 if (isSyncJob(js)) {
601 numSyncJobs++;
602 }
603 }
Matthew Williams01ac45b2014-07-22 20:44:12 -0700604 }
605 }
Christopher Tate7060b042014-06-09 19:50:00 -0700606 }
607 fis.close();
Christopher Tate7060b042014-06-09 19:50:00 -0700608 } catch (FileNotFoundException e) {
Christopher Tate616541d2017-07-26 14:27:38 -0700609 if (DEBUG) {
Christopher Tate7060b042014-06-09 19:50:00 -0700610 Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
611 }
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700612 } catch (XmlPullParserException | IOException e) {
613 Slog.wtf(TAG, "Error jobstore xml.", e);
Makoto Onukie7b02982017-08-24 14:23:36 -0700614 } finally {
615 if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
616 mPersistInfo.countAllJobsLoaded = numJobs;
617 mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
618 mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
619 }
Christopher Tate7060b042014-06-09 19:50:00 -0700620 }
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700621 Slog.i(TAG, "Read " + numJobs + " jobs");
Christopher Tate7060b042014-06-09 19:50:00 -0700622 }
623
Christopher Tate616541d2017-07-26 14:27:38 -0700624 private List<JobStatus> readJobMapImpl(FileInputStream fis, boolean rtcIsGood)
Matthew Williams900c67f2014-07-09 12:46:53 -0700625 throws XmlPullParserException, IOException {
Christopher Tate7060b042014-06-09 19:50:00 -0700626 XmlPullParser parser = Xml.newPullParser();
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +0100627 parser.setInput(fis, StandardCharsets.UTF_8.name());
Christopher Tate7060b042014-06-09 19:50:00 -0700628
629 int eventType = parser.getEventType();
630 while (eventType != XmlPullParser.START_TAG &&
631 eventType != XmlPullParser.END_DOCUMENT) {
632 eventType = parser.next();
riddle_hsu98bfb342015-07-30 21:52:58 +0800633 Slog.d(TAG, "Start tag: " + parser.getName());
Christopher Tate7060b042014-06-09 19:50:00 -0700634 }
635 if (eventType == XmlPullParser.END_DOCUMENT) {
636 if (DEBUG) {
637 Slog.d(TAG, "No persisted jobs.");
638 }
639 return null;
640 }
641
642 String tagName = parser.getName();
643 if ("job-info".equals(tagName)) {
644 final List<JobStatus> jobs = new ArrayList<JobStatus>();
645 // Read in version info.
646 try {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100647 int version = Integer.parseInt(parser.getAttributeValue(null, "version"));
Christopher Tate7060b042014-06-09 19:50:00 -0700648 if (version != JOBS_FILE_VERSION) {
649 Slog.d(TAG, "Invalid version number, aborting jobs file read.");
650 return null;
651 }
652 } catch (NumberFormatException e) {
653 Slog.e(TAG, "Invalid version number, aborting jobs file read.");
654 return null;
655 }
656 eventType = parser.next();
657 do {
658 // Read each <job/>
659 if (eventType == XmlPullParser.START_TAG) {
660 tagName = parser.getName();
661 // Start reading job.
662 if ("job".equals(tagName)) {
Christopher Tate616541d2017-07-26 14:27:38 -0700663 JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser);
Christopher Tate7060b042014-06-09 19:50:00 -0700664 if (persistedJob != null) {
665 if (DEBUG) {
666 Slog.d(TAG, "Read out " + persistedJob);
667 }
668 jobs.add(persistedJob);
669 } else {
670 Slog.d(TAG, "Error reading job from file.");
671 }
672 }
673 }
674 eventType = parser.next();
675 } while (eventType != XmlPullParser.END_DOCUMENT);
676 return jobs;
677 }
678 return null;
679 }
680
681 /**
682 * @param parser Xml parser at the beginning of a "<job/>" tag. The next "parser.next()" call
683 * will take the parser into the body of the job tag.
684 * @return Newly instantiated job holding all the information we just read out of the xml tag.
685 */
Christopher Tate616541d2017-07-26 14:27:38 -0700686 private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser)
687 throws XmlPullParserException, IOException {
Christopher Tate7060b042014-06-09 19:50:00 -0700688 JobInfo.Builder jobBuilder;
Shreyas Basarge8e64e2e2016-02-12 15:49:31 +0000689 int uid, sourceUserId;
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700690 long lastSuccessfulRunTime;
691 long lastFailedRunTime;
Christopher Tate7060b042014-06-09 19:50:00 -0700692
Shreyas Basarge5db09082016-01-07 13:38:29 +0000693 // Read out job identifier attributes and priority.
Christopher Tate7060b042014-06-09 19:50:00 -0700694 try {
695 jobBuilder = buildBuilderFromXml(parser);
Matthew Williamsd1c06752014-08-22 14:15:28 -0700696 jobBuilder.setPersisted(true);
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100697 uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000698
Shreyas Basarge968ac752016-01-11 23:09:26 +0000699 String val = parser.getAttributeValue(null, "priority");
700 if (val != null) {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100701 jobBuilder.setPriority(Integer.parseInt(val));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000702 }
Jeff Sharkey1b6519b2016-04-28 15:33:18 -0600703 val = parser.getAttributeValue(null, "flags");
704 if (val != null) {
705 jobBuilder.setFlags(Integer.parseInt(val));
706 }
Shreyas Basarge968ac752016-01-11 23:09:26 +0000707 val = parser.getAttributeValue(null, "sourceUserId");
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100708 sourceUserId = val == null ? -1 : Integer.parseInt(val);
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700709
710 val = parser.getAttributeValue(null, "lastSuccessfulRunTime");
711 lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val);
712
713 val = parser.getAttributeValue(null, "lastFailedRunTime");
714 lastFailedRunTime = val == null ? 0 : Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700715 } catch (NumberFormatException e) {
716 Slog.e(TAG, "Error parsing job's required fields, skipping");
717 return null;
718 }
719
Christopher Tate0213ace02016-02-24 14:18:35 -0800720 String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
Shreyas Basarge968ac752016-01-11 23:09:26 +0000721
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800722 final String sourceTag = parser.getAttributeValue(null, "sourceTag");
723
Christopher Tate7060b042014-06-09 19:50:00 -0700724 int eventType;
725 // Read out constraints tag.
726 do {
727 eventType = parser.next();
728 } while (eventType == XmlPullParser.TEXT); // Push through to next START_TAG.
729
730 if (!(eventType == XmlPullParser.START_TAG &&
731 XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) {
732 // Expecting a <constraints> start tag.
733 return null;
734 }
735 try {
736 buildConstraintsFromXml(jobBuilder, parser);
737 } catch (NumberFormatException e) {
738 Slog.d(TAG, "Error reading constraints, skipping.");
739 return null;
740 }
741 parser.next(); // Consume </constraints>
742
743 // Read out execution parameters tag.
744 do {
745 eventType = parser.next();
746 } while (eventType == XmlPullParser.TEXT);
747 if (eventType != XmlPullParser.START_TAG) {
748 return null;
749 }
750
Christopher Tate616541d2017-07-26 14:27:38 -0700751 // Tuple of (earliest runtime, latest runtime) in UTC.
752 final Pair<Long, Long> rtcRuntimes;
Christopher Tate7060b042014-06-09 19:50:00 -0700753 try {
Christopher Tate616541d2017-07-26 14:27:38 -0700754 rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
Christopher Tate7060b042014-06-09 19:50:00 -0700755 } catch (NumberFormatException e) {
756 if (DEBUG) {
757 Slog.d(TAG, "Error parsing execution time parameters, skipping.");
758 }
759 return null;
760 }
761
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700762 final long elapsedNow = sElapsedRealtimeClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700763 Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
764
Christopher Tate7060b042014-06-09 19:50:00 -0700765 if (XML_TAG_PERIODIC.equals(parser.getName())) {
766 try {
767 String val = parser.getAttributeValue(null, "period");
Tobias Thierer28532d02016-04-21 14:52:10 +0100768 final long periodMillis = Long.parseLong(val);
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000769 val = parser.getAttributeValue(null, "flex");
770 final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
Shreyas Basarge8e64e2e2016-02-12 15:49:31 +0000771 jobBuilder.setPeriodic(periodMillis, flexMillis);
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000772 // As a sanity check, cap the recreated run time to be no later than flex+period
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700773 // from now. This is the latest the periodic could be pushed out. This could
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000774 // happen if the periodic ran early (at flex time before period), and then the
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700775 // device rebooted.
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000776 if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
777 final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
778 + periodMillis;
779 final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
780 - flexMillis;
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700781 Slog.w(TAG,
782 String.format("Periodic job for uid='%d' persisted run-time is" +
783 " too big [%s, %s]. Clamping to [%s,%s]",
784 uid,
785 DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000),
786 DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000),
787 DateUtils.formatElapsedTime(
788 clampedEarlyRuntimeElapsed / 1000),
789 DateUtils.formatElapsedTime(
790 clampedLateRuntimeElapsed / 1000))
791 );
792 elapsedRuntimes =
793 Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
794 }
Christopher Tate7060b042014-06-09 19:50:00 -0700795 } catch (NumberFormatException e) {
796 Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
797 return null;
798 }
799 } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
800 try {
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700801 if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
802 jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
Christopher Tate7060b042014-06-09 19:50:00 -0700803 }
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700804 if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
Christopher Tate7060b042014-06-09 19:50:00 -0700805 jobBuilder.setOverrideDeadline(
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700806 elapsedRuntimes.second - elapsedNow);
Christopher Tate7060b042014-06-09 19:50:00 -0700807 }
808 } catch (NumberFormatException e) {
809 Slog.d(TAG, "Error reading job execution criteria, skipping.");
810 return null;
811 }
812 } else {
813 if (DEBUG) {
814 Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName());
815 }
816 // Expecting a parameters start tag.
817 return null;
818 }
819 maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
820
821 parser.nextTag(); // Consume parameters end tag.
822
823 // Read out extras Bundle.
824 do {
825 eventType = parser.next();
826 } while (eventType == XmlPullParser.TEXT);
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700827 if (!(eventType == XmlPullParser.START_TAG
828 && XML_TAG_EXTRAS.equals(parser.getName()))) {
Christopher Tate7060b042014-06-09 19:50:00 -0700829 if (DEBUG) {
830 Slog.d(TAG, "Error reading extras, skipping.");
831 }
832 return null;
833 }
834
835 PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
836 jobBuilder.setExtras(extras);
837 parser.nextTag(); // Consume </extras>
838
Christopher Tate0213ace02016-02-24 14:18:35 -0800839 // Migrate sync jobs forward from earlier, incomplete representation
840 if ("android".equals(sourcePackageName)
841 && extras != null
842 && extras.getBoolean("SyncManagerJob", false)) {
843 sourcePackageName = extras.getString("owningPackage", sourcePackageName);
844 if (DEBUG) {
845 Slog.i(TAG, "Fixing up sync job source package name from 'android' to '"
846 + sourcePackageName + "'");
847 }
848 }
849
850 // And now we're done
Christopher Tatea732f012017-10-26 17:26:53 -0700851 JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
852 final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
853 sourceUserId, elapsedNow);
854 long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
Shreyas Basarge968ac752016-01-11 23:09:26 +0000855 JobStatus js = new JobStatus(
Christopher Tatea732f012017-10-26 17:26:53 -0700856 jobBuilder.build(), uid, sourcePackageName, sourceUserId,
857 appBucket, currentHeartbeat, sourceTag,
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700858 elapsedRuntimes.first, elapsedRuntimes.second,
Christopher Tate616541d2017-07-26 14:27:38 -0700859 lastSuccessfulRunTime, lastFailedRunTime,
860 (rtcIsGood) ? null : rtcRuntimes);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000861 return js;
Christopher Tate7060b042014-06-09 19:50:00 -0700862 }
863
864 private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
865 // Pull out required fields from <job> attributes.
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100866 int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
Christopher Tate7060b042014-06-09 19:50:00 -0700867 String packageName = parser.getAttributeValue(null, "package");
868 String className = parser.getAttributeValue(null, "class");
869 ComponentName cname = new ComponentName(packageName, className);
870
871 return new JobInfo.Builder(jobId, cname);
872 }
873
874 private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700875 String val;
876
877 final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
878 final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
879 if (netCapabilities != null && netTransportTypes != null) {
880 final NetworkRequest request = new NetworkRequest.Builder().build();
881 // We're okay throwing NFE here; caught by caller
882 request.networkCapabilities.setCapabilities(
883 BitUtils.unpackBits(Long.parseLong(netCapabilities)));
884 request.networkCapabilities.setTransportTypes(
885 BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
886 jobBuilder.setRequiredNetwork(request);
887 } else {
888 // Read legacy values
889 val = parser.getAttributeValue(null, "connectivity");
890 if (val != null) {
891 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
892 }
893 val = parser.getAttributeValue(null, "metered");
894 if (val != null) {
895 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
896 }
897 val = parser.getAttributeValue(null, "unmetered");
898 if (val != null) {
899 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
900 }
901 val = parser.getAttributeValue(null, "not-roaming");
902 if (val != null) {
903 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
904 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600905 }
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700906
Christopher Tate7060b042014-06-09 19:50:00 -0700907 val = parser.getAttributeValue(null, "idle");
908 if (val != null) {
909 jobBuilder.setRequiresDeviceIdle(true);
910 }
911 val = parser.getAttributeValue(null, "charging");
912 if (val != null) {
913 jobBuilder.setRequiresCharging(true);
914 }
915 }
916
917 /**
918 * Builds the back-off policy out of the params tag. These attributes may not exist, depending
919 * on whether the back-off was set when the job was first scheduled.
920 */
921 private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
922 String val = parser.getAttributeValue(null, "initial-backoff");
923 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100924 long initialBackoff = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700925 val = parser.getAttributeValue(null, "backoff-policy");
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100926 int backoffPolicy = Integer.parseInt(val); // Will throw NFE which we catch higher up.
Christopher Tate7060b042014-06-09 19:50:00 -0700927 jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
928 }
929 }
930
931 /**
Christopher Tate616541d2017-07-26 14:27:38 -0700932 * Extract a job's earliest/latest run time data from XML. These are returned in
933 * unadjusted UTC wall clock time, because we do not yet know whether the system
934 * clock is reliable for purposes of calculating deltas from 'now'.
935 *
936 * @param parser
937 * @return A Pair of timestamps in UTC wall-clock time. The first is the earliest
938 * time at which the job is to become runnable, and the second is the deadline at
939 * which it becomes overdue to execute.
940 * @throws NumberFormatException
941 */
942 private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
943 throws NumberFormatException {
944 String val;
945 // Pull out execution time data.
946 val = parser.getAttributeValue(null, "delay");
947 final long earliestRunTimeRtc = (val != null)
948 ? Long.parseLong(val)
949 : JobStatus.NO_EARLIEST_RUNTIME;
950 val = parser.getAttributeValue(null, "deadline");
951 final long latestRunTimeRtc = (val != null)
952 ? Long.parseLong(val)
953 : JobStatus.NO_LATEST_RUNTIME;
954 return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
955 }
956
957 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700958 * Convenience function to read out and convert deadline and delay from xml into elapsed real
959 * time.
960 * @return A {@link android.util.Pair}, where the first value is the earliest elapsed runtime
961 * and the second is the latest elapsed runtime.
962 */
963 private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
964 throws NumberFormatException {
965 // Pull out execution time data.
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700966 final long nowWallclock = sSystemClock.millis();
967 final long nowElapsed = sElapsedRealtimeClock.millis();
Christopher Tate7060b042014-06-09 19:50:00 -0700968
969 long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
970 long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
971 String val = parser.getAttributeValue(null, "deadline");
972 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100973 long latestRuntimeWallclock = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700974 long maxDelayElapsed =
975 Math.max(latestRuntimeWallclock - nowWallclock, 0);
976 latestRunTimeElapsed = nowElapsed + maxDelayElapsed;
977 }
978 val = parser.getAttributeValue(null, "delay");
979 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100980 long earliestRuntimeWallclock = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700981 long minDelayElapsed =
982 Math.max(earliestRuntimeWallclock - nowWallclock, 0);
983 earliestRunTimeElapsed = nowElapsed + minDelayElapsed;
984
985 }
986 return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
987 }
988 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800989
Dianne Hackborn6466c1c2017-06-13 10:33:19 -0700990 static final class JobSet {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800991 // Key is the getUid() originator of the jobs in each sheaf
992 private SparseArray<ArraySet<JobStatus>> mJobs;
Suprabh Shukla106203b2017-11-02 21:23:44 -0700993 // Same data but with the key as getSourceUid() of the jobs in each sheaf
994 private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
Christopher Tate2f36fd62016-02-18 18:36:08 -0800995
996 public JobSet() {
997 mJobs = new SparseArray<ArraySet<JobStatus>>();
Suprabh Shukla106203b2017-11-02 21:23:44 -0700998 mJobsPerSourceUid = new SparseArray<>();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800999 }
1000
1001 public List<JobStatus> getJobsByUid(int uid) {
1002 ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
1003 ArraySet<JobStatus> jobs = mJobs.get(uid);
1004 if (jobs != null) {
1005 matchingJobs.addAll(jobs);
1006 }
1007 return matchingJobs;
1008 }
1009
1010 // By user, not by uid, so we need to traverse by key and check
1011 public List<JobStatus> getJobsByUser(int userId) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001012 final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
1013 for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
1014 if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
1015 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001016 if (jobs != null) {
1017 result.addAll(jobs);
1018 }
1019 }
1020 }
1021 return result;
1022 }
1023
1024 public boolean add(JobStatus job) {
1025 final int uid = job.getUid();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001026 final int sourceUid = job.getSourceUid();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001027 ArraySet<JobStatus> jobs = mJobs.get(uid);
1028 if (jobs == null) {
1029 jobs = new ArraySet<JobStatus>();
1030 mJobs.put(uid, jobs);
1031 }
Suprabh Shukla106203b2017-11-02 21:23:44 -07001032 ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
1033 if (jobsForSourceUid == null) {
1034 jobsForSourceUid = new ArraySet<>();
1035 mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
1036 }
1037 return jobs.add(job) && jobsForSourceUid.add(job);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001038 }
1039
1040 public boolean remove(JobStatus job) {
1041 final int uid = job.getUid();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001042 final ArraySet<JobStatus> jobs = mJobs.get(uid);
1043 final int sourceUid = job.getSourceUid();
1044 final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
1045 boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job);
1046 if (didRemove) {
1047 if (jobs.size() == 0) {
1048 // no more jobs for this uid; let the now-empty set object be GC'd.
1049 mJobs.remove(uid);
1050 }
1051 if (jobsForSourceUid.size() == 0) {
1052 mJobsPerSourceUid.remove(sourceUid);
1053 }
1054 return true;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001055 }
Suprabh Shukla106203b2017-11-02 21:23:44 -07001056 return false;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001057 }
1058
Suprabh Shukla106203b2017-11-02 21:23:44 -07001059 /**
1060 * Removes the jobs of all users not specified by the whitelist of user ids.
1061 * The jobs scheduled by non existent users will not be removed if they were
1062 */
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001063 public void removeJobsOfNonUsers(int[] whitelist) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001064 for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
1065 final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex));
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001066 if (!ArrayUtils.contains(whitelist, jobUserId)) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001067 mJobsPerSourceUid.removeAt(jobSetIndex);
1068 }
1069 }
1070 for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
1071 final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex);
1072 for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) {
1073 final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId();
1074 if (!ArrayUtils.contains(whitelist, jobUserId)) {
1075 jobsForUid.removeAt(jobIndex);
1076 }
1077 }
1078 if (jobsForUid.size() == 0) {
1079 mJobs.removeAt(jobSetIndex);
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001080 }
1081 }
1082 }
1083
Christopher Tate2f36fd62016-02-18 18:36:08 -08001084 public boolean contains(JobStatus job) {
1085 final int uid = job.getUid();
1086 ArraySet<JobStatus> jobs = mJobs.get(uid);
1087 return jobs != null && jobs.contains(job);
1088 }
1089
1090 public JobStatus get(int uid, int jobId) {
1091 ArraySet<JobStatus> jobs = mJobs.get(uid);
1092 if (jobs != null) {
1093 for (int i = jobs.size() - 1; i >= 0; i--) {
1094 JobStatus job = jobs.valueAt(i);
1095 if (job.getJobId() == jobId) {
1096 return job;
1097 }
1098 }
1099 }
1100 return null;
1101 }
1102
1103 // Inefficient; use only for testing
1104 public List<JobStatus> getAllJobs() {
1105 ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001106 for (int i = mJobs.size() - 1; i >= 0; i--) {
1107 ArraySet<JobStatus> jobs = mJobs.valueAt(i);
1108 if (jobs != null) {
1109 // Use a for loop over the ArraySet, so we don't need to make its
1110 // optional collection class iterator implementation or have to go
1111 // through a temporary array from toArray().
1112 for (int j = jobs.size() - 1; j >= 0; j--) {
1113 allJobs.add(jobs.valueAt(j));
1114 }
1115 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001116 }
1117 return allJobs;
1118 }
1119
1120 public void clear() {
1121 mJobs.clear();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001122 mJobsPerSourceUid.clear();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001123 }
1124
1125 public int size() {
1126 int total = 0;
1127 for (int i = mJobs.size() - 1; i >= 0; i--) {
1128 total += mJobs.valueAt(i).size();
1129 }
1130 return total;
1131 }
1132
1133 // We only want to count the jobs that this uid has scheduled on its own
1134 // behalf, not those that the app has scheduled on someone else's behalf.
1135 public int countJobsForUid(int uid) {
1136 int total = 0;
1137 ArraySet<JobStatus> jobs = mJobs.get(uid);
1138 if (jobs != null) {
1139 for (int i = jobs.size() - 1; i >= 0; i--) {
1140 JobStatus job = jobs.valueAt(i);
1141 if (job.getUid() == job.getSourceUid()) {
1142 total++;
1143 }
1144 }
1145 }
1146 return total;
1147 }
1148
1149 public void forEachJob(JobStatusFunctor functor) {
1150 for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
1151 ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
Christopher Tate41f3fe82017-12-08 17:52:09 -08001152 if (jobs != null) {
1153 for (int i = jobs.size() - 1; i >= 0; i--) {
1154 functor.process(jobs.valueAt(i));
1155 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001156 }
1157 }
1158 }
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001159
Suprabh Shukla106203b2017-11-02 21:23:44 -07001160 public void forEachJob(int callingUid, JobStatusFunctor functor) {
1161 ArraySet<JobStatus> jobs = mJobs.get(callingUid);
1162 if (jobs != null) {
1163 for (int i = jobs.size() - 1; i >= 0; i--) {
1164 functor.process(jobs.valueAt(i));
1165 }
1166 }
1167 }
1168
1169 public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
1170 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001171 if (jobs != null) {
1172 for (int i = jobs.size() - 1; i >= 0; i--) {
1173 functor.process(jobs.valueAt(i));
1174 }
1175 }
1176 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001177 }
Matthew Williams01ac45b2014-07-22 20:44:12 -07001178}