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();