Merge "Fix issue #32180780: Sync adapters inappropriately being run..." into oc-dev

This commit is contained in:
TreeHugger Robot
2017-04-21 19:23:06 +00:00
committed by Android (Google) Code Review
15 changed files with 562 additions and 374 deletions

View File

@@ -244,7 +244,7 @@ public class JobInfo implements Parcelable {
/**
* Bundle of extras which are returned to your application at execution time.
*/
public PersistableBundle getExtras() {
public @NonNull PersistableBundle getExtras() {
return extras;
}
@@ -252,7 +252,7 @@ public class JobInfo implements Parcelable {
* Bundle of transient extras which are returned to your application at execution time,
* but not persisted by the system.
*/
public Bundle getTransientExtras() {
public @NonNull Bundle getTransientExtras() {
return transientExtras;
}
@@ -260,7 +260,7 @@ public class JobInfo implements Parcelable {
* ClipData of information that is returned to your application at execution time,
* but not persisted by the system.
*/
public ClipData getClipData() {
public @Nullable ClipData getClipData() {
return clipData;
}
@@ -274,7 +274,7 @@ public class JobInfo implements Parcelable {
/**
* Name of the service endpoint that will be called back into by the JobScheduler.
*/
public ComponentName getService() {
public @NonNull ComponentName getService() {
return service;
}
@@ -327,8 +327,7 @@ public class JobInfo implements Parcelable {
* Which content: URIs must change for the job to be scheduled. Returns null
* if there are none required.
*/
@Nullable
public TriggerContentUri[] getTriggerContentUris() {
public @Nullable TriggerContentUri[] getTriggerContentUris() {
return triggerContentUris;
}
@@ -811,7 +810,7 @@ public class JobInfo implements Parcelable {
* @param jobService The endpoint that you implement that will receive the callback from the
* JobScheduler.
*/
public Builder(int jobId, ComponentName jobService) {
public Builder(int jobId, @NonNull ComponentName jobService) {
mJobService = jobService;
mJobId = jobId;
}
@@ -832,7 +831,7 @@ public class JobInfo implements Parcelable {
* Set optional extras. This is persisted, so we only allow primitive types.
* @param extras Bundle containing extras you want the scheduler to hold on to for you.
*/
public Builder setExtras(PersistableBundle extras) {
public Builder setExtras(@NonNull PersistableBundle extras) {
mExtras = extras;
return this;
}
@@ -842,7 +841,7 @@ public class JobInfo implements Parcelable {
* persisted with {@link #setPersisted(boolean)}; mixing the two is not allowed.
* @param extras Bundle containing extras you want the scheduler to hold on to for you.
*/
public Builder setTransientExtras(Bundle extras) {
public Builder setTransientExtras(@NonNull Bundle extras) {
mTransientExtras = extras;
return this;
}
@@ -869,7 +868,7 @@ public class JobInfo implements Parcelable {
* {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}, and
* {@link android.content.Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
*/
public Builder setClipData(ClipData clip, int grantFlags) {
public Builder setClipData(@Nullable ClipData clip, int grantFlags) {
mClipData = clip;
mClipGrantFlags = grantFlags;
return this;

View File

@@ -16,6 +16,8 @@
package android.app.job;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.IJobCallback;
import android.content.ClipData;
import android.net.Uri;
@@ -91,7 +93,7 @@ public class JobParameters implements Parcelable {
* {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will
* never be null. If you did not set any extras this will be an empty bundle.
*/
public PersistableBundle getExtras() {
public @NonNull PersistableBundle getExtras() {
return extras;
}
@@ -100,7 +102,7 @@ public class JobParameters implements Parcelable {
* {@link android.app.job.JobInfo.Builder#setTransientExtras(android.os.Bundle)}. This will
* never be null. If you did not set any extras this will be an empty bundle.
*/
public Bundle getTransientExtras() {
public @NonNull Bundle getTransientExtras() {
return transientExtras;
}
@@ -109,7 +111,7 @@ public class JobParameters implements Parcelable {
* {@link android.app.job.JobInfo.Builder#setClipData(ClipData, int)}. Will be null
* if it was not set.
*/
public ClipData getClipData() {
public @Nullable ClipData getClipData() {
return clipData;
}
@@ -140,7 +142,7 @@ public class JobParameters implements Parcelable {
* always use {@link #getTriggeredContentAuthorities()} to determine whether the job was
* triggered due to any content changes and the authorities they are associated with.
*/
public Uri[] getTriggeredContentUris() {
public @Nullable Uri[] getTriggeredContentUris() {
return mTriggeredContentUris;
}
@@ -152,7 +154,7 @@ public class JobParameters implements Parcelable {
* to retrieve the details of which URIs changed (as long as that has not exceeded the maximum
* number it can reported).
*/
public String[] getTriggeredContentAuthorities() {
public @Nullable String[] getTriggeredContentAuthorities() {
return mTriggeredContentAuthorities;
}
@@ -183,7 +185,7 @@ public class JobParameters implements Parcelable {
* (This means that for correct operation, you must always call dequeueWork() after you have
* completed other work, to check either for more work or allow the system to stop the job.)
*/
public JobWorkItem dequeueWork() {
public @Nullable JobWorkItem dequeueWork() {
try {
return getCallback().dequeueWork(getJobId());
} catch (RemoteException e) {
@@ -207,7 +209,7 @@ public class JobParameters implements Parcelable {
* @param work The work you have completed processing, as previously returned by
* {@link #dequeueWork()}
*/
public void completeWork(JobWorkItem work) {
public void completeWork(@NonNull JobWorkItem work) {
try {
if (!getCallback().completeWork(getJobId(), work.getWorkId())) {
throw new IllegalArgumentException("Given work is not active: " + work);

View File

@@ -72,7 +72,7 @@ public abstract class JobScheduler {
* you can schedule.
* @return An int representing ({@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}).
*/
public abstract int schedule(JobInfo job);
public abstract int schedule(@NonNull JobInfo job);
/**
* Similar to {@link #schedule}, but allows you to enqueue work for an existing job. If a job
@@ -108,7 +108,7 @@ public abstract class JobScheduler {
* @param work New work to enqueue. This will be available later when the job starts running.
* @return An int representing ({@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}).
*/
public abstract int enqueue(JobInfo job, JobWorkItem work);
public abstract int enqueue(@NonNull JobInfo job, @NonNull JobWorkItem work);
/**
*
@@ -121,7 +121,8 @@ public abstract class JobScheduler {
* @hide
*/
@SystemApi
public abstract int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag);
public abstract int scheduleAsPackage(@NonNull JobInfo job, @NonNull String packageName,
int userId, String tag);
/**
* Cancel a job that is pending in the JobScheduler.

View File

@@ -358,6 +358,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import com.android.server.job.JobSchedulerInternal;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
@@ -18179,6 +18180,9 @@ public class ActivityManagerService extends IActivityManager.Stub
return false;
}
int oldBackupUid;
int newBackupUid;
synchronized(this) {
// !!! TODO: currently no check here that we're already bound
BatteryStatsImpl.Uid.Pkg.Serv ss = null;
@@ -18219,6 +18223,8 @@ public class ActivityManagerService extends IActivityManager.Stub
proc.inFullBackup = true;
}
r.app = proc;
oldBackupUid = mBackupTarget != null ? mBackupTarget.appInfo.uid : -1;
newBackupUid = proc.inFullBackup ? r.appInfo.uid : -1;
mBackupTarget = r;
mBackupAppName = app.packageName;
@@ -18244,6 +18250,14 @@ public class ActivityManagerService extends IActivityManager.Stub
// know that it's scheduled for a backup-agent operation.
}
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
if (oldBackupUid != -1) {
js.removeBackingUpUid(oldBackupUid);
}
if (newBackupUid != -1) {
js.addBackingUpUid(newBackupUid);
}
return true;
}
@@ -18256,6 +18270,9 @@ public class ActivityManagerService extends IActivityManager.Stub
mBackupTarget = null;
mBackupAppName = null;
}
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
js.clearAllBackingUpUids();
}
// A backup agent has just come up
@@ -18293,6 +18310,8 @@ public class ActivityManagerService extends IActivityManager.Stub
return;
}
int oldBackupUid;
synchronized(this) {
try {
if (mBackupAppName == null) {
@@ -18310,6 +18329,8 @@ public class ActivityManagerService extends IActivityManager.Stub
updateOomAdjLocked(proc);
proc.inFullBackup = false;
oldBackupUid = mBackupTarget != null ? mBackupTarget.appInfo.uid : -1;
// If the app crashed during backup, 'thread' will be null here
if (proc.thread != null) {
try {
@@ -18325,7 +18346,13 @@ public class ActivityManagerService extends IActivityManager.Stub
mBackupAppName = null;
}
}
if (oldBackupUid != -1) {
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
js.removeBackingUpUid(oldBackupUid);
}
}
// =========================================================
// BROADCASTS
// =========================================================

View File

@@ -30,4 +30,11 @@ public interface JobSchedulerInternal {
* Returns a list of pending jobs scheduled by the system service.
*/
List<JobInfo> getSystemScheduledPendingJobs();
/**
* These are for activity manager to communicate to use what is currently performing backups.
*/
void addBackingUpUid(int uid);
void removeBackingUpUid(int uid);
void clearAllBackingUpUids();
}

View File

@@ -174,6 +174,11 @@ public final class JobSchedulerService extends com.android.server.SystemService
*/
final SparseIntArray mUidPriorityOverride = new SparseIntArray();
/**
* Which uids are currently performing backups, so we shouldn't allow their jobs to run.
*/
final SparseIntArray mBackingUpUids = new SparseIntArray();
// -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
/**
@@ -621,14 +626,30 @@ public final class JobSchedulerService extends com.android.server.SystemService
jobStatus.prepareLocked(ActivityManager.getService());
if (toCancel != null) {
cancelJobImpl(toCancel, jobStatus);
cancelJobImplLocked(toCancel, jobStatus);
}
if (work != null) {
// If work has been supplied, enqueue it into the new job.
jobStatus.enqueueWorkLocked(ActivityManager.getService(), work);
}
startTrackingJobLocked(jobStatus, toCancel);
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
// important for jobs with a 0 deadline constraint, since they will happen a fair
// amount, we want to handle them as quickly as possible, and semantically we want to
// make sure we have started holding the wake lock for the job before returning to
// the caller.
// If the job is not yet ready to run, there is nothing more to do -- we are
// now just waiting for one of its controllers to change state and schedule
// the job appropriately.
if (isReadyToBeExecutedLocked(jobStatus)) {
// This is a new job, we can just immediately put it on the pending
// list and try to run it.
mJobPackageTracker.notePending(jobStatus);
mPendingJobs.add(jobStatus);
maybeRunPendingJobsLocked();
}
}
return JobScheduler.RESULT_SUCCESS;
}
@@ -659,25 +680,23 @@ public final class JobSchedulerService extends com.android.server.SystemService
}
void cancelJobsForUser(int userHandle) {
List<JobStatus> jobsForUser;
synchronized (mLock) {
jobsForUser = mJobs.getJobsByUser(userHandle);
}
for (int i=0; i<jobsForUser.size(); i++) {
JobStatus toRemove = jobsForUser.get(i);
cancelJobImpl(toRemove, null);
final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
for (int i=0; i<jobsForUser.size(); i++) {
JobStatus toRemove = jobsForUser.get(i);
cancelJobImplLocked(toRemove, null);
}
}
}
void cancelJobsForPackageAndUid(String pkgName, int uid) {
List<JobStatus> jobsForUid;
synchronized (mLock) {
jobsForUid = mJobs.getJobsByUid(uid);
}
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
if (job.getSourcePackageName().equals(pkgName)) {
cancelJobImpl(job, null);
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
if (job.getSourcePackageName().equals(pkgName)) {
cancelJobImplLocked(job, null);
}
}
}
}
@@ -690,13 +709,12 @@ public final class JobSchedulerService extends com.android.server.SystemService
*
*/
public void cancelJobsForUid(int uid) {
List<JobStatus> jobsForUid;
synchronized (mLock) {
jobsForUid = mJobs.getJobsByUid(uid);
}
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
cancelJobImpl(toRemove, null);
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
cancelJobImplLocked(toRemove, null);
}
}
}
@@ -711,25 +729,23 @@ public final class JobSchedulerService extends com.android.server.SystemService
JobStatus toCancel;
synchronized (mLock) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
}
if (toCancel != null) {
cancelJobImpl(toCancel, null);
if (toCancel != null) {
cancelJobImplLocked(toCancel, null);
}
}
}
private void cancelJobImpl(JobStatus cancelled, JobStatus incomingJob) {
synchronized (mLock) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked(ActivityManager.getService());
stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
// Remove from pending queue.
if (mPendingJobs.remove(cancelled)) {
mJobPackageTracker.noteNonpending(cancelled);
}
// Cancel if running.
stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
reportActiveLocked();
private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob) {
if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString());
cancelled.unprepareLocked(ActivityManager.getService());
stopTrackingJobLocked(cancelled, incomingJob, true /* writeBack */);
// Remove from pending queue.
if (mPendingJobs.remove(cancelled)) {
mJobPackageTracker.noteNonpending(cancelled);
}
// Cancel if running.
stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
reportActiveLocked();
}
void updateUidState(int uid, int procState) {
@@ -770,8 +786,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
mLocalDeviceIdleController.setJobsActive(true);
}
}
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}
@@ -990,7 +1006,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
* @return A newly instantiated JobStatus with the same constraints as the last job except
* with adjusted timing constraints.
*
* @see JobHandler#maybeQueueReadyJobsForExecutionLockedH
* @see #maybeQueueReadyJobsForExecutionLocked
*/
private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
final long elapsedNowMillis = SystemClock.elapsedRealtime();
@@ -1128,7 +1144,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
mHandler.obtainMessage(MSG_JOB_EXPIRED, jobStatus).sendToTarget();
}
private class JobHandler extends Handler {
final private class JobHandler extends Handler {
public JobHandler(Looper looper) {
super(looper);
@@ -1140,283 +1156,278 @@ public final class JobSchedulerService extends com.android.server.SystemService
if (!mReadyToRock) {
return;
}
}
switch (message.what) {
case MSG_JOB_EXPIRED:
synchronized (mLock) {
switch (message.what) {
case MSG_JOB_EXPIRED: {
JobStatus runNow = (JobStatus) message.obj;
// runNow can be null, which is a controller's way of indicating that its
// state is such that all ready jobs should be run immediately.
if (runNow != null && isReadyToBeExecutedLocked(runNow)) {
mJobPackageTracker.notePending(runNow);
mPendingJobs.add(runNow);
} else {
queueReadyJobsForExecutionLocked();
}
queueReadyJobsForExecutionLockedH();
}
break;
case MSG_CHECK_JOB:
synchronized (mLock) {
} break;
case MSG_CHECK_JOB:
if (mReportedActive) {
// if jobs are currently being run, queue all ready jobs for execution.
queueReadyJobsForExecutionLockedH();
queueReadyJobsForExecutionLocked();
} else {
// Check the list of jobs and run some of them if we feel inclined.
maybeQueueReadyJobsForExecutionLockedH();
maybeQueueReadyJobsForExecutionLocked();
}
}
break;
case MSG_CHECK_JOB_GREEDY:
synchronized (mLock) {
queueReadyJobsForExecutionLockedH();
}
break;
case MSG_STOP_JOB:
cancelJobImpl((JobStatus)message.obj, null);
break;
}
maybeRunPendingJobsH();
// Don't remove JOB_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_JOB);
}
/**
* Run through list of jobs and execute all possible - at least one is expired so we do
* as many as we can.
*/
private void queueReadyJobsForExecutionLockedH() {
if (DEBUG) {
Slog.d(TAG, "queuing all ready jobs for execution:");
}
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
mJobs.forEachJob(mReadyQueueFunctor);
mReadyQueueFunctor.postProcess();
if (DEBUG) {
final int queuedJobs = mPendingJobs.size();
if (queuedJobs == 0) {
Slog.d(TAG, "No jobs pending.");
} else {
Slog.d(TAG, queuedJobs + " jobs queued.");
break;
case MSG_CHECK_JOB_GREEDY:
queueReadyJobsForExecutionLocked();
break;
case MSG_STOP_JOB:
cancelJobImplLocked((JobStatus) message.obj, null);
break;
}
maybeRunPendingJobsLocked();
// Don't remove JOB_EXPIRED in case one came along while processing the queue.
removeMessages(MSG_CHECK_JOB);
}
}
}
class ReadyJobQueueFunctor implements JobStatusFunctor {
ArrayList<JobStatus> newReadyJobs;
/**
* Run through list of jobs and execute all possible - at least one is expired so we do
* as many as we can.
*/
private void queueReadyJobsForExecutionLocked() {
if (DEBUG) {
Slog.d(TAG, "queuing all ready jobs for execution:");
}
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
mJobs.forEachJob(mReadyQueueFunctor);
mReadyQueueFunctor.postProcess();
@Override
public void process(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
if (DEBUG) {
Slog.d(TAG, " queued " + job.toShortString());
}
if (newReadyJobs == null) {
newReadyJobs = new ArrayList<JobStatus>();
}
newReadyJobs.add(job);
} else if (areJobConstraintsNotSatisfiedLocked(job)) {
stopJobOnServiceContextLocked(job,
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
}
}
public void postProcess() {
if (newReadyJobs != null) {
noteJobsPending(newReadyJobs);
mPendingJobs.addAll(newReadyJobs);
}
newReadyJobs = null;
if (DEBUG) {
final int queuedJobs = mPendingJobs.size();
if (queuedJobs == 0) {
Slog.d(TAG, "No jobs pending.");
} else {
Slog.d(TAG, queuedJobs + " jobs queued.");
}
}
private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
}
/**
* The state of at least one job has changed. Here is where we could enforce various
* policies on when we want to execute jobs.
* Right now the policy is such:
* If >1 of the ready jobs is idle mode we send all of them off
* if more than 2 network connectivity jobs are ready we send them all off.
* If more than 4 jobs total are ready we send them all off.
* TODO: It would be nice to consolidate these sort of high-level policies somewhere.
*/
class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
int chargingCount;
int batteryNotLowCount;
int storageNotLowCount;
int idleCount;
int backoffCount;
int connectivityCount;
int contentCount;
List<JobStatus> runnableJobs;
final class ReadyJobQueueFunctor implements JobStatusFunctor {
ArrayList<JobStatus> newReadyJobs;
public MaybeReadyJobQueueFunctor() {
reset();
}
// Functor method invoked for each job via JobStore.forEachJob()
@Override
public void process(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
try {
if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
job.getJob().getService().getPackageName())) {
Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ job.getJob().toString() + " -- package not allowed to start");
mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
return;
}
} catch (RemoteException e) {
}
if (job.getNumFailures() > 0) {
backoffCount++;
}
if (job.hasIdleConstraint()) {
idleCount++;
}
if (job.hasConnectivityConstraint()) {
connectivityCount++;
}
if (job.hasChargingConstraint()) {
chargingCount++;
}
if (job.hasBatteryNotLowConstraint()) {
batteryNotLowCount++;
}
if (job.hasStorageNotLowConstraint()) {
storageNotLowCount++;
}
if (job.hasContentTriggerConstraint()) {
contentCount++;
}
if (runnableJobs == null) {
runnableJobs = new ArrayList<>();
}
runnableJobs.add(job);
} else if (areJobConstraintsNotSatisfiedLocked(job)) {
stopJobOnServiceContextLocked(job,
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
}
}
public void postProcess() {
if (backoffCount > 0 ||
idleCount >= mConstants.MIN_IDLE_COUNT ||
connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
chargingCount >= mConstants.MIN_CHARGING_COUNT ||
batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT ||
contentCount >= mConstants.MIN_CONTENT_COUNT ||
(runnableJobs != null
&& runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
}
noteJobsPending(runnableJobs);
mPendingJobs.addAll(runnableJobs);
} else {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
}
}
// Be ready for next time
reset();
}
private void reset() {
chargingCount = 0;
idleCount = 0;
backoffCount = 0;
connectivityCount = 0;
batteryNotLowCount = 0;
storageNotLowCount = 0;
contentCount = 0;
runnableJobs = null;
}
}
private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
private void maybeQueueReadyJobsForExecutionLockedH() {
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
mJobs.forEachJob(mMaybeQueueFunctor);
mMaybeQueueFunctor.postProcess();
}
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
* - It's not pending.
* - It's not already running on a JSC.
* - The user that requested the job is running.
* - The component is enabled and runnable.
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
final boolean jobExists = mJobs.containsJob(job);
final boolean jobReady = job.isReady();
final boolean jobPending = mPendingJobs.contains(job);
final boolean jobActive = isCurrentlyActiveLocked(job);
final int userId = job.getUserId();
final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ " exists=" + jobExists
+ " ready=" + jobReady + " pending=" + jobPending
+ " active=" + jobActive + " userStarted=" + userStarted);
}
// Short circuit: don't do the expensive PM check unless we really think
// we might need to run this job now.
if (!jobExists || !userStarted || !jobReady || jobPending || jobActive) {
return false;
}
final boolean componentPresent;
try {
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
userId) != null);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ " componentPresent=" + componentPresent);
}
// Everything else checked out so far, so this is the final yes/no check
return componentPresent;
}
/**
* Criteria for cancelling an active job:
* - It's not ready
* - It's running on a JSC.
*/
private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
return !job.isReady() && isCurrentlyActiveLocked(job);
}
/**
* Reconcile jobs in the pending queue against available execution contexts.
* A controller can force a job into the pending queue even if it's already running, but
* here is where we decide whether to actually execute it.
*/
private void maybeRunPendingJobsH() {
synchronized (mLock) {
@Override
public void process(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
Slog.d(TAG, " queued " + job.toShortString());
}
assignJobsToContextsLocked();
reportActiveLocked();
if (newReadyJobs == null) {
newReadyJobs = new ArrayList<JobStatus>();
}
newReadyJobs.add(job);
} else if (areJobConstraintsNotSatisfiedLocked(job)) {
stopJobOnServiceContextLocked(job,
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
}
}
public void postProcess() {
if (newReadyJobs != null) {
noteJobsPending(newReadyJobs);
mPendingJobs.addAll(newReadyJobs);
}
newReadyJobs = null;
}
}
private final ReadyJobQueueFunctor mReadyQueueFunctor = new ReadyJobQueueFunctor();
/**
* The state of at least one job has changed. Here is where we could enforce various
* policies on when we want to execute jobs.
* Right now the policy is such:
* If >1 of the ready jobs is idle mode we send all of them off
* if more than 2 network connectivity jobs are ready we send them all off.
* If more than 4 jobs total are ready we send them all off.
* TODO: It would be nice to consolidate these sort of high-level policies somewhere.
*/
final class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
int chargingCount;
int batteryNotLowCount;
int storageNotLowCount;
int idleCount;
int backoffCount;
int connectivityCount;
int contentCount;
List<JobStatus> runnableJobs;
public MaybeReadyJobQueueFunctor() {
reset();
}
// Functor method invoked for each job via JobStore.forEachJob()
@Override
public void process(JobStatus job) {
if (isReadyToBeExecutedLocked(job)) {
try {
if (ActivityManager.getService().isAppStartModeDisabled(job.getUid(),
job.getJob().getService().getPackageName())) {
Slog.w(TAG, "Aborting job " + job.getUid() + ":"
+ job.getJob().toString() + " -- package not allowed to start");
mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
return;
}
} catch (RemoteException e) {
}
if (job.getNumFailures() > 0) {
backoffCount++;
}
if (job.hasIdleConstraint()) {
idleCount++;
}
if (job.hasConnectivityConstraint()) {
connectivityCount++;
}
if (job.hasChargingConstraint()) {
chargingCount++;
}
if (job.hasBatteryNotLowConstraint()) {
batteryNotLowCount++;
}
if (job.hasStorageNotLowConstraint()) {
storageNotLowCount++;
}
if (job.hasContentTriggerConstraint()) {
contentCount++;
}
if (runnableJobs == null) {
runnableJobs = new ArrayList<>();
}
runnableJobs.add(job);
} else if (areJobConstraintsNotSatisfiedLocked(job)) {
stopJobOnServiceContextLocked(job,
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
}
}
public void postProcess() {
if (backoffCount > 0 ||
idleCount >= mConstants.MIN_IDLE_COUNT ||
connectivityCount >= mConstants.MIN_CONNECTIVITY_COUNT ||
chargingCount >= mConstants.MIN_CHARGING_COUNT ||
batteryNotLowCount >= mConstants.MIN_BATTERY_NOT_LOW_COUNT ||
storageNotLowCount >= mConstants.MIN_STORAGE_NOT_LOW_COUNT ||
contentCount >= mConstants.MIN_CONTENT_COUNT ||
(runnableJobs != null
&& runnableJobs.size() >= mConstants.MIN_READY_JOBS_COUNT)) {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
}
noteJobsPending(runnableJobs);
mPendingJobs.addAll(runnableJobs);
} else {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
}
}
// Be ready for next time
reset();
}
private void reset() {
chargingCount = 0;
idleCount = 0;
backoffCount = 0;
connectivityCount = 0;
batteryNotLowCount = 0;
storageNotLowCount = 0;
contentCount = 0;
runnableJobs = null;
}
}
private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
private void maybeQueueReadyJobsForExecutionLocked() {
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
noteJobsNonpending(mPendingJobs);
mPendingJobs.clear();
mJobs.forEachJob(mMaybeQueueFunctor);
mMaybeQueueFunctor.postProcess();
}
/**
* Criteria for moving a job into the pending queue:
* - It's ready.
* - It's not pending.
* - It's not already running on a JSC.
* - The user that requested the job is running.
* - The component is enabled and runnable.
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
final boolean jobExists = mJobs.containsJob(job);
final boolean jobReady = job.isReady();
final boolean jobPending = mPendingJobs.contains(job);
final boolean jobActive = isCurrentlyActiveLocked(job);
final boolean jobBackingUp = mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0;
final int userId = job.getUserId();
final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ " exists=" + jobExists
+ " ready=" + jobReady + " pending=" + jobPending
+ " active=" + jobActive + " backingup=" + jobBackingUp
+ " userStarted=" + userStarted);
}
// Short circuit: don't do the expensive PM check unless we really think
// we might need to run this job now.
if (!jobExists || !userStarted || !jobReady || jobPending || jobActive || jobBackingUp) {
return false;
}
final boolean componentPresent;
try {
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
userId) != null);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ " componentPresent=" + componentPresent);
}
// Everything else checked out so far, so this is the final yes/no check
return componentPresent;
}
/**
* Criteria for cancelling an active job:
* - It's not ready
* - It's running on a JSC.
*/
private boolean areJobConstraintsNotSatisfiedLocked(JobStatus job) {
return !job.isReady() && isCurrentlyActiveLocked(job);
}
/**
* Reconcile jobs in the pending queue against available execution contexts.
* A controller can force a job into the pending queue even if it's already running, but
* here is where we decide whether to actually execute it.
*/
private void maybeRunPendingJobsLocked() {
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
}
assignJobsToContextsLocked();
reportActiveLocked();
}
private int adjustJobPriority(int curPriority, JobStatus job) {
@@ -1619,6 +1630,38 @@ public final class JobSchedulerService extends com.android.server.SystemService
return pendingJobs;
}
}
@Override
public void addBackingUpUid(int uid) {
synchronized (mLock) {
// No need to actually do anything here, since for a full backup the
// activity manager will kill the process which will kill the job (and
// cause it to restart, but now it can't run).
mBackingUpUids.put(uid, uid);
}
}
@Override
public void removeBackingUpUid(int uid) {
synchronized (mLock) {
mBackingUpUids.delete(uid);
// If there are any jobs for this uid, we need to rebuild the pending list
// in case they are now ready to run.
if (mJobs.countJobsForUid(uid) > 0) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}
@Override
public void clearAllBackingUpUids() {
synchronized (mLock) {
if (mBackingUpUids.size() > 0) {
mBackingUpUids.clear();
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
}
}
}
/**
@@ -1868,7 +1911,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
return JobSchedulerShellCommand.CMD_ERR_CONSTRAINTS;
}
mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
queueReadyJobsForExecutionLocked();
maybeRunPendingJobsLocked();
}
} catch (RemoteException e) {
// can't happen
@@ -2015,7 +2059,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
job.dump(pw, " ", true);
pw.print(" Ready: ");
pw.print(mHandler.isReadyToBeExecutedLocked(job));
pw.print(isReadyToBeExecutedLocked(job));
pw.print(" (job=");
pw.print(job.isReady());
pw.print(" user=");
@@ -2024,6 +2068,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
pw.print(!mPendingJobs.contains(job));
pw.print(" !active=");
pw.print(!isCurrentlyActiveLocked(job));
pw.print(" !backingup=");
pw.print(!(mBackingUpUids.indexOfKey(job.getSourceUid()) >= 0));
pw.print(" comp=");
boolean componentPresent = false;
try {
@@ -2052,6 +2098,24 @@ public final class JobSchedulerService extends com.android.server.SystemService
pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
}
}
if (mBackingUpUids.size() > 0) {
pw.println();
pw.println("Backing up uids:");
boolean first = true;
for (int i = 0; i < mBackingUpUids.size(); i++) {
int uid = mBackingUpUids.keyAt(i);
if (filterUidFinal == -1 || filterUidFinal == UserHandle.getAppId(uid)) {
if (first) {
pw.print(" ");
first = false;
} else {
pw.print(", ");
}
pw.print(UserHandle.formatUid(uid));
}
}
pw.println();
}
pw.println();
mJobPackageTracker.dump(pw, "", filterUidFinal);
pw.println();

View File

@@ -123,7 +123,8 @@ public class AppIdleController extends StateController {
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
}
@Override

View File

@@ -24,6 +24,7 @@ import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -32,9 +33,6 @@ import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Simple controller that tracks whether the phone is charging or not. The phone is considered to
@@ -47,7 +45,7 @@ public class BatteryController extends StateController {
private static final Object sCreationLock = new Object();
private static volatile BatteryController sController;
private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
private ChargingTracker mChargeTracker;
public static BatteryController get(JobSchedulerService taskManagerService) {
@@ -82,6 +80,7 @@ public class BatteryController extends StateController {
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (taskStatus.hasPowerConstraint()) {
mTrackedTasks.add(taskStatus);
taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());
taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow());
}
@@ -89,7 +88,7 @@ public class BatteryController extends StateController {
@Override
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
if (taskStatus.hasPowerConstraint()) {
if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
mTrackedTasks.remove(taskStatus);
}
}
@@ -103,7 +102,7 @@ public class BatteryController extends StateController {
boolean reportChange = false;
synchronized (mLock) {
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.get(i);
final JobStatus ts = mTrackedTasks.valueAt(i);
boolean previous = ts.setChargingConstraintSatisfied(stablePower);
if (previous != stablePower) {
reportChange = true;
@@ -251,7 +250,7 @@ public class BatteryController extends StateController {
pw.print(mTrackedTasks.size());
pw.println(":");
for (int i = 0; i < mTrackedTasks.size(); i++) {
final JobStatus js = mTrackedTasks.get(i);
final JobStatus js = mTrackedTasks.valueAt(i);
if (!js.shouldDump(filterUid)) {
continue;
}

View File

@@ -27,6 +27,7 @@ import android.net.NetworkInfo;
import android.net.NetworkPolicyManager;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -34,7 +35,6 @@ import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* Handles changes in connectivity.
@@ -54,7 +54,7 @@ public class ConnectivityController extends StateController implements
private boolean mValidated;
@GuardedBy("mLock")
private final ArrayList<JobStatus> mTrackedJobs = new ArrayList<JobStatus>();
private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
/** Singleton. */
private static ConnectivityController mSingleton;
@@ -87,13 +87,14 @@ public class ConnectivityController extends StateController implements
if (jobStatus.hasConnectivityConstraint()) {
updateConstraintsSatisfied(jobStatus, null);
mTrackedJobs.add(jobStatus);
jobStatus.setTrackingController(JobStatus.TRACKING_CONNECTIVITY);
}
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
if (jobStatus.hasConnectivityConstraint()) {
if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
mTrackedJobs.remove(jobStatus);
}
}
@@ -150,8 +151,8 @@ public class ConnectivityController extends StateController implements
private void updateTrackedJobs(int uid, NetworkCapabilities capabilities) {
synchronized (mLock) {
boolean changed = false;
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.get(i);
for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
final JobStatus js = mTrackedJobs.valueAt(i);
if (uid == -1 || uid == js.getSourceUid()) {
changed |= updateConstraintsSatisfied(js, capabilities);
}
@@ -168,8 +169,8 @@ public class ConnectivityController extends StateController implements
@Override
public void onNetworkActive() {
synchronized (mLock) {
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.get(i);
for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
final JobStatus js = mTrackedJobs.valueAt(i);
if (js.isReady()) {
if (DEBUG) {
Slog.d(TAG, "Running " + js + " due to network activity.");
@@ -239,7 +240,7 @@ public class ConnectivityController extends StateController implements
pw.print(mTrackedJobs.size());
pw.println(":");
for (int i = 0; i < mTrackedJobs.size(); i++) {
final JobStatus js = mTrackedJobs.get(i);
final JobStatus js = mTrackedJobs.valueAt(i);
if (js.shouldDump(filterUid)) {
pw.print(" #");
js.printUniqueId(pw);

View File

@@ -35,9 +35,6 @@ import com.android.server.job.StateChangedListener;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Controller for monitoring changes to content URIs through a ContentObserver.
@@ -61,11 +58,11 @@ public class ContentObserverController extends StateController {
private static final Object sCreationLock = new Object();
private static volatile ContentObserverController sController;
final private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
final private ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
/**
* Per-userid {@link JobInfo.TriggerContentUri} keyed ContentObserver cache.
*/
SparseArray<ArrayMap<JobInfo.TriggerContentUri, ObserverInstance>> mObservers =
final SparseArray<ArrayMap<JobInfo.TriggerContentUri, ObserverInstance>> mObservers =
new SparseArray<>();
final Handler mHandler;
@@ -101,6 +98,7 @@ public class ContentObserverController extends StateController {
Slog.i(TAG, "Tracking content-trigger job " + taskStatus);
}
mTrackedTasks.add(taskStatus);
taskStatus.setTrackingController(JobStatus.TRACKING_CONTENT);
boolean havePendingUris = false;
// If there is a previous job associated with the new job, propagate over
// any pending content URI trigger reports.
@@ -156,7 +154,8 @@ public class ContentObserverController extends StateController {
@Override
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
boolean forUpdate) {
if (taskStatus.hasContentTriggerConstraint()) {
if (taskStatus.clearTrackingController(JobStatus.TRACKING_CONTENT)) {
mTrackedTasks.remove(taskStatus);
if (taskStatus.contentObserverJobInstance != null) {
taskStatus.contentObserverJobInstance.unscheduleLocked();
if (incomingJob != null) {
@@ -190,7 +189,6 @@ public class ContentObserverController extends StateController {
if (DEBUG) {
Slog.i(TAG, "No longer tracking job " + taskStatus);
}
mTrackedTasks.remove(taskStatus);
}
}
@@ -374,9 +372,8 @@ public class ContentObserverController extends StateController {
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.println("Content:");
Iterator<JobStatus> it = mTrackedTasks.iterator();
while (it.hasNext()) {
JobStatus js = it.next();
for (int i = 0; i < mTrackedTasks.size(); i++) {
JobStatus js = mTrackedTasks.valueAt(i);
if (!js.shouldDump(filterUid)) {
continue;
}

View File

@@ -164,13 +164,12 @@ public class DeviceIdleJobsController extends StateController {
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
synchronized (mLock) {
updateTaskStateLocked(jobStatus);
}
updateTaskStateLocked(jobStatus);
}
@Override
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) {
public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
boolean forUpdate) {
}
@Override

View File

@@ -17,7 +17,6 @@
package com.android.server.job.controllers;
import java.io.PrintWriter;
import java.util.ArrayList;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -27,6 +26,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
import com.android.server.am.ActivityManagerService;
@@ -40,7 +40,7 @@ public class IdleController extends StateController {
// screen off or dreaming for at least this long
private long mInactivityIdleThreshold;
private long mIdleWindowSlop;
final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
IdlenessTracker mIdleTracker;
// Singleton factory
@@ -69,13 +69,17 @@ public class IdleController extends StateController {
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (taskStatus.hasIdleConstraint()) {
mTrackedTasks.add(taskStatus);
taskStatus.setTrackingController(JobStatus.TRACKING_IDLE);
taskStatus.setIdleConstraintSatisfied(mIdleTracker.isIdle());
}
}
@Override
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
mTrackedTasks.remove(taskStatus);
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
boolean forUpdate) {
if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) {
mTrackedTasks.remove(taskStatus);
}
}
/**
@@ -83,8 +87,8 @@ public class IdleController extends StateController {
*/
void reportNewIdleState(boolean isIdle) {
synchronized (mLock) {
for (JobStatus task : mTrackedTasks) {
task.setIdleConstraintSatisfied(isIdle);
for (int i = mTrackedTasks.size()-1; i >= 0; i--) {
mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle);
}
}
mStateChangedListener.onControllerStateChanged();
@@ -200,7 +204,7 @@ public class IdleController extends StateController {
pw.print(mTrackedTasks.size());
pw.println(":");
for (int i = 0; i < mTrackedTasks.size(); i++) {
final JobStatus js = mTrackedTasks.get(i);
final JobStatus js = mTrackedTasks.valueAt(i);
if (!js.shouldDump(filterUid)) {
continue;
}

View File

@@ -128,6 +128,38 @@ public final class JobStatus {
// Set to true if doze constraint was satisfied due to app being whitelisted.
public boolean dozeWhitelisted;
/**
* Flag for {@link #trackingControllers}: the battery controller is currently tracking this job.
*/
public static final int TRACKING_BATTERY = 1<<0;
/**
* Flag for {@link #trackingControllers}: the network connectivity controller is currently
* tracking this job.
*/
public static final int TRACKING_CONNECTIVITY = 1<<1;
/**
* Flag for {@link #trackingControllers}: the content observer controller is currently
* tracking this job.
*/
public static final int TRACKING_CONTENT = 1<<2;
/**
* Flag for {@link #trackingControllers}: the idle controller is currently tracking this job.
*/
public static final int TRACKING_IDLE = 1<<3;
/**
* Flag for {@link #trackingControllers}: the storage controller is currently tracking this job.
*/
public static final int TRACKING_STORAGE = 1<<4;
/**
* Flag for {@link #trackingControllers}: the time controller is currently tracking this job.
*/
public static final int TRACKING_TIME = 1<<5;
/**
* Bit mask of controllers that are currently tracking the job.
*/
private int trackingControllers;
// These are filled in by controllers when preparing for execution.
public ArraySet<Uri> changedUris;
public ArraySet<String> changedAuthorities;
@@ -609,6 +641,18 @@ public final class JobStatus {
return (satisfiedConstraints&constraint) != 0;
}
boolean clearTrackingController(int which) {
if ((trackingControllers&which) != 0) {
trackingControllers &= ~which;
return true;
}
return false;
}
void setTrackingController(int which) {
trackingControllers |= which;
}
public boolean shouldDump(int filterUid) {
return filterUid == -1 || UserHandle.getAppId(getUid()) == filterUid
|| UserHandle.getAppId(getSourceUid()) == filterUid;
@@ -925,6 +969,16 @@ public final class JobStatus {
pw.print(prefix); pw.println("Doze whitelisted: true");
}
}
if (trackingControllers != 0) {
pw.print(prefix); pw.print("Tracking:");
if ((trackingControllers&TRACKING_BATTERY) != 0) pw.print(" BATTERY");
if ((trackingControllers&TRACKING_CONNECTIVITY) != 0) pw.print(" CONNECTIVITY");
if ((trackingControllers&TRACKING_CONTENT) != 0) pw.print(" CONTENT");
if ((trackingControllers&TRACKING_IDLE) != 0) pw.print(" IDLE");
if ((trackingControllers&TRACKING_STORAGE) != 0) pw.print(" STORAGE");
if ((trackingControllers&TRACKING_TIME) != 0) pw.print(" TIME");
pw.println();
}
if (changedAuthorities != null) {
pw.print(prefix); pw.println("Changed authorities:");
for (int i=0; i<changedAuthorities.size(); i++) {

View File

@@ -20,9 +20,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -31,8 +31,6 @@ import com.android.server.job.StateChangedListener;
import com.android.server.storage.DeviceStorageMonitorService;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Simple controller that tracks the status of the device's storage.
@@ -43,7 +41,7 @@ public class StorageController extends StateController {
private static final Object sCreationLock = new Object();
private static volatile StorageController sController;
private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<JobStatus>();
private StorageTracker mStorageTracker;
public static StorageController get(JobSchedulerService taskManagerService) {
@@ -78,13 +76,15 @@ public class StorageController extends StateController {
public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
if (taskStatus.hasStorageNotLowConstraint()) {
mTrackedTasks.add(taskStatus);
taskStatus.setTrackingController(JobStatus.TRACKING_STORAGE);
taskStatus.setStorageNotLowConstraintSatisfied(mStorageTracker.isStorageNotLow());
}
}
@Override
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
if (taskStatus.hasPowerConstraint()) {
public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
boolean forUpdate) {
if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) {
mTrackedTasks.remove(taskStatus);
}
}
@@ -94,7 +94,7 @@ public class StorageController extends StateController {
boolean reportChange = false;
synchronized (mLock) {
for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
final JobStatus ts = mTrackedTasks.get(i);
final JobStatus ts = mTrackedTasks.valueAt(i);
boolean previous = ts.setStorageNotLowConstraintSatisfied(storageNotLow);
if (previous != storageNotLow) {
reportChange = true;
@@ -178,7 +178,7 @@ public class StorageController extends StateController {
pw.print(mTrackedTasks.size());
pw.println(":");
for (int i = 0; i < mTrackedTasks.size(); i++) {
final JobStatus js = mTrackedTasks.get(i);
final JobStatus js = mTrackedTasks.valueAt(i);
if (!js.shouldDump(filterUid)) {
continue;
}

View File

@@ -51,7 +51,7 @@ public class TimeController extends StateController {
private AlarmManager mAlarmService = null;
/** List of tracked jobs, sorted asc. by deadline */
private final List<JobStatus> mTrackedJobs = new LinkedList<JobStatus>();
private final List<JobStatus> mTrackedJobs = new LinkedList<>();
/** Singleton. */
private static TimeController mSingleton;
@@ -78,6 +78,20 @@ public class TimeController extends StateController {
public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
maybeStopTrackingJobLocked(job, null, false);
// First: check the constraints now, because if they are already satisfied
// then there is no need to track it. This gives us a fast path for a common
// pattern of having a job with a 0 deadline constraint ("run immediately").
// Unlike most controllers, once one of our constraints has been satisfied, it
// will never be unsatisfied (our time base can not go backwards).
final long nowElapsedMillis = SystemClock.elapsedRealtime();
if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
return;
} else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
nowElapsedMillis)) {
return;
}
boolean isInsert = false;
ListIterator<JobStatus> it = mTrackedJobs.listIterator(mTrackedJobs.size());
while (it.hasPrevious()) {
@@ -92,6 +106,7 @@ public class TimeController extends StateController {
it.next();
}
it.add(job);
job.setTrackingController(JobStatus.TRACKING_TIME);
maybeUpdateAlarmsLocked(
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
@@ -102,13 +117,15 @@ public class TimeController extends StateController {
/**
* When we stop tracking a job, we only need to update our alarms if the job we're no longer
* tracking was the one our alarms were based off of.
* Really an == comparison should be enough, but why play with fate? We'll do <=.
*/
@Override
public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob, boolean forUpdate) {
if (mTrackedJobs.remove(job)) {
checkExpiredDelaysAndResetAlarm();
checkExpiredDeadlinesAndResetAlarm();
public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob,
boolean forUpdate) {
if (job.clearTrackingController(JobStatus.TRACKING_TIME)) {
if (mTrackedJobs.remove(job)) {
checkExpiredDelaysAndResetAlarm();
checkExpiredDeadlinesAndResetAlarm();
}
}
}
@@ -147,17 +164,12 @@ public class TimeController extends StateController {
if (!job.hasDeadlineConstraint()) {
continue;
}
final long jobDeadline = job.getLatestRunTimeElapsed();
if (jobDeadline <= nowElapsedMillis) {
if (job.hasTimingDelayConstraint()) {
job.setTimingDelayConstraintSatisfied(true);
}
job.setDeadlineConstraintSatisfied(true);
if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
mStateChangedListener.onRunJobNow(job);
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
nextExpiryTime = jobDeadline;
nextExpiryTime = job.getLatestRunTimeElapsed();
nextExpiryUid = job.getSourceUid();
break;
}
@@ -166,6 +178,19 @@ public class TimeController extends StateController {
}
}
private boolean evaluateDeadlineConstraint(JobStatus job, long nowElapsedMillis) {
final long jobDeadline = job.getLatestRunTimeElapsed();
if (jobDeadline <= nowElapsedMillis) {
if (job.hasTimingDelayConstraint()) {
job.setTimingDelayConstraintSatisfied(true);
}
job.setDeadlineConstraintSatisfied(true);
return true;
}
return false;
}
/**
* Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
* tracked jobs and marks them as ready as appropriate.
@@ -182,9 +207,7 @@ public class TimeController extends StateController {
if (!job.hasTimingDelayConstraint()) {
continue;
}
final long jobDelayTime = job.getEarliestRunTime();
if (jobDelayTime <= nowElapsedMillis) {
job.setTimingDelayConstraintSatisfied(true);
if (evaluateTimingDelayConstraint(job, nowElapsedMillis)) {
if (canStopTrackingJobLocked(job)) {
it.remove();
}
@@ -194,6 +217,7 @@ public class TimeController extends StateController {
} else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) {
// If this job still doesn't have its delay constraint satisfied,
// then see if it is the next upcoming delay time for the alarm.
final long jobDelayTime = job.getEarliestRunTime();
if (nextDelayTime > jobDelayTime) {
nextDelayTime = jobDelayTime;
nextDelayUid = job.getSourceUid();
@@ -207,6 +231,15 @@ public class TimeController extends StateController {
}
}
private boolean evaluateTimingDelayConstraint(JobStatus job, long nowElapsedMillis) {
final long jobDelayTime = job.getEarliestRunTime();
if (jobDelayTime <= nowElapsedMillis) {
job.setTimingDelayConstraintSatisfied(true);
return true;
}
return false;
}
private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
int uid) {
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {