From 8db0fc15b85c6501a0418b17edee2d9c447b408a Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Tue, 12 Apr 2016 13:48:25 -0700 Subject: [PATCH] More work on issue #26390151: Add new JobScheduler API... ...for monitoring content providers We now have some delays before reporting URI changes, to allow them to batch together. Also clean up debug output, and fix some issues with how we were managing the content observer state. And while I am here, fix the device idle and app idle controllers to no longer maintain their own list of jobs, but just directly iterate over the JobStore. Change-Id: If3fdff23c00c2f1b99901a9be096d851562d3439 --- api/current.txt | 4 + api/system-current.txt | 4 + api/test-current.txt | 4 + core/java/android/app/job/JobInfo.java | 47 +++++ .../server/job/JobSchedulerService.java | 4 + .../job/controllers/AppIdleController.java | 98 ++++++--- .../ContentObserverController.java | 198 ++++++++++++------ .../controllers/DeviceIdleJobsController.java | 37 ++-- .../server/job/controllers/JobStatus.java | 38 ++++ 9 files changed, 326 insertions(+), 108 deletions(-) diff --git a/api/current.txt b/api/current.txt index 04e10050fc875..a27700f66b2f2 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 f2842622385fd..dcb0951fdf91d 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 a19a120dfbfb7..422fd12bdffad 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());