Merge "Fix issue #28035090: Disallow abuse of JobScheduler" into nyc-dev
am: e3f617b
* commit 'e3f617b23f202e11d4ee67d322609ee7b07b11bb':
Fix issue #28035090: Disallow abuse of JobScheduler
Change-Id: I8ed129b1c5daabbddc57fdb95efdd15ba101184d
This commit is contained in:
@@ -138,6 +138,20 @@ public class JobInfo implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public static final int PRIORITY_TOP_APP = 40;
|
public static final int PRIORITY_TOP_APP = 40;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjustment of {@link #getPriority} if the app has often (50% or more of the time)
|
||||||
|
* been running jobs.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final int PRIORITY_ADJ_OFTEN_RUNNING = -40;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjustment of {@link #getPriority} if the app has always (90% or more of the time)
|
||||||
|
* been running jobs.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final int PRIORITY_ADJ_ALWAYS_RUNNING = -80;
|
||||||
|
|
||||||
private final int jobId;
|
private final int jobId;
|
||||||
private final PersistableBundle extras;
|
private final PersistableBundle extras;
|
||||||
private final ComponentName service;
|
private final ComponentName service;
|
||||||
|
|||||||
361
services/core/java/com/android/server/job/JobPackageTracker.java
Normal file
361
services/core/java/com/android/server/job/JobPackageTracker.java
Normal file
@@ -0,0 +1,361 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.server.job;
|
||||||
|
|
||||||
|
import android.app.job.JobInfo;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.util.TimeUtils;
|
||||||
|
import com.android.server.job.controllers.JobStatus;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
public final class JobPackageTracker {
|
||||||
|
// We batch every 30 minutes.
|
||||||
|
static final long BATCHING_TIME = 30*60*1000;
|
||||||
|
// Number of historical data sets we keep.
|
||||||
|
static final int NUM_HISTORY = 5;
|
||||||
|
|
||||||
|
DataSet mCurDataSet = new DataSet();
|
||||||
|
DataSet[] mLastDataSets = new DataSet[NUM_HISTORY];
|
||||||
|
|
||||||
|
final static class PackageEntry {
|
||||||
|
long pastActiveTime;
|
||||||
|
long activeStartTime;
|
||||||
|
int activeCount;
|
||||||
|
boolean hadActive;
|
||||||
|
long pastActiveTopTime;
|
||||||
|
long activeTopStartTime;
|
||||||
|
int activeTopCount;
|
||||||
|
boolean hadActiveTop;
|
||||||
|
long pastPendingTime;
|
||||||
|
long pendingStartTime;
|
||||||
|
int pendingCount;
|
||||||
|
boolean hadPending;
|
||||||
|
|
||||||
|
public long getActiveTime(long now) {
|
||||||
|
long time = pastActiveTime;
|
||||||
|
if (activeCount > 0) {
|
||||||
|
time += now - activeStartTime;
|
||||||
|
}
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getActiveTopTime(long now) {
|
||||||
|
long time = pastActiveTopTime;
|
||||||
|
if (activeTopCount > 0) {
|
||||||
|
time += now - activeTopStartTime;
|
||||||
|
}
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPendingTime(long now) {
|
||||||
|
long time = pastPendingTime;
|
||||||
|
if (pendingCount > 0) {
|
||||||
|
time += now - pendingStartTime;
|
||||||
|
}
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final static class DataSet {
|
||||||
|
final SparseArray<ArrayMap<String, PackageEntry>> mEntries = new SparseArray<>();
|
||||||
|
final long mStartUptimeTime;
|
||||||
|
final long mStartElapsedTime;
|
||||||
|
final long mStartClockTime;
|
||||||
|
long mSummedTime;
|
||||||
|
|
||||||
|
public DataSet(DataSet otherTimes) {
|
||||||
|
mStartUptimeTime = otherTimes.mStartUptimeTime;
|
||||||
|
mStartElapsedTime = otherTimes.mStartElapsedTime;
|
||||||
|
mStartClockTime = otherTimes.mStartClockTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataSet() {
|
||||||
|
mStartUptimeTime = SystemClock.uptimeMillis();
|
||||||
|
mStartElapsedTime = SystemClock.elapsedRealtime();
|
||||||
|
mStartClockTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PackageEntry getOrCreateEntry(int uid, String pkg) {
|
||||||
|
ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
|
||||||
|
if (uidMap == null) {
|
||||||
|
uidMap = new ArrayMap<>();
|
||||||
|
mEntries.put(uid, uidMap);
|
||||||
|
}
|
||||||
|
PackageEntry entry = uidMap.get(pkg);
|
||||||
|
if (entry == null) {
|
||||||
|
entry = new PackageEntry();
|
||||||
|
uidMap.put(pkg, entry);
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PackageEntry getEntry(int uid, String pkg) {
|
||||||
|
ArrayMap<String, PackageEntry> uidMap = mEntries.get(uid);
|
||||||
|
if (uidMap == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return uidMap.get(pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
long getTotalTime(long now) {
|
||||||
|
if (mSummedTime > 0) {
|
||||||
|
return mSummedTime;
|
||||||
|
}
|
||||||
|
return now - mStartUptimeTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void incPending(int uid, String pkg, long now) {
|
||||||
|
PackageEntry pe = getOrCreateEntry(uid, pkg);
|
||||||
|
if (pe.pendingCount == 0) {
|
||||||
|
pe.pendingStartTime = now;
|
||||||
|
}
|
||||||
|
pe.pendingCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decPending(int uid, String pkg, long now) {
|
||||||
|
PackageEntry pe = getOrCreateEntry(uid, pkg);
|
||||||
|
if (pe.pendingCount == 1) {
|
||||||
|
pe.pastPendingTime += now - pe.pendingStartTime;
|
||||||
|
}
|
||||||
|
pe.pendingCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void incActive(int uid, String pkg, long now) {
|
||||||
|
PackageEntry pe = getOrCreateEntry(uid, pkg);
|
||||||
|
if (pe.activeCount == 0) {
|
||||||
|
pe.activeStartTime = now;
|
||||||
|
}
|
||||||
|
pe.activeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decActive(int uid, String pkg, long now) {
|
||||||
|
PackageEntry pe = getOrCreateEntry(uid, pkg);
|
||||||
|
if (pe.activeCount == 1) {
|
||||||
|
pe.pastActiveTime += now - pe.activeStartTime;
|
||||||
|
}
|
||||||
|
pe.activeCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void incActiveTop(int uid, String pkg, long now) {
|
||||||
|
PackageEntry pe = getOrCreateEntry(uid, pkg);
|
||||||
|
if (pe.activeTopCount == 0) {
|
||||||
|
pe.activeTopStartTime = now;
|
||||||
|
}
|
||||||
|
pe.activeTopCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decActiveTop(int uid, String pkg, long now) {
|
||||||
|
PackageEntry pe = getOrCreateEntry(uid, pkg);
|
||||||
|
if (pe.activeTopCount == 1) {
|
||||||
|
pe.pastActiveTopTime += now - pe.activeTopStartTime;
|
||||||
|
}
|
||||||
|
pe.activeTopCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish(DataSet next, long now) {
|
||||||
|
for (int i = mEntries.size() - 1; i >= 0; i--) {
|
||||||
|
ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
|
||||||
|
for (int j = uidMap.size() - 1; j >= 0; j--) {
|
||||||
|
PackageEntry pe = uidMap.valueAt(j);
|
||||||
|
if (pe.activeCount > 0 || pe.activeTopCount > 0 || pe.pendingCount > 0) {
|
||||||
|
// Propagate existing activity in to next data set.
|
||||||
|
PackageEntry nextPe = next.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
|
||||||
|
nextPe.activeStartTime = now;
|
||||||
|
nextPe.activeCount = pe.activeCount;
|
||||||
|
nextPe.activeTopStartTime = now;
|
||||||
|
nextPe.activeTopCount = pe.activeTopCount;
|
||||||
|
nextPe.pendingStartTime = now;
|
||||||
|
nextPe.pendingCount = pe.pendingCount;
|
||||||
|
// Finish it off.
|
||||||
|
if (pe.activeCount > 0) {
|
||||||
|
pe.pastActiveTime += now - pe.activeStartTime;
|
||||||
|
pe.activeCount = 0;
|
||||||
|
}
|
||||||
|
if (pe.activeTopCount > 0) {
|
||||||
|
pe.pastActiveTopTime += now - pe.activeTopStartTime;
|
||||||
|
pe.activeTopCount = 0;
|
||||||
|
}
|
||||||
|
if (pe.pendingCount > 0) {
|
||||||
|
pe.pastPendingTime += now - pe.pendingStartTime;
|
||||||
|
pe.pendingCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addTo(DataSet out, long now) {
|
||||||
|
out.mSummedTime += getTotalTime(now);
|
||||||
|
for (int i = mEntries.size() - 1; i >= 0; i--) {
|
||||||
|
ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
|
||||||
|
for (int j = uidMap.size() - 1; j >= 0; j--) {
|
||||||
|
PackageEntry pe = uidMap.valueAt(j);
|
||||||
|
PackageEntry outPe = out.getOrCreateEntry(mEntries.keyAt(i), uidMap.keyAt(j));
|
||||||
|
outPe.pastActiveTime += pe.pastActiveTime;
|
||||||
|
outPe.pastActiveTopTime += pe.pastActiveTopTime;
|
||||||
|
outPe.pastPendingTime += pe.pastPendingTime;
|
||||||
|
if (pe.activeCount > 0) {
|
||||||
|
outPe.pastActiveTime += now - pe.activeStartTime;
|
||||||
|
outPe.hadActive = true;
|
||||||
|
}
|
||||||
|
if (pe.activeTopCount > 0) {
|
||||||
|
outPe.pastActiveTopTime += now - pe.activeTopStartTime;
|
||||||
|
outPe.hadActiveTop = true;
|
||||||
|
}
|
||||||
|
if (pe.pendingCount > 0) {
|
||||||
|
outPe.pastPendingTime += now - pe.pendingStartTime;
|
||||||
|
outPe.hadPending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printDuration(PrintWriter pw, long period, long duration, String suffix) {
|
||||||
|
float fraction = duration / (float) period;
|
||||||
|
int percent = (int) ((fraction * 100) + .5f);
|
||||||
|
if (percent > 0) {
|
||||||
|
pw.print(" ");
|
||||||
|
pw.print(percent);
|
||||||
|
pw.print("% ");
|
||||||
|
pw.print(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump(PrintWriter pw, String header, String prefix, long now, long nowEllapsed) {
|
||||||
|
final long period = getTotalTime(now);
|
||||||
|
pw.print(prefix); pw.print(header); pw.print(" at ");
|
||||||
|
pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartClockTime).toString());
|
||||||
|
pw.print(" (");
|
||||||
|
TimeUtils.formatDuration(mStartElapsedTime, nowEllapsed, pw);
|
||||||
|
pw.print(") over ");
|
||||||
|
TimeUtils.formatDuration(period, pw);
|
||||||
|
pw.println(":");
|
||||||
|
final int NE = mEntries.size();
|
||||||
|
for (int i = 0; i < NE; i++) {
|
||||||
|
ArrayMap<String, PackageEntry> uidMap = mEntries.valueAt(i);
|
||||||
|
final int NP = uidMap.size();
|
||||||
|
for (int j = 0; j < NP; j++) {
|
||||||
|
PackageEntry pe = uidMap.valueAt(j);
|
||||||
|
pw.print(prefix); pw.print(" ");
|
||||||
|
UserHandle.formatUid(pw, mEntries.keyAt(i));
|
||||||
|
pw.print(" / "); pw.print(uidMap.keyAt(j));
|
||||||
|
pw.print(":");
|
||||||
|
printDuration(pw, period, pe.getPendingTime(now), "pending");
|
||||||
|
printDuration(pw, period, pe.getActiveTime(now), "active");
|
||||||
|
printDuration(pw, period, pe.getActiveTopTime(now), "active-top");
|
||||||
|
if (pe.pendingCount > 0 || pe.hadPending) {
|
||||||
|
pw.print(" (pending)");
|
||||||
|
}
|
||||||
|
if (pe.activeCount > 0 || pe.hadActive) {
|
||||||
|
pw.print(" (active)");
|
||||||
|
}
|
||||||
|
if (pe.activeTopCount > 0 || pe.hadActiveTop) {
|
||||||
|
pw.print(" (active-top)");
|
||||||
|
}
|
||||||
|
pw.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rebatchIfNeeded(long now) {
|
||||||
|
long totalTime = mCurDataSet.getTotalTime(now);
|
||||||
|
if (totalTime > BATCHING_TIME) {
|
||||||
|
DataSet last = mCurDataSet;
|
||||||
|
last.mSummedTime = totalTime;
|
||||||
|
mCurDataSet = new DataSet();
|
||||||
|
last.finish(mCurDataSet, now);
|
||||||
|
System.arraycopy(mLastDataSets, 0, mLastDataSets, 1, mLastDataSets.length-1);
|
||||||
|
mLastDataSets[0] = last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notePending(JobStatus job) {
|
||||||
|
final long now = SystemClock.uptimeMillis();
|
||||||
|
rebatchIfNeeded(now);
|
||||||
|
mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void noteNonpending(JobStatus job) {
|
||||||
|
final long now = SystemClock.uptimeMillis();
|
||||||
|
mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
|
||||||
|
rebatchIfNeeded(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void noteActive(JobStatus job) {
|
||||||
|
final long now = SystemClock.uptimeMillis();
|
||||||
|
rebatchIfNeeded(now);
|
||||||
|
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
|
||||||
|
mCurDataSet.incActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
|
||||||
|
} else {
|
||||||
|
mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void noteInactive(JobStatus job) {
|
||||||
|
final long now = SystemClock.uptimeMillis();
|
||||||
|
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
|
||||||
|
mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
|
||||||
|
} else {
|
||||||
|
mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now);
|
||||||
|
}
|
||||||
|
rebatchIfNeeded(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLoadFactor(JobStatus job) {
|
||||||
|
final int uid = job.getSourceUid();
|
||||||
|
final String pkg = job.getSourcePackageName();
|
||||||
|
PackageEntry cur = mCurDataSet.getEntry(uid, pkg);
|
||||||
|
PackageEntry last = mLastDataSets[0] != null ? mLastDataSets[0].getEntry(uid, pkg) : null;
|
||||||
|
if (cur == null && last == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final long now = SystemClock.uptimeMillis();
|
||||||
|
long time = cur.getActiveTime(now) + cur.getPendingTime(now);
|
||||||
|
long period = mCurDataSet.getTotalTime(now);
|
||||||
|
if (last != null) {
|
||||||
|
time += last.getActiveTime(now) + last.getPendingTime(now);
|
||||||
|
period += mLastDataSets[0].getTotalTime(now);
|
||||||
|
}
|
||||||
|
return time / (float)period;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dump(PrintWriter pw, String prefix) {
|
||||||
|
final long now = SystemClock.uptimeMillis();
|
||||||
|
final long nowEllapsed = SystemClock.elapsedRealtime();
|
||||||
|
final DataSet total;
|
||||||
|
if (mLastDataSets[0] != null) {
|
||||||
|
total = new DataSet(mLastDataSets[0]);
|
||||||
|
mLastDataSets[0].addTo(total, now);
|
||||||
|
} else {
|
||||||
|
total = new DataSet(mCurDataSet);
|
||||||
|
}
|
||||||
|
mCurDataSet.addTo(total, now);
|
||||||
|
for (int i = 1; i < mLastDataSets.length; i++) {
|
||||||
|
if (mLastDataSets[i] != null) {
|
||||||
|
mLastDataSets[i].dump(pw, "Historical stats", prefix, now, nowEllapsed);
|
||||||
|
pw.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total.dump(pw, "Current stats", prefix, now, nowEllapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,16 +93,24 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
public static final boolean DEBUG = false;
|
public static final boolean DEBUG = false;
|
||||||
|
|
||||||
/** The maximum number of concurrent jobs we run at one time. */
|
/** The maximum number of concurrent jobs we run at one time. */
|
||||||
private static final int MAX_JOB_CONTEXTS_COUNT = 8;
|
private static final int MAX_JOB_CONTEXTS_COUNT = 12;
|
||||||
|
/** The number of MAX_JOB_CONTEXTS_COUNT we reserve for the foreground app. */
|
||||||
|
private static final int FG_JOB_CONTEXTS_COUNT = 4;
|
||||||
/** Enforce a per-app limit on scheduled jobs? */
|
/** Enforce a per-app limit on scheduled jobs? */
|
||||||
private static final boolean ENFORCE_MAX_JOBS = true;
|
private static final boolean ENFORCE_MAX_JOBS = true;
|
||||||
/** The maximum number of jobs that we allow an unprivileged app to schedule */
|
/** The maximum number of jobs that we allow an unprivileged app to schedule */
|
||||||
private static final int MAX_JOBS_PER_APP = 100;
|
private static final int MAX_JOBS_PER_APP = 100;
|
||||||
|
/** This is the job execution factor that is considered to be heavy use of the system. */
|
||||||
|
private static final float HEAVY_USE_FACTOR = .9f;
|
||||||
|
/** This is the job execution factor that is considered to be moderate use of the system. */
|
||||||
|
private static final float MODERATE_USE_FACTOR = .5f;
|
||||||
|
|
||||||
/** Global local for all job scheduler state. */
|
/** Global local for all job scheduler state. */
|
||||||
final Object mLock = new Object();
|
final Object mLock = new Object();
|
||||||
/** Master list of jobs. */
|
/** Master list of jobs. */
|
||||||
final JobStore mJobs;
|
final JobStore mJobs;
|
||||||
|
/** Tracking amount of time each package runs for. */
|
||||||
|
final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
|
||||||
|
|
||||||
static final int MSG_JOB_EXPIRED = 0;
|
static final int MSG_JOB_EXPIRED = 0;
|
||||||
static final int MSG_CHECK_JOB = 1;
|
static final int MSG_CHECK_JOB = 1;
|
||||||
@@ -173,7 +181,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
* Current limit on the number of concurrent JobServiceContext entries we want to
|
* Current limit on the number of concurrent JobServiceContext entries we want to
|
||||||
* keep actively running a job.
|
* keep actively running a job.
|
||||||
*/
|
*/
|
||||||
int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
|
int mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Which uids are currently in the foreground.
|
* Which uids are currently in the foreground.
|
||||||
@@ -386,7 +394,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
|
stopTrackingJob(cancelled, incomingJob, true /* writeBack */);
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
// Remove from pending queue.
|
// Remove from pending queue.
|
||||||
mPendingJobs.remove(cancelled);
|
if (mPendingJobs.remove(cancelled)) {
|
||||||
|
mJobPackageTracker.noteNonpending(cancelled);
|
||||||
|
}
|
||||||
// Cancel if running.
|
// Cancel if running.
|
||||||
stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
|
stopJobOnServiceContextLocked(cancelled, JobParameters.REASON_CANCELED);
|
||||||
reportActive();
|
reportActive();
|
||||||
@@ -518,7 +528,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
// Create the "runners".
|
// Create the "runners".
|
||||||
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
|
for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
|
||||||
mActiveServices.add(
|
mActiveServices.add(
|
||||||
new JobServiceContext(this, mBatteryStats,
|
new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
|
||||||
getContext().getMainLooper()));
|
getContext().getMainLooper()));
|
||||||
}
|
}
|
||||||
// Attach jobs to their controllers.
|
// Attach jobs to their controllers.
|
||||||
@@ -604,6 +614,20 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void noteJobsPending(List<JobStatus> jobs) {
|
||||||
|
for (int i = jobs.size() - 1; i >= 0; i--) {
|
||||||
|
JobStatus job = jobs.get(i);
|
||||||
|
mJobPackageTracker.notePending(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void noteJobsNonpending(List<JobStatus> jobs) {
|
||||||
|
for (int i = jobs.size() - 1; i >= 0; i--) {
|
||||||
|
JobStatus job = jobs.get(i);
|
||||||
|
mJobPackageTracker.noteNonpending(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reschedules the given job based on the job's backoff policy. It doesn't make sense to
|
* Reschedules the given job based on the job's backoff policy. It doesn't make sense to
|
||||||
* specify an override deadline on a failed job (the failed job will run even though it's not
|
* specify an override deadline on a failed job (the failed job will run even though it's not
|
||||||
@@ -759,6 +783,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
// state is such that all ready jobs should be run immediately.
|
// state is such that all ready jobs should be run immediately.
|
||||||
if (runNow != null && !mPendingJobs.contains(runNow)
|
if (runNow != null && !mPendingJobs.contains(runNow)
|
||||||
&& mJobs.containsJob(runNow)) {
|
&& mJobs.containsJob(runNow)) {
|
||||||
|
mJobPackageTracker.notePending(runNow);
|
||||||
mPendingJobs.add(runNow);
|
mPendingJobs.add(runNow);
|
||||||
}
|
}
|
||||||
queueReadyJobsForExecutionLockedH();
|
queueReadyJobsForExecutionLockedH();
|
||||||
@@ -797,6 +822,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "queuing all ready jobs for execution:");
|
Slog.d(TAG, "queuing all ready jobs for execution:");
|
||||||
}
|
}
|
||||||
|
noteJobsNonpending(mPendingJobs);
|
||||||
mPendingJobs.clear();
|
mPendingJobs.clear();
|
||||||
mJobs.forEachJob(mReadyQueueFunctor);
|
mJobs.forEachJob(mReadyQueueFunctor);
|
||||||
mReadyQueueFunctor.postProcess();
|
mReadyQueueFunctor.postProcess();
|
||||||
@@ -832,6 +858,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
|
|
||||||
public void postProcess() {
|
public void postProcess() {
|
||||||
if (newReadyJobs != null) {
|
if (newReadyJobs != null) {
|
||||||
|
noteJobsPending(newReadyJobs);
|
||||||
mPendingJobs.addAll(newReadyJobs);
|
mPendingJobs.addAll(newReadyJobs);
|
||||||
}
|
}
|
||||||
newReadyJobs = null;
|
newReadyJobs = null;
|
||||||
@@ -910,6 +937,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
|
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLockedH: Running jobs.");
|
||||||
}
|
}
|
||||||
|
noteJobsPending(runnableJobs);
|
||||||
mPendingJobs.addAll(runnableJobs);
|
mPendingJobs.addAll(runnableJobs);
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -935,6 +963,7 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
private void maybeQueueReadyJobsForExecutionLockedH() {
|
private void maybeQueueReadyJobsForExecutionLockedH() {
|
||||||
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
|
if (DEBUG) Slog.d(TAG, "Maybe queuing ready jobs...");
|
||||||
|
|
||||||
|
noteJobsNonpending(mPendingJobs);
|
||||||
mPendingJobs.clear();
|
mPendingJobs.clear();
|
||||||
mJobs.forEachJob(mMaybeQueueFunctor);
|
mJobs.forEachJob(mMaybeQueueFunctor);
|
||||||
mMaybeQueueFunctor.postProcess();
|
mMaybeQueueFunctor.postProcess();
|
||||||
@@ -998,16 +1027,28 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int adjustJobPriority(int curPriority, JobStatus job) {
|
||||||
|
if (curPriority < JobInfo.PRIORITY_TOP_APP) {
|
||||||
|
float factor = mJobPackageTracker.getLoadFactor(job);
|
||||||
|
if (factor >= HEAVY_USE_FACTOR) {
|
||||||
|
curPriority += JobInfo.PRIORITY_ADJ_ALWAYS_RUNNING;
|
||||||
|
} else if (factor >= MODERATE_USE_FACTOR) {
|
||||||
|
curPriority += JobInfo.PRIORITY_ADJ_OFTEN_RUNNING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curPriority;
|
||||||
|
}
|
||||||
|
|
||||||
private int evaluateJobPriorityLocked(JobStatus job) {
|
private int evaluateJobPriorityLocked(JobStatus job) {
|
||||||
int priority = job.getPriority();
|
int priority = job.getPriority();
|
||||||
if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
|
if (priority >= JobInfo.PRIORITY_FOREGROUND_APP) {
|
||||||
return priority;
|
return adjustJobPriority(priority, job);
|
||||||
}
|
}
|
||||||
int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
|
int override = mUidPriorityOverride.get(job.getSourceUid(), 0);
|
||||||
if (override != 0) {
|
if (override != 0) {
|
||||||
return override;
|
return adjustJobPriority(override, job);
|
||||||
}
|
}
|
||||||
return priority;
|
return adjustJobPriority(priority, job);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1029,16 +1070,16 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
}
|
}
|
||||||
switch (memLevel) {
|
switch (memLevel) {
|
||||||
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
|
case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
|
||||||
mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - 2) * 2) / 3;
|
mMaxActiveJobs = ((MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) * 2) / 3;
|
||||||
break;
|
break;
|
||||||
case ProcessStats.ADJ_MEM_FACTOR_LOW:
|
case ProcessStats.ADJ_MEM_FACTOR_LOW:
|
||||||
mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - 2) / 3;
|
mMaxActiveJobs = (MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT) / 3;
|
||||||
break;
|
break;
|
||||||
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
|
case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
|
||||||
mMaxActiveJobs = 1;
|
mMaxActiveJobs = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - 2;
|
mMaxActiveJobs = MAX_JOB_CONTEXTS_COUNT - FG_JOB_CONTEXTS_COUNT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1134,7 +1175,9 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
|
if (!mActiveServices.get(i).executeRunnableJob(pendingJob)) {
|
||||||
Slog.d(TAG, "Error executing " + pendingJob);
|
Slog.d(TAG, "Error executing " + pendingJob);
|
||||||
}
|
}
|
||||||
mPendingJobs.remove(pendingJob);
|
if (mPendingJobs.remove(pendingJob)) {
|
||||||
|
mJobPackageTracker.noteNonpending(pendingJob);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!preservePreferredUid) {
|
if (!preservePreferredUid) {
|
||||||
@@ -1444,6 +1487,8 @@ public final class JobSchedulerService extends com.android.server.SystemService
|
|||||||
pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
|
pw.print(": "); pw.println(mUidPriorityOverride.valueAt(i));
|
||||||
}
|
}
|
||||||
pw.println();
|
pw.println();
|
||||||
|
mJobPackageTracker.dump(pw, "");
|
||||||
|
pw.println();
|
||||||
pw.println("Pending queue:");
|
pw.println("Pending queue:");
|
||||||
for (int i=0; i<mPendingJobs.size(); i++) {
|
for (int i=0; i<mPendingJobs.size(); i++) {
|
||||||
JobStatus job = mPendingJobs.get(i);
|
JobStatus job = mPendingJobs.get(i);
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
|||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final Object mLock;
|
private final Object mLock;
|
||||||
private final IBatteryStats mBatteryStats;
|
private final IBatteryStats mBatteryStats;
|
||||||
|
private final JobPackageTracker mJobPackageTracker;
|
||||||
private PowerManager.WakeLock mWakeLock;
|
private PowerManager.WakeLock mWakeLock;
|
||||||
|
|
||||||
// Execution state.
|
// Execution state.
|
||||||
@@ -136,16 +137,18 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
|||||||
/** Track when job will timeout. */
|
/** Track when job will timeout. */
|
||||||
private long mTimeoutElapsed;
|
private long mTimeoutElapsed;
|
||||||
|
|
||||||
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats, Looper looper) {
|
JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
|
||||||
this(service.getContext(), service.getLock(), batteryStats, service, looper);
|
JobPackageTracker tracker, Looper looper) {
|
||||||
|
this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
|
JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
|
||||||
JobCompletedListener completedListener, Looper looper) {
|
JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mLock = lock;
|
mLock = lock;
|
||||||
mBatteryStats = batteryStats;
|
mBatteryStats = batteryStats;
|
||||||
|
mJobPackageTracker = tracker;
|
||||||
mCallbackHandler = new JobServiceHandler(looper);
|
mCallbackHandler = new JobServiceHandler(looper);
|
||||||
mCompletedListener = completedListener;
|
mCompletedListener = completedListener;
|
||||||
mAvailable = true;
|
mAvailable = true;
|
||||||
@@ -208,6 +211,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
|||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// Whatever.
|
// Whatever.
|
||||||
}
|
}
|
||||||
|
mJobPackageTracker.noteActive(job);
|
||||||
mAvailable = false;
|
mAvailable = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -580,6 +584,7 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
completedJob = mRunningJob;
|
completedJob = mRunningJob;
|
||||||
|
mJobPackageTracker.noteInactive(completedJob);
|
||||||
try {
|
try {
|
||||||
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
|
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
|
||||||
mRunningJob.getSourceUid());
|
mRunningJob.getSourceUid());
|
||||||
|
|||||||
Reference in New Issue
Block a user