Merge "More work on issue #26390151: Add new JobScheduler API..." into nyc-dev

This commit is contained in:
Dianne Hackborn
2016-04-13 23:30:12 +00:00
committed by Android (Google) Code Review
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());