Merge "Limit scheduled jobs to 100 per app" into nyc-dev
am: 3e30b118f7
* commit '3e30b118f7bf09955ded313109b0de540c28985b':
Limit scheduled jobs to 100 per app
This commit is contained in:
@@ -49,14 +49,13 @@ import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.Process;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.app.IBatteryStats;
|
||||
import com.android.server.DeviceIdleController;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobStore.JobStatusFunctor;
|
||||
import com.android.server.job.controllers.AppIdleController;
|
||||
import com.android.server.job.controllers.BatteryController;
|
||||
import com.android.server.job.controllers.ConnectivityController;
|
||||
@@ -80,11 +79,15 @@ import com.android.server.job.controllers.TimeController;
|
||||
*/
|
||||
public final class JobSchedulerService extends com.android.server.SystemService
|
||||
implements StateChangedListener, JobCompletedListener {
|
||||
static final String TAG = "JobSchedulerService";
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
/** The number of concurrent jobs we run at one time. */
|
||||
private static final int MAX_JOB_CONTEXTS_COUNT
|
||||
= ActivityManager.isLowRamDeviceStatic() ? 3 : 6;
|
||||
static final String TAG = "JobSchedulerService";
|
||||
/** The maximum number of jobs that we allow an unprivileged app to schedule */
|
||||
private static final int MAX_JOBS_PER_APP = 100;
|
||||
|
||||
/** Global local for all job scheduler state. */
|
||||
final Object mLock = new Object();
|
||||
/** Master list of jobs. */
|
||||
@@ -250,6 +253,15 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
|
||||
JobStatus toCancel;
|
||||
synchronized (mLock) {
|
||||
// Jobs on behalf of others don't apply to the per-app job cap
|
||||
if (packageName == null) {
|
||||
if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
|
||||
Slog.w(TAG, "Too many jobs for uid " + uId);
|
||||
throw new IllegalStateException("Apps may not schedule more than "
|
||||
+ MAX_JOBS_PER_APP + " distinct jobs");
|
||||
}
|
||||
}
|
||||
|
||||
toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
|
||||
}
|
||||
startTrackingJob(jobStatus, toCancel);
|
||||
@@ -261,17 +273,15 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
|
||||
public List<JobInfo> getPendingJobs(int uid) {
|
||||
ArrayList<JobInfo> outList = new ArrayList<JobInfo>();
|
||||
synchronized (mLock) {
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobs();
|
||||
for (int i=0; i<jobs.size(); i++) {
|
||||
JobStatus job = jobs.valueAt(i);
|
||||
if (job.getUid() == uid) {
|
||||
outList.add(job.getJob());
|
||||
}
|
||||
List<JobStatus> jobs = mJobs.getJobsByUid(uid);
|
||||
ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
|
||||
for (int i = jobs.size() - 1; i >= 0; i--) {
|
||||
JobStatus job = jobs.get(i);
|
||||
outList.add(job.getJob());
|
||||
}
|
||||
return outList;
|
||||
}
|
||||
return outList;
|
||||
}
|
||||
|
||||
void cancelJobsForUser(int userHandle) {
|
||||
@@ -471,14 +481,16 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
getContext().getMainLooper()));
|
||||
}
|
||||
// Attach jobs to their controllers.
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobs();
|
||||
for (int i=0; i<jobs.size(); i++) {
|
||||
JobStatus job = jobs.valueAt(i);
|
||||
for (int controller=0; controller<mControllers.size(); controller++) {
|
||||
mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
|
||||
mControllers.get(controller).maybeStartTrackingJobLocked(job, null);
|
||||
mJobs.forEachJob(new JobStatusFunctor() {
|
||||
@Override
|
||||
public void process(JobStatus job) {
|
||||
for (int controller = 0; controller < mControllers.size(); controller++) {
|
||||
final StateController sc = mControllers.get(controller);
|
||||
sc.deviceIdleModeChanged(mDeviceIdleMode);
|
||||
sc.maybeStartTrackingJobLocked(job, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// GO GO GO!
|
||||
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
|
||||
}
|
||||
@@ -741,23 +753,13 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
* as many as we can.
|
||||
*/
|
||||
private void queueReadyJobsForExecutionLockedH() {
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobs();
|
||||
mPendingJobs.clear();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "queuing all ready jobs for execution:");
|
||||
}
|
||||
for (int i=0; i<jobs.size(); i++) {
|
||||
JobStatus job = jobs.valueAt(i);
|
||||
if (isReadyToBeExecutedLocked(job)) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " queued " + job.toShortString());
|
||||
}
|
||||
mPendingJobs.add(job);
|
||||
} else if (areJobConstraintsNotSatisfiedLocked(job)) {
|
||||
stopJobOnServiceContextLocked(job,
|
||||
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
|
||||
}
|
||||
}
|
||||
mPendingJobs.clear();
|
||||
mJobs.forEachJob(mReadyQueueFunctor);
|
||||
mReadyQueueFunctor.postProcess();
|
||||
|
||||
if (DEBUG) {
|
||||
final int queuedJobs = mPendingJobs.size();
|
||||
if (queuedJobs == 0) {
|
||||
@@ -768,6 +770,34 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
}
|
||||
|
||||
class ReadyJobQueueFunctor implements JobStatusFunctor {
|
||||
ArrayList<JobStatus> newReadyJobs;
|
||||
|
||||
@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) {
|
||||
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.
|
||||
@@ -777,18 +807,21 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
* 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.
|
||||
*/
|
||||
private void maybeQueueReadyJobsForExecutionLockedH() {
|
||||
mPendingJobs.clear();
|
||||
int chargingCount = 0;
|
||||
int idleCount = 0;
|
||||
int backoffCount = 0;
|
||||
int connectivityCount = 0;
|
||||
int contentCount = 0;
|
||||
List<JobStatus> runnableJobs = null;
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobs();
|
||||
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
|
||||
for (int i=0; i<jobs.size(); i++) {
|
||||
JobStatus job = jobs.valueAt(i);
|
||||
class MaybeReadyJobQueueFunctor implements JobStatusFunctor {
|
||||
int chargingCount;
|
||||
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 (ActivityManagerNative.getDefault().getAppStartMode(job.getUid(),
|
||||
@@ -797,7 +830,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
Slog.w(TAG, "Aborting job " + job.getUid() + ":"
|
||||
+ job.getJob().toString() + " -- package not allowed to start");
|
||||
mHandler.obtainMessage(MSG_STOP_JOB, job).sendToTarget();
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
@@ -825,23 +858,45 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED);
|
||||
}
|
||||
}
|
||||
if (backoffCount > 0 ||
|
||||
idleCount >= MIN_IDLE_COUNT ||
|
||||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
|
||||
chargingCount >= MIN_CHARGING_COUNT ||
|
||||
contentCount >= MIN_CONTENT_COUNT ||
|
||||
(runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
|
||||
}
|
||||
for (int i=0; i<runnableJobs.size(); i++) {
|
||||
mPendingJobs.add(runnableJobs.get(i));
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Not running anything.");
|
||||
|
||||
public void postProcess() {
|
||||
if (backoffCount > 0 ||
|
||||
idleCount >= MIN_IDLE_COUNT ||
|
||||
connectivityCount >= MIN_CONNECTIVITY_COUNT ||
|
||||
chargingCount >= MIN_CHARGING_COUNT ||
|
||||
contentCount >= MIN_CONTENT_COUNT ||
|
||||
(runnableJobs != null && runnableJobs.size() >= MIN_READY_JOBS_COUNT)) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
|
||||
}
|
||||
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;
|
||||
contentCount = 0;
|
||||
runnableJobs = null;
|
||||
}
|
||||
}
|
||||
private final MaybeReadyJobQueueFunctor mMaybeQueueFunctor = new MaybeReadyJobQueueFunctor();
|
||||
|
||||
private void maybeQueueReadyJobsForExecutionLockedH() {
|
||||
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
|
||||
|
||||
mPendingJobs.clear();
|
||||
mJobs.forEachJob(mMaybeQueueFunctor);
|
||||
mMaybeQueueFunctor.postProcess();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1090,17 +1145,27 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
@Override
|
||||
public int scheduleAsPackage(JobInfo job, String packageName, int userId)
|
||||
throws RemoteException {
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Scheduling job: " + job.toString() + " on behalf of " + packageName);
|
||||
Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
|
||||
+ " on behalf of " + packageName);
|
||||
}
|
||||
final int uid = Binder.getCallingUid();
|
||||
if (uid != Process.SYSTEM_UID) {
|
||||
throw new IllegalArgumentException("Only system process is allowed"
|
||||
+ "to set packageName");
|
||||
|
||||
if (packageName == null) {
|
||||
throw new NullPointerException("Must specify a package for scheduleAsPackage()");
|
||||
}
|
||||
|
||||
int mayScheduleForOthers = getContext().checkCallingOrSelfPermission(
|
||||
android.Manifest.permission.UPDATE_DEVICE_STATS);
|
||||
if (mayScheduleForOthers != PackageManager.PERMISSION_GRANTED) {
|
||||
throw new SecurityException("Caller uid " + callerUid
|
||||
+ " not permitted to schedule jobs for other apps");
|
||||
}
|
||||
|
||||
long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return JobSchedulerService.this.scheduleAsPackage(job, uid, packageName, userId);
|
||||
return JobSchedulerService.this.scheduleAsPackage(job, callerUid,
|
||||
packageName, userId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
@@ -1183,7 +1248,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
void dumpInternal(PrintWriter pw) {
|
||||
void dumpInternal(final PrintWriter pw) {
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
synchronized (mLock) {
|
||||
pw.print("Started users: ");
|
||||
@@ -1193,24 +1258,27 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
||||
pw.println();
|
||||
pw.println("Registered jobs:");
|
||||
if (mJobs.size() > 0) {
|
||||
ArraySet<JobStatus> jobs = mJobs.getJobs();
|
||||
for (int i=0; i<jobs.size(); i++) {
|
||||
JobStatus job = jobs.valueAt(i);
|
||||
pw.print(" Job #"); pw.print(i); pw.print(": ");
|
||||
pw.println(job.toShortString());
|
||||
job.dump(pw, " ");
|
||||
pw.print(" Ready: ");
|
||||
pw.print(mHandler.isReadyToBeExecutedLocked(job));
|
||||
pw.print(" (job=");
|
||||
pw.print(job.isReady());
|
||||
pw.print(" pending=");
|
||||
pw.print(mPendingJobs.contains(job));
|
||||
pw.print(" active=");
|
||||
pw.print(isCurrentlyActiveLocked(job));
|
||||
pw.print(" user=");
|
||||
pw.print(mStartedUsers.contains(job.getUserId()));
|
||||
pw.println(")");
|
||||
}
|
||||
mJobs.forEachJob(new JobStatusFunctor() {
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public void process(JobStatus job) {
|
||||
pw.print(" Job #"); pw.print(index++); pw.print(": ");
|
||||
pw.println(job.toShortString());
|
||||
job.dump(pw, " ");
|
||||
pw.print(" Ready: ");
|
||||
pw.print(mHandler.isReadyToBeExecutedLocked(job));
|
||||
pw.print(" (job=");
|
||||
pw.print(job.isReady());
|
||||
pw.print(" pending=");
|
||||
pw.print(mPendingJobs.contains(job));
|
||||
pw.print(" active=");
|
||||
pw.print(isCurrentlyActiveLocked(job));
|
||||
pw.print(" user=");
|
||||
pw.print(mStartedUsers.contains(job.getUserId()));
|
||||
pw.println(")");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pw.println(" None.");
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.util.AtomicFile;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -68,8 +69,8 @@ public class JobStore {
|
||||
|
||||
/** Threshold to adjust how often we want to write to the db. */
|
||||
private static final int MAX_OPS_BEFORE_WRITE = 1;
|
||||
final ArraySet<JobStatus> mJobSet;
|
||||
final Object mLock;
|
||||
final JobSet mJobSet; // per-caller-uid tracking
|
||||
final Context mContext;
|
||||
|
||||
private int mDirtyOperations;
|
||||
@@ -114,7 +115,7 @@ public class JobStore {
|
||||
jobDir.mkdirs();
|
||||
mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"));
|
||||
|
||||
mJobSet = new ArraySet<JobStatus>();
|
||||
mJobSet = new JobSet();
|
||||
|
||||
readJobMapFromDisk(mJobSet);
|
||||
}
|
||||
@@ -137,19 +138,6 @@ public class JobStore {
|
||||
return replaced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this jobStatus object already exists in the JobStore.
|
||||
*/
|
||||
public boolean containsJobIdForUid(int jobId, int uId) {
|
||||
for (int i=mJobSet.size()-1; i>=0; i--) {
|
||||
JobStatus ts = mJobSet.valueAt(i);
|
||||
if (ts.getUid() == uId && ts.getJobId() == jobId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean containsJob(JobStatus jobStatus) {
|
||||
return mJobSet.contains(jobStatus);
|
||||
}
|
||||
@@ -158,6 +146,10 @@ public class JobStore {
|
||||
return mJobSet.size();
|
||||
}
|
||||
|
||||
public int countJobsForUid(int uid) {
|
||||
return mJobSet.countJobsForUid(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the provided job. Will also delete the job if it was persisted.
|
||||
* @param writeBack If true, the job will be deleted (if it was persisted) immediately.
|
||||
@@ -188,14 +180,7 @@ public class JobStore {
|
||||
* @return A list of all the jobs scheduled by the provided user. Never null.
|
||||
*/
|
||||
public List<JobStatus> getJobsByUser(int userHandle) {
|
||||
List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
|
||||
for (int i=mJobSet.size()-1; i>=0; i--) {
|
||||
JobStatus ts = mJobSet.valueAt(i);
|
||||
if (UserHandle.getUserId(ts.getUid()) == userHandle) {
|
||||
matchingJobs.add(ts);
|
||||
}
|
||||
}
|
||||
return matchingJobs;
|
||||
return mJobSet.getJobsByUser(userHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,14 +188,7 @@ public class JobStore {
|
||||
* @return All JobStatus objects for a given uid from the master list. Never null.
|
||||
*/
|
||||
public List<JobStatus> getJobsByUid(int uid) {
|
||||
List<JobStatus> matchingJobs = new ArrayList<JobStatus>();
|
||||
for (int i=mJobSet.size()-1; i>=0; i--) {
|
||||
JobStatus ts = mJobSet.valueAt(i);
|
||||
if (ts.getUid() == uid) {
|
||||
matchingJobs.add(ts);
|
||||
}
|
||||
}
|
||||
return matchingJobs;
|
||||
return mJobSet.getJobsByUid(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,20 +197,21 @@ public class JobStore {
|
||||
* @return the JobStatus that matches the provided uId and jobId, or null if none found.
|
||||
*/
|
||||
public JobStatus getJobByUidAndJobId(int uid, int jobId) {
|
||||
for (int i=mJobSet.size()-1; i>=0; i--) {
|
||||
JobStatus ts = mJobSet.valueAt(i);
|
||||
if (ts.getUid() == uid && ts.getJobId() == jobId) {
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return mJobSet.get(uid, jobId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The live array of JobStatus objects.
|
||||
* Iterate over the set of all jobs, invoking the supplied functor on each. This is for
|
||||
* customers who need to examine each job; we'd much rather not have to generate
|
||||
* transient unified collections for them to iterate over and then discard, or creating
|
||||
* iterators every time a client needs to perform a sweep.
|
||||
*/
|
||||
public ArraySet<JobStatus> getJobs() {
|
||||
return mJobSet;
|
||||
public void forEachJob(JobStatusFunctor functor) {
|
||||
mJobSet.forEachJob(functor);
|
||||
}
|
||||
|
||||
public interface JobStatusFunctor {
|
||||
public void process(JobStatus jobStatus);
|
||||
}
|
||||
|
||||
/** Version of the db schema. */
|
||||
@@ -261,7 +240,7 @@ public class JobStore {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void readJobMapFromDisk(ArraySet<JobStatus> jobSet) {
|
||||
public void readJobMapFromDisk(JobSet jobSet) {
|
||||
new ReadJobMapFromDiskRunnable(jobSet).run();
|
||||
}
|
||||
|
||||
@@ -273,21 +252,19 @@ public class JobStore {
|
||||
@Override
|
||||
public void run() {
|
||||
final long startElapsed = SystemClock.elapsedRealtime();
|
||||
List<JobStatus> mStoreCopy = new ArrayList<JobStatus>();
|
||||
final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
|
||||
synchronized (mLock) {
|
||||
// Copy over the jobs so we can release the lock before writing.
|
||||
for (int i=0; i<mJobSet.size(); i++) {
|
||||
JobStatus jobStatus = mJobSet.valueAt(i);
|
||||
|
||||
if (!jobStatus.isPersisted()){
|
||||
continue;
|
||||
// Clone the jobs so we can release the lock before writing.
|
||||
mJobSet.forEachJob(new JobStatusFunctor() {
|
||||
@Override
|
||||
public void process(JobStatus job) {
|
||||
if (job.isPersisted()) {
|
||||
storeCopy.add(new JobStatus(job));
|
||||
}
|
||||
}
|
||||
|
||||
JobStatus copy = new JobStatus(jobStatus);
|
||||
mStoreCopy.add(copy);
|
||||
}
|
||||
});
|
||||
}
|
||||
writeJobsMapImpl(mStoreCopy);
|
||||
writeJobsMapImpl(storeCopy);
|
||||
if (JobSchedulerService.DEBUG) {
|
||||
Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
|
||||
- startElapsed) + "ms");
|
||||
@@ -440,13 +417,13 @@ public class JobStore {
|
||||
* need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
|
||||
*/
|
||||
private class ReadJobMapFromDiskRunnable implements Runnable {
|
||||
private final ArraySet<JobStatus> jobSet;
|
||||
private final JobSet jobSet;
|
||||
|
||||
/**
|
||||
* @param jobSet Reference to the (empty) set of JobStatus objects that back the JobStore,
|
||||
* so that after disk read we can populate it directly.
|
||||
*/
|
||||
ReadJobMapFromDiskRunnable(ArraySet<JobStatus> jobSet) {
|
||||
ReadJobMapFromDiskRunnable(JobSet jobSet) {
|
||||
this.jobSet = jobSet;
|
||||
}
|
||||
|
||||
@@ -759,4 +736,122 @@ public class JobStore {
|
||||
return Pair.create(earliestRunTimeElapsed, latestRunTimeElapsed);
|
||||
}
|
||||
}
|
||||
|
||||
static class JobSet {
|
||||
// Key is the getUid() originator of the jobs in each sheaf
|
||||
private SparseArray<ArraySet<JobStatus>> mJobs;
|
||||
|
||||
public JobSet() {
|
||||
mJobs = new SparseArray<ArraySet<JobStatus>>();
|
||||
}
|
||||
|
||||
public List<JobStatus> getJobsByUid(int uid) {
|
||||
ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
|
||||
ArraySet<JobStatus> jobs = mJobs.get(uid);
|
||||
if (jobs != null) {
|
||||
matchingJobs.addAll(jobs);
|
||||
}
|
||||
return matchingJobs;
|
||||
}
|
||||
|
||||
// By user, not by uid, so we need to traverse by key and check
|
||||
public List<JobStatus> getJobsByUser(int userId) {
|
||||
ArrayList<JobStatus> result = new ArrayList<JobStatus>();
|
||||
for (int i = mJobs.size() - 1; i >= 0; i--) {
|
||||
if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
|
||||
ArraySet<JobStatus> jobs = mJobs.get(i);
|
||||
if (jobs != null) {
|
||||
result.addAll(jobs);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean add(JobStatus job) {
|
||||
final int uid = job.getUid();
|
||||
ArraySet<JobStatus> jobs = mJobs.get(uid);
|
||||
if (jobs == null) {
|
||||
jobs = new ArraySet<JobStatus>();
|
||||
mJobs.put(uid, jobs);
|
||||
}
|
||||
return jobs.add(job);
|
||||
}
|
||||
|
||||
public boolean remove(JobStatus job) {
|
||||
final int uid = job.getUid();
|
||||
ArraySet<JobStatus> jobs = mJobs.get(uid);
|
||||
boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
|
||||
if (didRemove && jobs.size() == 0) {
|
||||
// no more jobs for this uid; let the now-empty set object be GC'd.
|
||||
mJobs.remove(uid);
|
||||
}
|
||||
return didRemove;
|
||||
}
|
||||
|
||||
public boolean contains(JobStatus job) {
|
||||
final int uid = job.getUid();
|
||||
ArraySet<JobStatus> jobs = mJobs.get(uid);
|
||||
return jobs != null && jobs.contains(job);
|
||||
}
|
||||
|
||||
public JobStatus get(int uid, int jobId) {
|
||||
ArraySet<JobStatus> jobs = mJobs.get(uid);
|
||||
if (jobs != null) {
|
||||
for (int i = jobs.size() - 1; i >= 0; i--) {
|
||||
JobStatus job = jobs.valueAt(i);
|
||||
if (job.getJobId() == jobId) {
|
||||
return job;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Inefficient; use only for testing
|
||||
public List<JobStatus> getAllJobs() {
|
||||
ArrayList<JobStatus> allJobs = new ArrayList<JobStatus>(size());
|
||||
for (int i = mJobs.size(); i >= 0; i--) {
|
||||
allJobs.addAll(mJobs.valueAt(i));
|
||||
}
|
||||
return allJobs;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mJobs.clear();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
int total = 0;
|
||||
for (int i = mJobs.size() - 1; i >= 0; i--) {
|
||||
total += mJobs.valueAt(i).size();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// We only want to count the jobs that this uid has scheduled on its own
|
||||
// behalf, not those that the app has scheduled on someone else's behalf.
|
||||
public int countJobsForUid(int uid) {
|
||||
int total = 0;
|
||||
ArraySet<JobStatus> jobs = mJobs.get(uid);
|
||||
if (jobs != null) {
|
||||
for (int i = jobs.size() - 1; i >= 0; i--) {
|
||||
JobStatus job = jobs.valueAt(i);
|
||||
if (job.getUid() == job.getSourceUid()) {
|
||||
total++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public void forEachJob(JobStatusFunctor functor) {
|
||||
for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
|
||||
ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
|
||||
for (int i = jobs.size() - 1; i >= 0; i--) {
|
||||
functor.process(jobs.valueAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.test.RenamingDelegatingContext;
|
||||
import android.util.Log;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import com.android.server.job.JobStore.JobSet;
|
||||
import com.android.server.job.controllers.JobStatus;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -62,11 +63,11 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
mTaskStoreUnderTest.add(ts);
|
||||
Thread.sleep(IO_WAIT);
|
||||
// Manually load tasks from xml file.
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
|
||||
assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
|
||||
final JobStatus loadedTaskStatus = jobStatusSet.iterator().next();
|
||||
final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0);
|
||||
assertTasksEqual(task, loadedTaskStatus.getJob());
|
||||
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
|
||||
assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
|
||||
@@ -97,10 +98,10 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
mTaskStoreUnderTest.add(taskStatus2);
|
||||
Thread.sleep(IO_WAIT);
|
||||
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
|
||||
Iterator<JobStatus> it = jobStatusSet.iterator();
|
||||
Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator();
|
||||
JobStatus loaded1 = it.next();
|
||||
JobStatus loaded2 = it.next();
|
||||
|
||||
@@ -145,10 +146,10 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
mTaskStoreUnderTest.add(taskStatus);
|
||||
Thread.sleep(IO_WAIT);
|
||||
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
|
||||
JobStatus loaded = jobStatusSet.iterator().next();
|
||||
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
|
||||
assertTasksEqual(task, loaded.getJob());
|
||||
}
|
||||
public void testWritingTaskWithSourcePackage() throws Exception {
|
||||
@@ -163,10 +164,10 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
mTaskStoreUnderTest.add(taskStatus);
|
||||
Thread.sleep(IO_WAIT);
|
||||
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
|
||||
JobStatus loaded = jobStatusSet.iterator().next();
|
||||
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
|
||||
assertEquals("Source package not equal.", loaded.getSourcePackageName(),
|
||||
taskStatus.getSourcePackageName());
|
||||
assertEquals("Source user not equal.", loaded.getSourceUserId(),
|
||||
@@ -184,10 +185,10 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
mTaskStoreUnderTest.add(taskStatus);
|
||||
Thread.sleep(IO_WAIT);
|
||||
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
|
||||
JobStatus loaded = jobStatusSet.iterator().next();
|
||||
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
|
||||
assertEquals("Period not equal.", loaded.getJob().getIntervalMillis(),
|
||||
taskStatus.getJob().getIntervalMillis());
|
||||
assertEquals("Flex not equal.", loaded.getJob().getFlexMillis(),
|
||||
@@ -211,10 +212,10 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
mTaskStoreUnderTest.add(js);
|
||||
Thread.sleep(IO_WAIT);
|
||||
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
|
||||
JobStatus loaded = jobStatusSet.iterator().next();
|
||||
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
|
||||
|
||||
// Assert early runtime was clamped to be under now + period. We can do <= here b/c we'll
|
||||
// call SystemClock.elapsedRealtime after doing the disk i/o.
|
||||
@@ -234,9 +235,9 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1);
|
||||
mTaskStoreUnderTest.add(js);
|
||||
Thread.sleep(IO_WAIT);
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
JobStatus loaded = jobStatusSet.iterator().next();
|
||||
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
|
||||
assertEquals("Priority not correctly persisted.", 42, loaded.getPriority());
|
||||
}
|
||||
|
||||
@@ -255,10 +256,10 @@ public class JobStoreTest extends AndroidTestCase {
|
||||
JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1);
|
||||
mTaskStoreUnderTest.add(jsPersisted);
|
||||
Thread.sleep(IO_WAIT);
|
||||
final ArraySet<JobStatus> jobStatusSet = new ArraySet<JobStatus>();
|
||||
final JobSet jobStatusSet = new JobSet();
|
||||
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet);
|
||||
assertEquals("Job count is incorrect.", 1, jobStatusSet.size());
|
||||
JobStatus jobStatus = jobStatusSet.iterator().next();
|
||||
JobStatus jobStatus = jobStatusSet.getAllJobs().iterator().next();
|
||||
assertEquals("Wrong job persisted.", 43, jobStatus.getJobId());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user