Merge "More work on issue #26390151: Add new JobScheduler API..." into nyc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9e31b3fd81
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user