diff --git a/api/current.txt b/api/current.txt index cf2801abf3604..baef0a4e222fb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6313,6 +6313,8 @@ package android.app.job { 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 @@ package android.app.job { 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 19474ed81a806..d8545f1269f5d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6581,6 +6581,8 @@ package android.app.job { 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 @@ package android.app.job { 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 3e36c779a2715..6a0f07138e1b7 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6317,6 +6317,8 @@ package android.app.job { 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 @@ package android.app.job { 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 09050b644d232..c84a0dcba0379 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -158,6 +158,8 @@ public class JobInfo implements Parcelable { 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; @@ -220,6 +222,22 @@ public class JobInfo implements Parcelable { return triggerContentUris; } + /** + * 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 @@ -321,6 +339,8 @@ public class JobInfo implements Parcelable { 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 @@ public class JobInfo implements Parcelable { 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 @@ public class JobInfo implements Parcelable { 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 @@ public class JobInfo implements Parcelable { private boolean mRequiresDeviceIdle; private int mNetworkType; private ArrayList mTriggerContentUris; + private long mTriggerContentUpdateDelay = -1; + private long mTriggerContentMaxDelay = -1; private boolean mIsPersisted; // One-off parameters. private long mMinLatencyMillis; @@ -587,6 +613,27 @@ public class JobInfo implements Parcelable { return this; } + /** + * 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, diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index c4a2c73126007..7df8ffd7c8221 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 @@ public final class JobSchedulerService extends com.android.server.SystemService 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 d8490d4de5165..02bc36ca5c105 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 android.util.Slog; 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 @@ public class AppIdleController extends StateController { // Singleton factory private static Object sCreationLock = new Object(); private static volatile AppIdleController sController; - final ArrayList mTrackedTasks = new ArrayList(); + 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 @@ public class AppIdleController extends StateController { } } - 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 @@ public class AppIdleController extends StateController { @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 @@ public class AppIdleController extends StateController { @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 @@ public class AppIdleController extends StateController { 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 @@ public class AppIdleController extends StateController { 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 b2f1958bac830..c5b1a3d88052c 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.content.Context; 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 @@ public class ContentObserverController extends StateController { */ 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 mTrackedTasks = new ArrayList(); ArrayMap mObservers = new ArrayMap<>(); - final Handler mHandler = new Handler(); + final Handler mHandler; public static ContentObserverController get(JobSchedulerService taskManagerService) { synchronized (sCreationLock) { @@ -72,6 +78,7 @@ public class ContentObserverController extends StateController { private ContentObserverController(StateChangedListener stateChangedListener, Context context, Object lock) { super(stateChangedListener, context, lock); + mHandler = new Handler(context.getMainLooper()); } @Override @@ -113,6 +120,11 @@ public class ContentObserverController extends StateController { 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 @@ public class ContentObserverController extends StateController { 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 @@ public class ContentObserverController extends StateController { } } - class ObserverInstance extends ContentObserver { + final class ObserverInstance extends ContentObserver { final Uri mUri; - final ArrayList mJobs = new ArrayList<>(); + final ArraySet mJobs = new ArraySet<>(); public ObserverInstance(Handler handler, Uri uri) { super(handler); @@ -188,11 +203,10 @@ public class ContentObserverController extends StateController { @Override public void onChange(boolean selfChange, Uri uri) { - boolean reportChange = false; synchronized (mLock) { final int N = mJobs.size(); for (int i=0; i(); } @@ -203,26 +217,38 @@ public class ContentObserverController extends StateController { 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 { - private final JobStatus mJobStatus; - private ArraySet mChangedUris; - private ArraySet 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 mMyObservers = new ArrayList<>(); + final JobStatus mJobStatus; + final Runnable mExecuteRunner; + final Runnable mTimeoutRunner; + ArraySet mChangedUris; + ArraySet 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 @@ public class ContentObserverController extends StateController { 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 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())); + } + if (printed) { + pw.println(); } - 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 mTrackedTasks = new ArrayList(); private static DeviceIdleJobsController sController; + private final JobSchedulerService mJobSchedulerService; private final PowerManager mPowerManager; private final DeviceIdleController.LocalService mLocalDeviceIdleController; @@ -57,6 +58,12 @@ public class DeviceIdleJobsController extends StateController { 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 @@ public class DeviceIdleJobsController extends StateController { } }; - 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 @@ public class DeviceIdleJobsController extends StateController { } 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 @@ public class DeviceIdleJobsController extends StateController { @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 39905d8e57e22..dd70758007602 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 @@ public final class JobStatus { // 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 @@ public final class JobStatus { 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 @@ public final class JobStatus { 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());