1 /* 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 17 package com.android.server.job; 18 19 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 20 import static com.android.server.job.JobSchedulerService.sSystemClock; 21 22 import android.annotation.Nullable; 23 import android.app.ActivityManager; 24 import android.app.IActivityManager; 25 import android.app.job.JobInfo; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.net.NetworkRequest; 29 import android.os.Environment; 30 import android.os.Handler; 31 import android.os.PersistableBundle; 32 import android.os.Process; 33 import android.os.SystemClock; 34 import android.os.UserHandle; 35 import android.text.format.DateUtils; 36 import android.util.ArraySet; 37 import android.util.AtomicFile; 38 import android.util.Pair; 39 import android.util.Slog; 40 import android.util.SparseArray; 41 import android.util.Xml; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.util.ArrayUtils; 45 import com.android.internal.util.BitUtils; 46 import com.android.internal.util.FastXmlSerializer; 47 import com.android.server.IoThread; 48 import com.android.server.LocalServices; 49 import com.android.server.job.JobSchedulerInternal.JobStorePersistStats; 50 import com.android.server.job.controllers.JobStatus; 51 52 import org.xmlpull.v1.XmlPullParser; 53 import org.xmlpull.v1.XmlPullParserException; 54 import org.xmlpull.v1.XmlSerializer; 55 56 import java.io.ByteArrayOutputStream; 57 import java.io.File; 58 import java.io.FileInputStream; 59 import java.io.FileNotFoundException; 60 import java.io.FileOutputStream; 61 import java.io.IOException; 62 import java.nio.charset.StandardCharsets; 63 import java.util.ArrayList; 64 import java.util.List; 65 import java.util.Set; 66 import java.util.function.Consumer; 67 import java.util.function.Predicate; 68 69 /** 70 * 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. 73 * 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. 79 * 80 * Test: 81 * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java 82 */ 83 public final class JobStore { 84 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; 89 90 final Object mLock; 91 final JobSet mJobSet; // per-caller-uid and per-source-uid tracking 92 final Context mContext; 93 94 // Bookkeeping around incorrect boot-time system clock 95 private final long mXmlTimestamp; 96 private boolean mRtcGood; 97 98 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 106 private JobStorePersistStats mPersistInfo = new JobStorePersistStats(); 107 108 /** 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(), 113 jobManagerService.getLock(), Environment.getDataDirectory()); 114 } 115 return sSingleton; 116 } 117 } 118 119 /** 120 * @return A freshly initialized job store object, with no loaded jobs. 121 */ 122 @VisibleForTesting 123 public static JobStore initAndGetForTesting(Context context, File dataDir) { 124 JobStore jobStoreUnderTest = new JobStore(context, new Object(), dataDir); 125 jobStoreUnderTest.clear(); 126 return jobStoreUnderTest; 127 } 128 129 /** 130 * Construct the instance of the job store. This results in a blocking read from disk. 131 */ 132 private JobStore(Context context, Object lock, File dataDir) { 133 mLock = lock; 134 mContext = context; 135 mDirtyOperations = 0; 136 137 File systemDir = new File(dataDir, "system"); 138 File jobDir = new File(systemDir, "job"); 139 jobDir.mkdirs(); 140 mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), "jobs"); 141 142 mJobSet = new JobSet(); 143 144 // 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(); 156 mRtcGood = (sSystemClock.millis() > mXmlTimestamp); 157 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) { 176 final long elapsedNow = sElapsedRealtimeClock.millis(); 177 178 // Find the jobs that need to be fixed up, collecting them for post-iteration 179 // replacement with their new versions 180 forEachJob(job -> { 181 final Pair<Long, Long> utcTimes = job.getPersistedUtcTimes(); 182 if (utcTimes != null) { 183 Pair<Long, Long> elapsedRuntimes = 184 convertRtcBoundsToElapsed(utcTimes, elapsedNow); 185 toAdd.add(new JobStatus(job, job.getBaseHeartbeat(), 186 elapsedRuntimes.first, elapsedRuntimes.second, 187 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime())); 188 toRemove.add(job); 189 } 190 }); 191 } 192 193 /** 194 * Add a job to the master list, persisting it if necessary. If the JobStatus already exists, 195 * it will be replaced. 196 * @param jobStatus Job to add. 197 * @return Whether or not an equivalent JobStatus was replaced by this operation. 198 */ 199 public boolean add(JobStatus jobStatus) { 200 boolean replaced = mJobSet.remove(jobStatus); 201 mJobSet.add(jobStatus); 202 if (jobStatus.isPersisted()) { 203 maybeWriteStatusToDiskAsync(); 204 } 205 if (DEBUG) { 206 Slog.d(TAG, "Added job status to store: " + jobStatus); 207 } 208 return replaced; 209 } 210 211 boolean containsJob(JobStatus jobStatus) { 212 return mJobSet.contains(jobStatus); 213 } 214 215 public int size() { 216 return mJobSet.size(); 217 } 218 219 public JobStorePersistStats getPersistStats() { 220 return mPersistInfo; 221 } 222 223 public int countJobsForUid(int uid) { 224 return mJobSet.countJobsForUid(uid); 225 } 226 227 /** 228 * Remove the provided job. Will also delete the job if it was persisted. 229 * @param writeBack If true, the job will be deleted (if it was persisted) immediately. 230 * @return Whether or not the job existed to be removed. 231 */ 232 public boolean remove(JobStatus jobStatus, boolean writeBack) { 233 boolean removed = mJobSet.remove(jobStatus); 234 if (!removed) { 235 if (DEBUG) { 236 Slog.d(TAG, "Couldn't remove job: didn't exist: " + jobStatus); 237 } 238 return false; 239 } 240 if (writeBack && jobStatus.isPersisted()) { 241 maybeWriteStatusToDiskAsync(); 242 } 243 return removed; 244 } 245 246 /** 247 * Remove the jobs of users not specified in the whitelist. 248 * @param whitelist Array of User IDs whose jobs are not to be removed. 249 */ 250 public void removeJobsOfNonUsers(int[] whitelist) { 251 mJobSet.removeJobsOfNonUsers(whitelist); 252 } 253 254 @VisibleForTesting 255 public void clear() { 256 mJobSet.clear(); 257 maybeWriteStatusToDiskAsync(); 258 } 259 260 /** 261 * @param userHandle User for whom we are querying the list of jobs. 262 * @return A list of all the jobs scheduled for the provided user. Never null. 263 */ 264 public List<JobStatus> getJobsByUser(int userHandle) { 265 return mJobSet.getJobsByUser(userHandle); 266 } 267 268 /** 269 * @param uid Uid of the requesting app. 270 * @return All JobStatus objects for a given uid from the master list. Never null. 271 */ 272 public List<JobStatus> getJobsByUid(int uid) { 273 return mJobSet.getJobsByUid(uid); 274 } 275 276 /** 277 * @param uid Uid of the requesting app. 278 * @param jobId Job id, specified at schedule-time. 279 * @return the JobStatus that matches the provided uId and jobId, or null if none found. 280 */ 281 public JobStatus getJobByUidAndJobId(int uid, int jobId) { 282 return mJobSet.get(uid, jobId); 283 } 284 285 /** 286 * Iterate over the set of all jobs, invoking the supplied functor on each. This is for 287 * customers who need to examine each job; we'd much rather not have to generate 288 * transient unified collections for them to iterate over and then discard, or creating 289 * iterators every time a client needs to perform a sweep. 290 */ 291 public void forEachJob(Consumer<JobStatus> functor) { 292 mJobSet.forEachJob(null, functor); 293 } 294 295 public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate, 296 Consumer<JobStatus> functor) { 297 mJobSet.forEachJob(filterPredicate, functor); 298 } 299 300 public void forEachJob(int uid, Consumer<JobStatus> functor) { 301 mJobSet.forEachJob(uid, functor); 302 } 303 304 public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) { 305 mJobSet.forEachJobForSourceUid(sourceUid, functor); 306 } 307 308 /** Version of the db schema. */ 309 private static final int JOBS_FILE_VERSION = 0; 310 /** Tag corresponds to constraints this job needs. */ 311 private static final String XML_TAG_PARAMS_CONSTRAINTS = "constraints"; 312 /** Tag corresponds to execution parameters. */ 313 private static final String XML_TAG_PERIODIC = "periodic"; 314 private static final String XML_TAG_ONEOFF = "one-off"; 315 private static final String XML_TAG_EXTRAS = "extras"; 316 317 /** 318 * Every time the state changes we write all the jobs in one swath, instead of trying to 319 * track incremental changes. 320 */ 321 private void maybeWriteStatusToDiskAsync() { 322 mDirtyOperations++; 323 if (mDirtyOperations >= MAX_OPS_BEFORE_WRITE) { 324 if (DEBUG) { 325 Slog.v(TAG, "Writing jobs to disk."); 326 } 327 mIoHandler.removeCallbacks(mWriteRunnable); 328 mIoHandler.post(mWriteRunnable); 329 } 330 } 331 332 @VisibleForTesting 333 public void readJobMapFromDisk(JobSet jobSet, boolean rtcGood) { 334 new ReadJobMapFromDiskRunnable(jobSet, rtcGood).run(); 335 } 336 337 /** 338 * Runnable that writes {@link #mJobSet} out to xml. 339 * NOTE: This Runnable locks on mLock 340 */ 341 private final Runnable mWriteRunnable = new Runnable() { 342 @Override 343 public void run() { 344 final long startElapsed = sElapsedRealtimeClock.millis(); 345 final List<JobStatus> storeCopy = new ArrayList<JobStatus>(); 346 synchronized (mLock) { 347 // Clone the jobs so we can release the lock before writing. 348 mJobSet.forEachJob(null, (job) -> { 349 if (job.isPersisted()) { 350 storeCopy.add(new JobStatus(job)); 351 } 352 }); 353 } 354 writeJobsMapImpl(storeCopy); 355 if (DEBUG) { 356 Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis() 357 - startElapsed) + "ms"); 358 } 359 } 360 361 private void writeJobsMapImpl(List<JobStatus> jobList) { 362 int numJobs = 0; 363 int numSystemJobs = 0; 364 int numSyncJobs = 0; 365 try { 366 final long startTime = SystemClock.uptimeMillis(); 367 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 368 XmlSerializer out = new FastXmlSerializer(); 369 out.setOutput(baos, StandardCharsets.UTF_8.name()); 370 out.startDocument(null, true); 371 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 372 373 out.startTag(null, "job-info"); 374 out.attribute(null, "version", Integer.toString(JOBS_FILE_VERSION)); 375 for (int i=0; i<jobList.size(); i++) { 376 JobStatus jobStatus = jobList.get(i); 377 if (DEBUG) { 378 Slog.d(TAG, "Saving job " + jobStatus.getJobId()); 379 } 380 out.startTag(null, "job"); 381 addAttributesToJobTag(out, jobStatus); 382 writeConstraintsToXml(out, jobStatus); 383 writeExecutionCriteriaToXml(out, jobStatus); 384 writeBundleToXml(jobStatus.getJob().getExtras(), out); 385 out.endTag(null, "job"); 386 387 numJobs++; 388 if (jobStatus.getUid() == Process.SYSTEM_UID) { 389 numSystemJobs++; 390 if (isSyncJob(jobStatus)) { 391 numSyncJobs++; 392 } 393 } 394 } 395 out.endTag(null, "job-info"); 396 out.endDocument(); 397 398 // Write out to disk in one fell swoop. 399 FileOutputStream fos = mJobsFile.startWrite(startTime); 400 fos.write(baos.toByteArray()); 401 mJobsFile.finishWrite(fos); 402 mDirtyOperations = 0; 403 } catch (IOException e) { 404 if (DEBUG) { 405 Slog.v(TAG, "Error writing out job data.", e); 406 } 407 } catch (XmlPullParserException e) { 408 if (DEBUG) { 409 Slog.d(TAG, "Error persisting bundle.", e); 410 } 411 } finally { 412 mPersistInfo.countAllJobsSaved = numJobs; 413 mPersistInfo.countSystemServerJobsSaved = numSystemJobs; 414 mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs; 415 } 416 } 417 418 /** Write out a tag with data comprising the required fields and priority of this job and 419 * its client. 420 */ 421 private void addAttributesToJobTag(XmlSerializer out, JobStatus jobStatus) 422 throws IOException { 423 out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId())); 424 out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName()); 425 out.attribute(null, "class", jobStatus.getServiceComponent().getClassName()); 426 if (jobStatus.getSourcePackageName() != null) { 427 out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName()); 428 } 429 if (jobStatus.getSourceTag() != null) { 430 out.attribute(null, "sourceTag", jobStatus.getSourceTag()); 431 } 432 out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId())); 433 out.attribute(null, "uid", Integer.toString(jobStatus.getUid())); 434 out.attribute(null, "priority", String.valueOf(jobStatus.getPriority())); 435 out.attribute(null, "flags", String.valueOf(jobStatus.getFlags())); 436 if (jobStatus.getInternalFlags() != 0) { 437 out.attribute(null, "internalFlags", String.valueOf(jobStatus.getInternalFlags())); 438 } 439 440 out.attribute(null, "lastSuccessfulRunTime", 441 String.valueOf(jobStatus.getLastSuccessfulRunTime())); 442 out.attribute(null, "lastFailedRunTime", 443 String.valueOf(jobStatus.getLastFailedRunTime())); 444 } 445 446 private void writeBundleToXml(PersistableBundle extras, XmlSerializer out) 447 throws IOException, XmlPullParserException { 448 out.startTag(null, XML_TAG_EXTRAS); 449 PersistableBundle extrasCopy = deepCopyBundle(extras, 10); 450 extrasCopy.saveToXml(out); 451 out.endTag(null, XML_TAG_EXTRAS); 452 } 453 454 private PersistableBundle deepCopyBundle(PersistableBundle bundle, int maxDepth) { 455 if (maxDepth <= 0) { 456 return null; 457 } 458 PersistableBundle copy = (PersistableBundle) bundle.clone(); 459 Set<String> keySet = bundle.keySet(); 460 for (String key: keySet) { 461 Object o = copy.get(key); 462 if (o instanceof PersistableBundle) { 463 PersistableBundle bCopy = deepCopyBundle((PersistableBundle) o, maxDepth-1); 464 copy.putPersistableBundle(key, bCopy); 465 } 466 } 467 return copy; 468 } 469 470 /** 471 * Write out a tag with data identifying this job's constraints. If the constraint isn't here 472 * it doesn't apply. 473 */ 474 private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException { 475 out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS); 476 if (jobStatus.hasConnectivityConstraint()) { 477 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork(); 478 out.attribute(null, "net-capabilities", Long.toString( 479 BitUtils.packBits(network.networkCapabilities.getCapabilities()))); 480 out.attribute(null, "net-unwanted-capabilities", Long.toString( 481 BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities()))); 482 483 out.attribute(null, "net-transport-types", Long.toString( 484 BitUtils.packBits(network.networkCapabilities.getTransportTypes()))); 485 } 486 if (jobStatus.hasIdleConstraint()) { 487 out.attribute(null, "idle", Boolean.toString(true)); 488 } 489 if (jobStatus.hasChargingConstraint()) { 490 out.attribute(null, "charging", Boolean.toString(true)); 491 } 492 if (jobStatus.hasBatteryNotLowConstraint()) { 493 out.attribute(null, "battery-not-low", Boolean.toString(true)); 494 } 495 out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS); 496 } 497 498 private void writeExecutionCriteriaToXml(XmlSerializer out, JobStatus jobStatus) 499 throws IOException { 500 final JobInfo job = jobStatus.getJob(); 501 if (jobStatus.getJob().isPeriodic()) { 502 out.startTag(null, XML_TAG_PERIODIC); 503 out.attribute(null, "period", Long.toString(job.getIntervalMillis())); 504 out.attribute(null, "flex", Long.toString(job.getFlexMillis())); 505 } else { 506 out.startTag(null, XML_TAG_ONEOFF); 507 } 508 509 // If we still have the persisted times, we need to record those directly because 510 // we haven't yet been able to calculate the usual elapsed-timebase bounds 511 // correctly due to wall-clock uncertainty. 512 Pair <Long, Long> utcJobTimes = jobStatus.getPersistedUtcTimes(); 513 if (DEBUG && utcJobTimes != null) { 514 Slog.i(TAG, "storing original UTC timestamps for " + jobStatus); 515 } 516 517 final long nowRTC = sSystemClock.millis(); 518 final long nowElapsed = sElapsedRealtimeClock.millis(); 519 if (jobStatus.hasDeadlineConstraint()) { 520 // Wall clock deadline. 521 final long deadlineWallclock = (utcJobTimes == null) 522 ? nowRTC + (jobStatus.getLatestRunTimeElapsed() - nowElapsed) 523 : utcJobTimes.second; 524 out.attribute(null, "deadline", Long.toString(deadlineWallclock)); 525 } 526 if (jobStatus.hasTimingDelayConstraint()) { 527 final long delayWallclock = (utcJobTimes == null) 528 ? nowRTC + (jobStatus.getEarliestRunTime() - nowElapsed) 529 : utcJobTimes.first; 530 out.attribute(null, "delay", Long.toString(delayWallclock)); 531 } 532 533 // Only write out back-off policy if it differs from the default. 534 // This also helps the case where the job is idle -> these aren't allowed to specify 535 // back-off. 536 if (jobStatus.getJob().getInitialBackoffMillis() != JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS 537 || jobStatus.getJob().getBackoffPolicy() != JobInfo.DEFAULT_BACKOFF_POLICY) { 538 out.attribute(null, "backoff-policy", Integer.toString(job.getBackoffPolicy())); 539 out.attribute(null, "initial-backoff", Long.toString(job.getInitialBackoffMillis())); 540 } 541 if (job.isPeriodic()) { 542 out.endTag(null, XML_TAG_PERIODIC); 543 } else { 544 out.endTag(null, XML_TAG_ONEOFF); 545 } 546 } 547 }; 548 549 /** 550 * Translate the supplied RTC times to the elapsed timebase, with clamping appropriate 551 * to interpreting them as a job's delay + deadline times for alarm-setting purposes. 552 * @param rtcTimes a Pair<Long, Long> in which {@code first} is the "delay" earliest 553 * allowable runtime for the job, and {@code second} is the "deadline" time at which 554 * the job becomes overdue. 555 */ 556 private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes, 557 long nowElapsed) { 558 final long nowWallclock = sSystemClock.millis(); 559 final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME) 560 ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0) 561 : JobStatus.NO_EARLIEST_RUNTIME; 562 final long latest = (rtcTimes.second < JobStatus.NO_LATEST_RUNTIME) 563 ? nowElapsed + Math.max(rtcTimes.second - nowWallclock, 0) 564 : JobStatus.NO_LATEST_RUNTIME; 565 return Pair.create(earliest, latest); 566 } 567 568 private static boolean isSyncJob(JobStatus status) { 569 return com.android.server.content.SyncJobService.class.getName() 570 .equals(status.getServiceComponent().getClassName()); 571 } 572 573 /** 574 * Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't 575 * need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}. 576 */ 577 private final class ReadJobMapFromDiskRunnable implements Runnable { 578 private final JobSet jobSet; 579 private final boolean rtcGood; 580 581 /** 582 * @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore, 583 * so that after disk read we can populate it directly. 584 */ 585 ReadJobMapFromDiskRunnable(JobSet jobSet, boolean rtcIsGood) { 586 this.jobSet = jobSet; 587 this.rtcGood = rtcIsGood; 588 } 589 590 @Override 591 public void run() { 592 int numJobs = 0; 593 int numSystemJobs = 0; 594 int numSyncJobs = 0; 595 try { 596 List<JobStatus> jobs; 597 FileInputStream fis = mJobsFile.openRead(); 598 synchronized (mLock) { 599 jobs = readJobMapImpl(fis, rtcGood); 600 if (jobs != null) { 601 long now = sElapsedRealtimeClock.millis(); 602 IActivityManager am = ActivityManager.getService(); 603 for (int i=0; i<jobs.size(); i++) { 604 JobStatus js = jobs.get(i); 605 js.prepareLocked(am); 606 js.enqueueTime = now; 607 this.jobSet.add(js); 608 609 numJobs++; 610 if (js.getUid() == Process.SYSTEM_UID) { 611 numSystemJobs++; 612 if (isSyncJob(js)) { 613 numSyncJobs++; 614 } 615 } 616 } 617 } 618 } 619 fis.close(); 620 } catch (FileNotFoundException e) { 621 if (DEBUG) { 622 Slog.d(TAG, "Could not find jobs file, probably there was nothing to load."); 623 } 624 } catch (XmlPullParserException | IOException e) { 625 Slog.wtf(TAG, "Error jobstore xml.", e); 626 } finally { 627 if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once. 628 mPersistInfo.countAllJobsLoaded = numJobs; 629 mPersistInfo.countSystemServerJobsLoaded = numSystemJobs; 630 mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs; 631 } 632 } 633 Slog.i(TAG, "Read " + numJobs + " jobs"); 634 } 635 636 private List<JobStatus> readJobMapImpl(FileInputStream fis, boolean rtcIsGood) 637 throws XmlPullParserException, IOException { 638 XmlPullParser parser = Xml.newPullParser(); 639 parser.setInput(fis, StandardCharsets.UTF_8.name()); 640 641 int eventType = parser.getEventType(); 642 while (eventType != XmlPullParser.START_TAG && 643 eventType != XmlPullParser.END_DOCUMENT) { 644 eventType = parser.next(); 645 Slog.d(TAG, "Start tag: " + parser.getName()); 646 } 647 if (eventType == XmlPullParser.END_DOCUMENT) { 648 if (DEBUG) { 649 Slog.d(TAG, "No persisted jobs."); 650 } 651 return null; 652 } 653 654 String tagName = parser.getName(); 655 if ("job-info".equals(tagName)) { 656 final List<JobStatus> jobs = new ArrayList<JobStatus>(); 657 // Read in version info. 658 try { 659 int version = Integer.parseInt(parser.getAttributeValue(null, "version")); 660 if (version != JOBS_FILE_VERSION) { 661 Slog.d(TAG, "Invalid version number, aborting jobs file read."); 662 return null; 663 } 664 } catch (NumberFormatException e) { 665 Slog.e(TAG, "Invalid version number, aborting jobs file read."); 666 return null; 667 } 668 eventType = parser.next(); 669 do { 670 // Read each <job/> 671 if (eventType == XmlPullParser.START_TAG) { 672 tagName = parser.getName(); 673 // Start reading job. 674 if ("job".equals(tagName)) { 675 JobStatus persistedJob = restoreJobFromXml(rtcIsGood, parser); 676 if (persistedJob != null) { 677 if (DEBUG) { 678 Slog.d(TAG, "Read out " + persistedJob); 679 } 680 jobs.add(persistedJob); 681 } else { 682 Slog.d(TAG, "Error reading job from file."); 683 } 684 } 685 } 686 eventType = parser.next(); 687 } while (eventType != XmlPullParser.END_DOCUMENT); 688 return jobs; 689 } 690 return null; 691 } 692 693 /** 694 * @param parser Xml parser at the beginning of a "<job/>" tag. The next "parser.next()" call 695 * will take the parser into the body of the job tag. 696 * @return Newly instantiated job holding all the information we just read out of the xml tag. 697 */ 698 private JobStatus restoreJobFromXml(boolean rtcIsGood, XmlPullParser parser) 699 throws XmlPullParserException, IOException { 700 JobInfo.Builder jobBuilder; 701 int uid, sourceUserId; 702 long lastSuccessfulRunTime; 703 long lastFailedRunTime; 704 int internalFlags = 0; 705 706 // Read out job identifier attributes and priority. 707 try { 708 jobBuilder = buildBuilderFromXml(parser); 709 jobBuilder.setPersisted(true); 710 uid = Integer.parseInt(parser.getAttributeValue(null, "uid")); 711 712 String val = parser.getAttributeValue(null, "priority"); 713 if (val != null) { 714 jobBuilder.setPriority(Integer.parseInt(val)); 715 } 716 val = parser.getAttributeValue(null, "flags"); 717 if (val != null) { 718 jobBuilder.setFlags(Integer.parseInt(val)); 719 } 720 val = parser.getAttributeValue(null, "internalFlags"); 721 if (val != null) { 722 internalFlags = Integer.parseInt(val); 723 } 724 val = parser.getAttributeValue(null, "sourceUserId"); 725 sourceUserId = val == null ? -1 : Integer.parseInt(val); 726 727 val = parser.getAttributeValue(null, "lastSuccessfulRunTime"); 728 lastSuccessfulRunTime = val == null ? 0 : Long.parseLong(val); 729 730 val = parser.getAttributeValue(null, "lastFailedRunTime"); 731 lastFailedRunTime = val == null ? 0 : Long.parseLong(val); 732 } catch (NumberFormatException e) { 733 Slog.e(TAG, "Error parsing job's required fields, skipping"); 734 return null; 735 } 736 737 String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName"); 738 final String sourceTag = parser.getAttributeValue(null, "sourceTag"); 739 740 int eventType; 741 // Read out constraints tag. 742 do { 743 eventType = parser.next(); 744 } while (eventType == XmlPullParser.TEXT); // Push through to next START_TAG. 745 746 if (!(eventType == XmlPullParser.START_TAG && 747 XML_TAG_PARAMS_CONSTRAINTS.equals(parser.getName()))) { 748 // Expecting a <constraints> start tag. 749 return null; 750 } 751 try { 752 buildConstraintsFromXml(jobBuilder, parser); 753 } catch (NumberFormatException e) { 754 Slog.d(TAG, "Error reading constraints, skipping."); 755 return null; 756 } 757 parser.next(); // Consume </constraints> 758 759 // Read out execution parameters tag. 760 do { 761 eventType = parser.next(); 762 } while (eventType == XmlPullParser.TEXT); 763 if (eventType != XmlPullParser.START_TAG) { 764 return null; 765 } 766 767 // Tuple of (earliest runtime, latest runtime) in UTC. 768 final Pair<Long, Long> rtcRuntimes; 769 try { 770 rtcRuntimes = buildRtcExecutionTimesFromXml(parser); 771 } catch (NumberFormatException e) { 772 if (DEBUG) { 773 Slog.d(TAG, "Error parsing execution time parameters, skipping."); 774 } 775 return null; 776 } 777 778 final long elapsedNow = sElapsedRealtimeClock.millis(); 779 Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow); 780 781 if (XML_TAG_PERIODIC.equals(parser.getName())) { 782 try { 783 String val = parser.getAttributeValue(null, "period"); 784 final long periodMillis = Long.parseLong(val); 785 val = parser.getAttributeValue(null, "flex"); 786 final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis; 787 jobBuilder.setPeriodic(periodMillis, flexMillis); 788 // As a sanity check, cap the recreated run time to be no later than flex+period 789 // from now. This is the latest the periodic could be pushed out. This could 790 // happen if the periodic ran early (at flex time before period), and then the 791 // device rebooted. 792 if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) { 793 final long clampedLateRuntimeElapsed = elapsedNow + flexMillis 794 + periodMillis; 795 final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed 796 - flexMillis; 797 Slog.w(TAG, 798 String.format("Periodic job for uid='%d' persisted run-time is" + 799 " too big [%s, %s]. Clamping to [%s,%s]", 800 uid, 801 DateUtils.formatElapsedTime(elapsedRuntimes.first / 1000), 802 DateUtils.formatElapsedTime(elapsedRuntimes.second / 1000), 803 DateUtils.formatElapsedTime( 804 clampedEarlyRuntimeElapsed / 1000), 805 DateUtils.formatElapsedTime( 806 clampedLateRuntimeElapsed / 1000)) 807 ); 808 elapsedRuntimes = 809 Pair.create(clampedEarlyRuntimeElapsed, clampedLateRuntimeElapsed); 810 } 811 } catch (NumberFormatException e) { 812 Slog.d(TAG, "Error reading periodic execution criteria, skipping."); 813 return null; 814 } 815 } else if (XML_TAG_ONEOFF.equals(parser.getName())) { 816 try { 817 if (elapsedRuntimes.first != JobStatus.NO_EARLIEST_RUNTIME) { 818 jobBuilder.setMinimumLatency(elapsedRuntimes.first - elapsedNow); 819 } 820 if (elapsedRuntimes.second != JobStatus.NO_LATEST_RUNTIME) { 821 jobBuilder.setOverrideDeadline( 822 elapsedRuntimes.second - elapsedNow); 823 } 824 } catch (NumberFormatException e) { 825 Slog.d(TAG, "Error reading job execution criteria, skipping."); 826 return null; 827 } 828 } else { 829 if (DEBUG) { 830 Slog.d(TAG, "Invalid parameter tag, skipping - " + parser.getName()); 831 } 832 // Expecting a parameters start tag. 833 return null; 834 } 835 maybeBuildBackoffPolicyFromXml(jobBuilder, parser); 836 837 parser.nextTag(); // Consume parameters end tag. 838 839 // Read out extras Bundle. 840 do { 841 eventType = parser.next(); 842 } while (eventType == XmlPullParser.TEXT); 843 if (!(eventType == XmlPullParser.START_TAG 844 && XML_TAG_EXTRAS.equals(parser.getName()))) { 845 if (DEBUG) { 846 Slog.d(TAG, "Error reading extras, skipping."); 847 } 848 return null; 849 } 850 851 PersistableBundle extras = PersistableBundle.restoreFromXml(parser); 852 jobBuilder.setExtras(extras); 853 parser.nextTag(); // Consume </extras> 854 855 // Migrate sync jobs forward from earlier, incomplete representation 856 if ("android".equals(sourcePackageName) 857 && extras != null 858 && extras.getBoolean("SyncManagerJob", false)) { 859 sourcePackageName = extras.getString("owningPackage", sourcePackageName); 860 if (DEBUG) { 861 Slog.i(TAG, "Fixing up sync job source package name from 'android' to '" 862 + sourcePackageName + "'"); 863 } 864 } 865 866 // And now we're done 867 JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class); 868 final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName, 869 sourceUserId, elapsedNow); 870 long currentHeartbeat = service != null ? service.currentHeartbeat() : 0; 871 JobStatus js = new JobStatus( 872 jobBuilder.build(), uid, sourcePackageName, sourceUserId, 873 appBucket, currentHeartbeat, sourceTag, 874 elapsedRuntimes.first, elapsedRuntimes.second, 875 lastSuccessfulRunTime, lastFailedRunTime, 876 (rtcIsGood) ? null : rtcRuntimes, internalFlags); 877 return js; 878 } 879 880 private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException { 881 // Pull out required fields from <job> attributes. 882 int jobId = Integer.parseInt(parser.getAttributeValue(null, "jobid")); 883 String packageName = parser.getAttributeValue(null, "package"); 884 String className = parser.getAttributeValue(null, "class"); 885 ComponentName cname = new ComponentName(packageName, className); 886 887 return new JobInfo.Builder(jobId, cname); 888 } 889 890 private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) { 891 String val; 892 893 final String netCapabilities = parser.getAttributeValue(null, "net-capabilities"); 894 final String netUnwantedCapabilities = parser.getAttributeValue( 895 null, "net-unwanted-capabilities"); 896 final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types"); 897 if (netCapabilities != null && netTransportTypes != null) { 898 final NetworkRequest request = new NetworkRequest.Builder().build(); 899 final long unwantedCapabilities = netUnwantedCapabilities != null 900 ? Long.parseLong(netUnwantedCapabilities) 901 : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities()); 902 903 // We're okay throwing NFE here; caught by caller 904 request.networkCapabilities.setCapabilities( 905 BitUtils.unpackBits(Long.parseLong(netCapabilities)), 906 BitUtils.unpackBits(unwantedCapabilities)); 907 request.networkCapabilities.setTransportTypes( 908 BitUtils.unpackBits(Long.parseLong(netTransportTypes))); 909 jobBuilder.setRequiredNetwork(request); 910 } else { 911 // Read legacy values 912 val = parser.getAttributeValue(null, "connectivity"); 913 if (val != null) { 914 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); 915 } 916 val = parser.getAttributeValue(null, "metered"); 917 if (val != null) { 918 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED); 919 } 920 val = parser.getAttributeValue(null, "unmetered"); 921 if (val != null) { 922 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); 923 } 924 val = parser.getAttributeValue(null, "not-roaming"); 925 if (val != null) { 926 jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING); 927 } 928 } 929 930 val = parser.getAttributeValue(null, "idle"); 931 if (val != null) { 932 jobBuilder.setRequiresDeviceIdle(true); 933 } 934 val = parser.getAttributeValue(null, "charging"); 935 if (val != null) { 936 jobBuilder.setRequiresCharging(true); 937 } 938 } 939 940 /** 941 * Builds the back-off policy out of the params tag. These attributes may not exist, depending 942 * on whether the back-off was set when the job was first scheduled. 943 */ 944 private void maybeBuildBackoffPolicyFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) { 945 String val = parser.getAttributeValue(null, "initial-backoff"); 946 if (val != null) { 947 long initialBackoff = Long.parseLong(val); 948 val = parser.getAttributeValue(null, "backoff-policy"); 949 int backoffPolicy = Integer.parseInt(val); // Will throw NFE which we catch higher up. 950 jobBuilder.setBackoffCriteria(initialBackoff, backoffPolicy); 951 } 952 } 953 954 /** 955 * Extract a job's earliest/latest run time data from XML. These are returned in 956 * unadjusted UTC wall clock time, because we do not yet know whether the system 957 * clock is reliable for purposes of calculating deltas from 'now'. 958 * 959 * @param parser 960 * @return A Pair of timestamps in UTC wall-clock time. The first is the earliest 961 * time at which the job is to become runnable, and the second is the deadline at 962 * which it becomes overdue to execute. 963 * @throws NumberFormatException 964 */ 965 private Pair<Long, Long> buildRtcExecutionTimesFromXml(XmlPullParser parser) 966 throws NumberFormatException { 967 String val; 968 // Pull out execution time data. 969 val = parser.getAttributeValue(null, "delay"); 970 final long earliestRunTimeRtc = (val != null) 971 ? Long.parseLong(val) 972 : JobStatus.NO_EARLIEST_RUNTIME; 973 val = parser.getAttributeValue(null, "deadline"); 974 final long latestRunTimeRtc = (val != null) 975 ? Long.parseLong(val) 976 : JobStatus.NO_LATEST_RUNTIME; 977 return Pair.create(earliestRunTimeRtc, latestRunTimeRtc); 978 } 979 980 /** 981 * Convenience function to read out and convert deadline and delay from xml into elapsed real 982 * time. 983 * @return A {@link android.util.Pair}, where the first value is the earliest elapsed runtime 984 * and the second is the latest elapsed runtime. 985 */ 986 private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser) 987 throws NumberFormatException { 988 // Pull out execution time data. 989 final long nowWallclock = sSystemClock.millis(); 990 final long nowElapsed = sElapsedRealtimeClock.millis(); 991 992 long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME; 993 long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME; 994 String val = parser.getAttributeValue(null, "deadline"); 995 if (val != null) { 996 long latestRuntimeWallclock = Long.parseLong(val); 997 long maxDelayElapsed = 998 Math.max(latestRuntimeWallclock - nowWallclock, 0); 999 latestRunTimeElapsed = nowElapsed + maxDelayElapsed; 1000 } 1001 val = parser.getAttributeValue(null, "delay"); 1002 if (val != null) { 1003 long earliestRuntimeWallclock = Long.parseLong(val); 1004 long minDelayElapsed = 1005 Math.max(earliestRuntimeWallclock - nowWallclock, 0); 1006 earliestRunTimeElapsed = nowElapsed + minDelayElapsed; 1007 1008 } 1009 return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed); 1010 } 1011 } 1012 1013 static final class JobSet { 1014 @VisibleForTesting // Key is the getUid() originator of the jobs in each sheaf 1015 final SparseArray<ArraySet<JobStatus>> mJobs; 1016 1017 @VisibleForTesting // Same data but with the key as getSourceUid() of the jobs in each sheaf 1018 final SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid; 1019 1020 public JobSet() { 1021 mJobs = new SparseArray<ArraySet<JobStatus>>(); 1022 mJobsPerSourceUid = new SparseArray<>(); 1023 } 1024 1025 public List<JobStatus> getJobsByUid(int uid) { 1026 ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>(); 1027 ArraySet<JobStatus> jobs = mJobs.get(uid); 1028 if (jobs != null) { 1029 matchingJobs.addAll(jobs); 1030 } 1031 return matchingJobs; 1032 } 1033 1034 // By user, not by uid, so we need to traverse by key and check 1035 public List<JobStatus> getJobsByUser(int userId) { 1036 final ArrayList<JobStatus> result = new ArrayList<JobStatus>(); 1037 for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) { 1038 if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) { 1039 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i); 1040 if (jobs != null) { 1041 result.addAll(jobs); 1042 } 1043 } 1044 } 1045 return result; 1046 } 1047 1048 public boolean add(JobStatus job) { 1049 final int uid = job.getUid(); 1050 final int sourceUid = job.getSourceUid(); 1051 ArraySet<JobStatus> jobs = mJobs.get(uid); 1052 if (jobs == null) { 1053 jobs = new ArraySet<JobStatus>(); 1054 mJobs.put(uid, jobs); 1055 } 1056 ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); 1057 if (jobsForSourceUid == null) { 1058 jobsForSourceUid = new ArraySet<>(); 1059 mJobsPerSourceUid.put(sourceUid, jobsForSourceUid); 1060 } 1061 final boolean added = jobs.add(job); 1062 final boolean addedInSource = jobsForSourceUid.add(job); 1063 if (added != addedInSource) { 1064 Slog.wtf(TAG, "mJobs and mJobsPerSourceUid mismatch; caller= " + added 1065 + " source= " + addedInSource); 1066 } 1067 return added || addedInSource; 1068 } 1069 1070 public boolean remove(JobStatus job) { 1071 final int uid = job.getUid(); 1072 final ArraySet<JobStatus> jobs = mJobs.get(uid); 1073 final int sourceUid = job.getSourceUid(); 1074 final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid); 1075 final boolean didRemove = jobs != null && jobs.remove(job); 1076 final boolean sourceRemove = jobsForSourceUid != null && jobsForSourceUid.remove(job); 1077 if (didRemove != sourceRemove) { 1078 Slog.wtf(TAG, "Job presence mismatch; caller=" + didRemove 1079 + " source=" + sourceRemove); 1080 } 1081 if (didRemove || sourceRemove) { 1082 // no more jobs for this uid? let the now-empty set objects be GC'd. 1083 if (jobs != null && jobs.size() == 0) { 1084 mJobs.remove(uid); 1085 } 1086 if (jobsForSourceUid != null && jobsForSourceUid.size() == 0) { 1087 mJobsPerSourceUid.remove(sourceUid); 1088 } 1089 return true; 1090 } 1091 return false; 1092 } 1093 1094 /** 1095 * Removes the jobs of all users not specified by the whitelist of user ids. 1096 * This will remove jobs scheduled *by* non-existent users as well as jobs scheduled *for* 1097 * non-existent users 1098 */ 1099 public void removeJobsOfNonUsers(final int[] whitelist) { 1100 final Predicate<JobStatus> noSourceUser = 1101 job -> !ArrayUtils.contains(whitelist, job.getSourceUserId()); 1102 final Predicate<JobStatus> noCallingUser = 1103 job -> !ArrayUtils.contains(whitelist, job.getUserId()); 1104 removeAll(noSourceUser.or(noCallingUser)); 1105 } 1106 1107 private void removeAll(Predicate<JobStatus> predicate) { 1108 for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) { 1109 final ArraySet<JobStatus> jobs = mJobs.valueAt(jobSetIndex); 1110 for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) { 1111 if (predicate.test(jobs.valueAt(jobIndex))) { 1112 jobs.removeAt(jobIndex); 1113 } 1114 } 1115 if (jobs.size() == 0) { 1116 mJobs.removeAt(jobSetIndex); 1117 } 1118 } 1119 for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) { 1120 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(jobSetIndex); 1121 for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) { 1122 if (predicate.test(jobs.valueAt(jobIndex))) { 1123 jobs.removeAt(jobIndex); 1124 } 1125 } 1126 if (jobs.size() == 0) { 1127 mJobsPerSourceUid.removeAt(jobSetIndex); 1128 } 1129 } 1130 } 1131 1132 public boolean contains(JobStatus job) { 1133 final int uid = job.getUid(); 1134 ArraySet<JobStatus> jobs = mJobs.get(uid); 1135 return jobs != null && jobs.contains(job); 1136 } 1137 1138 public JobStatus get(int uid, int jobId) { 1139 ArraySet<JobStatus> jobs = mJobs.get(uid); 1140 if (jobs != null) { 1141 for (int i = jobs.size() - 1; i >= 0; i--) { 1142 JobStatus job = jobs.valueAt(i); 1143 if (job.getJobId() == jobId) { 1144 return job; 1145 } 1146 } 1147 } 1148 return null; 1149 } 1150 1151 // Inefficient; use only for testing 1152 public List<JobStatus> getAllJobs() { 1153 ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size()); 1154 for (int i = mJobs.size() - 1; i >= 0; i--) { 1155 ArraySet<JobStatus> jobs = mJobs.valueAt(i); 1156 if (jobs != null) { 1157 // Use a for loop over the ArraySet, so we don't need to make its 1158 // optional collection class iterator implementation or have to go 1159 // through a temporary array from toArray(). 1160 for (int j = jobs.size() - 1; j >= 0; j--) { 1161 allJobs.add(jobs.valueAt(j)); 1162 } 1163 } 1164 } 1165 return allJobs; 1166 } 1167 1168 public void clear() { 1169 mJobs.clear(); 1170 mJobsPerSourceUid.clear(); 1171 } 1172 1173 public int size() { 1174 int total = 0; 1175 for (int i = mJobs.size() - 1; i >= 0; i--) { 1176 total += mJobs.valueAt(i).size(); 1177 } 1178 return total; 1179 } 1180 1181 // We only want to count the jobs that this uid has scheduled on its own 1182 // behalf, not those that the app has scheduled on someone else's behalf. 1183 public int countJobsForUid(int uid) { 1184 int total = 0; 1185 ArraySet<JobStatus> jobs = mJobs.get(uid); 1186 if (jobs != null) { 1187 for (int i = jobs.size() - 1; i >= 0; i--) { 1188 JobStatus job = jobs.valueAt(i); 1189 if (job.getUid() == job.getSourceUid()) { 1190 total++; 1191 } 1192 } 1193 } 1194 return total; 1195 } 1196 1197 public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate, 1198 Consumer<JobStatus> functor) { 1199 for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) { 1200 ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex); 1201 if (jobs != null) { 1202 for (int i = jobs.size() - 1; i >= 0; i--) { 1203 final JobStatus jobStatus = jobs.valueAt(i); 1204 if ((filterPredicate == null) || filterPredicate.test(jobStatus)) { 1205 functor.accept(jobStatus); 1206 } 1207 } 1208 } 1209 } 1210 } 1211 1212 public void forEachJob(int callingUid, Consumer<JobStatus> functor) { 1213 ArraySet<JobStatus> jobs = mJobs.get(callingUid); 1214 if (jobs != null) { 1215 for (int i = jobs.size() - 1; i >= 0; i--) { 1216 functor.accept(jobs.valueAt(i)); 1217 } 1218 } 1219 } 1220 1221 public void forEachJobForSourceUid(int sourceUid, Consumer<JobStatus> functor) { 1222 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid); 1223 if (jobs != null) { 1224 for (int i = jobs.size() - 1; i >= 0; i--) { 1225 functor.accept(jobs.valueAt(i)); 1226 } 1227 } 1228 } 1229 } 1230 } 1231