blob: a24a4ac3823bf2b60c0e9b8604150968893698a2 [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.
Makoto Onuki15407842018-01-19 14:23:11 -080075 *
76 * Test:
77 * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
Christopher Tate7060b042014-06-09 19:50:00 -070078 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -070079public final class JobStore {
Christopher Tate7060b042014-06-09 19:50:00 -070080 private static final String TAG = "JobStore";
81 private static final boolean DEBUG = JobSchedulerService.DEBUG;
82
83 /** Threshold to adjust how often we want to write to the db. */
84 private static final int MAX_OPS_BEFORE_WRITE = 1;
Christopher Tate616541d2017-07-26 14:27:38 -070085
Dianne Hackborn33d31c52016-02-16 10:30:33 -080086 final Object mLock;
Christopher Tate2f36fd62016-02-18 18:36:08 -080087 final JobSet mJobSet; // per-caller-uid tracking
Christopher Tate7060b042014-06-09 19:50:00 -070088 final Context mContext;
89
Christopher Tate616541d2017-07-26 14:27:38 -070090 // Bookkeeping around incorrect boot-time system clock
91 private final long mXmlTimestamp;
92 private boolean mRtcGood;
93
Christopher Tate7060b042014-06-09 19:50:00 -070094 private int mDirtyOperations;
95
96 private static final Object sSingletonLock = new Object();
97 private final AtomicFile mJobsFile;
98 /** Handler backed by IoThread for writing to disk. */
99 private final Handler mIoHandler = IoThread.getHandler();
100 private static JobStore sSingleton;
101
Makoto Onukie7b02982017-08-24 14:23:36 -0700102 private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
103
Christopher Tate7060b042014-06-09 19:50:00 -0700104 /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
105 static JobStore initAndGet(JobSchedulerService jobManagerService) {
106 synchronized (sSingletonLock) {
107 if (sSingleton == null) {
108 sSingleton = new JobStore(jobManagerService.getContext(),
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800109 jobManagerService.getLock(), Environment.getDataDirectory());
Christopher Tate7060b042014-06-09 19:50:00 -0700110 }
111 return sSingleton;
112 }
113 }
114
Matthew Williams01ac45b2014-07-22 20:44:12 -0700115 /**
116 * @return A freshly initialized job store object, with no loaded jobs.
117 */
Christopher Tate7060b042014-06-09 19:50:00 -0700118 @VisibleForTesting
Matthew Williams01ac45b2014-07-22 20:44:12 -0700119 public static JobStore initAndGetForTesting(Context context, File dataDir) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800120 JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir);
Matthew Williams01ac45b2014-07-22 20:44:12 -0700121 jobStoreUnderTest.clear();
122 return jobStoreUnderTest;
Christopher Tate7060b042014-06-09 19:50:00 -0700123 }
124
Matthew Williams01ac45b2014-07-22 20:44:12 -0700125 /**
126 * Construct the instance of the job store. This results in a blocking read from disk.
127 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800128 private JobStore(Context context, Object lock, File dataDir) {
129 mLock = lock;
Christopher Tate7060b042014-06-09 19:50:00 -0700130 mContext = context;
131 mDirtyOperations = 0;
132
133 File systemDir = new File(dataDir, "system");
134 File jobDir = new File(systemDir, "job");
135 jobDir.mkdirs();
136 mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
137
Christopher Tate2f36fd62016-02-18 18:36:08 -0800138 mJobSet = new JobSet();
Christopher Tate7060b042014-06-09 19:50:00 -0700139
Christopher Tate616541d2017-07-26 14:27:38 -0700140 // If the current RTC is earlier than the timestamp on our persisted jobs file,
141 // we suspect that the RTC is uninitialized and so we cannot draw conclusions
142 // about persisted job scheduling.
143 //
144 // Note that if the persisted jobs file does not exist, we proceed with the
145 // assumption that the RTC is good. This is less work and is safe: if the
146 // clock updates to sanity then we'll be saving the persisted jobs file in that
147 // correct state, which is normal; or we'll wind up writing the jobs file with
148 // an incorrect historical timestamp. That's fine; at worst we'll reboot with
149 // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
150 // settle into normal operation.
151 mXmlTimestamp = mJobsFile.getLastModifiedTime();
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700152 mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
Christopher Tate616541d2017-07-26 14:27:38 -0700153
154 readJobMapFromDisk(mJobSet, mRtcGood);
155 }
156
157 public boolean jobTimesInflatedValid() {
158 return mRtcGood;
159 }
160
161 public boolean clockNowValidToInflate(long now) {
162 return now >= mXmlTimestamp;
163 }
164
165 /**
166 * Find all the jobs that were affected by RTC clock uncertainty at boot time. Returns
167 * parallel lists of the existing JobStatus objects and of new, equivalent JobStatus instances
168 * with now-corrected time bounds.
169 */
170 public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
171 final ArrayList<JobStatus> toRemove) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700172 final long elapsedNow = sElapsedRealtimeClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700173
174 // Find the jobs that need to be fixed up, collecting them for post-iteration
175 // replacement with their new versions
176 forEachJob(job -> {
177 final Pair<Long, Long> utcTimes = job.getPersistedUtcTimes();
178 if (utcTimes != null) {
179 Pair<Long, Long> elapsedRuntimes =
180 convertRtcBoundsToElapsed(utcTimes, elapsedNow);
Christopher Tatea732f012017-10-26 17:26:53 -0700181 toAdd.add(new JobStatus(job, job.getBaseHeartbeat(),
182 elapsedRuntimes.first, elapsedRuntimes.second,
Christopher Tate616541d2017-07-26 14:27:38 -0700183 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()));
184 toRemove.add(job);
185 }
186 });
Christopher Tate7060b042014-06-09 19:50:00 -0700187 }
188
189 /**
190 * Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
191 * it will be replaced.
192 * @param jobStatus Job to add.
193 * @return Whether or not an equivalent JobStatus was replaced by this operation.
194 */
195 public boolean add(JobStatus jobStatus) {
196 boolean replaced = mJobSet.remove(jobStatus);
197 mJobSet.add(jobStatus);
198 if (jobStatus.isPersisted()) {
199 maybeWriteStatusToDiskAsync();
200 }
201 if (DEBUG) {
202 Slog.d(TAG, "Added job status to store: " + jobStatus);
203 }
204 return replaced;
205 }
206
Matthew Williams48a30db2014-09-23 13:39:36 -0700207 boolean containsJob(JobStatus jobStatus) {
208 return mJobSet.contains(jobStatus);
209 }
210
Christopher Tate7060b042014-06-09 19:50:00 -0700211 public int size() {
212 return mJobSet.size();
213 }
214
Makoto Onukie7b02982017-08-24 14:23:36 -0700215 public JobStorePersistStats getPersistStats() {
216 return mPersistInfo;
217 }
218
Christopher Tate2f36fd62016-02-18 18:36:08 -0800219 public int countJobsForUid(int uid) {
220 return mJobSet.countJobsForUid(uid);
221 }
222
Christopher Tate7060b042014-06-09 19:50:00 -0700223 /**
224 * Remove the provided job. Will also delete the job if it was persisted.
Shreyas Basarge73f10252016-02-11 17:06:13 +0000225 * @param writeBack If true, the job will be deleted (if it was persisted) immediately.
Christopher Tate7060b042014-06-09 19:50:00 -0700226 * @return Whether or not the job existed to be removed.
227 */
Shreyas Basarge73f10252016-02-11 17:06:13 +0000228 public boolean remove(JobStatus jobStatus, boolean writeBack) {
Christopher Tate7060b042014-06-09 19:50:00 -0700229 boolean removed = mJobSet.remove(jobStatus);
230 if (!removed) {
231 if (DEBUG) {
232 Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus);
233 }
234 return false;
235 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000236 if (writeBack && jobStatus.isPersisted()) {
Matthew Williams900c67f2014-07-09 12:46:53 -0700237 maybeWriteStatusToDiskAsync();
238 }
Christopher Tate7060b042014-06-09 19:50:00 -0700239 return removed;
240 }
241
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -0700242 /**
243 * Remove the jobs of users not specified in the whitelist.
244 * @param whitelist Array of User IDs whose jobs are not to be removed.
245 */
246 public void removeJobsOfNonUsers(int[] whitelist) {
247 mJobSet.removeJobsOfNonUsers(whitelist);
248 }
249
Christopher Tate7060b042014-06-09 19:50:00 -0700250 @VisibleForTesting
251 public void clear() {
252 mJobSet.clear();
253 maybeWriteStatusToDiskAsync();
254 }
255
Matthew Williams48a30db2014-09-23 13:39:36 -0700256 /**
257 * @param userHandle User for whom we are querying the list of jobs.
Suprabh Shukla106203b2017-11-02 21:23:44 -0700258 * @return A list of all the jobs scheduled for the provided user. Never null.
Matthew Williams48a30db2014-09-23 13:39:36 -0700259 */
Christopher Tate7060b042014-06-09 19:50:00 -0700260 public List<JobStatus> getJobsByUser(int userHandle) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800261 return mJobSet.getJobsByUser(userHandle);
Christopher Tate7060b042014-06-09 19:50:00 -0700262 }
263
264 /**
265 * @param uid Uid of the requesting app.
Matthew Williams48a30db2014-09-23 13:39:36 -0700266 * @return All JobStatus objects for a given uid from the master list. Never null.
Christopher Tate7060b042014-06-09 19:50:00 -0700267 */
268 public List<JobStatus> getJobsByUid(int uid) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800269 return mJobSet.getJobsByUid(uid);
Christopher Tate7060b042014-06-09 19:50:00 -0700270 }
271
272 /**
273 * @param uid Uid of the requesting app.
274 * @param jobId Job id, specified at schedule-time.
275 * @return the JobStatus that matches the provided uId and jobId, or null if none found.
276 */
277 public JobStatus getJobByUidAndJobId(int uid, int jobId) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800278 return mJobSet.get(uid, jobId);
Christopher Tate7060b042014-06-09 19:50:00 -0700279 }
280
281 /**
Christopher Tate2f36fd62016-02-18 18:36:08 -0800282 * Iterate over the set of all jobs, invoking the supplied functor on each. This is for
283 * customers who need to examine each job; we'd much rather not have to generate
284 * transient unified collections for them to iterate over and then discard, or creating
285 * iterators every time a client needs to perform a sweep.
Christopher Tate7060b042014-06-09 19:50:00 -0700286 */
Christopher Tate2f36fd62016-02-18 18:36:08 -0800287 public void forEachJob(JobStatusFunctor functor) {
288 mJobSet.forEachJob(functor);
289 }
290
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000291 public void forEachJob(int uid, JobStatusFunctor functor) {
292 mJobSet.forEachJob(uid, functor);
293 }
294
Suprabh Shukla106203b2017-11-02 21:23:44 -0700295 public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
296 mJobSet.forEachJobForSourceUid(sourceUid, functor);
297 }
298
Christopher Tate2f36fd62016-02-18 18:36:08 -0800299 public interface JobStatusFunctor {
300 public void process(JobStatus jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700301 }
302
303 /** Version of the db schema. */
304 private static final int JOBS_FILE_VERSION = 0;
305 /** Tag corresponds to constraints this job needs. */
306 private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
307 /** Tag corresponds to execution parameters. */
308 private static final String XML_TAG_PERIODIC = "periodic";
309 private static final String XML_TAG_ONEOFF = "one-off";
310 private static final String XML_TAG_EXTRAS = "extras";
311
312 /**
313 * Every time the state changes we write all the jobs in one swath, instead of trying to
314 * track incremental changes.
Christopher Tate7060b042014-06-09 19:50:00 -0700315 */
316 private void maybeWriteStatusToDiskAsync() {
317 mDirtyOperations++;
318 if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) {
319 if (DEBUG) {
320 Slog.v(TAG, "Writing jobs to disk.");
321 }
Christopher Tate616541d2017-07-26 14:27:38 -0700322 mIoHandler.removeCallbacks(mWriteRunnable);
323 mIoHandler.post(mWriteRunnable);
Christopher Tate7060b042014-06-09 19:50:00 -0700324 }
325 }
326
Matthew Williams01ac45b2014-07-22 20:44:12 -0700327 @VisibleForTesting
Christopher Tate616541d2017-07-26 14:27:38 -0700328 public void readJobMapFromDisk(JobSet jobSet, boolean rtcGood) {
329 new ReadJobMapFromDiskRunnable(jobSet, rtcGood).run();
Christopher Tate7060b042014-06-09 19:50:00 -0700330 }
331
332 /**
333 * Runnable that writes {@link #mJobSet} out to xml.
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800334 * NOTE: This Runnable locks on mLock
Christopher Tate7060b042014-06-09 19:50:00 -0700335 */
Christopher Tate616541d2017-07-26 14:27:38 -0700336 private final Runnable mWriteRunnable = new Runnable() {
Christopher Tate7060b042014-06-09 19:50:00 -0700337 @Override
338 public void run() {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700339 final long startElapsed = sElapsedRealtimeClock.millis();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800340 final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800341 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800342 // Clone the jobs so we can release the lock before writing.
343 mJobSet.forEachJob(new JobStatusFunctor() {
344 @Override
345 public void process(JobStatus job) {
346 if (job.isPersisted()) {
347 storeCopy.add(new JobStatus(job));
348 }
Shreyas Basarge7ef490f2015-12-03 16:45:22 +0000349 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800350 });
Christopher Tate7060b042014-06-09 19:50:00 -0700351 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800352 writeJobsMapImpl(storeCopy);
Christopher Tate616541d2017-07-26 14:27:38 -0700353 if (DEBUG) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700354 Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
Christopher Tate7060b042014-06-09 19:50:00 -0700355 - startElapsed) + "ms");
356 }
357 }
358
Matthew Williams49a85b62014-06-12 11:02:34 -0700359 private void writeJobsMapImpl(List<JobStatus> jobList) {
Makoto Onukie7b02982017-08-24 14:23:36 -0700360 int numJobs = 0;
361 int numSystemJobs = 0;
362 int numSyncJobs = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700363 try {
364 ByteArrayOutputStream baos = new ByteArrayOutputStream();
365 XmlSerializer out = new FastXmlSerializer();
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +0100366 out.setOutput(baos, StandardCharsets.UTF_8.name());
Christopher Tate7060b042014-06-09 19:50:00 -0700367 out.startDocument(null, true);
368 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
369
370 out.startTag(null, "job-info");
371 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700372 for (int i=0; i<jobList.size(); i++) {
373 JobStatus jobStatus = jobList.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700374 if (DEBUG) {
375 Slog.d(TAG, "Saving job " + jobStatus.getJobId());
376 }
377 out.startTag(null, "job");
Shreyas Basarge5db09082016-01-07 13:38:29 +0000378 addAttributesToJobTag(out, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700379 writeConstraintsToXml(out, jobStatus);
380 writeExecutionCriteriaToXml(out, jobStatus);
Dianne Hackborna47223f2017-03-30 13:49:13 -0700381 writeBundleToXml(jobStatus.getJob().getExtras(), out);
Christopher Tate7060b042014-06-09 19:50:00 -0700382 out.endTag(null, "job");
Makoto Onukie7b02982017-08-24 14:23:36 -0700383
384 numJobs++;
385 if (jobStatus.getUid() == Process.SYSTEM_UID) {
386 numSystemJobs++;
387 if (isSyncJob(jobStatus)) {
388 numSyncJobs++;
389 }
390 }
Christopher Tate7060b042014-06-09 19:50:00 -0700391 }
392 out.endTag(null, "job-info");
393 out.endDocument();
394
Christopher Tate616541d2017-07-26 14:27:38 -0700395 // Write out to disk in one fell swoop.
Christopher Tate7060b042014-06-09 19:50:00 -0700396 FileOutputStream fos = mJobsFile.startWrite();
397 fos.write(baos.toByteArray());
398 mJobsFile.finishWrite(fos);
399 mDirtyOperations = 0;
400 } catch (IOException e) {
401 if (DEBUG) {
402 Slog.v(TAG, "Error writing out job data.", e);
403 }
404 } catch (XmlPullParserException e) {
405 if (DEBUG) {
406 Slog.d(TAG, "Error persisting bundle.", e);
407 }
Makoto Onukie7b02982017-08-24 14:23:36 -0700408 } finally {
409 mPersistInfo.countAllJobsSaved = numJobs;
410 mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
411 mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
Christopher Tate7060b042014-06-09 19:50:00 -0700412 }
413 }
414
Shreyas Basarge5db09082016-01-07 13:38:29 +0000415 /** Write out a tag with data comprising the required fields and priority of this job and
416 * its client.
417 */
418 private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
Christopher Tate7060b042014-06-09 19:50:00 -0700419 throws IOException {
420 out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
421 out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
422 out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
Shreyas Basarge968ac752016-01-11 23:09:26 +0000423 if (jobStatus.getSourcePackageName() != null) {
424 out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
425 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800426 if (jobStatus.getSourceTag() != null) {
427 out.attribute(null, "sourceTag", jobStatus.getSourceTag());
428 }
Shreyas Basarge968ac752016-01-11 23:09:26 +0000429 out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
Christopher Tate7060b042014-06-09 19:50:00 -0700430 out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000431 out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
Jeff Sharkey1b6519b2016-04-28 15:33:18 -0600432 out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
Makoto Onuki15407842018-01-19 14:23:11 -0800433 if (jobStatus.getInternalFlags() != 0) {
434 out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
435 }
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700436
437 out.attribute(null, "lastSuccessfulRunTime",
438 String.valueOf(jobStatus.getLastSuccessfulRunTime()));
439 out.attribute(null, "lastFailedRunTime",
440 String.valueOf(jobStatus.getLastFailedRunTime()));
Christopher Tate7060b042014-06-09 19:50:00 -0700441 }
442
443 private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
444 throws IOException, XmlPullParserException {
445 out.startTag(null, XML_TAG_EXTRAS);
Shreyas Basarged09973b2015-12-16 18:10:05 +0000446 PersistableBundle extrasCopy = deepCopyBundle(extras, 10);
447 extrasCopy.saveToXml(out);
Christopher Tate7060b042014-06-09 19:50:00 -0700448 out.endTag(null, XML_TAG_EXTRAS);
449 }
Shreyas Basarged09973b2015-12-16 18:10:05 +0000450
451 private PersistableBundle deepCopyBundle(PersistableBundle bundle, int maxDepth) {
452 if (maxDepth <= 0) {
453 return null;
454 }
455 PersistableBundle copy = (PersistableBundle) bundle.clone();
456 Set<String> keySet = bundle.keySet();
457 for (String key: keySet) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000458 Object o = copy.get(key);
459 if (o instanceof PersistableBundle) {
460 PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1);
Shreyas Basarged09973b2015-12-16 18:10:05 +0000461 copy.putPersistableBundle(key, bCopy);
462 }
463 }
464 return copy;
465 }
466
Christopher Tate7060b042014-06-09 19:50:00 -0700467 /**
468 * Write out a tag with data identifying this job's constraints. If the constraint isn't here
469 * it doesn't apply.
470 */
471 private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
472 out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700473 if (jobStatus.hasConnectivityConstraint()) {
474 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
475 out.attribute(null, "net-capabilities", Long.toString(
476 BitUtils.packBits(network.networkCapabilities.getCapabilities())));
477 out.attribute(null, "net-transport-types", Long.toString(
478 BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
Christopher Tate7060b042014-06-09 19:50:00 -0700479 }
480 if (jobStatus.hasIdleConstraint()) {
481 out.attribute(null, "idle", Boolean.toString(true));
482 }
483 if (jobStatus.hasChargingConstraint()) {
484 out.attribute(null, "charging", Boolean.toString(true));
485 }
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800486 if (jobStatus.hasBatteryNotLowConstraint()) {
487 out.attribute(null, "battery-not-low", Boolean.toString(true));
488 }
Christopher Tate7060b042014-06-09 19:50:00 -0700489 out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
490 }
491
492 private void writeExecutionCriteriaToXml(XmlSerializer out, JobStatus jobStatus)
493 throws IOException {
494 final JobInfo job = jobStatus.getJob();
495 if (jobStatus.getJob().isPeriodic()) {
496 out.startTag(null, XML_TAG_PERIODIC);
497 out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000498 out.attribute(null, "flex", Long.toString(job.getFlexMillis()));
Christopher Tate7060b042014-06-09 19:50:00 -0700499 } else {
500 out.startTag(null, XML_TAG_ONEOFF);
501 }
502
Christopher Tate616541d2017-07-26 14:27:38 -0700503 // If we still have the persisted times, we need to record those directly because
504 // we haven't yet been able to calculate the usual elapsed-timebase bounds
505 // correctly due to wall-clock uncertainty.
506 Pair <Long, Long> utcJobTimes = jobStatus.getPersistedUtcTimes();
507 if (DEBUG && utcJobTimes != null) {
508 Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
509 }
510
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700511 final long nowRTC = sSystemClock.millis();
512 final long nowElapsed = sElapsedRealtimeClock.millis();
Christopher Tate7060b042014-06-09 19:50:00 -0700513 if (jobStatus.hasDeadlineConstraint()) {
514 // Wall clock deadline.
Christopher Tate616541d2017-07-26 14:27:38 -0700515 final long deadlineWallclock = (utcJobTimes == null)
516 ? nowRTC + (jobStatus.getLatestRunTimeElapsed() - nowElapsed)
517 : utcJobTimes.second;
Christopher Tate7060b042014-06-09 19:50:00 -0700518 out.attribute(null, "deadline", Long.toString(deadlineWallclock));
519 }
520 if (jobStatus.hasTimingDelayConstraint()) {
Christopher Tate616541d2017-07-26 14:27:38 -0700521 final long delayWallclock = (utcJobTimes == null)
522 ? nowRTC + (jobStatus.getEarliestRunTime() - nowElapsed)
523 : utcJobTimes.first;
Christopher Tate7060b042014-06-09 19:50:00 -0700524 out.attribute(null, "delay", Long.toString(delayWallclock));
525 }
526
527 // Only write out back-off policy if it differs from the default.
528 // This also helps the case where the job is idle -> these aren't allowed to specify
529 // back-off.
530 if (jobStatus.getJob().getInitialBackoffMillis() != JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS
531 || jobStatus.getJob().getBackoffPolicy() != JobInfo.DEFAULT_BACKOFF_POLICY) {
532 out.attribute(null, "backoff-policy", Integer.toString(job.getBackoffPolicy()));
533 out.attribute(null, "initial-backoff", Long.toString(job.getInitialBackoffMillis()));
534 }
535 if (job.isPeriodic()) {
536 out.endTag(null, XML_TAG_PERIODIC);
537 } else {
538 out.endTag(null, XML_TAG_ONEOFF);
539 }
540 }
Christopher Tate616541d2017-07-26 14:27:38 -0700541 };
542
543 /**
544 * Translate the supplied RTC times to the elapsed timebase, with clamping appropriate
545 * to interpreting them as a job's delay + deadline times for alarm-setting purposes.
546 * @param rtcTimes a Pair<Long, Long> in which {@code first} is the "delay" earliest
547 * allowable runtime for the job, and {@code second} is the "deadline" time at which
548 * the job becomes overdue.
549 */
550 private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
551 long nowElapsed) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700552 final long nowWallclock = sSystemClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700553 final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
554 ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
555 : JobStatus.NO_EARLIEST_RUNTIME;
556 final long latest = (rtcTimes.second < JobStatus.NO_LATEST_RUNTIME)
557 ? nowElapsed + Math.max(rtcTimes.second - nowWallclock, 0)
558 : JobStatus.NO_LATEST_RUNTIME;
559 return Pair.create(earliest, latest);
Christopher Tate7060b042014-06-09 19:50:00 -0700560 }
561
Makoto Onukie7b02982017-08-24 14:23:36 -0700562 private static boolean isSyncJob(JobStatus status) {
563 return com.android.server.content.SyncJobService.class.getName()
564 .equals(status.getServiceComponent().getClassName());
565 }
566
Christopher Tate7060b042014-06-09 19:50:00 -0700567 /**
Matthew Williams01ac45b2014-07-22 20:44:12 -0700568 * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
569 * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
Christopher Tate7060b042014-06-09 19:50:00 -0700570 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -0700571 private final class ReadJobMapFromDiskRunnable implements Runnable {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800572 private final JobSet jobSet;
Christopher Tate616541d2017-07-26 14:27:38 -0700573 private final boolean rtcGood;
Matthew Williams01ac45b2014-07-22 20:44:12 -0700574
575 /**
576 * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
577 * so that after disk read we can populate it directly.
578 */
Christopher Tate616541d2017-07-26 14:27:38 -0700579 ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood) {
Matthew Williams01ac45b2014-07-22 20:44:12 -0700580 this.jobSet = jobSet;
Christopher Tate616541d2017-07-26 14:27:38 -0700581 this.rtcGood = rtcIsGood;
Christopher Tate7060b042014-06-09 19:50:00 -0700582 }
583
584 @Override
585 public void run() {
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700586 int numJobs = 0;
Makoto Onukie7b02982017-08-24 14:23:36 -0700587 int numSystemJobs = 0;
588 int numSyncJobs = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700589 try {
590 List<JobStatus> jobs;
591 FileInputStream fis = mJobsFile.openRead();
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800592 synchronized (mLock) {
Christopher Tate616541d2017-07-26 14:27:38 -0700593 jobs = readJobMapImpl(fis, rtcGood);
Matthew Williams01ac45b2014-07-22 20:44:12 -0700594 if (jobs != null) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700595 long now = sElapsedRealtimeClock.millis();
Dianne Hackborna47223f2017-03-30 13:49:13 -0700596 IActivityManager am = ActivityManager.getService();
Matthew Williams01ac45b2014-07-22 20:44:12 -0700597 for (int i=0; i<jobs.size(); i++) {
Dianne Hackborna47223f2017-03-30 13:49:13 -0700598 JobStatus js = jobs.get(i);
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700599 js.prepareLocked(am);
Dianne Hackbornbfc23312017-05-11 11:53:24 -0700600 js.enqueueTime = now;
Dianne Hackborna47223f2017-03-30 13:49:13 -0700601 this.jobSet.add(js);
Makoto Onukie7b02982017-08-24 14:23:36 -0700602
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700603 numJobs++;
Makoto Onukie7b02982017-08-24 14:23:36 -0700604 if (js.getUid() == Process.SYSTEM_UID) {
605 numSystemJobs++;
606 if (isSyncJob(js)) {
607 numSyncJobs++;
608 }
609 }
Matthew Williams01ac45b2014-07-22 20:44:12 -0700610 }
611 }
Christopher Tate7060b042014-06-09 19:50:00 -0700612 }
613 fis.close();
Christopher Tate7060b042014-06-09 19:50:00 -0700614 } catch (FileNotFoundException e) {
Christopher Tate616541d2017-07-26 14:27:38 -0700615 if (DEBUG) {
Christopher Tate7060b042014-06-09 19:50:00 -0700616 Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
617 }
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700618 } catch (XmlPullParserException | IOException e) {
619 Slog.wtf(TAG, "Error jobstore xml.", e);
Makoto Onukie7b02982017-08-24 14:23:36 -0700620 } finally {
621 if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
622 mPersistInfo.countAllJobsLoaded = numJobs;
623 mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
624 mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
625 }
Christopher Tate7060b042014-06-09 19:50:00 -0700626 }
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700627 Slog.i(TAG, "Read " + numJobs + " jobs");
Christopher Tate7060b042014-06-09 19:50:00 -0700628 }
629
Christopher Tate616541d2017-07-26 14:27:38 -0700630 private List<JobStatus> readJobMapImpl(FileInputStream fis, boolean rtcIsGood)
Matthew Williams900c67f2014-07-09 12:46:53 -0700631 throws XmlPullParserException, IOException {
Christopher Tate7060b042014-06-09 19:50:00 -0700632 XmlPullParser parser = Xml.newPullParser();
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +0100633 parser.setInput(fis, StandardCharsets.UTF_8.name());
Christopher Tate7060b042014-06-09 19:50:00 -0700634
635 int eventType = parser.getEventType();
636 while (eventType != XmlPullParser.START_TAG &&
637 eventType != XmlPullParser.END_DOCUMENT) {
638 eventType = parser.next();
riddle_hsu98bfb342015-07-30 21:52:58 +0800639 Slog.d(TAG, "Start tag: " + parser.getName());
Christopher Tate7060b042014-06-09 19:50:00 -0700640 }
641 if (eventType == XmlPullParser.END_DOCUMENT) {
642 if (DEBUG) {
643 Slog.d(TAG, "No persisted jobs.");
644 }
645 return null;
646 }
647
648 String tagName = parser.getName();
649 if ("job-info".equals(tagName)) {
650 final List<JobStatus> jobs = new ArrayList<JobStatus>();
651 // Read in version info.
652 try {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100653 int version = Integer.parseInt(parser.getAttributeValue(null, "version"));
Christopher Tate7060b042014-06-09 19:50:00 -0700654 if (version != JOBS_FILE_VERSION) {
655 Slog.d(TAG, "Invalid version number, aborting jobs file read.");
656 return null;
657 }
658 } catch (NumberFormatException e) {
659 Slog.e(TAG, "Invalid version number, aborting jobs file read.");
660 return null;
661 }
662 eventType = parser.next();
663 do {
664 // Read each <job/>
665 if (eventType == XmlPullParser.START_TAG) {
666 tagName = parser.getName();
667 // Start reading job.
668 if ("job".equals(tagName)) {
Christopher Tate616541d2017-07-26 14:27:38 -0700669 JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser);
Christopher Tate7060b042014-06-09 19:50:00 -0700670 if (persistedJob != null) {
671 if (DEBUG) {
672 Slog.d(TAG, "Read out " + persistedJob);
673 }
674 jobs.add(persistedJob);
675 } else {
676 Slog.d(TAG, "Error reading job from file.");
677 }
678 }
679 }
680 eventType = parser.next();
681 } while (eventType != XmlPullParser.END_DOCUMENT);
682 return jobs;
683 }
684 return null;
685 }
686
687 /**
688 * @param parser Xml parser at the beginning of a "<job/>" tag. The next "parser.next()" call
689 * will take the parser into the body of the job tag.
690 * @return Newly instantiated job holding all the information we just read out of the xml tag.
691 */
Christopher Tate616541d2017-07-26 14:27:38 -0700692 private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser)
693 throws XmlPullParserException, IOException {
Christopher Tate7060b042014-06-09 19:50:00 -0700694 JobInfo.Builder jobBuilder;
Shreyas Basarge8e64e2e2016-02-12 15:49:31 +0000695 int uid, sourceUserId;
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700696 long lastSuccessfulRunTime;
697 long lastFailedRunTime;
Makoto Onuki15407842018-01-19 14:23:11 -0800698 int internalFlags = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700699
Shreyas Basarge5db09082016-01-07 13:38:29 +0000700 // Read out job identifier attributes and priority.
Christopher Tate7060b042014-06-09 19:50:00 -0700701 try {
702 jobBuilder = buildBuilderFromXml(parser);
Matthew Williamsd1c06752014-08-22 14:15:28 -0700703 jobBuilder.setPersisted(true);
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100704 uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000705
Shreyas Basarge968ac752016-01-11 23:09:26 +0000706 String val = parser.getAttributeValue(null, "priority");
707 if (val != null) {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100708 jobBuilder.setPriority(Integer.parseInt(val));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000709 }
Jeff Sharkey1b6519b2016-04-28 15:33:18 -0600710 val = parser.getAttributeValue(null, "flags");
711 if (val != null) {
712 jobBuilder.setFlags(Integer.parseInt(val));
713 }
Makoto Onuki15407842018-01-19 14:23:11 -0800714 val = parser.getAttributeValue(null, "internalFlags");
715 if (val != null) {
716 internalFlags = Integer.parseInt(val);
717 }
Shreyas Basarge968ac752016-01-11 23:09:26 +0000718 val = parser.getAttributeValue(null, "sourceUserId");
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100719 sourceUserId = val == null ? -1 : Integer.parseInt(val);
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700720
721 val = parser.getAttributeValue(null, "lastSuccessfulRunTime");
722 lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val);
723
724 val = parser.getAttributeValue(null, "lastFailedRunTime");
725 lastFailedRunTime = val == null ? 0 : Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700726 } catch (NumberFormatException e) {
727 Slog.e(TAG, "Error parsing job's required fields, skipping");
728 return null;
729 }
730
Christopher Tate0213ace02016-02-24 14:18:35 -0800731 String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800732 final String sourceTag = parser.getAttributeValue(null, "sourceTag");
733
Christopher Tate7060b042014-06-09 19:50:00 -0700734 int eventType;
735 // Read out constraints tag.
736 do {
737 eventType = parser.next();
738 } while (eventType == XmlPullParser.TEXT); // Push through to next START_TAG.
739
740 if (!(eventType == XmlPullParser.START_TAG &&
741 XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) {
742 // Expecting a <constraints> start tag.
743 return null;
744 }
745 try {
746 buildConstraintsFromXml(jobBuilder, parser);
747 } catch (NumberFormatException e) {
748 Slog.d(TAG, "Error reading constraints, skipping.");
749 return null;
750 }
751 parser.next(); // Consume </constraints>
752
753 // Read out execution parameters tag.
754 do {
755 eventType = parser.next();
756 } while (eventType == XmlPullParser.TEXT);
757 if (eventType != XmlPullParser.START_TAG) {
758 return null;
759 }
760
Christopher Tate616541d2017-07-26 14:27:38 -0700761 // Tuple of (earliest runtime, latest runtime) in UTC.
762 final Pair<Long, Long> rtcRuntimes;
Christopher Tate7060b042014-06-09 19:50:00 -0700763 try {
Christopher Tate616541d2017-07-26 14:27:38 -0700764 rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
Christopher Tate7060b042014-06-09 19:50:00 -0700765 } catch (NumberFormatException e) {
766 if (DEBUG) {
767 Slog.d(TAG, "Error parsing execution time parameters, skipping.");
768 }
769 return null;
770 }
771
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700772 final long elapsedNow = sElapsedRealtimeClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700773 Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
774
Christopher Tate7060b042014-06-09 19:50:00 -0700775 if (XML_TAG_PERIODIC.equals(parser.getName())) {
776 try {
777 String val = parser.getAttributeValue(null, "period");
Tobias Thierer28532d02016-04-21 14:52:10 +0100778 final long periodMillis = Long.parseLong(val);
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000779 val = parser.getAttributeValue(null, "flex");
780 final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
Shreyas Basarge8e64e2e2016-02-12 15:49:31 +0000781 jobBuilder.setPeriodic(periodMillis, flexMillis);
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000782 // As a sanity check, cap the recreated run time to be no later than flex+period
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700783 // from now. This is the latest the periodic could be pushed out. This could
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000784 // happen if the periodic ran early (at flex time before period), and then the
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700785 // device rebooted.
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000786 if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
787 final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
788 + periodMillis;
789 final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
790 - flexMillis;
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700791 Slog.w(TAG,
792 String.format("Periodic job for uid='%d' persisted run-time is" +
793 " too big [%s, %s]. Clamping to [%s,%s]",
794 uid,
795 DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000),
796 DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000),
797 DateUtils.formatElapsedTime(
798 clampedEarlyRuntimeElapsed / 1000),
799 DateUtils.formatElapsedTime(
800 clampedLateRuntimeElapsed / 1000))
801 );
802 elapsedRuntimes =
803 Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
804 }
Christopher Tate7060b042014-06-09 19:50:00 -0700805 } catch (NumberFormatException e) {
806 Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
807 return null;
808 }
809 } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
810 try {
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700811 if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
812 jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
Christopher Tate7060b042014-06-09 19:50:00 -0700813 }
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700814 if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
Christopher Tate7060b042014-06-09 19:50:00 -0700815 jobBuilder.setOverrideDeadline(
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700816 elapsedRuntimes.second - elapsedNow);
Christopher Tate7060b042014-06-09 19:50:00 -0700817 }
818 } catch (NumberFormatException e) {
819 Slog.d(TAG, "Error reading job execution criteria, skipping.");
820 return null;
821 }
822 } else {
823 if (DEBUG) {
824 Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName());
825 }
826 // Expecting a parameters start tag.
827 return null;
828 }
829 maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
830
831 parser.nextTag(); // Consume parameters end tag.
832
833 // Read out extras Bundle.
834 do {
835 eventType = parser.next();
836 } while (eventType == XmlPullParser.TEXT);
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700837 if (!(eventType == XmlPullParser.START_TAG
838 && XML_TAG_EXTRAS.equals(parser.getName()))) {
Christopher Tate7060b042014-06-09 19:50:00 -0700839 if (DEBUG) {
840 Slog.d(TAG, "Error reading extras, skipping.");
841 }
842 return null;
843 }
844
845 PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
846 jobBuilder.setExtras(extras);
847 parser.nextTag(); // Consume </extras>
848
Christopher Tate0213ace02016-02-24 14:18:35 -0800849 // Migrate sync jobs forward from earlier, incomplete representation
850 if ("android".equals(sourcePackageName)
851 && extras != null
852 && extras.getBoolean("SyncManagerJob", false)) {
853 sourcePackageName = extras.getString("owningPackage", sourcePackageName);
854 if (DEBUG) {
855 Slog.i(TAG, "Fixing up sync job source package name from 'android' to '"
856 + sourcePackageName + "'");
857 }
858 }
859
860 // And now we're done
Christopher Tatea732f012017-10-26 17:26:53 -0700861 JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
862 final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
863 sourceUserId, elapsedNow);
864 long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
Shreyas Basarge968ac752016-01-11 23:09:26 +0000865 JobStatus js = new JobStatus(
Christopher Tatea732f012017-10-26 17:26:53 -0700866 jobBuilder.build(), uid, sourcePackageName, sourceUserId,
867 appBucket, currentHeartbeat, sourceTag,
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700868 elapsedRuntimes.first, elapsedRuntimes.second,
Christopher Tate616541d2017-07-26 14:27:38 -0700869 lastSuccessfulRunTime, lastFailedRunTime,
Makoto Onuki15407842018-01-19 14:23:11 -0800870 (rtcIsGood) ? null : rtcRuntimes, internalFlags);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000871 return js;
Christopher Tate7060b042014-06-09 19:50:00 -0700872 }
873
874 private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
875 // Pull out required fields from <job> attributes.
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100876 int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
Christopher Tate7060b042014-06-09 19:50:00 -0700877 String packageName = parser.getAttributeValue(null, "package");
878 String className = parser.getAttributeValue(null, "class");
879 ComponentName cname = new ComponentName(packageName, className);
880
881 return new JobInfo.Builder(jobId, cname);
882 }
883
884 private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700885 String val;
886
887 final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
888 final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
889 if (netCapabilities != null && netTransportTypes != null) {
890 final NetworkRequest request = new NetworkRequest.Builder().build();
891 // We're okay throwing NFE here; caught by caller
892 request.networkCapabilities.setCapabilities(
893 BitUtils.unpackBits(Long.parseLong(netCapabilities)));
894 request.networkCapabilities.setTransportTypes(
895 BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
896 jobBuilder.setRequiredNetwork(request);
897 } else {
898 // Read legacy values
899 val = parser.getAttributeValue(null, "connectivity");
900 if (val != null) {
901 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
902 }
903 val = parser.getAttributeValue(null, "metered");
904 if (val != null) {
905 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
906 }
907 val = parser.getAttributeValue(null, "unmetered");
908 if (val != null) {
909 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
910 }
911 val = parser.getAttributeValue(null, "not-roaming");
912 if (val != null) {
913 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
914 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600915 }
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700916
Christopher Tate7060b042014-06-09 19:50:00 -0700917 val = parser.getAttributeValue(null, "idle");
918 if (val != null) {
919 jobBuilder.setRequiresDeviceIdle(true);
920 }
921 val = parser.getAttributeValue(null, "charging");
922 if (val != null) {
923 jobBuilder.setRequiresCharging(true);
924 }
925 }
926
927 /**
928 * Builds the back-off policy out of the params tag. These attributes may not exist, depending
929 * on whether the back-off was set when the job was first scheduled.
930 */
931 private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
932 String val = parser.getAttributeValue(null, "initial-backoff");
933 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100934 long initialBackoff = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700935 val = parser.getAttributeValue(null, "backoff-policy");
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100936 int backoffPolicy = Integer.parseInt(val); // Will throw NFE which we catch higher up.
Christopher Tate7060b042014-06-09 19:50:00 -0700937 jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
938 }
939 }
940
941 /**
Christopher Tate616541d2017-07-26 14:27:38 -0700942 * Extract a job's earliest/latest run time data from XML. These are returned in
943 * unadjusted UTC wall clock time, because we do not yet know whether the system
944 * clock is reliable for purposes of calculating deltas from 'now'.
945 *
946 * @param parser
947 * @return A Pair of timestamps in UTC wall-clock time. The first is the earliest
948 * time at which the job is to become runnable, and the second is the deadline at
949 * which it becomes overdue to execute.
950 * @throws NumberFormatException
951 */
952 private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
953 throws NumberFormatException {
954 String val;
955 // Pull out execution time data.
956 val = parser.getAttributeValue(null, "delay");
957 final long earliestRunTimeRtc = (val != null)
958 ? Long.parseLong(val)
959 : JobStatus.NO_EARLIEST_RUNTIME;
960 val = parser.getAttributeValue(null, "deadline");
961 final long latestRunTimeRtc = (val != null)
962 ? Long.parseLong(val)
963 : JobStatus.NO_LATEST_RUNTIME;
964 return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
965 }
966
967 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700968 * Convenience function to read out and convert deadline and delay from xml into elapsed real
969 * time.
970 * @return A {@link android.util.Pair}, where the first value is the earliest elapsed runtime
971 * and the second is the latest elapsed runtime.
972 */
973 private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
974 throws NumberFormatException {
975 // Pull out execution time data.
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700976 final long nowWallclock = sSystemClock.millis();
977 final long nowElapsed = sElapsedRealtimeClock.millis();
Christopher Tate7060b042014-06-09 19:50:00 -0700978
979 long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
980 long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
981 String val = parser.getAttributeValue(null, "deadline");
982 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100983 long latestRuntimeWallclock = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700984 long maxDelayElapsed =
985 Math.max(latestRuntimeWallclock - nowWallclock, 0);
986 latestRunTimeElapsed = nowElapsed + maxDelayElapsed;
987 }
988 val = parser.getAttributeValue(null, "delay");
989 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100990 long earliestRuntimeWallclock = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700991 long minDelayElapsed =
992 Math.max(earliestRuntimeWallclock - nowWallclock, 0);
993 earliestRunTimeElapsed = nowElapsed + minDelayElapsed;
994
995 }
996 return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
997 }
998 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800999
Dianne Hackborn6466c1c2017-06-13 10:33:19 -07001000 static final class JobSet {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001001 // Key is the getUid() originator of the jobs in each sheaf
1002 private SparseArray<ArraySet<JobStatus>> mJobs;
Suprabh Shukla106203b2017-11-02 21:23:44 -07001003 // Same data but with the key as getSourceUid() of the jobs in each sheaf
1004 private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001005
1006 public JobSet() {
1007 mJobs = new SparseArray<ArraySet<JobStatus>>();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001008 mJobsPerSourceUid = new SparseArray<>();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001009 }
1010
1011 public List<JobStatus> getJobsByUid(int uid) {
1012 ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
1013 ArraySet<JobStatus> jobs = mJobs.get(uid);
1014 if (jobs != null) {
1015 matchingJobs.addAll(jobs);
1016 }
1017 return matchingJobs;
1018 }
1019
1020 // By user, not by uid, so we need to traverse by key and check
1021 public List<JobStatus> getJobsByUser(int userId) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001022 final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
1023 for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
1024 if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
1025 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001026 if (jobs != null) {
1027 result.addAll(jobs);
1028 }
1029 }
1030 }
1031 return result;
1032 }
1033
1034 public boolean add(JobStatus job) {
1035 final int uid = job.getUid();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001036 final int sourceUid = job.getSourceUid();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001037 ArraySet<JobStatus> jobs = mJobs.get(uid);
1038 if (jobs == null) {
1039 jobs = new ArraySet<JobStatus>();
1040 mJobs.put(uid, jobs);
1041 }
Suprabh Shukla106203b2017-11-02 21:23:44 -07001042 ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
1043 if (jobsForSourceUid == null) {
1044 jobsForSourceUid = new ArraySet<>();
1045 mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
1046 }
1047 return jobs.add(job) && jobsForSourceUid.add(job);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001048 }
1049
1050 public boolean remove(JobStatus job) {
1051 final int uid = job.getUid();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001052 final ArraySet<JobStatus> jobs = mJobs.get(uid);
1053 final int sourceUid = job.getSourceUid();
1054 final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
Christopher Tate19a2f242018-01-24 16:43:33 -08001055 final boolean didRemove = jobs != null && jobs.remove(job);
1056 final boolean sourceRemove = jobsForSourceUid != null && jobsForSourceUid.remove(job);
1057 if (didRemove != sourceRemove) {
1058 Slog.wtf(TAG, "Job presence mismatch; caller=" + didRemove
1059 + " source=" + sourceRemove);
1060 }
1061 if (didRemove || sourceRemove) {
1062 // no more jobs for this uid? let the now-empty set objects be GC'd.
1063 if (jobs != null && jobs.size() == 0) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001064 mJobs.remove(uid);
1065 }
Christopher Tate19a2f242018-01-24 16:43:33 -08001066 if (jobsForSourceUid != null && jobsForSourceUid.size() == 0) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001067 mJobsPerSourceUid.remove(sourceUid);
1068 }
1069 return true;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001070 }
Suprabh Shukla106203b2017-11-02 21:23:44 -07001071 return false;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001072 }
1073
Suprabh Shukla106203b2017-11-02 21:23:44 -07001074 /**
1075 * Removes the jobs of all users not specified by the whitelist of user ids.
1076 * The jobs scheduled by non existent users will not be removed if they were
1077 */
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001078 public void removeJobsOfNonUsers(int[] whitelist) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001079 for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
1080 final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex));
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001081 if (!ArrayUtils.contains(whitelist, jobUserId)) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001082 mJobsPerSourceUid.removeAt(jobSetIndex);
1083 }
1084 }
1085 for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
1086 final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex);
1087 for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) {
1088 final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId();
1089 if (!ArrayUtils.contains(whitelist, jobUserId)) {
1090 jobsForUid.removeAt(jobIndex);
1091 }
1092 }
1093 if (jobsForUid.size() == 0) {
1094 mJobs.removeAt(jobSetIndex);
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001095 }
1096 }
1097 }
1098
Christopher Tate2f36fd62016-02-18 18:36:08 -08001099 public boolean contains(JobStatus job) {
1100 final int uid = job.getUid();
1101 ArraySet<JobStatus> jobs = mJobs.get(uid);
1102 return jobs != null && jobs.contains(job);
1103 }
1104
1105 public JobStatus get(int uid, int jobId) {
1106 ArraySet<JobStatus> jobs = mJobs.get(uid);
1107 if (jobs != null) {
1108 for (int i = jobs.size() - 1; i >= 0; i--) {
1109 JobStatus job = jobs.valueAt(i);
1110 if (job.getJobId() == jobId) {
1111 return job;
1112 }
1113 }
1114 }
1115 return null;
1116 }
1117
1118 // Inefficient; use only for testing
1119 public List<JobStatus> getAllJobs() {
1120 ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001121 for (int i = mJobs.size() - 1; i >= 0; i--) {
1122 ArraySet<JobStatus> jobs = mJobs.valueAt(i);
1123 if (jobs != null) {
1124 // Use a for loop over the ArraySet, so we don't need to make its
1125 // optional collection class iterator implementation or have to go
1126 // through a temporary array from toArray().
1127 for (int j = jobs.size() - 1; j >= 0; j--) {
1128 allJobs.add(jobs.valueAt(j));
1129 }
1130 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001131 }
1132 return allJobs;
1133 }
1134
1135 public void clear() {
1136 mJobs.clear();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001137 mJobsPerSourceUid.clear();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001138 }
1139
1140 public int size() {
1141 int total = 0;
1142 for (int i = mJobs.size() - 1; i >= 0; i--) {
1143 total += mJobs.valueAt(i).size();
1144 }
1145 return total;
1146 }
1147
1148 // We only want to count the jobs that this uid has scheduled on its own
1149 // behalf, not those that the app has scheduled on someone else's behalf.
1150 public int countJobsForUid(int uid) {
1151 int total = 0;
1152 ArraySet<JobStatus> jobs = mJobs.get(uid);
1153 if (jobs != null) {
1154 for (int i = jobs.size() - 1; i >= 0; i--) {
1155 JobStatus job = jobs.valueAt(i);
1156 if (job.getUid() == job.getSourceUid()) {
1157 total++;
1158 }
1159 }
1160 }
1161 return total;
1162 }
1163
1164 public void forEachJob(JobStatusFunctor functor) {
1165 for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
1166 ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
Christopher Tate41f3fe82017-12-08 17:52:09 -08001167 if (jobs != null) {
1168 for (int i = jobs.size() - 1; i >= 0; i--) {
1169 functor.process(jobs.valueAt(i));
1170 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001171 }
1172 }
1173 }
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001174
Suprabh Shukla106203b2017-11-02 21:23:44 -07001175 public void forEachJob(int callingUid, JobStatusFunctor functor) {
1176 ArraySet<JobStatus> jobs = mJobs.get(callingUid);
1177 if (jobs != null) {
1178 for (int i = jobs.size() - 1; i >= 0; i--) {
1179 functor.process(jobs.valueAt(i));
1180 }
1181 }
1182 }
1183
1184 public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
1185 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001186 if (jobs != null) {
1187 for (int i = jobs.size() - 1; i >= 0; i--) {
1188 functor.process(jobs.valueAt(i));
1189 }
1190 }
1191 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001192 }
Matthew Williams01ac45b2014-07-22 20:44:12 -07001193}