import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.app.ProcessStats;
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobStatusFunctor;
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;
+ /** The maximum number of concurrent jobs we run at one time. */
+ private static final int MAX_JOB_CONTEXTS_COUNT = 8;
/** Enforce a per-app limit on scheduled jobs? */
private static final boolean ENFORCE_MAX_JOBS = false;
/** The maximum number of jobs that we allow an unprivileged app to schedule */
boolean mReportedActive;
/**
+ * Current limit on the number of concurrent JobServiceContext entries we want to
+ * keep actively running a job.
+ */
+ int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
+
+ /**
* Which uids are currently in the foreground.
*/
- final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
+ final SparseIntArray mUidPriorityOverride = new SparseIntArray();
+
+ // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
+
+ /**
+ * This array essentially stores the state of mActiveServices array.
+ * The ith index stores the job present on the ith JobServiceContext.
+ * We manipulate this array until we arrive at what jobs should be running on
+ * what JobServiceContext.
+ */
+ JobStatus[] mTmpAssignContextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
+ /**
+ * Indicates whether we need to act on this jobContext id
+ */
+ boolean[] mTmpAssignAct = new boolean[MAX_JOB_CONTEXTS_COUNT];
+ /**
+ * The uid whose jobs we would like to assign to a context.
+ */
+ int[] mTmpAssignPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
/**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
void updateUidState(int uid, int procState) {
synchronized (mLock) {
- boolean foreground = procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- boolean changed = false;
- if (foreground) {
- if (!mForegroundUids.get(uid)) {
- changed = true;
- mForegroundUids.put(uid, true);
- }
+ if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ // Only use this if we are exactly the top app. All others can live
+ // with just the foreground priority. This means that persistent processes
+ // can never be the top app priority... that is fine.
+ mUidPriorityOverride.put(uid, JobInfo.PRIORITY_TOP_APP);
+ } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ mUidPriorityOverride.put(uid, JobInfo.PRIORITY_FOREGROUND_APP);
} else {
- int index = mForegroundUids.indexOfKey(uid);
- if (index >= 0) {
- mForegroundUids.removeAt(index);
- changed = true;
- }
+ mUidPriorityOverride.delete(uid);
}
}
}
if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
return priority;
}
- if (mForegroundUids.get(job.getSourceUid())) {
- return JobInfo.PRIORITY_FOREGROUND_APP;
+ int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
+ if (override != 0) {
+ return override;
}
return priority;
}
Slog.d(TAG, printPendingQueue());
}
- // This array essentially stores the state of mActiveServices array.
- // ith index stores the job present on the ith JobServiceContext.
- // We manipulate this array until we arrive at what jobs should be running on
- // what JobServiceContext.
- JobStatus[] contextIdToJobMap = new JobStatus[MAX_JOB_CONTEXTS_COUNT];
- // Indicates whether we need to act on this jobContext id
- boolean[] act = new boolean[MAX_JOB_CONTEXTS_COUNT];
- int[] preferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
- for (int i=0; i<mActiveServices.size(); i++) {
- contextIdToJobMap[i] = mActiveServices.get(i).getRunningJob();
- preferredUidForContext[i] = mActiveServices.get(i).getPreferredUid();
+ int memLevel;
+ try {
+ memLevel = ActivityManagerNative.getDefault().getMemoryTrimLevel();
+ } catch (RemoteException e) {
+ memLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+ }
+ switch (memLevel) {
+ case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
+ mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - 2) * 2) / 3;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_LOW:
+ mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - 2) / 3;
+ break;
+ case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+ mMaxActiveJobs = 1;
+ break;
+ default:
+ mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
+ break;
+ }
+
+ JobStatus[] contextIdToJobMap = mTmpAssignContextIdToJobMap;
+ boolean[] act = mTmpAssignAct;
+ int[] preferredUidForContext = mTmpAssignPreferredUidForContext;
+ int numActive = 0;
+ for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
+ final JobServiceContext js = mActiveServices.get(i);
+ if ((contextIdToJobMap[i] = js.getRunningJob()) != null) {
+ numActive++;
+ }
+ act[i] = false;
+ preferredUidForContext[i] = js.getPreferredUid();
}
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs initial"));
}
- Iterator<JobStatus> it = mPendingJobs.iterator();
- while (it.hasNext()) {
- JobStatus nextPending = it.next();
+ for (int i=0; i<mPendingJobs.size(); i++) {
+ JobStatus nextPending = mPendingJobs.get(i);
// If job is already running, go to next job.
int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
continue;
}
- nextPending.lastEvaluatedPriority = evaluateJobPriorityLocked(nextPending);
+ final int priority = evaluateJobPriorityLocked(nextPending);
+ nextPending.lastEvaluatedPriority = priority;
// Find a context for nextPending. The context should be available OR
// it should have lowest priority among all running jobs
// (sharing the same Uid as nextPending)
int minPriority = Integer.MAX_VALUE;
int minPriorityContextId = -1;
- for (int i=0; i<mActiveServices.size(); i++) {
- JobStatus job = contextIdToJobMap[i];
- int preferredUid = preferredUidForContext[i];
- if (job == null && (preferredUid == nextPending.getUid() ||
- preferredUid == JobServiceContext.NO_PREFERRED_UID) ) {
- minPriorityContextId = i;
- break;
- }
+ for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
+ JobStatus job = contextIdToJobMap[j];
+ int preferredUid = preferredUidForContext[j];
if (job == null) {
+ if ((numActive < mMaxActiveJobs || priority >= JobInfo.PRIORITY_TOP_APP) &&
+ (preferredUid == nextPending.getUid() ||
+ preferredUid == JobServiceContext.NO_PREFERRED_UID)) {
+ // This slot is free, and we haven't yet hit the limit on
+ // concurrent jobs... we can just throw the job in to here.
+ minPriorityContextId = j;
+ numActive++;
+ break;
+ }
// No job on this context, but nextPending can't run here because
- // the context has a preferred Uid.
+ // the context has a preferred Uid or we have reached the limit on
+ // concurrent jobs.
continue;
}
if (job.getUid() != nextPending.getUid()) {
}
if (minPriority > nextPending.lastEvaluatedPriority) {
minPriority = nextPending.lastEvaluatedPriority;
- minPriorityContextId = i;
+ minPriorityContextId = j;
}
}
if (minPriorityContextId != -1) {
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
}
- for (int i=0; i<mActiveServices.size(); i++) {
+ for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
boolean preservePreferredUid = false;
if (act[i]) {
JobStatus js = mActiveServices.get(i).getRunningJob();
public void process(JobStatus job) {
pw.print(" Job #"); pw.print(index++); pw.print(": ");
pw.println(job.toShortString());
- job.dump(pw, " ");
+ job.dump(pw, " ", true);
pw.print(" Ready: ");
pw.print(mHandler.isReadyToBeExecutedLocked(job));
pw.print(" (job=");
mControllers.get(i).dumpControllerStateLocked(pw);
}
pw.println();
- pw.println("Foreground uids:");
- for (int i=0; i<mForegroundUids.size(); i++) {
- pw.print(" "); pw.println(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+ pw.println("Uid priority overrides:");
+ for (int i=0; i< mUidPriorityOverride.size(); i++) {
+ pw.print(" "); pw.print(UserHandle.formatUid(mUidPriorityOverride.keyAt(i)));
+ pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
}
pw.println();
pw.println("Pending queue:");
JobStatus job = mPendingJobs.get(i);
pw.print(" Pending #"); pw.print(i); pw.print(": ");
pw.println(job.toShortString());
+ job.dump(pw, " ", false);
int priority = evaluateJobPriorityLocked(job);
if (priority != JobInfo.PRIORITY_DEFAULT) {
pw.print(" Evaluated priority: "); pw.println(priority);
pw.println("Active jobs:");
for (int i=0; i<mActiveServices.size(); i++) {
JobServiceContext jsc = mActiveServices.get(i);
+ pw.print(" Slot #"); pw.print(i); pw.print(": ");
if (jsc.getRunningJob() == null) {
+ pw.println("inactive");
continue;
} else {
- final long timeout = jsc.getTimeoutElapsed();
- pw.print("Running for: ");
- pw.print((now - jsc.getExecutionStartTimeElapsed())/1000);
- pw.print("s timeout=");
- pw.print(timeout);
- pw.print(" fromnow=");
- pw.println(timeout-now);
- jsc.getRunningJob().dump(pw, " ");
+ pw.println(jsc.getRunningJob().toShortString());
+ pw.print(" Running for: ");
+ TimeUtils.formatDuration(now - jsc.getExecutionStartTimeElapsed(), pw);
+ pw.print(", timeout at: ");
+ TimeUtils.formatDuration(jsc.getTimeoutElapsed() - now, pw);
+ pw.println();
+ jsc.getRunningJob().dump(pw, " ", false);
int priority = evaluateJobPriorityLocked(jsc.getRunningJob());
if (priority != JobInfo.PRIORITY_DEFAULT) {
- pw.print(" Evaluated priority: "); pw.println(priority);
+ pw.print(" Evaluated priority: "); pw.println(priority);
}
}
}
pw.print("mReadyToRock="); pw.println(mReadyToRock);
pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
pw.print("mReportedActive="); pw.println(mReportedActive);
+ pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
}
pw.println();
}
sb.append(job.getService().getPackageName());
sb.append('/');
sb.append(this.sourceTag);
+ if (sourcePackageName != null) {
+ sb.append('/');
+ sb.append(this.sourcePackageName);
+ }
this.batteryName = sb.toString();
} else {
this.batteryName = job.getService().flattenToShortString();
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" jId=");
sb.append(job.getId());
- sb.append(" uid=");
+ sb.append(' ');
UserHandle.formatUid(sb, callingUid);
sb.append(' ');
- sb.append(job.getService().flattenToShortString());
+ sb.append(batteryName);
return sb.toString();
}
}
// Dumpsys infrastructure
- public void dump(PrintWriter pw, String prefix) {
+ public void dump(PrintWriter pw, String prefix, boolean full) {
pw.print(prefix); UserHandle.formatUid(pw, callingUid);
pw.print(" tag="); pw.println(tag);
pw.print(prefix);
pw.print("Source: uid="); UserHandle.formatUid(pw, getSourceUid());
pw.print(" user="); pw.print(getSourceUserId());
pw.print(" pkg="); pw.println(getSourcePackageName());
- pw.print(prefix); pw.println("JobInfo:");
- pw.print(prefix); pw.print(" Service: ");
- pw.println(job.getService().flattenToShortString());
- if (job.isPeriodic()) {
- pw.print(prefix); pw.print(" PERIODIC: interval=");
- TimeUtils.formatDuration(job.getIntervalMillis(), pw);
- pw.print(" flex=");
- TimeUtils.formatDuration(job.getFlexMillis(), pw);
- pw.println();
- }
- if (job.isPersisted()) {
- pw.print(prefix); pw.println(" PERSISTED");
- }
- if (job.getPriority() != 0) {
- pw.print(prefix); pw.print(" Priority: ");
- pw.println(job.getPriority());
- }
- pw.print(prefix); pw.print(" Requires: charging=");
- pw.print(job.isRequireCharging());
- pw.print(" deviceIdle=");
- pw.println(job.isRequireDeviceIdle());
- if (job.getTriggerContentUris() != null) {
- pw.print(prefix); pw.println(" Trigger content URIs:");
- for (int i=0; i<job.getTriggerContentUris().length; i++) {
- JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
- pw.print(prefix); pw.print(" ");
- pw.print(Integer.toHexString(trig.getFlags()));
- pw.print(' ' );
- pw.println(trig.getUri());
+ if (full) {
+ pw.print(prefix); pw.println("JobInfo:"); pw.print(prefix);
+ pw.print(" Service: "); pw.println(job.getService().flattenToShortString());
+ if (job.isPeriodic()) {
+ pw.print(prefix); pw.print(" PERIODIC: interval=");
+ TimeUtils.formatDuration(job.getIntervalMillis(), pw);
+ pw.print(" flex="); TimeUtils.formatDuration(job.getFlexMillis(), pw);
+ pw.println();
}
- }
- if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
- pw.print(prefix); pw.print(" Network type: ");
- pw.println(job.getNetworkType());
- }
- if (job.getMinLatencyMillis() != 0) {
- pw.print(prefix); pw.print(" Minimum latency: ");
- TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
- pw.println();
- }
- if (job.getMaxExecutionDelayMillis() != 0) {
- pw.print(prefix); pw.print(" Max execution delay: ");
- TimeUtils.formatDuration(job.getMaxExecutionDelayMillis(), pw);
+ if (job.isPersisted()) {
+ pw.print(prefix); pw.println(" PERSISTED");
+ }
+ if (job.getPriority() != 0) {
+ pw.print(prefix); pw.print(" Priority: "); pw.println(job.getPriority());
+ }
+ pw.print(prefix); pw.print(" Requires: charging=");
+ pw.print(job.isRequireCharging()); pw.print(" deviceIdle=");
+ pw.println(job.isRequireDeviceIdle());
+ if (job.getTriggerContentUris() != null) {
+ pw.print(prefix); pw.println(" Trigger content URIs:");
+ for (int i = 0; i < job.getTriggerContentUris().length; i++) {
+ JobInfo.TriggerContentUri trig = job.getTriggerContentUris()[i];
+ pw.print(prefix); pw.print(" ");
+ pw.print(Integer.toHexString(trig.getFlags()));
+ pw.print(' '); pw.println(trig.getUri());
+ }
+ }
+ if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
+ pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType());
+ }
+ if (job.getMinLatencyMillis() != 0) {
+ pw.print(prefix); pw.print(" Minimum latency: ");
+ TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
+ pw.println();
+ }
+ if (job.getMaxExecutionDelayMillis() != 0) {
+ pw.print(prefix); pw.print(" Max execution delay: ");
+ TimeUtils.formatDuration(job.getMaxExecutionDelayMillis(), pw);
+ pw.println();
+ }
+ pw.print(prefix); pw.print(" Backoff: policy="); pw.print(job.getBackoffPolicy());
+ pw.print(" initial="); TimeUtils.formatDuration(job.getInitialBackoffMillis(), pw);
pw.println();
- }
- pw.print(prefix); pw.print(" Backoff: policy=");
- pw.print(job.getBackoffPolicy());
- pw.print(" initial=");
- TimeUtils.formatDuration(job.getInitialBackoffMillis(), pw);
- pw.println();
- if (job.hasEarlyConstraint()) {
- pw.print(prefix); pw.println(" Has early constraint");
- }
- if (job.hasLateConstraint()) {
- pw.print(prefix); pw.println(" Has late constraint");
+ if (job.hasEarlyConstraint()) {
+ pw.print(prefix); pw.println(" Has early constraint");
+ }
+ if (job.hasLateConstraint()) {
+ pw.print(prefix); pw.println(" Has late constraint");
+ }
}
pw.print(prefix); pw.print("Required constraints:");
dumpConstraints(pw, requiredConstraints);
pw.println();
- pw.print(prefix); pw.print("Satisfied constraints:");
- dumpConstraints(pw, satisfiedConstraints);
- pw.println();
+ if (full) {
+ pw.print(prefix); pw.print("Satisfied constraints:");
+ dumpConstraints(pw, satisfiedConstraints);
+ pw.println();
+ }
if (changedAuthorities != null) {
pw.print(prefix); pw.println("Changed authorities:");
for (int i=0; i<changedAuthorities.size(); i++) {