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
This commit is contained in:
Dianne Hackborn
2016-04-12 13:48:25 -07:00
parent b53a36b8cf
commit 8db0fc15b8
9 changed files with 326 additions and 108 deletions

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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<TriggerContentUri> 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,

View File

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

View File

@@ -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<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 @@ 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) {

View File

@@ -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<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 @@ 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<JobInstance> mJobs = new ArrayList<>();
final ArraySet<JobInstance> 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<N; i++) {
JobInstance inst = mJobs.get(i);
JobInstance inst = mJobs.valueAt(i);
if (inst.mChangedUris == null) {
inst.mChangedUris = new ArraySet<>();
}
@@ -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<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 @@ 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<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 @@ public class ContentObserverController extends StateController {
@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()));
}
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<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));

View File

@@ -28,6 +28,7 @@ import com.android.internal.util.ArrayUtils;
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 @@ public class DeviceIdleJobsController extends StateController {
// 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 @@ 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();
}
}

View File

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