blob: 62553067996dcbceebf0ff1134737e6535cf9188 [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
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -070022import android.annotation.Nullable;
Dianne Hackborna47223f2017-03-30 13:49:13 -070023import android.app.ActivityManager;
24import android.app.IActivityManager;
Christopher Tate7060b042014-06-09 19:50:00 -070025import android.app.job.JobInfo;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070026import android.content.ComponentName;
Christopher Tate7060b042014-06-09 19:50:00 -070027import android.content.Context;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070028import android.net.NetworkRequest;
Christopher Tate7060b042014-06-09 19:50:00 -070029import android.os.Environment;
30import android.os.Handler;
31import android.os.PersistableBundle;
Makoto Onukie7b02982017-08-24 14:23:36 -070032import android.os.Process;
Dianne Hackborne17b4452018-01-10 13:15:40 -080033import android.os.SystemClock;
Christopher Tate7060b042014-06-09 19:50:00 -070034import android.os.UserHandle;
Matthew Williamsfa8e5082015-10-15 15:59:12 -070035import android.text.format.DateUtils;
Christopher Tate7060b042014-06-09 19:50:00 -070036import android.util.ArraySet;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070037import android.util.AtomicFile;
Christopher Tate7060b042014-06-09 19:50:00 -070038import android.util.Pair;
39import android.util.Slog;
Christopher Tate2f36fd62016-02-18 18:36:08 -080040import android.util.SparseArray;
Christopher Tate7060b042014-06-09 19:50:00 -070041import android.util.Xml;
42
43import com.android.internal.annotations.VisibleForTesting;
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -070044import com.android.internal.util.ArrayUtils;
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070045import com.android.internal.util.BitUtils;
Christopher Tate7060b042014-06-09 19:50:00 -070046import com.android.internal.util.FastXmlSerializer;
47import com.android.server.IoThread;
Christopher Tatea732f012017-10-26 17:26:53 -070048import com.android.server.LocalServices;
Makoto Onukie7b02982017-08-24 14:23:36 -070049import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
Christopher Tate7060b042014-06-09 19:50:00 -070050import com.android.server.job.controllers.JobStatus;
51
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070052import org.xmlpull.v1.XmlPullParser;
53import org.xmlpull.v1.XmlPullParserException;
54import org.xmlpull.v1.XmlSerializer;
55
Christopher Tate7060b042014-06-09 19:50:00 -070056import java.io.ByteArrayOutputStream;
57import java.io.File;
58import java.io.FileInputStream;
59import java.io.FileNotFoundException;
60import java.io.FileOutputStream;
61import java.io.IOException;
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +010062import java.nio.charset.StandardCharsets;
Christopher Tate7060b042014-06-09 19:50:00 -070063import java.util.ArrayList;
Christopher Tate7060b042014-06-09 19:50:00 -070064import java.util.List;
Shreyas Basarged09973b2015-12-16 18:10:05 +000065import java.util.Set;
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -070066import java.util.function.Consumer;
Suprabh Shukla7b21bb52018-01-26 17:19:28 -080067import java.util.function.Predicate;
Christopher Tate7060b042014-06-09 19:50:00 -070068
Christopher Tate7060b042014-06-09 19:50:00 -070069/**
Matthew Williams48a30db2014-09-23 13:39:36 -070070 * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
71 * reference, so none of the functions in this class should make a copy.
72 * Also handles read/write of persisted jobs.
Christopher Tate7060b042014-06-09 19:50:00 -070073 *
74 * Note on locking:
75 * All callers to this class must <strong>lock on the class object they are calling</strong>.
76 * This is important b/c {@link com.android.server.job.JobStore.WriteJobsMapToDiskRunnable}
77 * and {@link com.android.server.job.JobStore.ReadJobMapFromDiskRunnable} lock on that
78 * object.
Makoto Onuki15407842018-01-19 14:23:11 -080079 *
80 * Test:
81 * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
Christopher Tate7060b042014-06-09 19:50:00 -070082 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -070083public final class JobStore {
Christopher Tate7060b042014-06-09 19:50:00 -070084 private static final String TAG = "JobStore";
85 private static final boolean DEBUG = JobSchedulerService.DEBUG;
86
87 /** Threshold to adjust how often we want to write to the db. */
88 private static final int MAX_OPS_BEFORE_WRITE = 1;
Christopher Tate616541d2017-07-26 14:27:38 -070089
Dianne Hackborn33d31c52016-02-16 10:30:33 -080090 final Object mLock;
Suprabh Shukla7b21bb52018-01-26 17:19:28 -080091 final JobSet mJobSet; // per-caller-uid and per-source-uid tracking
Christopher Tate7060b042014-06-09 19:50:00 -070092 final Context mContext;
93
Christopher Tate616541d2017-07-26 14:27:38 -070094 // Bookkeeping around incorrect boot-time system clock
95 private final long mXmlTimestamp;
96 private boolean mRtcGood;
97
Christopher Tate7060b042014-06-09 19:50:00 -070098 private int mDirtyOperations;
99
100 private static final Object sSingletonLock = new Object();
101 private final AtomicFile mJobsFile;
102 /** Handler backed by IoThread for writing to disk. */
103 private final Handler mIoHandler = IoThread.getHandler();
104 private static JobStore sSingleton;
105
Makoto Onukie7b02982017-08-24 14:23:36 -0700106 private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
107
Christopher Tate7060b042014-06-09 19:50:00 -0700108 /** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
109 static JobStore initAndGet(JobSchedulerService jobManagerService) {
110 synchronized (sSingletonLock) {
111 if (sSingleton == null) {
112 sSingleton = new JobStore(jobManagerService.getContext(),
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800113 jobManagerService.getLock(), Environment.getDataDirectory());
Christopher Tate7060b042014-06-09 19:50:00 -0700114 }
115 return sSingleton;
116 }
117 }
118
Matthew Williams01ac45b2014-07-22 20:44:12 -0700119 /**
120 * @return A freshly initialized job store object, with no loaded jobs.
121 */
Christopher Tate7060b042014-06-09 19:50:00 -0700122 @VisibleForTesting
Matthew Williams01ac45b2014-07-22 20:44:12 -0700123 public static JobStore initAndGetForTesting(Context context, File dataDir) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800124 JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir);
Matthew Williams01ac45b2014-07-22 20:44:12 -0700125 jobStoreUnderTest.clear();
126 return jobStoreUnderTest;
Christopher Tate7060b042014-06-09 19:50:00 -0700127 }
128
Matthew Williams01ac45b2014-07-22 20:44:12 -0700129 /**
130 * Construct the instance of the job store. This results in a blocking read from disk.
131 */
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800132 private JobStore(Context context, Object lock, File dataDir) {
133 mLock = lock;
Christopher Tate7060b042014-06-09 19:50:00 -0700134 mContext = context;
135 mDirtyOperations = 0;
136
137 File systemDir = new File(dataDir, "system");
138 File jobDir = new File(systemDir, "job");
139 jobDir.mkdirs();
Dianne Hackborne17b4452018-01-10 13:15:40 -0800140 mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), "jobs");
Christopher Tate7060b042014-06-09 19:50:00 -0700141
Christopher Tate2f36fd62016-02-18 18:36:08 -0800142 mJobSet = new JobSet();
Christopher Tate7060b042014-06-09 19:50:00 -0700143
Christopher Tate616541d2017-07-26 14:27:38 -0700144 // If the current RTC is earlier than the timestamp on our persisted jobs file,
145 // we suspect that the RTC is uninitialized and so we cannot draw conclusions
146 // about persisted job scheduling.
147 //
148 // Note that if the persisted jobs file does not exist, we proceed with the
149 // assumption that the RTC is good. This is less work and is safe: if the
150 // clock updates to sanity then we'll be saving the persisted jobs file in that
151 // correct state, which is normal; or we'll wind up writing the jobs file with
152 // an incorrect historical timestamp. That's fine; at worst we'll reboot with
153 // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
154 // settle into normal operation.
155 mXmlTimestamp = mJobsFile.getLastModifiedTime();
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700156 mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
Christopher Tate616541d2017-07-26 14:27:38 -0700157
158 readJobMapFromDisk(mJobSet, mRtcGood);
159 }
160
161 public boolean jobTimesInflatedValid() {
162 return mRtcGood;
163 }
164
165 public boolean clockNowValidToInflate(long now) {
166 return now >= mXmlTimestamp;
167 }
168
169 /**
170 * Find all the jobs that were affected by RTC clock uncertainty at boot time. Returns
171 * parallel lists of the existing JobStatus objects and of new, equivalent JobStatus instances
172 * with now-corrected time bounds.
173 */
174 public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
175 final ArrayList<JobStatus> toRemove) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700176 final long elapsedNow = sElapsedRealtimeClock.millis();
Christopher Tatec4c05452019-03-05 16:02:51 -0800177 final IActivityManager am = ActivityManager.getService();
Christopher Tate616541d2017-07-26 14:27:38 -0700178
179 // Find the jobs that need to be fixed up, collecting them for post-iteration
180 // replacement with their new versions
181 forEachJob(job -> {
182 final Pair<Long, Long> utcTimes = job.getPersistedUtcTimes();
183 if (utcTimes != null) {
184 Pair<Long, Long> elapsedRuntimes =
185 convertRtcBoundsToElapsed(utcTimes, elapsedNow);
Christopher Tatec4c05452019-03-05 16:02:51 -0800186 JobStatus newJob = new JobStatus(job, job.getBaseHeartbeat(),
Christopher Tatea732f012017-10-26 17:26:53 -0700187 elapsedRuntimes.first, elapsedRuntimes.second,
Christopher Tatec4c05452019-03-05 16:02:51 -0800188 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
189 newJob.prepareLocked(am);
190 toAdd.add(newJob);
Christopher Tate616541d2017-07-26 14:27:38 -0700191 toRemove.add(job);
192 }
193 });
Christopher Tate7060b042014-06-09 19:50:00 -0700194 }
195
196 /**
197 * Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
198 * it will be replaced.
199 * @param jobStatus Job to add.
200 * @return Whether or not an equivalent JobStatus was replaced by this operation.
201 */
202 public boolean add(JobStatus jobStatus) {
203 boolean replaced = mJobSet.remove(jobStatus);
204 mJobSet.add(jobStatus);
205 if (jobStatus.isPersisted()) {
206 maybeWriteStatusToDiskAsync();
207 }
208 if (DEBUG) {
209 Slog.d(TAG, "Added job status to store: " + jobStatus);
210 }
211 return replaced;
212 }
213
Matthew Williams48a30db2014-09-23 13:39:36 -0700214 boolean containsJob(JobStatus jobStatus) {
215 return mJobSet.contains(jobStatus);
216 }
217
Christopher Tate7060b042014-06-09 19:50:00 -0700218 public int size() {
219 return mJobSet.size();
220 }
221
Makoto Onukie7b02982017-08-24 14:23:36 -0700222 public JobStorePersistStats getPersistStats() {
223 return mPersistInfo;
224 }
225
Christopher Tate2f36fd62016-02-18 18:36:08 -0800226 public int countJobsForUid(int uid) {
227 return mJobSet.countJobsForUid(uid);
228 }
229
Christopher Tate7060b042014-06-09 19:50:00 -0700230 /**
231 * Remove the provided job. Will also delete the job if it was persisted.
Shreyas Basarge73f10252016-02-11 17:06:13 +0000232 * @param writeBack If true, the job will be deleted (if it was persisted) immediately.
Christopher Tate7060b042014-06-09 19:50:00 -0700233 * @return Whether or not the job existed to be removed.
234 */
Shreyas Basarge73f10252016-02-11 17:06:13 +0000235 public boolean remove(JobStatus jobStatus, boolean writeBack) {
Christopher Tate7060b042014-06-09 19:50:00 -0700236 boolean removed = mJobSet.remove(jobStatus);
237 if (!removed) {
238 if (DEBUG) {
239 Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus);
240 }
241 return false;
242 }
Shreyas Basarge73f10252016-02-11 17:06:13 +0000243 if (writeBack && jobStatus.isPersisted()) {
Matthew Williams900c67f2014-07-09 12:46:53 -0700244 maybeWriteStatusToDiskAsync();
245 }
Christopher Tate7060b042014-06-09 19:50:00 -0700246 return removed;
247 }
248
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -0700249 /**
250 * Remove the jobs of users not specified in the whitelist.
251 * @param whitelist Array of User IDs whose jobs are not to be removed.
252 */
253 public void removeJobsOfNonUsers(int[] whitelist) {
254 mJobSet.removeJobsOfNonUsers(whitelist);
255 }
256
Christopher Tate7060b042014-06-09 19:50:00 -0700257 @VisibleForTesting
258 public void clear() {
259 mJobSet.clear();
260 maybeWriteStatusToDiskAsync();
261 }
262
Matthew Williams48a30db2014-09-23 13:39:36 -0700263 /**
264 * @param userHandle User for whom we are querying the list of jobs.
Suprabh Shukla106203b2017-11-02 21:23:44 -0700265 * @return A list of all the jobs scheduled for the provided user. Never null.
Matthew Williams48a30db2014-09-23 13:39:36 -0700266 */
Christopher Tate7060b042014-06-09 19:50:00 -0700267 public List<JobStatus> getJobsByUser(int userHandle) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800268 return mJobSet.getJobsByUser(userHandle);
Christopher Tate7060b042014-06-09 19:50:00 -0700269 }
270
271 /**
272 * @param uid Uid of the requesting app.
Matthew Williams48a30db2014-09-23 13:39:36 -0700273 * @return All JobStatus objects for a given uid from the master list. Never null.
Christopher Tate7060b042014-06-09 19:50:00 -0700274 */
275 public List<JobStatus> getJobsByUid(int uid) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800276 return mJobSet.getJobsByUid(uid);
Christopher Tate7060b042014-06-09 19:50:00 -0700277 }
278
279 /**
280 * @param uid Uid of the requesting app.
281 * @param jobId Job id, specified at schedule-time.
282 * @return the JobStatus that matches the provided uId and jobId, or null if none found.
283 */
284 public JobStatus getJobByUidAndJobId(int uid, int jobId) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800285 return mJobSet.get(uid, jobId);
Christopher Tate7060b042014-06-09 19:50:00 -0700286 }
287
288 /**
Christopher Tate2f36fd62016-02-18 18:36:08 -0800289 * Iterate over the set of all jobs, invoking the supplied functor on each. This is for
290 * customers who need to examine each job; we'd much rather not have to generate
291 * transient unified collections for them to iterate over and then discard, or creating
292 * iterators every time a client needs to perform a sweep.
Christopher Tate7060b042014-06-09 19:50:00 -0700293 */
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -0700294 public void forEachJob(Consumer<JobStatus> functor) {
295 mJobSet.forEachJob(null, functor);
Christopher Tate2f36fd62016-02-18 18:36:08 -0800296 }
297
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -0700298 public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
299 Consumer<JobStatus> functor) {
300 mJobSet.forEachJob(filterPredicate, functor);
301 }
302
303 public void forEachJob(int uid, Consumer<JobStatus> functor) {
Shreyas Basargecbf5ae92016-03-08 16:13:06 +0000304 mJobSet.forEachJob(uid, functor);
305 }
306
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -0700307 public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
Suprabh Shukla106203b2017-11-02 21:23:44 -0700308 mJobSet.forEachJobForSourceUid(sourceUid, functor);
309 }
310
Christopher Tate7060b042014-06-09 19:50:00 -0700311 /** Version of the db schema. */
312 private static final int JOBS_FILE_VERSION = 0;
313 /** Tag corresponds to constraints this job needs. */
314 private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints";
315 /** Tag corresponds to execution parameters. */
316 private static final String XML_TAG_PERIODIC = "periodic";
317 private static final String XML_TAG_ONEOFF = "one-off";
318 private static final String XML_TAG_EXTRAS = "extras";
319
320 /**
321 * Every time the state changes we write all the jobs in one swath, instead of trying to
322 * track incremental changes.
Christopher Tate7060b042014-06-09 19:50:00 -0700323 */
324 private void maybeWriteStatusToDiskAsync() {
325 mDirtyOperations++;
326 if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) {
327 if (DEBUG) {
328 Slog.v(TAG, "Writing jobs to disk.");
329 }
Christopher Tate616541d2017-07-26 14:27:38 -0700330 mIoHandler.removeCallbacks(mWriteRunnable);
331 mIoHandler.post(mWriteRunnable);
Christopher Tate7060b042014-06-09 19:50:00 -0700332 }
333 }
334
Matthew Williams01ac45b2014-07-22 20:44:12 -0700335 @VisibleForTesting
Christopher Tate616541d2017-07-26 14:27:38 -0700336 public void readJobMapFromDisk(JobSet jobSet, boolean rtcGood) {
337 new ReadJobMapFromDiskRunnable(jobSet, rtcGood).run();
Christopher Tate7060b042014-06-09 19:50:00 -0700338 }
339
340 /**
341 * Runnable that writes {@link #mJobSet} out to xml.
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800342 * NOTE: This Runnable locks on mLock
Christopher Tate7060b042014-06-09 19:50:00 -0700343 */
Christopher Tate616541d2017-07-26 14:27:38 -0700344 private final Runnable mWriteRunnable = new Runnable() {
Christopher Tate7060b042014-06-09 19:50:00 -0700345 @Override
346 public void run() {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700347 final long startElapsed = sElapsedRealtimeClock.millis();
Christopher Tate2f36fd62016-02-18 18:36:08 -0800348 final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800349 synchronized (mLock) {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800350 // Clone the jobs so we can release the lock before writing.
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -0700351 mJobSet.forEachJob(null, (job) -> {
352 if (job.isPersisted()) {
353 storeCopy.add(new JobStatus(job));
Shreyas Basarge7ef490f2015-12-03 16:45:22 +0000354 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800355 });
Christopher Tate7060b042014-06-09 19:50:00 -0700356 }
Christopher Tate2f36fd62016-02-18 18:36:08 -0800357 writeJobsMapImpl(storeCopy);
Christopher Tate616541d2017-07-26 14:27:38 -0700358 if (DEBUG) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700359 Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
Christopher Tate7060b042014-06-09 19:50:00 -0700360 - startElapsed) + "ms");
361 }
362 }
363
Matthew Williams49a85b62014-06-12 11:02:34 -0700364 private void writeJobsMapImpl(List<JobStatus> jobList) {
Makoto Onukie7b02982017-08-24 14:23:36 -0700365 int numJobs = 0;
366 int numSystemJobs = 0;
367 int numSyncJobs = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700368 try {
Dianne Hackborne17b4452018-01-10 13:15:40 -0800369 final long startTime = SystemClock.uptimeMillis();
Christopher Tate7060b042014-06-09 19:50:00 -0700370 ByteArrayOutputStream baos = new ByteArrayOutputStream();
371 XmlSerializer out = new FastXmlSerializer();
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +0100372 out.setOutput(baos, StandardCharsets.UTF_8.name());
Christopher Tate7060b042014-06-09 19:50:00 -0700373 out.startDocument(null, true);
374 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
375
376 out.startTag(null, "job-info");
377 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION));
Dianne Hackbornfdb19562014-07-11 16:03:36 -0700378 for (int i=0; i<jobList.size(); i++) {
379 JobStatus jobStatus = jobList.get(i);
Christopher Tate7060b042014-06-09 19:50:00 -0700380 if (DEBUG) {
381 Slog.d(TAG, "Saving job " + jobStatus.getJobId());
382 }
383 out.startTag(null, "job");
Shreyas Basarge5db09082016-01-07 13:38:29 +0000384 addAttributesToJobTag(out, jobStatus);
Christopher Tate7060b042014-06-09 19:50:00 -0700385 writeConstraintsToXml(out, jobStatus);
386 writeExecutionCriteriaToXml(out, jobStatus);
Dianne Hackborna47223f2017-03-30 13:49:13 -0700387 writeBundleToXml(jobStatus.getJob().getExtras(), out);
Christopher Tate7060b042014-06-09 19:50:00 -0700388 out.endTag(null, "job");
Makoto Onukie7b02982017-08-24 14:23:36 -0700389
390 numJobs++;
391 if (jobStatus.getUid() == Process.SYSTEM_UID) {
392 numSystemJobs++;
393 if (isSyncJob(jobStatus)) {
394 numSyncJobs++;
395 }
396 }
Christopher Tate7060b042014-06-09 19:50:00 -0700397 }
398 out.endTag(null, "job-info");
399 out.endDocument();
400
Christopher Tate616541d2017-07-26 14:27:38 -0700401 // Write out to disk in one fell swoop.
Dianne Hackborne17b4452018-01-10 13:15:40 -0800402 FileOutputStream fos = mJobsFile.startWrite(startTime);
Christopher Tate7060b042014-06-09 19:50:00 -0700403 fos.write(baos.toByteArray());
404 mJobsFile.finishWrite(fos);
405 mDirtyOperations = 0;
406 } catch (IOException e) {
407 if (DEBUG) {
408 Slog.v(TAG, "Error writing out job data.", e);
409 }
410 } catch (XmlPullParserException e) {
411 if (DEBUG) {
412 Slog.d(TAG, "Error persisting bundle.", e);
413 }
Makoto Onukie7b02982017-08-24 14:23:36 -0700414 } finally {
415 mPersistInfo.countAllJobsSaved = numJobs;
416 mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
417 mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
Christopher Tate7060b042014-06-09 19:50:00 -0700418 }
419 }
420
Shreyas Basarge5db09082016-01-07 13:38:29 +0000421 /** Write out a tag with data comprising the required fields and priority of this job and
422 * its client.
423 */
424 private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus)
Christopher Tate7060b042014-06-09 19:50:00 -0700425 throws IOException {
426 out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
427 out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
428 out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
Shreyas Basarge968ac752016-01-11 23:09:26 +0000429 if (jobStatus.getSourcePackageName() != null) {
430 out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
431 }
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800432 if (jobStatus.getSourceTag() != null) {
433 out.attribute(null, "sourceTag", jobStatus.getSourceTag());
434 }
Shreyas Basarge968ac752016-01-11 23:09:26 +0000435 out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
Christopher Tate7060b042014-06-09 19:50:00 -0700436 out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000437 out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
Jeff Sharkey1b6519b2016-04-28 15:33:18 -0600438 out.attribute(null, "flags", String.valueOf(jobStatus.getFlags()));
Makoto Onuki15407842018-01-19 14:23:11 -0800439 if (jobStatus.getInternalFlags() != 0) {
440 out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags()));
441 }
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700442
443 out.attribute(null, "lastSuccessfulRunTime",
444 String.valueOf(jobStatus.getLastSuccessfulRunTime()));
445 out.attribute(null, "lastFailedRunTime",
446 String.valueOf(jobStatus.getLastFailedRunTime()));
Christopher Tate7060b042014-06-09 19:50:00 -0700447 }
448
449 private void writeBundleToXml(PersistableBundle extras, XmlSerializer out)
450 throws IOException, XmlPullParserException {
451 out.startTag(null, XML_TAG_EXTRAS);
Shreyas Basarged09973b2015-12-16 18:10:05 +0000452 PersistableBundle extrasCopy = deepCopyBundle(extras, 10);
453 extrasCopy.saveToXml(out);
Christopher Tate7060b042014-06-09 19:50:00 -0700454 out.endTag(null, XML_TAG_EXTRAS);
455 }
Shreyas Basarged09973b2015-12-16 18:10:05 +0000456
457 private PersistableBundle deepCopyBundle(PersistableBundle bundle, int maxDepth) {
458 if (maxDepth <= 0) {
459 return null;
460 }
461 PersistableBundle copy = (PersistableBundle) bundle.clone();
462 Set<String> keySet = bundle.keySet();
463 for (String key: keySet) {
Shreyas Basarge5db09082016-01-07 13:38:29 +0000464 Object o = copy.get(key);
465 if (o instanceof PersistableBundle) {
466 PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1);
Shreyas Basarged09973b2015-12-16 18:10:05 +0000467 copy.putPersistableBundle(key, bCopy);
468 }
469 }
470 return copy;
471 }
472
Christopher Tate7060b042014-06-09 19:50:00 -0700473 /**
474 * Write out a tag with data identifying this job's constraints. If the constraint isn't here
475 * it doesn't apply.
476 */
477 private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
478 out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700479 if (jobStatus.hasConnectivityConstraint()) {
480 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
481 out.attribute(null, "net-capabilities", Long.toString(
482 BitUtils.packBits(network.networkCapabilities.getCapabilities())));
Pavel Maltsev3ec36ef2018-03-15 16:17:48 -0700483 out.attribute(null, "net-unwanted-capabilities", Long.toString(
484 BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities())));
485
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700486 out.attribute(null, "net-transport-types", Long.toString(
487 BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
Christopher Tate7060b042014-06-09 19:50:00 -0700488 }
489 if (jobStatus.hasIdleConstraint()) {
490 out.attribute(null, "idle", Boolean.toString(true));
491 }
492 if (jobStatus.hasChargingConstraint()) {
493 out.attribute(null, "charging", Boolean.toString(true));
494 }
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800495 if (jobStatus.hasBatteryNotLowConstraint()) {
496 out.attribute(null, "battery-not-low", Boolean.toString(true));
497 }
Christopher Tate7060b042014-06-09 19:50:00 -0700498 out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
499 }
500
501 private void writeExecutionCriteriaToXml(XmlSerializer out, JobStatus jobStatus)
502 throws IOException {
503 final JobInfo job = jobStatus.getJob();
504 if (jobStatus.getJob().isPeriodic()) {
505 out.startTag(null, XML_TAG_PERIODIC);
506 out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000507 out.attribute(null, "flex", Long.toString(job.getFlexMillis()));
Christopher Tate7060b042014-06-09 19:50:00 -0700508 } else {
509 out.startTag(null, XML_TAG_ONEOFF);
510 }
511
Christopher Tate616541d2017-07-26 14:27:38 -0700512 // If we still have the persisted times, we need to record those directly because
513 // we haven't yet been able to calculate the usual elapsed-timebase bounds
514 // correctly due to wall-clock uncertainty.
515 Pair <Long, Long> utcJobTimes = jobStatus.getPersistedUtcTimes();
516 if (DEBUG && utcJobTimes != null) {
517 Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
518 }
519
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700520 final long nowRTC = sSystemClock.millis();
521 final long nowElapsed = sElapsedRealtimeClock.millis();
Christopher Tate7060b042014-06-09 19:50:00 -0700522 if (jobStatus.hasDeadlineConstraint()) {
523 // Wall clock deadline.
Christopher Tate616541d2017-07-26 14:27:38 -0700524 final long deadlineWallclock = (utcJobTimes == null)
525 ? nowRTC + (jobStatus.getLatestRunTimeElapsed() - nowElapsed)
526 : utcJobTimes.second;
Christopher Tate7060b042014-06-09 19:50:00 -0700527 out.attribute(null, "deadline", Long.toString(deadlineWallclock));
528 }
529 if (jobStatus.hasTimingDelayConstraint()) {
Christopher Tate616541d2017-07-26 14:27:38 -0700530 final long delayWallclock = (utcJobTimes == null)
531 ? nowRTC + (jobStatus.getEarliestRunTime() - nowElapsed)
532 : utcJobTimes.first;
Christopher Tate7060b042014-06-09 19:50:00 -0700533 out.attribute(null, "delay", Long.toString(delayWallclock));
534 }
535
536 // Only write out back-off policy if it differs from the default.
537 // This also helps the case where the job is idle -> these aren't allowed to specify
538 // back-off.
539 if (jobStatus.getJob().getInitialBackoffMillis() != JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS
540 || jobStatus.getJob().getBackoffPolicy() != JobInfo.DEFAULT_BACKOFF_POLICY) {
541 out.attribute(null, "backoff-policy", Integer.toString(job.getBackoffPolicy()));
542 out.attribute(null, "initial-backoff", Long.toString(job.getInitialBackoffMillis()));
543 }
544 if (job.isPeriodic()) {
545 out.endTag(null, XML_TAG_PERIODIC);
546 } else {
547 out.endTag(null, XML_TAG_ONEOFF);
548 }
549 }
Christopher Tate616541d2017-07-26 14:27:38 -0700550 };
551
552 /**
553 * Translate the supplied RTC times to the elapsed timebase, with clamping appropriate
554 * to interpreting them as a job's delay + deadline times for alarm-setting purposes.
555 * @param rtcTimes a Pair<Long, Long> in which {@code first} is the "delay" earliest
556 * allowable runtime for the job, and {@code second} is the "deadline" time at which
557 * the job becomes overdue.
558 */
559 private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
560 long nowElapsed) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700561 final long nowWallclock = sSystemClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700562 final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
563 ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
564 : JobStatus.NO_EARLIEST_RUNTIME;
565 final long latest = (rtcTimes.second < JobStatus.NO_LATEST_RUNTIME)
566 ? nowElapsed + Math.max(rtcTimes.second - nowWallclock, 0)
567 : JobStatus.NO_LATEST_RUNTIME;
568 return Pair.create(earliest, latest);
Christopher Tate7060b042014-06-09 19:50:00 -0700569 }
570
Makoto Onukie7b02982017-08-24 14:23:36 -0700571 private static boolean isSyncJob(JobStatus status) {
572 return com.android.server.content.SyncJobService.class.getName()
573 .equals(status.getServiceComponent().getClassName());
574 }
575
Christopher Tate7060b042014-06-09 19:50:00 -0700576 /**
Matthew Williams01ac45b2014-07-22 20:44:12 -0700577 * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
578 * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
Christopher Tate7060b042014-06-09 19:50:00 -0700579 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -0700580 private final class ReadJobMapFromDiskRunnable implements Runnable {
Christopher Tate2f36fd62016-02-18 18:36:08 -0800581 private final JobSet jobSet;
Christopher Tate616541d2017-07-26 14:27:38 -0700582 private final boolean rtcGood;
Matthew Williams01ac45b2014-07-22 20:44:12 -0700583
584 /**
585 * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
586 * so that after disk read we can populate it directly.
587 */
Christopher Tate616541d2017-07-26 14:27:38 -0700588 ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood) {
Matthew Williams01ac45b2014-07-22 20:44:12 -0700589 this.jobSet = jobSet;
Christopher Tate616541d2017-07-26 14:27:38 -0700590 this.rtcGood = rtcIsGood;
Christopher Tate7060b042014-06-09 19:50:00 -0700591 }
592
593 @Override
594 public void run() {
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700595 int numJobs = 0;
Makoto Onukie7b02982017-08-24 14:23:36 -0700596 int numSystemJobs = 0;
597 int numSyncJobs = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700598 try {
599 List<JobStatus> jobs;
600 FileInputStream fis = mJobsFile.openRead();
Dianne Hackborn33d31c52016-02-16 10:30:33 -0800601 synchronized (mLock) {
Christopher Tate616541d2017-07-26 14:27:38 -0700602 jobs = readJobMapImpl(fis, rtcGood);
Matthew Williams01ac45b2014-07-22 20:44:12 -0700603 if (jobs != null) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700604 long now = sElapsedRealtimeClock.millis();
Dianne Hackborna47223f2017-03-30 13:49:13 -0700605 IActivityManager am = ActivityManager.getService();
Matthew Williams01ac45b2014-07-22 20:44:12 -0700606 for (int i=0; i<jobs.size(); i++) {
Dianne Hackborna47223f2017-03-30 13:49:13 -0700607 JobStatus js = jobs.get(i);
Dianne Hackborn7da13d72017-04-04 17:17:35 -0700608 js.prepareLocked(am);
Dianne Hackbornbfc23312017-05-11 11:53:24 -0700609 js.enqueueTime = now;
Dianne Hackborna47223f2017-03-30 13:49:13 -0700610 this.jobSet.add(js);
Makoto Onukie7b02982017-08-24 14:23:36 -0700611
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700612 numJobs++;
Makoto Onukie7b02982017-08-24 14:23:36 -0700613 if (js.getUid() == Process.SYSTEM_UID) {
614 numSystemJobs++;
615 if (isSyncJob(js)) {
616 numSyncJobs++;
617 }
618 }
Matthew Williams01ac45b2014-07-22 20:44:12 -0700619 }
620 }
Christopher Tate7060b042014-06-09 19:50:00 -0700621 }
622 fis.close();
Christopher Tate7060b042014-06-09 19:50:00 -0700623 } catch (FileNotFoundException e) {
Christopher Tate616541d2017-07-26 14:27:38 -0700624 if (DEBUG) {
Christopher Tate7060b042014-06-09 19:50:00 -0700625 Slog.d(TAG, "Could not find jobs file, probably there was nothing to load.");
626 }
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700627 } catch (XmlPullParserException | IOException e) {
628 Slog.wtf(TAG, "Error jobstore xml.", e);
Makoto Onukie7b02982017-08-24 14:23:36 -0700629 } finally {
630 if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
631 mPersistInfo.countAllJobsLoaded = numJobs;
632 mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
633 mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
634 }
Christopher Tate7060b042014-06-09 19:50:00 -0700635 }
Makoto Onukidd4b14f2017-08-17 14:03:48 -0700636 Slog.i(TAG, "Read " + numJobs + " jobs");
Christopher Tate7060b042014-06-09 19:50:00 -0700637 }
638
Christopher Tate616541d2017-07-26 14:27:38 -0700639 private List<JobStatus> readJobMapImpl(FileInputStream fis, boolean rtcIsGood)
Matthew Williams900c67f2014-07-09 12:46:53 -0700640 throws XmlPullParserException, IOException {
Christopher Tate7060b042014-06-09 19:50:00 -0700641 XmlPullParser parser = Xml.newPullParser();
Wojciech Staszkiewicz4f117542015-05-08 14:58:46 +0100642 parser.setInput(fis, StandardCharsets.UTF_8.name());
Christopher Tate7060b042014-06-09 19:50:00 -0700643
644 int eventType = parser.getEventType();
645 while (eventType != XmlPullParser.START_TAG &&
646 eventType != XmlPullParser.END_DOCUMENT) {
647 eventType = parser.next();
riddle_hsu98bfb342015-07-30 21:52:58 +0800648 Slog.d(TAG, "Start tag: " + parser.getName());
Christopher Tate7060b042014-06-09 19:50:00 -0700649 }
650 if (eventType == XmlPullParser.END_DOCUMENT) {
651 if (DEBUG) {
652 Slog.d(TAG, "No persisted jobs.");
653 }
654 return null;
655 }
656
657 String tagName = parser.getName();
658 if ("job-info".equals(tagName)) {
659 final List<JobStatus> jobs = new ArrayList<JobStatus>();
660 // Read in version info.
661 try {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100662 int version = Integer.parseInt(parser.getAttributeValue(null, "version"));
Christopher Tate7060b042014-06-09 19:50:00 -0700663 if (version != JOBS_FILE_VERSION) {
664 Slog.d(TAG, "Invalid version number, aborting jobs file read.");
665 return null;
666 }
667 } catch (NumberFormatException e) {
668 Slog.e(TAG, "Invalid version number, aborting jobs file read.");
669 return null;
670 }
671 eventType = parser.next();
672 do {
673 // Read each <job/>
674 if (eventType == XmlPullParser.START_TAG) {
675 tagName = parser.getName();
676 // Start reading job.
677 if ("job".equals(tagName)) {
Christopher Tate616541d2017-07-26 14:27:38 -0700678 JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser);
Christopher Tate7060b042014-06-09 19:50:00 -0700679 if (persistedJob != null) {
680 if (DEBUG) {
681 Slog.d(TAG, "Read out " + persistedJob);
682 }
683 jobs.add(persistedJob);
684 } else {
685 Slog.d(TAG, "Error reading job from file.");
686 }
687 }
688 }
689 eventType = parser.next();
690 } while (eventType != XmlPullParser.END_DOCUMENT);
691 return jobs;
692 }
693 return null;
694 }
695
696 /**
697 * @param parser Xml parser at the beginning of a "<job/>" tag. The next "parser.next()" call
698 * will take the parser into the body of the job tag.
699 * @return Newly instantiated job holding all the information we just read out of the xml tag.
700 */
Christopher Tate616541d2017-07-26 14:27:38 -0700701 private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser)
702 throws XmlPullParserException, IOException {
Christopher Tate7060b042014-06-09 19:50:00 -0700703 JobInfo.Builder jobBuilder;
Shreyas Basarge8e64e2e2016-02-12 15:49:31 +0000704 int uid, sourceUserId;
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700705 long lastSuccessfulRunTime;
706 long lastFailedRunTime;
Makoto Onuki15407842018-01-19 14:23:11 -0800707 int internalFlags = 0;
Christopher Tate7060b042014-06-09 19:50:00 -0700708
Shreyas Basarge5db09082016-01-07 13:38:29 +0000709 // Read out job identifier attributes and priority.
Christopher Tate7060b042014-06-09 19:50:00 -0700710 try {
711 jobBuilder = buildBuilderFromXml(parser);
Matthew Williamsd1c06752014-08-22 14:15:28 -0700712 jobBuilder.setPersisted(true);
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100713 uid = Integer.parseInt(parser.getAttributeValue(null, "uid"));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000714
Shreyas Basarge968ac752016-01-11 23:09:26 +0000715 String val = parser.getAttributeValue(null, "priority");
716 if (val != null) {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100717 jobBuilder.setPriority(Integer.parseInt(val));
Shreyas Basarge5db09082016-01-07 13:38:29 +0000718 }
Jeff Sharkey1b6519b2016-04-28 15:33:18 -0600719 val = parser.getAttributeValue(null, "flags");
720 if (val != null) {
721 jobBuilder.setFlags(Integer.parseInt(val));
722 }
Makoto Onuki15407842018-01-19 14:23:11 -0800723 val = parser.getAttributeValue(null, "internalFlags");
724 if (val != null) {
725 internalFlags = Integer.parseInt(val);
726 }
Shreyas Basarge968ac752016-01-11 23:09:26 +0000727 val = parser.getAttributeValue(null, "sourceUserId");
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100728 sourceUserId = val == null ? -1 : Integer.parseInt(val);
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700729
730 val = parser.getAttributeValue(null, "lastSuccessfulRunTime");
731 lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val);
732
733 val = parser.getAttributeValue(null, "lastFailedRunTime");
734 lastFailedRunTime = val == null ? 0 : Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700735 } catch (NumberFormatException e) {
736 Slog.e(TAG, "Error parsing job's required fields, skipping");
737 return null;
738 }
739
Christopher Tate0213ace02016-02-24 14:18:35 -0800740 String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
Dianne Hackborn1085ff62016-02-23 17:04:58 -0800741 final String sourceTag = parser.getAttributeValue(null, "sourceTag");
742
Christopher Tate7060b042014-06-09 19:50:00 -0700743 int eventType;
744 // Read out constraints tag.
745 do {
746 eventType = parser.next();
747 } while (eventType == XmlPullParser.TEXT); // Push through to next START_TAG.
748
749 if (!(eventType == XmlPullParser.START_TAG &&
750 XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) {
751 // Expecting a <constraints> start tag.
752 return null;
753 }
754 try {
755 buildConstraintsFromXml(jobBuilder, parser);
756 } catch (NumberFormatException e) {
757 Slog.d(TAG, "Error reading constraints, skipping.");
758 return null;
759 }
760 parser.next(); // Consume </constraints>
761
762 // Read out execution parameters tag.
763 do {
764 eventType = parser.next();
765 } while (eventType == XmlPullParser.TEXT);
766 if (eventType != XmlPullParser.START_TAG) {
767 return null;
768 }
769
Christopher Tate616541d2017-07-26 14:27:38 -0700770 // Tuple of (earliest runtime, latest runtime) in UTC.
771 final Pair<Long, Long> rtcRuntimes;
Christopher Tate7060b042014-06-09 19:50:00 -0700772 try {
Christopher Tate616541d2017-07-26 14:27:38 -0700773 rtcRuntimes = buildRtcExecutionTimesFromXml(parser);
Christopher Tate7060b042014-06-09 19:50:00 -0700774 } catch (NumberFormatException e) {
775 if (DEBUG) {
776 Slog.d(TAG, "Error parsing execution time parameters, skipping.");
777 }
778 return null;
779 }
780
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700781 final long elapsedNow = sElapsedRealtimeClock.millis();
Christopher Tate616541d2017-07-26 14:27:38 -0700782 Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
783
Christopher Tate7060b042014-06-09 19:50:00 -0700784 if (XML_TAG_PERIODIC.equals(parser.getName())) {
785 try {
786 String val = parser.getAttributeValue(null, "period");
Tobias Thierer28532d02016-04-21 14:52:10 +0100787 final long periodMillis = Long.parseLong(val);
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000788 val = parser.getAttributeValue(null, "flex");
789 final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
Shreyas Basarge8e64e2e2016-02-12 15:49:31 +0000790 jobBuilder.setPeriodic(periodMillis, flexMillis);
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000791 // As a sanity check, cap the recreated run time to be no later than flex+period
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700792 // from now. This is the latest the periodic could be pushed out. This could
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000793 // happen if the periodic ran early (at flex time before period), and then the
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700794 // device rebooted.
Shreyas Basarge89ee6182015-12-17 15:16:36 +0000795 if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
796 final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
797 + periodMillis;
798 final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
799 - flexMillis;
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700800 Slog.w(TAG,
801 String.format("Periodic job for uid='%d' persisted run-time is" +
802 " too big [%s, %s]. Clamping to [%s,%s]",
803 uid,
804 DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000),
805 DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000),
806 DateUtils.formatElapsedTime(
807 clampedEarlyRuntimeElapsed / 1000),
808 DateUtils.formatElapsedTime(
809 clampedLateRuntimeElapsed / 1000))
810 );
811 elapsedRuntimes =
812 Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed);
813 }
Christopher Tate7060b042014-06-09 19:50:00 -0700814 } catch (NumberFormatException e) {
815 Slog.d(TAG, "Error reading periodic execution criteria, skipping.");
816 return null;
817 }
818 } else if (XML_TAG_ONEOFF.equals(parser.getName())) {
819 try {
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700820 if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) {
821 jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow);
Christopher Tate7060b042014-06-09 19:50:00 -0700822 }
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700823 if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) {
Christopher Tate7060b042014-06-09 19:50:00 -0700824 jobBuilder.setOverrideDeadline(
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700825 elapsedRuntimes.second - elapsedNow);
Christopher Tate7060b042014-06-09 19:50:00 -0700826 }
827 } catch (NumberFormatException e) {
828 Slog.d(TAG, "Error reading job execution criteria, skipping.");
829 return null;
830 }
831 } else {
832 if (DEBUG) {
833 Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName());
834 }
835 // Expecting a parameters start tag.
836 return null;
837 }
838 maybeBuildBackoffPolicyFromXml(jobBuilder, parser);
839
840 parser.nextTag(); // Consume parameters end tag.
841
842 // Read out extras Bundle.
843 do {
844 eventType = parser.next();
845 } while (eventType == XmlPullParser.TEXT);
Matthew Williamsfa8e5082015-10-15 15:59:12 -0700846 if (!(eventType == XmlPullParser.START_TAG
847 && XML_TAG_EXTRAS.equals(parser.getName()))) {
Christopher Tate7060b042014-06-09 19:50:00 -0700848 if (DEBUG) {
849 Slog.d(TAG, "Error reading extras, skipping.");
850 }
851 return null;
852 }
853
854 PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
855 jobBuilder.setExtras(extras);
856 parser.nextTag(); // Consume </extras>
857
Christopher Tate0213ace02016-02-24 14:18:35 -0800858 // Migrate sync jobs forward from earlier, incomplete representation
859 if ("android".equals(sourcePackageName)
860 && extras != null
861 && extras.getBoolean("SyncManagerJob", false)) {
862 sourcePackageName = extras.getString("owningPackage", sourcePackageName);
863 if (DEBUG) {
864 Slog.i(TAG, "Fixing up sync job source package name from 'android' to '"
865 + sourcePackageName + "'");
866 }
867 }
868
869 // And now we're done
Christopher Tatea732f012017-10-26 17:26:53 -0700870 JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
871 final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
872 sourceUserId, elapsedNow);
873 long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
Shreyas Basarge968ac752016-01-11 23:09:26 +0000874 JobStatus js = new JobStatus(
Christopher Tatea732f012017-10-26 17:26:53 -0700875 jobBuilder.build(), uid, sourcePackageName, sourceUserId,
876 appBucket, currentHeartbeat, sourceTag,
Makoto Onukiab8a67f2017-06-20 12:20:34 -0700877 elapsedRuntimes.first, elapsedRuntimes.second,
Christopher Tate616541d2017-07-26 14:27:38 -0700878 lastSuccessfulRunTime, lastFailedRunTime,
Makoto Onuki15407842018-01-19 14:23:11 -0800879 (rtcIsGood) ? null : rtcRuntimes, internalFlags);
Shreyas Basarge968ac752016-01-11 23:09:26 +0000880 return js;
Christopher Tate7060b042014-06-09 19:50:00 -0700881 }
882
883 private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
884 // Pull out required fields from <job> attributes.
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100885 int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid"));
Christopher Tate7060b042014-06-09 19:50:00 -0700886 String packageName = parser.getAttributeValue(null, "package");
887 String className = parser.getAttributeValue(null, "class");
888 ComponentName cname = new ComponentName(packageName, className);
889
890 return new JobInfo.Builder(jobId, cname);
891 }
892
893 private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700894 String val;
895
896 final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
Pavel Maltsev3ec36ef2018-03-15 16:17:48 -0700897 final String netUnwantedCapabilities = parser.getAttributeValue(
898 null, "net-unwanted-capabilities");
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700899 final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
900 if (netCapabilities != null && netTransportTypes != null) {
901 final NetworkRequest request = new NetworkRequest.Builder().build();
Pavel Maltsev3ec36ef2018-03-15 16:17:48 -0700902 final long unwantedCapabilities = netUnwantedCapabilities != null
903 ? Long.parseLong(netUnwantedCapabilities)
904 : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities());
905
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700906 // We're okay throwing NFE here; caught by caller
907 request.networkCapabilities.setCapabilities(
Pavel Maltsev3ec36ef2018-03-15 16:17:48 -0700908 BitUtils.unpackBits(Long.parseLong(netCapabilities)),
909 BitUtils.unpackBits(unwantedCapabilities));
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700910 request.networkCapabilities.setTransportTypes(
911 BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
912 jobBuilder.setRequiredNetwork(request);
913 } else {
914 // Read legacy values
915 val = parser.getAttributeValue(null, "connectivity");
916 if (val != null) {
917 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
918 }
919 val = parser.getAttributeValue(null, "metered");
920 if (val != null) {
921 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
922 }
923 val = parser.getAttributeValue(null, "unmetered");
924 if (val != null) {
925 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
926 }
927 val = parser.getAttributeValue(null, "not-roaming");
928 if (val != null) {
929 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
930 }
Jeff Sharkeyf07c7b92016-04-22 09:50:16 -0600931 }
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700932
Christopher Tate7060b042014-06-09 19:50:00 -0700933 val = parser.getAttributeValue(null, "idle");
934 if (val != null) {
935 jobBuilder.setRequiresDeviceIdle(true);
936 }
937 val = parser.getAttributeValue(null, "charging");
938 if (val != null) {
939 jobBuilder.setRequiresCharging(true);
940 }
941 }
942
943 /**
944 * Builds the back-off policy out of the params tag. These attributes may not exist, depending
945 * on whether the back-off was set when the job was first scheduled.
946 */
947 private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
948 String val = parser.getAttributeValue(null, "initial-backoff");
949 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100950 long initialBackoff = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -0700951 val = parser.getAttributeValue(null, "backoff-policy");
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100952 int backoffPolicy = Integer.parseInt(val); // Will throw NFE which we catch higher up.
Christopher Tate7060b042014-06-09 19:50:00 -0700953 jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy);
954 }
955 }
956
957 /**
Christopher Tate616541d2017-07-26 14:27:38 -0700958 * Extract a job's earliest/latest run time data from XML. These are returned in
959 * unadjusted UTC wall clock time, because we do not yet know whether the system
960 * clock is reliable for purposes of calculating deltas from 'now'.
961 *
962 * @param parser
963 * @return A Pair of timestamps in UTC wall-clock time. The first is the earliest
964 * time at which the job is to become runnable, and the second is the deadline at
965 * which it becomes overdue to execute.
966 * @throws NumberFormatException
967 */
968 private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser)
969 throws NumberFormatException {
970 String val;
971 // Pull out execution time data.
972 val = parser.getAttributeValue(null, "delay");
973 final long earliestRunTimeRtc = (val != null)
974 ? Long.parseLong(val)
975 : JobStatus.NO_EARLIEST_RUNTIME;
976 val = parser.getAttributeValue(null, "deadline");
977 final long latestRunTimeRtc = (val != null)
978 ? Long.parseLong(val)
979 : JobStatus.NO_LATEST_RUNTIME;
980 return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
981 }
982
983 /**
Christopher Tate7060b042014-06-09 19:50:00 -0700984 * Convenience function to read out and convert deadline and delay from xml into elapsed real
985 * time.
986 * @return A {@link android.util.Pair}, where the first value is the earliest elapsed runtime
987 * and the second is the latest elapsed runtime.
988 */
989 private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
990 throws NumberFormatException {
991 // Pull out execution time data.
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700992 final long nowWallclock = sSystemClock.millis();
993 final long nowElapsed = sElapsedRealtimeClock.millis();
Christopher Tate7060b042014-06-09 19:50:00 -0700994
995 long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
996 long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
997 String val = parser.getAttributeValue(null, "deadline");
998 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +0100999 long latestRuntimeWallclock = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -07001000 long maxDelayElapsed =
1001 Math.max(latestRuntimeWallclock - nowWallclock, 0);
1002 latestRunTimeElapsed = nowElapsed + maxDelayElapsed;
1003 }
1004 val = parser.getAttributeValue(null, "delay");
1005 if (val != null) {
Tobias Thierer28532d02016-04-21 14:52:10 +01001006 long earliestRuntimeWallclock = Long.parseLong(val);
Christopher Tate7060b042014-06-09 19:50:00 -07001007 long minDelayElapsed =
1008 Math.max(earliestRuntimeWallclock - nowWallclock, 0);
1009 earliestRunTimeElapsed = nowElapsed + minDelayElapsed;
1010
1011 }
1012 return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
1013 }
1014 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001015
Dianne Hackborn6466c1c2017-06-13 10:33:19 -07001016 static final class JobSet {
Suprabh Shukla7b21bb52018-01-26 17:19:28 -08001017 @VisibleForTesting // Key is the getUid() originator of the jobs in each sheaf
1018 final SparseArray<ArraySet<JobStatus>> mJobs;
1019
1020 @VisibleForTesting // Same data but with the key as getSourceUid() of the jobs in each sheaf
1021 final SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001022
1023 public JobSet() {
1024 mJobs = new SparseArray<ArraySet<JobStatus>>();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001025 mJobsPerSourceUid = new SparseArray<>();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001026 }
1027
1028 public List<JobStatus> getJobsByUid(int uid) {
1029 ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
1030 ArraySet<JobStatus> jobs = mJobs.get(uid);
1031 if (jobs != null) {
1032 matchingJobs.addAll(jobs);
1033 }
1034 return matchingJobs;
1035 }
1036
1037 // By user, not by uid, so we need to traverse by key and check
1038 public List<JobStatus> getJobsByUser(int userId) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001039 final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
1040 for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
1041 if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
1042 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
Christopher Tate2f36fd62016-02-18 18:36:08 -08001043 if (jobs != null) {
1044 result.addAll(jobs);
1045 }
1046 }
1047 }
1048 return result;
1049 }
1050
1051 public boolean add(JobStatus job) {
1052 final int uid = job.getUid();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001053 final int sourceUid = job.getSourceUid();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001054 ArraySet<JobStatus> jobs = mJobs.get(uid);
1055 if (jobs == null) {
1056 jobs = new ArraySet<JobStatus>();
1057 mJobs.put(uid, jobs);
1058 }
Suprabh Shukla106203b2017-11-02 21:23:44 -07001059 ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
1060 if (jobsForSourceUid == null) {
1061 jobsForSourceUid = new ArraySet<>();
1062 mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
1063 }
Suprabh Shukla7b21bb52018-01-26 17:19:28 -08001064 final boolean added = jobs.add(job);
1065 final boolean addedInSource = jobsForSourceUid.add(job);
1066 if (added != addedInSource) {
1067 Slog.wtf(TAG, "mJobs and mJobsPerSourceUid mismatch; caller= " + added
1068 + " source= " + addedInSource);
1069 }
1070 return added || addedInSource;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001071 }
1072
1073 public boolean remove(JobStatus job) {
1074 final int uid = job.getUid();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001075 final ArraySet<JobStatus> jobs = mJobs.get(uid);
1076 final int sourceUid = job.getSourceUid();
1077 final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
Christopher Tate19a2f242018-01-24 16:43:33 -08001078 final boolean didRemove = jobs != null && jobs.remove(job);
1079 final boolean sourceRemove = jobsForSourceUid != null && jobsForSourceUid.remove(job);
1080 if (didRemove != sourceRemove) {
1081 Slog.wtf(TAG, "Job presence mismatch; caller=" + didRemove
1082 + " source=" + sourceRemove);
1083 }
1084 if (didRemove || sourceRemove) {
1085 // no more jobs for this uid? let the now-empty set objects be GC'd.
1086 if (jobs != null && jobs.size() == 0) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001087 mJobs.remove(uid);
1088 }
Christopher Tate19a2f242018-01-24 16:43:33 -08001089 if (jobsForSourceUid != null && jobsForSourceUid.size() == 0) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001090 mJobsPerSourceUid.remove(sourceUid);
1091 }
1092 return true;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001093 }
Suprabh Shukla106203b2017-11-02 21:23:44 -07001094 return false;
Christopher Tate2f36fd62016-02-18 18:36:08 -08001095 }
1096
Suprabh Shukla106203b2017-11-02 21:23:44 -07001097 /**
1098 * Removes the jobs of all users not specified by the whitelist of user ids.
Suprabh Shukla7b21bb52018-01-26 17:19:28 -08001099 * This will remove jobs scheduled *by* non-existent users as well as jobs scheduled *for*
1100 * non-existent users
Suprabh Shukla106203b2017-11-02 21:23:44 -07001101 */
Suprabh Shukla7b21bb52018-01-26 17:19:28 -08001102 public void removeJobsOfNonUsers(final int[] whitelist) {
1103 final Predicate<JobStatus> noSourceUser =
1104 job -> !ArrayUtils.contains(whitelist, job.getSourceUserId());
1105 final Predicate<JobStatus> noCallingUser =
1106 job -> !ArrayUtils.contains(whitelist, job.getUserId());
1107 removeAll(noSourceUser.or(noCallingUser));
1108 }
1109
1110 private void removeAll(Predicate<JobStatus> predicate) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001111 for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
Suprabh Shukla7b21bb52018-01-26 17:19:28 -08001112 final ArraySet<JobStatus> jobs = mJobs.valueAt(jobSetIndex);
1113 for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
1114 if (predicate.test(jobs.valueAt(jobIndex))) {
1115 jobs.removeAt(jobIndex);
Suprabh Shukla106203b2017-11-02 21:23:44 -07001116 }
1117 }
Suprabh Shukla7b21bb52018-01-26 17:19:28 -08001118 if (jobs.size() == 0) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001119 mJobs.removeAt(jobSetIndex);
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001120 }
1121 }
Suprabh Shukla7b21bb52018-01-26 17:19:28 -08001122 for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
1123 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(jobSetIndex);
1124 for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
1125 if (predicate.test(jobs.valueAt(jobIndex))) {
1126 jobs.removeAt(jobIndex);
1127 }
1128 }
1129 if (jobs.size() == 0) {
1130 mJobsPerSourceUid.removeAt(jobSetIndex);
1131 }
1132 }
Michael Wachenschwanz3f2b6552017-05-15 13:53:09 -07001133 }
1134
Christopher Tate2f36fd62016-02-18 18:36:08 -08001135 public boolean contains(JobStatus job) {
1136 final int uid = job.getUid();
1137 ArraySet<JobStatus> jobs = mJobs.get(uid);
1138 return jobs != null && jobs.contains(job);
1139 }
1140
1141 public JobStatus get(int uid, int jobId) {
1142 ArraySet<JobStatus> jobs = mJobs.get(uid);
1143 if (jobs != null) {
1144 for (int i = jobs.size() - 1; i >= 0; i--) {
1145 JobStatus job = jobs.valueAt(i);
1146 if (job.getJobId() == jobId) {
1147 return job;
1148 }
1149 }
1150 }
1151 return null;
1152 }
1153
1154 // Inefficient; use only for testing
1155 public List<JobStatus> getAllJobs() {
1156 ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
Dianne Hackborne9a988c2016-05-27 17:59:40 -07001157 for (int i = mJobs.size() - 1; i >= 0; i--) {
1158 ArraySet<JobStatus> jobs = mJobs.valueAt(i);
1159 if (jobs != null) {
1160 // Use a for loop over the ArraySet, so we don't need to make its
1161 // optional collection class iterator implementation or have to go
1162 // through a temporary array from toArray().
1163 for (int j = jobs.size() - 1; j >= 0; j--) {
1164 allJobs.add(jobs.valueAt(j));
1165 }
1166 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001167 }
1168 return allJobs;
1169 }
1170
1171 public void clear() {
1172 mJobs.clear();
Suprabh Shukla106203b2017-11-02 21:23:44 -07001173 mJobsPerSourceUid.clear();
Christopher Tate2f36fd62016-02-18 18:36:08 -08001174 }
1175
1176 public int size() {
1177 int total = 0;
1178 for (int i = mJobs.size() - 1; i >= 0; i--) {
1179 total += mJobs.valueAt(i).size();
1180 }
1181 return total;
1182 }
1183
1184 // We only want to count the jobs that this uid has scheduled on its own
1185 // behalf, not those that the app has scheduled on someone else's behalf.
1186 public int countJobsForUid(int uid) {
1187 int total = 0;
1188 ArraySet<JobStatus> jobs = mJobs.get(uid);
1189 if (jobs != null) {
1190 for (int i = jobs.size() - 1; i >= 0; i--) {
1191 JobStatus job = jobs.valueAt(i);
1192 if (job.getUid() == job.getSourceUid()) {
1193 total++;
1194 }
1195 }
1196 }
1197 return total;
1198 }
1199
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -07001200 public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
1201 Consumer<JobStatus> functor) {
Christopher Tate2f36fd62016-02-18 18:36:08 -08001202 for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
1203 ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
Christopher Tate41f3fe82017-12-08 17:52:09 -08001204 if (jobs != null) {
1205 for (int i = jobs.size() - 1; i >= 0; i--) {
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -07001206 final JobStatus jobStatus = jobs.valueAt(i);
1207 if ((filterPredicate == null) || filterPredicate.test(jobStatus)) {
1208 functor.accept(jobStatus);
1209 }
Christopher Tate41f3fe82017-12-08 17:52:09 -08001210 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001211 }
1212 }
1213 }
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001214
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -07001215 public void forEachJob(int callingUid, Consumer<JobStatus> functor) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001216 ArraySet<JobStatus> jobs = mJobs.get(callingUid);
1217 if (jobs != null) {
1218 for (int i = jobs.size() - 1; i >= 0; i--) {
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -07001219 functor.accept(jobs.valueAt(i));
Suprabh Shukla106203b2017-11-02 21:23:44 -07001220 }
1221 }
1222 }
1223
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -07001224 public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) {
Suprabh Shukla106203b2017-11-02 21:23:44 -07001225 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001226 if (jobs != null) {
1227 for (int i = jobs.size() - 1; i >= 0; i--) {
Jeff Sharkeyfee8c7b2018-02-21 22:18:45 -07001228 functor.accept(jobs.valueAt(i));
Shreyas Basargecbf5ae92016-03-08 16:13:06 +00001229 }
1230 }
1231 }
Christopher Tate2f36fd62016-02-18 18:36:08 -08001232 }
Matthew Williams01ac45b2014-07-22 20:44:12 -07001233}