From b0001f6fb1383d9824c2733896b0b348e7f77240 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 16 Feb 2016 10:30:33 -0800 Subject: [PATCH] Remove internal locking from JobStatus. Now all state of JobStatus is implicitly protected by the lock of whoever is using it -- in this case the global lock for the JobSchedulerService. This allows us to remove all of the atomic variables and just replace those with a simple bit field. The required constraints for a job are now statically defined once a JobStatus is created, and don't change. (They wouldn't change before, but now this is absolutely specified to be the case.) This required tweaking the constructors a bit so that the earliest and latest run times are computed as part of the core class initialization. Also clarified methods on StateController that are called with the lock held, and took advantage of that in the various controllers to not now redundantly re-acquire the lock. Change-Id: I595c5e7d1bff1bd2ff906d612581af82878a25ee --- .../server/job/JobSchedulerService.java | 54 ++- .../java/com/android/server/job/JobStore.java | 3 +- .../job/controllers/AppIdleController.java | 47 ++- .../job/controllers/BatteryController.java | 34 +- .../controllers/ConnectivityController.java | 26 +- .../ContentObserverController.java | 208 ++++++------ .../job/controllers/IdleController.java | 36 +- .../server/job/controllers/JobStatus.java | 315 +++++++++++------- .../job/controllers/StateController.java | 8 +- .../job/controllers/TimeController.java | 88 +++-- .../com/android/server/job/JobStoreTest.java | 20 +- 11 files changed, 426 insertions(+), 413 deletions(-) diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 25b54acf6b786..811c94720a366 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -237,7 +237,7 @@ public final class JobSchedulerService extends com.android.server.SystemService } public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId) { - JobStatus jobStatus = new JobStatus(getLock(), job, uId, packageName, userId); + JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId); try { if (ActivityManagerNative.getDefault().getAppStartMode(uId, job.getService().getPackageName()) == ActivityManager.APP_START_MODE_DISABLED) { @@ -476,7 +476,7 @@ public final class JobSchedulerService extends com.android.server.SystemService JobStatus job = jobs.valueAt(i); for (int controller=0; controller(); } runnableJobs.add(job); - } else if (areJobConstraintsNotSatisfied(job)) { + } else if (areJobConstraintsNotSatisfiedLocked(job)) { stopJobOnServiceContextLocked(job, JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED); } @@ -875,7 +869,7 @@ public final class JobSchedulerService extends com.android.server.SystemService * - It's not ready * - It's running on a JSC. */ - private boolean areJobConstraintsNotSatisfied(JobStatus job) { + private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) { return !job.isReady() && isCurrentlyActiveLocked(job); } @@ -893,7 +887,7 @@ public final class JobSchedulerService extends com.android.server.SystemService if (DEBUG) { Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs."); } - assignJobsToContextsH(); + assignJobsToContextsLocked(); reportActive(); } } @@ -905,7 +899,7 @@ public final class JobSchedulerService extends com.android.server.SystemService * run higher priority ones. * Lock on mJobs before calling this function. */ - private void assignJobsToContextsH() { + private void assignJobsToContextsLocked() { if (DEBUG) { Slog.d(TAG, printPendingQueue()); } @@ -990,7 +984,7 @@ public final class JobSchedulerService extends com.android.server.SystemService } for (int ic=0; ic JobStatus js = new JobStatus( - mLock, jobBuilder.build(), uid, sourcePackageName, sourceUserId, + jobBuilder.build(), uid, sourcePackageName, sourceUserId, elapsedRuntimes.first, elapsedRuntimes.second); return js; } diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java index f7f34aec60386..f0c579f1ae98f 100644 --- a/services/core/java/com/android/server/job/controllers/AppIdleController.java +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -64,39 +64,34 @@ public class AppIdleController extends StateController { } @Override - public void maybeStartTrackingJob(JobStatus jobStatus, JobStatus lastJob) { - synchronized (mLock) { - mTrackedTasks.add(jobStatus); - String packageName = jobStatus.getSourcePackageName(); - final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName, - jobStatus.getSourceUid(), jobStatus.getSourceUserId()); - if (DEBUG) { - Slog.d(LOG_TAG, "Start tracking, setting idle state of " - + packageName + " to " + appIdle); - } - jobStatus.appNotIdleConstraintSatisfied.set(!appIdle); + public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { + mTrackedTasks.add(jobStatus); + String packageName = jobStatus.getSourcePackageName(); + final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName, + jobStatus.getSourceUid(), jobStatus.getSourceUserId()); + if (DEBUG) { + Slog.d(LOG_TAG, "Start tracking, setting idle state of " + + packageName + " to " + appIdle); } + jobStatus.setAppNotIdleConstraintSatisfied(!appIdle); } @Override - public void maybeStopTrackingJob(JobStatus jobStatus, boolean forUpdate) { - synchronized (mLock) { - mTrackedTasks.remove(jobStatus); - } + public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) { + mTrackedTasks.remove(jobStatus); } @Override - public void dumpControllerState(PrintWriter pw) { + public void dumpControllerStateLocked(PrintWriter pw) { pw.println("AppIdle"); pw.println("Parole On: " + mAppIdleParoleOn); - synchronized (mLock) { - for (JobStatus task : mTrackedTasks) { - pw.print(task.getSourcePackageName()); - pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get()); - pw.print(", "); - } - pw.println(); + for (JobStatus task : mTrackedTasks) { + pw.print(task.getSourcePackageName()); + pw.print(":idle=" + + ((task.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0)); + pw.print(", "); } + pw.println(); } void setAppIdleParoleOn(boolean isAppIdleParoleOn) { @@ -114,8 +109,7 @@ public class AppIdleController extends StateController { if (DEBUG) { Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle); } - if (task.appNotIdleConstraintSatisfied.get() == appIdle) { - task.appNotIdleConstraintSatisfied.set(!appIdle); + if (task.setAppNotIdleConstraintSatisfied(!appIdle)) { changed = true; } } @@ -137,12 +131,11 @@ public class AppIdleController extends StateController { for (JobStatus task : mTrackedTasks) { if (task.getSourcePackageName().equals(packageName) && task.getSourceUserId() == userId) { - if (task.appNotIdleConstraintSatisfied.get() != !idle) { + if (task.setAppNotIdleConstraintSatisfied(!idle)) { if (DEBUG) { Slog.d(LOG_TAG, "App Idle state changed, setting idle state of " + packageName + " to " + idle); } - task.appNotIdleConstraintSatisfied.set(!idle); changed = true; } } diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java index b101c82649837..ac9f425201956 100644 --- a/services/core/java/com/android/server/job/controllers/BatteryController.java +++ b/services/core/java/com/android/server/job/controllers/BatteryController.java @@ -78,22 +78,18 @@ public class BatteryController extends StateController { } @Override - public void maybeStartTrackingJob(JobStatus taskStatus, JobStatus lastJob) { + public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { final boolean isOnStablePower = mChargeTracker.isOnStablePower(); if (taskStatus.hasChargingConstraint()) { - synchronized (mLock) { - mTrackedTasks.add(taskStatus); - taskStatus.chargingConstraintSatisfied.set(isOnStablePower); - } + mTrackedTasks.add(taskStatus); + taskStatus.setChargingConstraintSatisfied(isOnStablePower); } } @Override - public void maybeStopTrackingJob(JobStatus taskStatus, boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) { if (taskStatus.hasChargingConstraint()) { - synchronized (mLock) { - mTrackedTasks.remove(taskStatus); - } + mTrackedTasks.remove(taskStatus); } } @@ -105,7 +101,7 @@ public class BatteryController extends StateController { boolean reportChange = false; synchronized (mLock) { for (JobStatus ts : mTrackedTasks) { - boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower); + boolean previous = ts.setChargingConstraintSatisfied(stablePower); if (previous != stablePower) { reportChange = true; } @@ -198,18 +194,16 @@ public class BatteryController extends StateController { } @Override - public void dumpControllerState(PrintWriter pw) { + public void dumpControllerStateLocked(PrintWriter pw) { pw.println("Batt."); pw.println("Stable power: " + mChargeTracker.isOnStablePower()); - synchronized (mLock) { - Iterator it = mTrackedTasks.iterator(); - if (it.hasNext()) { - pw.print(String.valueOf(it.next().hashCode())); - } - while (it.hasNext()) { - pw.print("," + String.valueOf(it.next().hashCode())); - } - pw.println(); + Iterator it = mTrackedTasks.iterator(); + if (it.hasNext()) { + pw.print(String.valueOf(it.next().hashCode())); } + while (it.hasNext()) { + pw.print("," + String.valueOf(it.next().hashCode())); + } + pw.println(); } } diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java index 29b54c2eb7166..bd06645b2b16c 100644 --- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java +++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java @@ -83,22 +83,18 @@ public class ConnectivityController extends StateController implements } @Override - public void maybeStartTrackingJob(JobStatus jobStatus, JobStatus lastJob) { + public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) { - synchronized (mLock) { - jobStatus.connectivityConstraintSatisfied.set(mNetworkConnected); - jobStatus.unmeteredConstraintSatisfied.set(mNetworkUnmetered); - mTrackedJobs.add(jobStatus); - } + jobStatus.setConnectivityConstraintSatisfied(mNetworkConnected); + jobStatus.setUnmeteredConstraintSatisfied(mNetworkUnmetered); + mTrackedJobs.add(jobStatus); } } @Override - public void maybeStopTrackingJob(JobStatus jobStatus, boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, boolean forUpdate) { if (jobStatus.hasConnectivityConstraint() || jobStatus.hasUnmeteredConstraint()) { - synchronized (mLock) { - mTrackedJobs.remove(jobStatus); - } + mTrackedJobs.remove(jobStatus); } } @@ -112,12 +108,8 @@ public class ConnectivityController extends StateController implements if (js.getUserId() != userId) { continue; } - boolean prevIsConnected = - js.connectivityConstraintSatisfied.getAndSet(mNetworkConnected); - boolean prevIsMetered = js.unmeteredConstraintSatisfied.getAndSet(mNetworkUnmetered); - if (prevIsConnected != mNetworkConnected || prevIsMetered != mNetworkUnmetered) { - changed = true; - } + changed |= js.setConnectivityConstraintSatisfied(mNetworkConnected); + changed |= js.setUnmeteredConstraintSatisfied(mNetworkUnmetered); } if (changed) { mStateChangedListener.onControllerStateChanged(); @@ -189,7 +181,7 @@ public class ConnectivityController extends StateController implements }; @Override - public void dumpControllerState(PrintWriter pw) { + public void dumpControllerStateLocked(PrintWriter pw) { pw.println("Conn."); pw.println("connected: " + mNetworkConnected + " unmetered: " + mNetworkUnmetered); for (JobStatus js: mTrackedJobs) { diff --git a/services/core/java/com/android/server/job/controllers/ContentObserverController.java b/services/core/java/com/android/server/job/controllers/ContentObserverController.java index af994f037af76..c5cf30f4739dc 100644 --- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java +++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java @@ -75,87 +75,81 @@ public class ContentObserverController extends StateController { } @Override - public void maybeStartTrackingJob(JobStatus taskStatus, JobStatus lastJob) { + public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { if (taskStatus.hasContentTriggerConstraint()) { - synchronized (mLock) { - if (taskStatus.contentObserverJobInstance == null) { - taskStatus.contentObserverJobInstance = new JobInstance(taskStatus); - } - mTrackedTasks.add(taskStatus); - boolean havePendingUris = false; - // If there is a previous job associated with the new job, propagate over - // any pending content URI trigger reports. - if (lastJob != null && lastJob.contentObserverJobInstance != null - && lastJob.contentObserverJobInstance - != taskStatus.contentObserverJobInstance - && lastJob.contentObserverJobInstance.mChangedAuthorities != null) { - havePendingUris = true; + if (taskStatus.contentObserverJobInstance == null) { + taskStatus.contentObserverJobInstance = new JobInstance(taskStatus); + } + mTrackedTasks.add(taskStatus); + boolean havePendingUris = false; + // If there is a previous job associated with the new job, propagate over + // any pending content URI trigger reports. + if (lastJob != null && lastJob.contentObserverJobInstance != null + && lastJob.contentObserverJobInstance + != taskStatus.contentObserverJobInstance + && lastJob.contentObserverJobInstance.mChangedAuthorities != null) { + havePendingUris = true; + taskStatus.contentObserverJobInstance.mChangedAuthorities + = lastJob.contentObserverJobInstance.mChangedAuthorities; + taskStatus.contentObserverJobInstance.mChangedUris + = lastJob.contentObserverJobInstance.mChangedUris; + lastJob.contentObserverJobInstance.mChangedAuthorities = null; + lastJob.contentObserverJobInstance.mChangedUris = null; + } + // If we have previously reported changed authorities/uris, then we failed + // to complete the job with them so will re-record them to report again. + if (taskStatus.changedAuthorities != null) { + havePendingUris = true; + if (taskStatus.contentObserverJobInstance.mChangedAuthorities == null) { taskStatus.contentObserverJobInstance.mChangedAuthorities - = lastJob.contentObserverJobInstance.mChangedAuthorities; - taskStatus.contentObserverJobInstance.mChangedUris - = lastJob.contentObserverJobInstance.mChangedUris; - lastJob.contentObserverJobInstance.mChangedAuthorities = null; - lastJob.contentObserverJobInstance.mChangedUris = null; + = new ArraySet<>(); } - // If we have previously reported changed authorities/uris, then we failed - // to complete the job with them so will re-record them to report again. - if (taskStatus.changedAuthorities != null) { - havePendingUris = true; - if (taskStatus.contentObserverJobInstance.mChangedAuthorities == null) { - taskStatus.contentObserverJobInstance.mChangedAuthorities - = new ArraySet<>(); + for (String auth : taskStatus.changedAuthorities) { + taskStatus.contentObserverJobInstance.mChangedAuthorities.add(auth); + } + if (taskStatus.changedUris != null) { + if (taskStatus.contentObserverJobInstance.mChangedUris == null) { + taskStatus.contentObserverJobInstance.mChangedUris = new ArraySet<>(); } - for (String auth : taskStatus.changedAuthorities) { - taskStatus.contentObserverJobInstance.mChangedAuthorities.add(auth); + for (Uri uri : taskStatus.changedUris) { + taskStatus.contentObserverJobInstance.mChangedUris.add(uri); } - if (taskStatus.changedUris != null) { - if (taskStatus.contentObserverJobInstance.mChangedUris == null) { - taskStatus.contentObserverJobInstance.mChangedUris = new ArraySet<>(); - } - for (Uri uri : taskStatus.changedUris) { - taskStatus.contentObserverJobInstance.mChangedUris.add(uri); - } - } - taskStatus.changedAuthorities = null; - taskStatus.changedUris = null; } taskStatus.changedAuthorities = null; taskStatus.changedUris = null; - taskStatus.contentTriggerConstraintSatisfied.set(havePendingUris); + } + taskStatus.changedAuthorities = null; + taskStatus.changedUris = null; + taskStatus.setContentTriggerConstraintSatisfied(havePendingUris); + } + } + + @Override + public void prepareForExecutionLocked(JobStatus taskStatus) { + if (taskStatus.hasContentTriggerConstraint()) { + if (taskStatus.contentObserverJobInstance != null) { + taskStatus.changedUris = taskStatus.contentObserverJobInstance.mChangedUris; + taskStatus.changedAuthorities + = taskStatus.contentObserverJobInstance.mChangedAuthorities; + taskStatus.contentObserverJobInstance.mChangedUris = null; + taskStatus.contentObserverJobInstance.mChangedAuthorities = null; } } } @Override - public void prepareForExecution(JobStatus taskStatus) { + public void maybeStopTrackingJobLocked(JobStatus taskStatus, boolean forUpdate) { if (taskStatus.hasContentTriggerConstraint()) { - synchronized (mLock) { + if (!forUpdate) { + // We won't do this reset if being called for an update, because + // we know it will be immediately followed by maybeStartTrackingJobLocked... + // and we don't want to lose any content changes in-between. if (taskStatus.contentObserverJobInstance != null) { - taskStatus.changedUris = taskStatus.contentObserverJobInstance.mChangedUris; - taskStatus.changedAuthorities - = taskStatus.contentObserverJobInstance.mChangedAuthorities; - taskStatus.contentObserverJobInstance.mChangedUris = null; - taskStatus.contentObserverJobInstance.mChangedAuthorities = null; + taskStatus.contentObserverJobInstance.detach(); + taskStatus.contentObserverJobInstance = null; } } - } - } - - @Override - public void maybeStopTrackingJob(JobStatus taskStatus, boolean forUpdate) { - if (taskStatus.hasContentTriggerConstraint()) { - synchronized (mLock) { - if (!forUpdate) { - // We won't do this reset if being called for an update, because - // we know it will be immediately followed by maybeStartTrackingJob... - // and we don't want to lose any content changes in-between. - if (taskStatus.contentObserverJobInstance != null) { - taskStatus.contentObserverJobInstance.detach(); - taskStatus.contentObserverJobInstance = null; - } - } - mTrackedTasks.remove(taskStatus); - } + mTrackedTasks.remove(taskStatus); } } @@ -199,9 +193,7 @@ public class ContentObserverController extends StateController { inst.mChangedAuthorities = new ArraySet<>(); } inst.mChangedAuthorities.add(uri.getAuthority()); - boolean previous - = inst.mJobStatus.contentTriggerConstraintSatisfied.getAndSet(true); - if (!previous) { + if (inst.mJobStatus.setContentTriggerConstraintSatisfied(true)) { reportChange = true; } } @@ -255,50 +247,48 @@ public class ContentObserverController extends StateController { } @Override - public void dumpControllerState(PrintWriter pw) { + public void dumpControllerStateLocked(PrintWriter pw) { pw.println("Content."); - synchronized (mLock) { - Iterator it = mTrackedTasks.iterator(); - if (it.hasNext()) { - pw.print(String.valueOf(it.next().hashCode())); - } - while (it.hasNext()) { - pw.print("," + String.valueOf(it.next().hashCode())); - } - pw.println(); - int N = mObservers.size(); - if (N > 0) { - pw.println("URIs:"); - for (int i = 0; i < N; i++) { - ObserverInstance obs = mObservers.valueAt(i); - pw.print(" "); - pw.print(mObservers.keyAt(i)); - pw.println(":"); - pw.print(" "); - pw.println(obs); - pw.println(" Jobs:"); - int M = obs.mJobs.size(); - for (int j=0; j it = mTrackedTasks.iterator(); + if (it.hasNext()) { + pw.print(String.valueOf(it.next().hashCode())); + } + while (it.hasNext()) { + pw.print("," + String.valueOf(it.next().hashCode())); + } + pw.println(); + int N = mObservers.size(); + if (N > 0) { + pw.println("URIs:"); + for (int i = 0; i < N; i++) { + ObserverInstance obs = mObservers.valueAt(i); + pw.print(" "); + pw.print(mObservers.keyAt(i)); + pw.println(":"); + pw.print(" "); + pw.println(obs); + pw.println(" Jobs:"); + int M = obs.mJobs.size(); + for (int j=0; j changedUris; @@ -81,32 +93,18 @@ public class JobStatus { */ ContentObserverController.JobInstance contentObserverJobInstance; - /** - * Earliest point in the future at which this job will be eligible to run. A value of 0 - * indicates there is no delay constraint. See {@link #hasTimingDelayConstraint()}. - */ - private long earliestRunTimeElapsedMillis; - /** - * Latest point in the future at which this job must be run. A value of {@link Long#MAX_VALUE} - * indicates there is no deadline constraint. See {@link #hasDeadlineConstraint()}. - */ - private long latestRunTimeElapsedMillis; - /** How many times this job has failed, used to compute back-off. */ - private final int numFailures; - /** Provide a handle to the service that this job will be run on. */ public int getServiceToken() { return callingUid; } - private JobStatus(Object lock, JobInfo job, int callingUid, String sourcePackageName, - int sourceUserId, int numFailures) { - this.lock = lock; + private JobStatus(JobInfo job, int callingUid, String sourcePackageName, + int sourceUserId, int numFailures, long earliestRunTimeElapsedMillis, + long latestRunTimeElapsedMillis) { this.job = job; this.callingUid = callingUid; this.name = job.getService().flattenToShortString(); this.tag = "*job*/" + this.name; - this.numFailures = numFailures; int tempSourceUid = -1; if (sourceUserId != -1 && sourcePackageName != null) { @@ -126,40 +124,42 @@ public class JobStatus { this.sourceUserId = sourceUserId; this.sourcePackageName = sourcePackageName; } + + this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis; + this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; + this.numFailures = numFailures; + + int requiredConstraints = 0; + if (job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY) { + requiredConstraints |= CONSTRAINT_CONNECTIVITY; + } + if (job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED) { + requiredConstraints |= CONSTRAINT_UNMETERED; + } + if (job.isRequireCharging()) { + requiredConstraints |= CONSTRAINT_CHARGING; + } + if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) { + requiredConstraints |= CONSTRAINT_TIMING_DELAY; + } + if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) { + requiredConstraints |= CONSTRAINT_DEADLINE; + } + if (job.isRequireDeviceIdle()) { + requiredConstraints |= CONSTRAINT_IDLE; + } + if (job.getTriggerContentUris() != null) { + requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER; + } + this.requiredConstraints = requiredConstraints; } /** Copy constructor. */ public JobStatus(JobStatus jobStatus) { - this(jobStatus.lock, jobStatus.getJob(), jobStatus.getUid(), + this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), - jobStatus.getNumFailures()); - this.earliestRunTimeElapsedMillis = jobStatus.getEarliestRunTime(); - this.latestRunTimeElapsedMillis = jobStatus.getLatestRunTimeElapsed(); - } - - /** - * Create a newly scheduled job. - * @param callingUid Uid of the package that scheduled this job. - * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates - * the calling package is the source. - * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the - * calling userId. - */ - public JobStatus(Object lock, JobInfo job, int callingUid, String sourcePackageName, - int sourceUserId) { - this(lock, job, callingUid, sourcePackageName, sourceUserId, 0); - - final long elapsedNow = SystemClock.elapsedRealtime(); - - if (job.isPeriodic()) { - latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); - earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); - } else { - earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? - elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME; - latestRunTimeElapsedMillis = job.hasLateConstraint() ? - elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; - } + jobStatus.getNumFailures(), jobStatus.getEarliestRunTime(), + jobStatus.getLatestRunTimeElapsed()); } /** @@ -169,23 +169,43 @@ public class JobStatus { * wallclock runtime rather than resetting it on every boot. * We consider a freshly loaded job to no longer be in back-off. */ - public JobStatus(Object lock, JobInfo job, int callingUid, String sourcePackageName, + public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) { - this(lock, job, callingUid, sourcePackageName, sourceUserId, 0); - - this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis; - this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; + this(job, callingUid, sourcePackageName, sourceUserId, 0, earliestRunTimeElapsedMillis, + latestRunTimeElapsedMillis); } /** Create a new job to be rescheduled with the provided parameters. */ public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, long newLatestRuntimeElapsedMillis, int backoffAttempt) { - this(rescheduling.lock, rescheduling.job, rescheduling.getUid(), + this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), - rescheduling.getSourceUserId(), backoffAttempt); + rescheduling.getSourceUserId(), backoffAttempt, newEarliestRuntimeElapsedMillis, + newLatestRuntimeElapsedMillis); + } - earliestRunTimeElapsedMillis = newEarliestRuntimeElapsedMillis; - latestRunTimeElapsedMillis = newLatestRuntimeElapsedMillis; + /** + * Create a newly scheduled job. + * @param callingUid Uid of the package that scheduled this job. + * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates + * the calling package is the source. + * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the + */ + public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName, + int sourceUserId) { + final long elapsedNow = SystemClock.elapsedRealtime(); + final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis; + if (job.isPeriodic()) { + latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis(); + earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis(); + } else { + earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ? + elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME; + latestRunTimeElapsedMillis = job.hasLateConstraint() ? + elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME; + } + return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, 0, + earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis); } public JobInfo getJob() { @@ -241,31 +261,31 @@ public class JobStatus { } public boolean hasConnectivityConstraint() { - return job.getNetworkType() == JobInfo.NETWORK_TYPE_ANY; + return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0; } public boolean hasUnmeteredConstraint() { - return job.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED; + return (requiredConstraints&CONSTRAINT_UNMETERED) != 0; } public boolean hasChargingConstraint() { - return job.isRequireCharging(); + return (requiredConstraints&CONSTRAINT_CHARGING) != 0; } public boolean hasTimingDelayConstraint() { - return earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME; + return (requiredConstraints&CONSTRAINT_TIMING_DELAY) != 0; } public boolean hasDeadlineConstraint() { - return latestRunTimeElapsedMillis != NO_LATEST_RUNTIME; + return (requiredConstraints&CONSTRAINT_DEADLINE) != 0; } public boolean hasIdleConstraint() { - return job.isRequireDeviceIdle(); + return (requiredConstraints&CONSTRAINT_IDLE) != 0; } public boolean hasContentTriggerConstraint() { - return job.getTriggerContentUris() != null; + return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0; } public boolean isPersisted() { @@ -280,35 +300,74 @@ public class JobStatus { return latestRunTimeElapsedMillis; } + boolean setChargingConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_CHARGING, state); + } + + boolean setTimingDelayConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_TIMING_DELAY, state); + } + + boolean setDeadlineConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_DEADLINE, state); + } + + boolean setIdleConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_IDLE, state); + } + + boolean setUnmeteredConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_UNMETERED, state); + } + + boolean setConnectivityConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state); + } + + boolean setAppNotIdleConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state); + } + + boolean setContentTriggerConstraintSatisfied(boolean state) { + return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state); + } + + boolean setConstraintSatisfied(int constraint, boolean state) { + boolean old = (satisfiedConstraints&constraint) != 0; + if (old == state) { + return false; + } + satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); + return true; + } + /** * @return Whether or not this job is ready to run, based on its requirements. This is true if * the constraints are satisfied or the deadline on the job has expired. */ public boolean isReady() { - synchronized (lock) { - // Deadline constraint trumps other constraints (except for periodic jobs where deadline - // (is an implementation detail. A periodic job should only run if it's constraints are - // satisfied). - // AppNotIdle implicit constraint trumps all! - return (isConstraintsSatisfied() - || (!job.isPeriodic() - && hasDeadlineConstraint() && deadlineConstraintSatisfied.get())) - && appNotIdleConstraintSatisfied.get(); - } + // Deadline constraint trumps other constraints (except for periodic jobs where deadline + // (is an implementation detail. A periodic job should only run if it's constraints are + // satisfied). + // AppNotIdle implicit constraint trumps all! + return (isConstraintsSatisfied() + || (!job.isPeriodic() + && hasDeadlineConstraint() && (satisfiedConstraints&CONSTRAINT_DEADLINE) != 0)) + && (satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0; } + static final int CONSTRAINTS_OF_INTEREST = + CONSTRAINT_CHARGING | CONSTRAINT_TIMING_DELAY | + CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED | + CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER; + /** * @return Whether the constraints set on this job are satisfied. */ public boolean isConstraintsSatisfied() { - synchronized (lock) { - return (!hasChargingConstraint() || chargingConstraintSatisfied.get()) - && (!hasTimingDelayConstraint() || timeDelayConstraintSatisfied.get()) - && (!hasConnectivityConstraint() || connectivityConstraintSatisfied.get()) - && (!hasUnmeteredConstraint() || unmeteredConstraintSatisfied.get()) - && (!hasIdleConstraint() || idleConstraintSatisfied.get()) - && (!hasContentTriggerConstraint() || contentTriggerConstraintSatisfied.get()); - } + final int req = requiredConstraints & CONSTRAINTS_OF_INTEREST; + final int sat = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; + return (sat & req) == req; } public boolean matches(int uid, int jobId) { @@ -327,7 +386,7 @@ public class JobStatus { + ",I=" + job.isRequireDeviceIdle() + ",U=" + (job.getTriggerContentUris() != null) + ",F=" + numFailures + ",P=" + job.isPersisted() - + ",ANI=" + appNotIdleConstraintSatisfied.get() + + ",ANI=" + ((satisfiedConstraints&CONSTRAINT_APP_NOT_IDLE) != 0) + (isReady() ? "(READY)" : "") + "]"; } @@ -362,6 +421,33 @@ public class JobStatus { return sb.toString(); } + void dumpConstraints(PrintWriter pw, int constraints) { + if ((constraints&CONSTRAINT_CHARGING) != 0) { + pw.print(" CHARGING"); + } + if ((constraints&CONSTRAINT_TIMING_DELAY) != 0) { + pw.print(" TIMING_DELAY"); + } + if ((constraints&CONSTRAINT_DEADLINE) != 0) { + pw.print(" DEADLINE"); + } + if ((constraints&CONSTRAINT_IDLE) != 0) { + pw.print(" IDLE"); + } + if ((constraints&CONSTRAINT_UNMETERED) != 0) { + pw.print(" UNMETERED"); + } + if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) { + pw.print(" CONNECTIVITY"); + } + if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) { + pw.print(" APP_NOT_IDLE"); + } + if ((constraints&CONSTRAINT_CONTENT_TRIGGER) != 0) { + pw.print(" CONTENT_TRIGGER"); + } + } + // Dumpsys infrastructure public void dump(PrintWriter pw, String prefix) { pw.print(prefix); UserHandle.formatUid(pw, callingUid); @@ -426,39 +512,12 @@ public class JobStatus { if (job.hasLateConstraint()) { pw.print(prefix); pw.println(" Has late constraint"); } - pw.print(prefix); pw.println("Constraints:"); - if (hasChargingConstraint()) { - pw.print(prefix); pw.print(" Charging: "); - pw.println(chargingConstraintSatisfied.get()); - } - if (hasTimingDelayConstraint()) { - pw.print(prefix); pw.print(" Time delay: "); - pw.println(timeDelayConstraintSatisfied.get()); - } - if (hasDeadlineConstraint()) { - pw.print(prefix); pw.print(" Deadline: "); - pw.println(deadlineConstraintSatisfied.get()); - } - if (hasIdleConstraint()) { - pw.print(prefix); pw.print(" System idle: "); - pw.println(idleConstraintSatisfied.get()); - } - if (hasUnmeteredConstraint()) { - pw.print(prefix); pw.print(" Unmetered: "); - pw.println(unmeteredConstraintSatisfied.get()); - } - if (hasConnectivityConstraint()) { - pw.print(prefix); pw.print(" Connectivity: "); - pw.println(connectivityConstraintSatisfied.get()); - } - if (hasIdleConstraint()) { - pw.print(prefix); pw.print(" App not idle: "); - pw.println(appNotIdleConstraintSatisfied.get()); - } - if (hasContentTriggerConstraint()) { - pw.print(prefix); pw.print(" Content trigger: "); - pw.println(contentTriggerConstraintSatisfied.get()); - } + pw.print(prefix); pw.print("Required constraints:"); + dumpConstraints(pw, requiredConstraints); + pw.println(); + pw.print(prefix); pw.print("Satisfied constraints:"); + dumpConstraints(pw, satisfiedConstraints); + pw.println(); if (changedAuthorities != null) { pw.print(prefix); pw.println("Changed authorities:"); for (int i=0; i it = mTrackedJobs.listIterator(mTrackedJobs.size()); - while (it.hasPrevious()) { - JobStatus ts = it.previous(); - if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) { - // Insert - isInsert = true; - break; - } + public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) { + if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) { + maybeStopTrackingJobLocked(job, false); + boolean isInsert = false; + ListIterator it = mTrackedJobs.listIterator(mTrackedJobs.size()); + while (it.hasPrevious()) { + JobStatus ts = it.previous(); + if (ts.getLatestRunTimeElapsed() < job.getLatestRunTimeElapsed()) { + // Insert + isInsert = true; + break; } - if (isInsert) { - it.next(); - } - it.add(job); - maybeUpdateAlarms( - job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE, - job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE); } + if (isInsert) { + it.next(); + } + it.add(job); + maybeUpdateAlarmsLocked( + job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE, + job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE); } } @@ -103,12 +101,10 @@ public class TimeController extends StateController { * Really an == comparison should be enough, but why play with fate? We'll do <=. */ @Override - public void maybeStopTrackingJob(JobStatus job, boolean forUpdate) { - synchronized (mLock) { - if (mTrackedJobs.remove(job)) { - checkExpiredDelaysAndResetAlarm(); - checkExpiredDeadlinesAndResetAlarm(); - } + public void maybeStopTrackingJobLocked(JobStatus job, boolean forUpdate) { + if (mTrackedJobs.remove(job)) { + checkExpiredDelaysAndResetAlarm(); + checkExpiredDeadlinesAndResetAlarm(); } } @@ -118,14 +114,14 @@ public class TimeController extends StateController { * the job's deadline is fulfilled - unlike other controllers a time constraint can't toggle * back and forth. */ - private boolean canStopTrackingJob(JobStatus job) { + private boolean canStopTrackingJobLocked(JobStatus job) { return (!job.hasTimingDelayConstraint() || - job.timeDelayConstraintSatisfied.get()) && + (job.satisfiedConstraints&JobStatus.CONSTRAINT_TIMING_DELAY) != 0) && (!job.hasDeadlineConstraint() || - job.deadlineConstraintSatisfied.get()); + (job.satisfiedConstraints&JobStatus.CONSTRAINT_DEADLINE) != 0); } - private void ensureAlarmService() { + private void ensureAlarmServiceLocked() { if (mAlarmService == null) { mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); } @@ -149,7 +145,7 @@ public class TimeController extends StateController { final long jobDeadline = job.getLatestRunTimeElapsed(); if (jobDeadline <= nowElapsedMillis) { - job.deadlineConstraintSatisfied.set(true); + job.setDeadlineConstraintSatisfied(true); mStateChangedListener.onRunJobNow(job); it.remove(); } else { // Sorted by expiry time, so take the next one and stop. @@ -157,7 +153,7 @@ public class TimeController extends StateController { break; } } - setDeadlineExpiredAlarm(nextExpiryTime); + setDeadlineExpiredAlarmLocked(nextExpiryTime); } } @@ -178,8 +174,8 @@ public class TimeController extends StateController { } final long jobDelayTime = job.getEarliestRunTime(); if (jobDelayTime <= nowElapsedMillis) { - job.timeDelayConstraintSatisfied.set(true); - if (canStopTrackingJob(job)) { + job.setTimingDelayConstraintSatisfied(true); + if (canStopTrackingJobLocked(job)) { it.remove(); } if (job.isReady()) { @@ -194,16 +190,16 @@ public class TimeController extends StateController { if (ready) { mStateChangedListener.onControllerStateChanged(); } - setDelayExpiredAlarm(nextDelayTime); + setDelayExpiredAlarmLocked(nextDelayTime); } } - private void maybeUpdateAlarms(long delayExpiredElapsed, long deadlineExpiredElapsed) { + private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed) { if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) { - setDelayExpiredAlarm(delayExpiredElapsed); + setDelayExpiredAlarmLocked(delayExpiredElapsed); } if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) { - setDeadlineExpiredAlarm(deadlineExpiredElapsed); + setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed); } } @@ -212,10 +208,11 @@ public class TimeController extends StateController { * delay will expire. * This alarm will wake up the phone. */ - private void setDelayExpiredAlarm(long alarmTimeElapsedMillis) { + private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis; - updateAlarmWithListener(DELAY_TAG, mNextDelayExpiredListener, mNextDelayExpiredElapsedMillis); + updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener, + mNextDelayExpiredElapsedMillis); } /** @@ -223,10 +220,11 @@ public class TimeController extends StateController { * deadline will expire. * This alarm will wake up the phone. */ - private void setDeadlineExpiredAlarm(long alarmTimeElapsedMillis) { + private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis) { alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis); mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis; - updateAlarmWithListener(DEADLINE_TAG, mDeadlineExpiredListener, mNextJobExpiredElapsedMillis); + updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener, + mNextJobExpiredElapsedMillis); } private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) { @@ -237,9 +235,9 @@ public class TimeController extends StateController { return proposedAlarmTimeElapsedMillis; } - private void updateAlarmWithListener(String tag, OnAlarmListener listener, + private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener, long alarmTimeElapsed) { - ensureAlarmService(); + ensureAlarmServiceLocked(); if (alarmTimeElapsed == Long.MAX_VALUE) { mAlarmService.cancel(listener); } else { @@ -274,7 +272,7 @@ public class TimeController extends StateController { }; @Override - public void dumpControllerState(PrintWriter pw) { + public void dumpControllerStateLocked(PrintWriter pw) { final long nowElapsed = SystemClock.elapsedRealtime(); pw.println("Alarms (" + SystemClock.elapsedRealtime() + ")"); pw.println( diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index fbcd156c99759..c7860368289a7 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -58,7 +58,7 @@ public class JobStoreTest extends AndroidTestCase { .setMinimumLatency(runFromMillis) .setPersisted(true) .build(); - final JobStatus ts = new JobStatus(this, task, SOME_UID, null, -1); + final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1); mTaskStoreUnderTest.add(ts); Thread.sleep(IO_WAIT); // Manually load tasks from xml file. @@ -91,8 +91,8 @@ public class JobStoreTest extends AndroidTestCase { .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setPersisted(true) .build(); - final JobStatus taskStatus1 = new JobStatus(this, task1, SOME_UID, null, -1); - final JobStatus taskStatus2 = new JobStatus(this, task2, SOME_UID, null, -1); + final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, SOME_UID, null, -1); + final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1); mTaskStoreUnderTest.add(taskStatus1); mTaskStoreUnderTest.add(taskStatus2); Thread.sleep(IO_WAIT); @@ -140,7 +140,7 @@ public class JobStoreTest extends AndroidTestCase { extras.putInt("into", 3); b.setExtras(extras); final JobInfo task = b.build(); - JobStatus taskStatus = new JobStatus(this, task, SOME_UID, null, -1); + JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1); mTaskStoreUnderTest.add(taskStatus); Thread.sleep(IO_WAIT); @@ -157,7 +157,7 @@ public class JobStoreTest extends AndroidTestCase { .setPeriodic(10000L) .setRequiresCharging(true) .setPersisted(true); - JobStatus taskStatus = new JobStatus(this, b.build(), SOME_UID, + JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, "com.google.android.gms", 0); mTaskStoreUnderTest.add(taskStatus); @@ -179,7 +179,7 @@ public class JobStoreTest extends AndroidTestCase { .setPeriodic(5*60*60*1000, 1*60*60*1000) .setRequiresCharging(true) .setPersisted(true); - JobStatus taskStatus = new JobStatus(this, b.build(), SOME_UID, null, -1); + JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1); mTaskStoreUnderTest.add(taskStatus); Thread.sleep(IO_WAIT); @@ -204,7 +204,7 @@ public class JobStoreTest extends AndroidTestCase { SystemClock.elapsedRealtime() + (TWO_HOURS * ONE_HOUR) + TWO_HOURS; // > period+flex final long invalidEarlyRuntimeElapsedMillis = invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period). - final JobStatus js = new JobStatus(this, b.build(), SOME_UID, "somePackage", + final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage", 0 /* sourceUserId */, invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis); @@ -231,7 +231,7 @@ public class JobStoreTest extends AndroidTestCase { .setOverrideDeadline(5000) .setPriority(42) .setPersisted(true); - final JobStatus js = new JobStatus(this, b.build(), SOME_UID, null, -1); + final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1); mTaskStoreUnderTest.add(js); Thread.sleep(IO_WAIT); final ArraySet jobStatusSet = new ArraySet(); @@ -247,12 +247,12 @@ public class JobStoreTest extends AndroidTestCase { JobInfo.Builder b = new Builder(42, mComponent) .setOverrideDeadline(10000) .setPersisted(false); - JobStatus jsNonPersisted = new JobStatus(this, b.build(), SOME_UID, null, -1); + JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1); mTaskStoreUnderTest.add(jsNonPersisted); b = new Builder(43, mComponent) .setOverrideDeadline(10000) .setPersisted(true); - JobStatus jsPersisted = new JobStatus(this, b.build(), SOME_UID, null, -1); + JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1); mTaskStoreUnderTest.add(jsPersisted); Thread.sleep(IO_WAIT); final ArraySet jobStatusSet = new ArraySet();