Merge "More work on issue #26390151: Add new JobScheduler API..." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index cf2801a..baef0a4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6313,6 +6313,8 @@
method public static final long getMinimumPeriod();
method public int getNetworkType();
method public android.content.ComponentName getService();
+ method public long getTriggerContentMaxDelay();
+ method public long getTriggerContentUpdateDelay();
method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
method public boolean isPeriodic();
method public boolean isPersisted();
@@ -6343,6 +6345,8 @@
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+ method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
+ method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
}
public static final class JobInfo.TriggerContentUri implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 19474ed..d8545f1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6581,6 +6581,8 @@
method public static final long getMinimumPeriod();
method public int getNetworkType();
method public android.content.ComponentName getService();
+ method public long getTriggerContentMaxDelay();
+ method public long getTriggerContentUpdateDelay();
method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
method public boolean isPeriodic();
method public boolean isPersisted();
@@ -6611,6 +6613,8 @@
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+ method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
+ method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
}
public static final class JobInfo.TriggerContentUri implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 3e36c77..6a0f071 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6317,6 +6317,8 @@
method public static final long getMinimumPeriod();
method public int getNetworkType();
method public android.content.ComponentName getService();
+ method public long getTriggerContentMaxDelay();
+ method public long getTriggerContentUpdateDelay();
method public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
method public boolean isPeriodic();
method public boolean isPersisted();
@@ -6347,6 +6349,8 @@
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
method public android.app.job.JobInfo.Builder setRequiresDeviceIdle(boolean);
+ method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
+ method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
}
public static final class JobInfo.TriggerContentUri implements android.os.Parcelable {
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 09050b6..c84a0dc 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -158,6 +158,8 @@
private final boolean requireCharging;
private final boolean requireDeviceIdle;
private final TriggerContentUri[] triggerContentUris;
+ private final long triggerContentUpdateDelay;
+ private final long triggerContentMaxDelay;
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
private final int networkType;
@@ -221,6 +223,22 @@
}
/**
+ * When triggering on content URI changes, this is the delay from when a change
+ * is detected until the job is scheduled.
+ */
+ public long getTriggerContentUpdateDelay() {
+ return triggerContentUpdateDelay;
+ }
+
+ /**
+ * When triggering on content URI changes, this is the maximum delay we will
+ * use before scheduling the job.
+ */
+ public long getTriggerContentMaxDelay() {
+ return triggerContentMaxDelay;
+ }
+
+ /**
* One of {@link android.app.job.JobInfo#NETWORK_TYPE_ANY},
* {@link android.app.job.JobInfo#NETWORK_TYPE_NONE}, or
* {@link android.app.job.JobInfo#NETWORK_TYPE_UNMETERED}.
@@ -321,6 +339,8 @@
requireCharging = in.readInt() == 1;
requireDeviceIdle = in.readInt() == 1;
triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
+ triggerContentUpdateDelay = in.readLong();
+ triggerContentMaxDelay = in.readLong();
networkType = in.readInt();
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
@@ -344,6 +364,8 @@
triggerContentUris = b.mTriggerContentUris != null
? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()])
: null;
+ triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
+ triggerContentMaxDelay = b.mTriggerContentMaxDelay;
networkType = b.mNetworkType;
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
@@ -371,6 +393,8 @@
out.writeInt(requireCharging ? 1 : 0);
out.writeInt(requireDeviceIdle ? 1 : 0);
out.writeTypedArray(triggerContentUris, flags);
+ out.writeLong(triggerContentUpdateDelay);
+ out.writeLong(triggerContentMaxDelay);
out.writeInt(networkType);
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
@@ -482,6 +506,8 @@
private boolean mRequiresDeviceIdle;
private int mNetworkType;
private ArrayList<TriggerContentUri> mTriggerContentUris;
+ private long mTriggerContentUpdateDelay = -1;
+ private long mTriggerContentMaxDelay = -1;
private boolean mIsPersisted;
// One-off parameters.
private long mMinLatencyMillis;
@@ -588,6 +614,27 @@
}
/**
+ * Set the delay (in milliseconds) from when a content change is detected until
+ * the job is scheduled. If there are more changes during that time, the delay
+ * will be reset to start at the time of the most recent change.
+ * @param durationMs Delay after most recent content change, in milliseconds.
+ */
+ public Builder setTriggerContentUpdateDelay(long durationMs) {
+ mTriggerContentUpdateDelay = durationMs;
+ return this;
+ }
+
+ /**
+ * Set the maximum total delay (in milliseconds) that is allowed from the first
+ * time a content change is detected until the job is scheduled.
+ * @param durationMs Delay after initial content change, in milliseconds.
+ */
+ public Builder setTriggerContentMaxDelay(long durationMs) {
+ mTriggerContentMaxDelay = durationMs;
+ return this;
+ }
+
+ /**
* Specify that this job should recur with the provided interval, not more than once per
* period. You have no control over when within this interval this job will be executed,
* only the guarantee that it will be executed at most once within this interval.
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index c4a2c731..7df8ffd 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -257,6 +257,10 @@
return mLock;
}
+ public JobStore getJobStore() {
+ return mJobs;
+ }
+
@Override
public void onStartUser(int userHandle) {
mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
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 d8490d4..02bc36ca 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -22,6 +22,7 @@
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobStore;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
@@ -41,10 +42,52 @@
// Singleton factory
private static Object sCreationLock = new Object();
private static volatile AppIdleController sController;
- final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
+ private final JobSchedulerService mJobSchedulerService;
private final UsageStatsManagerInternal mUsageStatsInternal;
boolean mAppIdleParoleOn;
+ final class GlobalUpdateFunc implements JobStore.JobStatusFunctor {
+ boolean mChanged;
+
+ @Override public void process(JobStatus jobStatus) {
+ String packageName = jobStatus.getSourcePackageName();
+ final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
+ jobStatus.getSourceUid(), jobStatus.getSourceUserId());
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
+ }
+ if (jobStatus.setAppNotIdleConstraintSatisfied(!appIdle)) {
+ mChanged = true;
+ }
+ }
+ };
+
+ final static class PackageUpdateFunc implements JobStore.JobStatusFunctor {
+ final int mUserId;
+ final String mPackage;
+ final boolean mIdle;
+ boolean mChanged;
+
+ PackageUpdateFunc(int userId, String pkg, boolean idle) {
+ mUserId = userId;
+ mPackage = pkg;
+ mIdle = idle;
+ }
+
+ @Override public void process(JobStatus jobStatus) {
+ if (jobStatus.getSourcePackageName().equals(mPackage)
+ && jobStatus.getSourceUserId() == mUserId) {
+ if (jobStatus.setAppNotIdleConstraintSatisfied(!mIdle)) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
+ + mPackage + " to " + mIdle);
+ }
+ mChanged = true;
+ }
+ }
+ }
+ };
+
public static AppIdleController get(JobSchedulerService service) {
synchronized (sCreationLock) {
if (sController == null) {
@@ -55,9 +98,9 @@
}
}
- private AppIdleController(StateChangedListener stateChangedListener, Context context,
- Object lock) {
- super(stateChangedListener, context, lock);
+ private AppIdleController(JobSchedulerService service, Context context, Object lock) {
+ super(service, context, lock);
+ mJobSchedulerService = service;
mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class);
mAppIdleParoleOn = mUsageStatsInternal.isAppIdleParoleOn();
mUsageStatsInternal.addAppIdleStateChangeListener(new AppIdleStateChangeListener());
@@ -65,7 +108,6 @@
@Override
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());
@@ -78,19 +120,20 @@
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
- mTrackedTasks.remove(jobStatus);
}
@Override
- public void dumpControllerStateLocked(PrintWriter pw) {
+ public void dumpControllerStateLocked(final PrintWriter pw) {
pw.println("AppIdle");
pw.println("Parole On: " + mAppIdleParoleOn);
- for (JobStatus task : mTrackedTasks) {
- pw.print(task.getSourcePackageName());
- pw.print(":runnable="
- + ((task.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0));
- pw.print(", ");
- }
+ mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
+ @Override public void process(JobStatus jobStatus) {
+ pw.print(" ");
+ pw.print(jobStatus.getSourcePackageName());
+ pw.print(": runnable=");
+ pw.println((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
+ }
+ });
pw.println();
}
@@ -102,16 +145,10 @@
return;
}
mAppIdleParoleOn = isAppIdleParoleOn;
- for (JobStatus task : mTrackedTasks) {
- String packageName = task.getSourcePackageName();
- final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
- task.getSourceUid(), task.getSourceUserId());
- if (DEBUG) {
- Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
- }
- if (task.setAppNotIdleConstraintSatisfied(!appIdle)) {
- changed = true;
- }
+ GlobalUpdateFunc update = new GlobalUpdateFunc();
+ mJobSchedulerService.getJobStore().forEachJob(update);
+ if (update.mChanged) {
+ changed = true;
}
}
if (changed) {
@@ -128,17 +165,10 @@
if (mAppIdleParoleOn) {
return;
}
- for (JobStatus task : mTrackedTasks) {
- if (task.getSourcePackageName().equals(packageName)
- && task.getSourceUserId() == userId) {
- if (task.setAppNotIdleConstraintSatisfied(!idle)) {
- if (DEBUG) {
- Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
- + packageName + " to " + idle);
- }
- changed = true;
- }
- }
+ PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
+ mJobSchedulerService.getJobStore().forEachJob(update);
+ if (update.mChanged) {
+ changed = true;
}
}
if (changed) {
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 b2f1958..c5b1a3d 100644
--- a/services/core/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/services/core/java/com/android/server/job/controllers/ContentObserverController.java
@@ -21,6 +21,7 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.util.TimeUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -46,12 +47,17 @@
*/
private static final int MAX_URIS_REPORTED = 50;
+ /**
+ * At this point we consider it urgent to schedule the job ASAP.
+ */
+ private static final int URIS_URGENT_THRESHOLD = 40;
+
private static final Object sCreationLock = new Object();
private static volatile ContentObserverController sController;
final private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
ArrayMap<Uri, ObserverInstance> mObservers = new ArrayMap<>();
- final Handler mHandler = new Handler();
+ final Handler mHandler;
public static ContentObserverController get(JobSchedulerService taskManagerService) {
synchronized (sCreationLock) {
@@ -72,6 +78,7 @@
private ContentObserverController(StateChangedListener stateChangedListener, Context context,
Object lock) {
super(stateChangedListener, context, lock);
+ mHandler = new Handler(context.getMainLooper());
}
@Override
@@ -113,6 +120,11 @@
taskStatus.changedUris = null;
taskStatus.setContentTriggerConstraintSatisfied(havePendingUris);
}
+ if (lastJob != null && lastJob.contentObserverJobInstance != null) {
+ // And now we can detach the instance state from the last job.
+ lastJob.contentObserverJobInstance.detachLocked();
+ lastJob.contentObserverJobInstance = null;
+ }
}
@Override
@@ -133,30 +145,33 @@
boolean forUpdate) {
if (taskStatus.hasContentTriggerConstraint()) {
if (taskStatus.contentObserverJobInstance != null) {
- if (incomingJob != null && taskStatus.contentObserverJobInstance != null
- && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
- // We are stopping this job, but it is going to be replaced by this given
- // incoming job. We want to propagate our state over to it, so we don't
- // lose any content changes that had happend since the last one started.
- // If there is a previous job associated with the new job, propagate over
- // any pending content URI trigger reports.
- if (incomingJob.contentObserverJobInstance == null) {
- incomingJob.contentObserverJobInstance = new JobInstance(incomingJob);
+ taskStatus.contentObserverJobInstance.unscheduleLocked();
+ if (incomingJob != null) {
+ if (taskStatus.contentObserverJobInstance != null
+ && taskStatus.contentObserverJobInstance.mChangedAuthorities != null) {
+ // We are stopping this job, but it is going to be replaced by this given
+ // incoming job. We want to propagate our state over to it, so we don't
+ // lose any content changes that had happend since the last one started.
+ // If there is a previous job associated with the new job, propagate over
+ // any pending content URI trigger reports.
+ if (incomingJob.contentObserverJobInstance == null) {
+ incomingJob.contentObserverJobInstance = new JobInstance(incomingJob);
+ }
+ incomingJob.contentObserverJobInstance.mChangedAuthorities
+ = taskStatus.contentObserverJobInstance.mChangedAuthorities;
+ incomingJob.contentObserverJobInstance.mChangedUris
+ = taskStatus.contentObserverJobInstance.mChangedUris;
+ taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
+ taskStatus.contentObserverJobInstance.mChangedUris = null;
}
- incomingJob.contentObserverJobInstance.mChangedAuthorities
- = taskStatus.contentObserverJobInstance.mChangedAuthorities;
- incomingJob.contentObserverJobInstance.mChangedUris
- = taskStatus.contentObserverJobInstance.mChangedUris;
- taskStatus.contentObserverJobInstance.mChangedAuthorities = null;
- taskStatus.contentObserverJobInstance.mChangedUris = null;
+ // We won't detach the content observers here, because we want to
+ // allow them to continue monitoring so we don't miss anything... and
+ // since we are giving an incomingJob here, we know this will be
+ // immediately followed by a start tracking of that job.
} else {
- // 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.contentObserverJobInstance.detach();
- taskStatus.contentObserverJobInstance = null;
- }
+ // But here there is no incomingJob, so nothing coming up, so time to detach.
+ taskStatus.contentObserverJobInstance.detachLocked();
+ taskStatus.contentObserverJobInstance = null;
}
}
mTrackedTasks.remove(taskStatus);
@@ -177,9 +192,9 @@
}
}
- class ObserverInstance extends ContentObserver {
+ final class ObserverInstance extends ContentObserver {
final Uri mUri;
- final ArrayList<JobInstance> mJobs = new ArrayList<>();
+ final ArraySet<JobInstance> mJobs = new ArraySet<>();
public ObserverInstance(Handler handler, Uri uri) {
super(handler);
@@ -188,11 +203,10 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
- boolean reportChange = false;
synchronized (mLock) {
final int N = mJobs.size();
for (int i=0; i<N; i++) {
- JobInstance inst = mJobs.get(i);
+ JobInstance inst = mJobs.valueAt(i);
if (inst.mChangedUris == null) {
inst.mChangedUris = new ArraySet<>();
}
@@ -203,26 +217,38 @@
inst.mChangedAuthorities = new ArraySet<>();
}
inst.mChangedAuthorities.add(uri.getAuthority());
- if (inst.mJobStatus.setContentTriggerConstraintSatisfied(true)) {
- reportChange = true;
- }
+ inst.scheduleLocked();
}
}
- // Let the scheduler know that state has changed. This may or may not result in an
- // execution.
- if (reportChange) {
- mStateChangedListener.onControllerStateChanged();
- }
}
}
- class JobInstance extends ArrayList<ObserverInstance> {
- private final JobStatus mJobStatus;
- private ArraySet<Uri> mChangedUris;
- private ArraySet<String> mChangedAuthorities;
+ static final class TriggerRunnable implements Runnable {
+ final JobInstance mInstance;
+
+ TriggerRunnable(JobInstance instance) {
+ mInstance = instance;
+ }
+
+ @Override public void run() {
+ mInstance.trigger();
+ }
+ }
+
+ final class JobInstance {
+ final ArrayList<ObserverInstance> mMyObservers = new ArrayList<>();
+ final JobStatus mJobStatus;
+ final Runnable mExecuteRunner;
+ final Runnable mTimeoutRunner;
+ ArraySet<Uri> mChangedUris;
+ ArraySet<String> mChangedAuthorities;
+
+ boolean mTriggerPending;
JobInstance(JobStatus jobStatus) {
mJobStatus = jobStatus;
+ mExecuteRunner = new TriggerRunnable(this);
+ mTimeoutRunner = new TriggerRunnable(this);
final JobInfo.TriggerContentUri[] uris = jobStatus.getJob().getTriggerContentUris();
if (uris != null) {
for (JobInfo.TriggerContentUri uri : uris) {
@@ -238,15 +264,54 @@
obs);
}
obs.mJobs.add(this);
- add(obs);
+ mMyObservers.add(obs);
}
}
}
- void detach() {
- final int N = size();
+ void trigger() {
+ boolean reportChange = false;
+ synchronized (mLock) {
+ if (mTriggerPending) {
+ if (mJobStatus.setContentTriggerConstraintSatisfied(true)) {
+ reportChange = true;
+ }
+ unscheduleLocked();
+ }
+ }
+ // Let the scheduler know that state has changed. This may or may not result in an
+ // execution.
+ if (reportChange) {
+ mStateChangedListener.onControllerStateChanged();
+ }
+ }
+
+ void scheduleLocked() {
+ if (!mTriggerPending) {
+ mTriggerPending = true;
+ mHandler.postDelayed(mTimeoutRunner, mJobStatus.getTriggerContentMaxDelay());
+ }
+ mHandler.removeCallbacks(mExecuteRunner);
+ if (mChangedUris.size() >= URIS_URGENT_THRESHOLD) {
+ // If we start getting near the limit, GO NOW!
+ mHandler.post(mExecuteRunner);
+ } else {
+ mHandler.postDelayed(mExecuteRunner, mJobStatus.getTriggerContentUpdateDelay());
+ }
+ }
+
+ void unscheduleLocked() {
+ if (mTriggerPending) {
+ mHandler.removeCallbacks(mExecuteRunner);
+ mHandler.removeCallbacks(mTimeoutRunner);
+ mTriggerPending = false;
+ }
+ }
+
+ void detachLocked() {
+ final int N = mMyObservers.size();
for (int i=0; i<N; i++) {
- final ObserverInstance obs = get(i);
+ final ObserverInstance obs = mMyObservers.get(i);
obs.mJobs.remove(this);
if (obs.mJobs.size() == 0) {
mContext.getContentResolver().unregisterContentObserver(obs);
@@ -259,39 +324,54 @@
@Override
public void dumpControllerStateLocked(PrintWriter pw) {
pw.println("Content.");
+ boolean printed = false;
Iterator<JobStatus> it = mTrackedTasks.iterator();
- if (it.hasNext()) {
- pw.print(String.valueOf(it.next().hashCode()));
- }
while (it.hasNext()) {
- pw.print("," + String.valueOf(it.next().hashCode()));
+ if (!printed) {
+ pw.print(" ");
+ printed = true;
+ } else {
+ pw.print(",");
+ }
+ pw.print(System.identityHashCode(it.next()));
}
- pw.println();
+ if (printed) {
+ pw.println();
+ }
int N = mObservers.size();
if (N > 0) {
- pw.println("URIs:");
+ pw.println(" Observers:");
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:");
+ pw.print(mObservers.keyAt(i));
+ pw.print(" (");
+ pw.print(System.identityHashCode(obs));
+ pw.println("):");
+ pw.println(" Jobs:");
int M = obs.mJobs.size();
for (int j=0; j<M; j++) {
- JobInstance inst = obs.mJobs.get(j);
- pw.print(" ");
- pw.print(inst.hashCode());
+ JobInstance inst = obs.mJobs.valueAt(j);
+ pw.print(" ");
+ pw.print(System.identityHashCode(inst.mJobStatus));
if (inst.mChangedAuthorities != null) {
pw.println(":");
- pw.println(" Changed Authorities:");
+ if (inst.mTriggerPending) {
+ pw.print(" Trigger pending: update=");
+ TimeUtils.formatDuration(
+ inst.mJobStatus.getTriggerContentUpdateDelay(), pw);
+ pw.print(", max=");
+ TimeUtils.formatDuration(
+ inst.mJobStatus.getTriggerContentMaxDelay(), pw);
+ pw.println();
+ }
+ pw.println(" Changed Authorities:");
for (int k=0; k<inst.mChangedAuthorities.size(); k++) {
pw.print(" ");
pw.println(inst.mChangedAuthorities.valueAt(k));
}
if (inst.mChangedUris != null) {
- pw.println(" Changed URIs:");
+ pw.println(" Changed URIs:");
for (int k = 0; k<inst.mChangedUris.size(); k++) {
pw.print(" ");
pw.println(inst.mChangedUris.valueAt(k));
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 64887e8..fe563d2 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -28,6 +28,7 @@
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobStore;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
@@ -45,9 +46,9 @@
// Singleton factory
private static Object sCreationLock = new Object();
- final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private static DeviceIdleJobsController sController;
+ private final JobSchedulerService mJobSchedulerService;
private final PowerManager mPowerManager;
private final DeviceIdleController.LocalService mLocalDeviceIdleController;
@@ -57,6 +58,12 @@
private boolean mDeviceIdleMode;
private int[] mDeviceIdleWhitelistAppIds;
+ final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
+ @Override public void process(JobStatus jobStatus) {
+ updateTaskStateLocked(jobStatus);
+ }
+ };
+
/**
* Returns a singleton for the DeviceIdleJobsController
*/
@@ -87,10 +94,11 @@
}
};
- private DeviceIdleJobsController(StateChangedListener stateChangedListener, Context context,
+ private DeviceIdleJobsController(JobSchedulerService jobSchedulerService, Context context,
Object lock) {
- super(stateChangedListener, context, lock);
+ super(jobSchedulerService, context, lock);
+ mJobSchedulerService = jobSchedulerService;
// Register for device idle mode changes
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mLocalDeviceIdleController =
@@ -115,9 +123,7 @@
}
mDeviceIdleMode = enabled;
if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
- for (JobStatus task : mTrackedTasks) {
- updateTaskStateLocked(task);
- }
+ mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
}
// Inform the job scheduler service about idle mode changes
if (changed) {
@@ -160,25 +166,26 @@
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
synchronized (mLock) {
- mTrackedTasks.add(jobStatus);
updateTaskStateLocked(jobStatus);
}
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
- mTrackedTasks.remove(jobStatus);
}
@Override
- public void dumpControllerStateLocked(PrintWriter pw) {
+ public void dumpControllerStateLocked(final PrintWriter pw) {
pw.println("DeviceIdleJobsController");
- for (JobStatus task : mTrackedTasks) {
- pw.print(task.getSourcePackageName());
- pw.print(":runnable="
- + ((task.satisfiedConstraints & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0));
- pw.print(", ");
- }
+ mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
+ @Override public void process(JobStatus jobStatus) {
+ pw.print(" ");
+ pw.print(jobStatus.getSourcePackageName());
+ pw.print(": runnable=");
+ pw.println((jobStatus.satisfiedConstraints
+ & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
+ }
+ });
pw.println();
}
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 39905d8..dd70758 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -61,6 +61,18 @@
// Full override: ignore all constraints including API-affecting like connectivity
public static final int OVERRIDE_FULL = 2;
+ /** If not specified, trigger update delay is 10 seconds. */
+ public static final long DEFAULT_TRIGGER_UPDATE_DELAY = 10*1000;
+
+ /** The minimum possible update delay is 1/2 second. */
+ public static final long MIN_TRIGGER_UPDATE_DELAY = 500;
+
+ /** If not specified, trigger maxumum delay is 2 minutes. */
+ public static final long DEFAULT_TRIGGER_MAX_DELAY = 2*60*1000;
+
+ /** The minimum possible update delay is 1 second. */
+ public static final long MIN_TRIGGER_MAX_DELAY = 1000;
+
final JobInfo job;
/** Uid of the package requesting this job. */
final int callingUid;
@@ -320,6 +332,22 @@
return (requiredConstraints&CONSTRAINT_CONTENT_TRIGGER) != 0;
}
+ public long getTriggerContentUpdateDelay() {
+ long time = job.getTriggerContentUpdateDelay();
+ if (time < 0) {
+ return DEFAULT_TRIGGER_UPDATE_DELAY;
+ }
+ return Math.max(time, MIN_TRIGGER_UPDATE_DELAY);
+ }
+
+ public long getTriggerContentMaxDelay() {
+ long time = job.getTriggerContentMaxDelay();
+ if (time < 0) {
+ return DEFAULT_TRIGGER_MAX_DELAY;
+ }
+ return Math.max(time, MIN_TRIGGER_MAX_DELAY);
+ }
+
public boolean isPersisted() {
return job.isPersisted();
}
@@ -540,6 +568,16 @@
pw.print(Integer.toHexString(trig.getFlags()));
pw.print(' '); pw.println(trig.getUri());
}
+ if (job.getTriggerContentUpdateDelay() >= 0) {
+ pw.print(prefix); pw.print(" Trigger update delay: ");
+ TimeUtils.formatDuration(job.getTriggerContentUpdateDelay(), pw);
+ pw.println();
+ }
+ if (job.getTriggerContentMaxDelay() >= 0) {
+ pw.print(prefix); pw.print(" Trigger max delay: ");
+ TimeUtils.formatDuration(job.getTriggerContentMaxDelay(), pw);
+ pw.println();
+ }
}
if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType());