Removing JobScheduler heartbeat code.
Data shows that the quota based system has a small impact on battery life while still addressing the core issues we had with the heartbeat system. We can continue to tweak numbers and make some policy adjustments to the quota system, but at this point, it appears safe to remove the heartbeat code. Bug: 138324538 Test: atest com.android.server.cts.JobSchedulerIncidentTest Test: atest com.android.server.job.JobSchedulerServiceTest Test: atest com.android.server.job.JobStoreTest Test: atest com.android.server.job.controllers.ConnectivityControllerTest Test: atest com.android.server.job.controllers.JobStatusTest Test: atest com.android.server.job.controllers.QuotaControllerTest Test: atest CtsJobSchedulerTestCases Change-Id: I2b025ca426387ece123c0b8ed8bcc143f6c6d6db
This commit is contained in:
@@ -25,7 +25,6 @@ import android.annotation.UserIdInt;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.IUidObserver;
|
||||
import android.app.job.IJobScheduler;
|
||||
@@ -95,7 +94,6 @@ import com.android.server.FgThread;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
|
||||
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
|
||||
import com.android.server.job.JobSchedulerServiceDumpProto.RegisteredJob;
|
||||
import com.android.server.job.controllers.BackgroundJobsController;
|
||||
import com.android.server.job.controllers.BatteryController;
|
||||
import com.android.server.job.controllers.ConnectivityController;
|
||||
@@ -117,7 +115,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
@@ -240,18 +237,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
final SparseIntArray mBackingUpUids = new SparseIntArray();
|
||||
|
||||
/**
|
||||
* Count standby heartbeats, and keep track of which beat each bucket's jobs will
|
||||
* next become runnable. Index into this array is by normalized bucket:
|
||||
* { ACTIVE, WORKING, FREQUENT, RARE, NEVER }. The ACTIVE and NEVER bucket
|
||||
* milestones are not updated: ACTIVE apps get jobs whenever they ask for them,
|
||||
* and NEVER apps don't get them at all.
|
||||
*/
|
||||
final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE };
|
||||
long mHeartbeat = 0;
|
||||
long mLastHeartbeatTime = sElapsedRealtimeClock.millis();
|
||||
|
||||
/**
|
||||
* Named indices into the STANDBY_BEATS array, for clarity in referring to
|
||||
* Named indices into standby bucket arrays, for clarity in referring to
|
||||
* specific buckets' bookkeeping.
|
||||
*/
|
||||
public static final int ACTIVE_INDEX = 0;
|
||||
@@ -260,21 +246,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
public static final int RARE_INDEX = 3;
|
||||
public static final int NEVER_INDEX = 4;
|
||||
|
||||
/**
|
||||
* Bookkeeping about when jobs last run. We keep our own record in heartbeat time,
|
||||
* rather than rely on Usage Stats' timestamps, because heartbeat time can be
|
||||
* manipulated for testing purposes and we need job runnability to track that rather
|
||||
* than real time.
|
||||
*
|
||||
* Outer SparseArray slices by user handle; inner map of package name to heartbeat
|
||||
* is a HashMap<> rather than ArrayMap<> because we expect O(hundreds) of keys
|
||||
* and it will be accessed in a known-hot code path.
|
||||
*/
|
||||
final SparseArray<HashMap<String, Long>> mLastJobHeartbeats = new SparseArray<>();
|
||||
|
||||
static final String HEARTBEAT_TAG = "*job.heartbeat*";
|
||||
final HeartbeatAlarmListener mHeartbeatAlarm = new HeartbeatAlarmListener();
|
||||
|
||||
// -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
|
||||
|
||||
private class ConstantsObserver extends ContentObserver {
|
||||
@@ -311,11 +282,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
Slog.e(TAG, "Bad jobscheduler settings", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (mConstants.USE_HEARTBEATS) {
|
||||
// Reset the heartbeat alarm based on the new heartbeat duration
|
||||
setNextHeartbeatAlarm();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,13 +431,15 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
|
||||
private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
|
||||
private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
|
||||
private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time";
|
||||
private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
|
||||
private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
|
||||
private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
|
||||
private static final String DEPRECATED_KEY_STANDBY_HEARTBEAT_TIME =
|
||||
"standby_heartbeat_time";
|
||||
private static final String DEPRECATED_KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
|
||||
private static final String DEPRECATED_KEY_STANDBY_FREQUENT_BEATS =
|
||||
"standby_frequent_beats";
|
||||
private static final String DEPRECATED_KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
|
||||
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
|
||||
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
|
||||
private static final String KEY_USE_HEARTBEATS = "use_heartbeats";
|
||||
private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats";
|
||||
|
||||
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
|
||||
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
|
||||
@@ -488,13 +456,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
|
||||
private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
|
||||
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
|
||||
private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L;
|
||||
private static final int DEFAULT_STANDBY_WORKING_BEATS = 11; // ~ 2 hours, with 11min beats
|
||||
private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 43; // ~ 8 hours
|
||||
private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
|
||||
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
|
||||
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
|
||||
private static final boolean DEFAULT_USE_HEARTBEATS = false;
|
||||
|
||||
/**
|
||||
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
|
||||
@@ -617,26 +580,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* The minimum backoff time to allow for exponential backoff.
|
||||
*/
|
||||
long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
|
||||
/**
|
||||
* How often we recalculate runnability based on apps' standby bucket assignment.
|
||||
* This should be prime relative to common time interval lengths such as a quarter-
|
||||
* hour or day, so that the heartbeat drifts relative to wall-clock milestones.
|
||||
*/
|
||||
long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
|
||||
/**
|
||||
* Mapping: standby bucket -> number of heartbeats between each sweep of that
|
||||
* bucket's jobs.
|
||||
*
|
||||
* Bucket assignments as recorded in the JobStatus objects are normalized to be
|
||||
* indices into this array, rather than the raw constants used
|
||||
* by AppIdleHistory.
|
||||
*/
|
||||
final int[] STANDBY_BEATS = {
|
||||
0,
|
||||
DEFAULT_STANDBY_WORKING_BEATS,
|
||||
DEFAULT_STANDBY_FREQUENT_BEATS,
|
||||
DEFAULT_STANDBY_RARE_BEATS
|
||||
};
|
||||
|
||||
/**
|
||||
* The fraction of a job's running window that must pass before we
|
||||
* consider running it when the network is congested.
|
||||
@@ -647,11 +591,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* we consider matching it against a metered network.
|
||||
*/
|
||||
public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
|
||||
/**
|
||||
* Whether to use heartbeats or rolling window for quota management. True will use
|
||||
* heartbeats, false will use a rolling window.
|
||||
*/
|
||||
public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS;
|
||||
|
||||
private final KeyValueListParser mParser = new KeyValueListParser(',');
|
||||
|
||||
@@ -709,19 +648,10 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
DEFAULT_MIN_LINEAR_BACKOFF_TIME);
|
||||
MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME,
|
||||
DEFAULT_MIN_EXP_BACKOFF_TIME);
|
||||
STANDBY_HEARTBEAT_TIME = mParser.getDurationMillis(KEY_STANDBY_HEARTBEAT_TIME,
|
||||
DEFAULT_STANDBY_HEARTBEAT_TIME);
|
||||
STANDBY_BEATS[WORKING_INDEX] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
|
||||
DEFAULT_STANDBY_WORKING_BEATS);
|
||||
STANDBY_BEATS[FREQUENT_INDEX] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
|
||||
DEFAULT_STANDBY_FREQUENT_BEATS);
|
||||
STANDBY_BEATS[RARE_INDEX] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
|
||||
DEFAULT_STANDBY_RARE_BEATS);
|
||||
CONN_CONGESTION_DELAY_FRAC = mParser.getFloat(KEY_CONN_CONGESTION_DELAY_FRAC,
|
||||
DEFAULT_CONN_CONGESTION_DELAY_FRAC);
|
||||
CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
|
||||
DEFAULT_CONN_PREFETCH_RELAX_FRAC);
|
||||
USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS);
|
||||
}
|
||||
|
||||
void dump(IndentingPrintWriter pw) {
|
||||
@@ -757,17 +687,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println();
|
||||
pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println();
|
||||
pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println();
|
||||
pw.printPair(KEY_STANDBY_HEARTBEAT_TIME, STANDBY_HEARTBEAT_TIME).println();
|
||||
pw.print("standby_beats={");
|
||||
pw.print(STANDBY_BEATS[0]);
|
||||
for (int i = 1; i < STANDBY_BEATS.length; i++) {
|
||||
pw.print(", ");
|
||||
pw.print(STANDBY_BEATS[i]);
|
||||
}
|
||||
pw.println('}');
|
||||
pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
|
||||
pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
|
||||
pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println();
|
||||
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
@@ -797,13 +718,8 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT);
|
||||
proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME);
|
||||
proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
|
||||
proto.write(ConstantsProto.STANDBY_HEARTBEAT_TIME_MS, STANDBY_HEARTBEAT_TIME);
|
||||
for (int period : STANDBY_BEATS) {
|
||||
proto.write(ConstantsProto.STANDBY_BEATS, period);
|
||||
}
|
||||
proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
|
||||
proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
|
||||
proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1441,9 +1357,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
|
||||
mAppStateTracker = Preconditions.checkNotNull(
|
||||
LocalServices.getService(AppStateTracker.class));
|
||||
if (mConstants.USE_HEARTBEATS) {
|
||||
setNextHeartbeatAlarm();
|
||||
}
|
||||
|
||||
// Register br for package removals and user removals.
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
@@ -1647,7 +1560,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
delayMillis =
|
||||
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
|
||||
JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(),
|
||||
JobStatus newJob = new JobStatus(failureToReschedule,
|
||||
elapsedNowMillis + delayMillis,
|
||||
JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
|
||||
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
|
||||
@@ -1682,8 +1595,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
* {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
|
||||
* to underscheduling at least, rather than if we had taken the last execution time to be the
|
||||
* start of the execution.
|
||||
* <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat
|
||||
* tracking as though the job were newly-scheduled.
|
||||
*
|
||||
* @return A new job representing the execution criteria for this instantiation of the
|
||||
* recurring job.
|
||||
*/
|
||||
@@ -1736,7 +1648,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
if (newLatestRuntimeElapsed < elapsedNow) {
|
||||
Slog.wtf(TAG, "Rescheduling calculated latest runtime in the past: "
|
||||
+ newLatestRuntimeElapsed);
|
||||
return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
|
||||
return new JobStatus(periodicToReschedule,
|
||||
elapsedNow + period - flex, elapsedNow + period,
|
||||
0 /* backoffAttempt */,
|
||||
sSystemClock.millis() /* lastSuccessfulRunTime */,
|
||||
@@ -1751,64 +1663,13 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
newEarliestRunTimeElapsed / 1000 + ", " + newLatestRuntimeElapsed / 1000
|
||||
+ "]s");
|
||||
}
|
||||
return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
|
||||
return new JobStatus(periodicToReschedule,
|
||||
newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
|
||||
0 /* backoffAttempt */,
|
||||
sSystemClock.millis() /* lastSuccessfulRunTime */,
|
||||
periodicToReschedule.getLastFailedRunTime());
|
||||
}
|
||||
|
||||
/*
|
||||
* We default to "long enough ago that every bucket's jobs are immediately runnable" to
|
||||
* avoid starvation of apps in uncommon-use buckets that might arise from repeated
|
||||
* reboot behavior.
|
||||
*/
|
||||
long heartbeatWhenJobsLastRun(String packageName, final @UserIdInt int userId) {
|
||||
// The furthest back in pre-boot time that we need to bother with
|
||||
long heartbeat = -mConstants.STANDBY_BEATS[RARE_INDEX];
|
||||
boolean cacheHit = false;
|
||||
synchronized (mLock) {
|
||||
HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
|
||||
if (jobPackages != null) {
|
||||
long cachedValue = jobPackages.getOrDefault(packageName, Long.MAX_VALUE);
|
||||
if (cachedValue < Long.MAX_VALUE) {
|
||||
cacheHit = true;
|
||||
heartbeat = cachedValue;
|
||||
}
|
||||
}
|
||||
if (!cacheHit) {
|
||||
// We haven't seen it yet; ask usage stats about it
|
||||
final long timeSinceJob = mUsageStats.getTimeSinceLastJobRun(packageName, userId);
|
||||
if (timeSinceJob < Long.MAX_VALUE) {
|
||||
// Usage stats knows about it from before, so calculate back from that
|
||||
// and go from there.
|
||||
heartbeat = mHeartbeat - (timeSinceJob / mConstants.STANDBY_HEARTBEAT_TIME);
|
||||
}
|
||||
// If usage stats returned its "not found" MAX_VALUE, we still have the
|
||||
// negative default 'heartbeat' value we established above
|
||||
setLastJobHeartbeatLocked(packageName, userId, heartbeat);
|
||||
}
|
||||
}
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "Last job heartbeat " + heartbeat + " for "
|
||||
+ packageName + "/" + userId);
|
||||
}
|
||||
return heartbeat;
|
||||
}
|
||||
|
||||
long heartbeatWhenJobsLastRun(JobStatus job) {
|
||||
return heartbeatWhenJobsLastRun(job.getSourcePackageName(), job.getSourceUserId());
|
||||
}
|
||||
|
||||
void setLastJobHeartbeatLocked(String packageName, int userId, long heartbeat) {
|
||||
HashMap<String, Long> jobPackages = mLastJobHeartbeats.get(userId);
|
||||
if (jobPackages == null) {
|
||||
jobPackages = new HashMap<>();
|
||||
mLastJobHeartbeats.put(userId, jobPackages);
|
||||
}
|
||||
jobPackages.put(packageName, heartbeat);
|
||||
}
|
||||
|
||||
// JobCompletedListener implementations.
|
||||
|
||||
/**
|
||||
@@ -2176,82 +2037,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
mMaybeQueueFunctor.postProcess();
|
||||
}
|
||||
|
||||
/**
|
||||
* Heartbeat tracking. The heartbeat alarm is intentionally non-wakeup.
|
||||
*/
|
||||
class HeartbeatAlarmListener implements AlarmManager.OnAlarmListener {
|
||||
|
||||
@Override
|
||||
public void onAlarm() {
|
||||
synchronized (mLock) {
|
||||
final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime;
|
||||
final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME;
|
||||
if (beatsElapsed > 0) {
|
||||
mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME;
|
||||
advanceHeartbeatLocked(beatsElapsed);
|
||||
}
|
||||
}
|
||||
setNextHeartbeatAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
// Intentionally does not touch the alarm timing
|
||||
void advanceHeartbeatLocked(long beatsElapsed) {
|
||||
if (!mConstants.USE_HEARTBEATS) {
|
||||
return;
|
||||
}
|
||||
mHeartbeat += beatsElapsed;
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed
|
||||
+ " to " + mHeartbeat);
|
||||
}
|
||||
// Don't update ACTIVE or NEVER bucket milestones. Note that mHeartbeat
|
||||
// will be equal to mNextBucketHeartbeat[bucket] for one beat, during which
|
||||
// new jobs scheduled by apps in that bucket will be permitted to run
|
||||
// immediately.
|
||||
boolean didAdvanceBucket = false;
|
||||
for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) {
|
||||
// Did we reach or cross a bucket boundary?
|
||||
if (mHeartbeat >= mNextBucketHeartbeat[i]) {
|
||||
didAdvanceBucket = true;
|
||||
}
|
||||
while (mHeartbeat > mNextBucketHeartbeat[i]) {
|
||||
mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i];
|
||||
}
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, " Bucket " + i + " next heartbeat "
|
||||
+ mNextBucketHeartbeat[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (didAdvanceBucket) {
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "Hit bucket boundary; reevaluating job runnability");
|
||||
}
|
||||
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
void setNextHeartbeatAlarm() {
|
||||
final long heartbeatLength;
|
||||
synchronized (mLock) {
|
||||
if (!mConstants.USE_HEARTBEATS) {
|
||||
return;
|
||||
}
|
||||
heartbeatLength = mConstants.STANDBY_HEARTBEAT_TIME;
|
||||
}
|
||||
final long now = sElapsedRealtimeClock.millis();
|
||||
final long nextBeatOrdinal = (now + heartbeatLength) / heartbeatLength;
|
||||
final long nextHeartbeat = nextBeatOrdinal * heartbeatLength;
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.i(TAG, "Setting heartbeat alarm for " + nextHeartbeat
|
||||
+ " = " + TimeUtils.formatDuration(nextHeartbeat - now));
|
||||
}
|
||||
AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
|
||||
am.setExact(AlarmManager.ELAPSED_REALTIME, nextHeartbeat,
|
||||
HEARTBEAT_TAG, mHeartbeatAlarm, mHandler);
|
||||
}
|
||||
|
||||
/** Returns true if both the calling and source users for the job are started. */
|
||||
private boolean areUsersStartedLocked(final JobStatus job) {
|
||||
boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
|
||||
@@ -2323,54 +2108,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mConstants.USE_HEARTBEATS) {
|
||||
// If the app is in a non-active standby bucket, make sure we've waited
|
||||
// an appropriate amount of time since the last invocation. During device-
|
||||
// wide parole, standby bucketing is ignored.
|
||||
//
|
||||
// Jobs in 'active' apps are not subject to standby, nor are jobs that are
|
||||
// specifically marked as exempt.
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
|
||||
+ " parole=" + mInParole + " active=" + job.uidActive
|
||||
+ " exempt=" + job.getJob().isExemptedFromAppStandby());
|
||||
}
|
||||
if (!mInParole
|
||||
&& !job.uidActive
|
||||
&& !job.getJob().isExemptedFromAppStandby()) {
|
||||
final int bucket = job.getStandbyBucket();
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, " bucket=" + bucket + " heartbeat=" + mHeartbeat
|
||||
+ " next=" + mNextBucketHeartbeat[bucket]);
|
||||
}
|
||||
if (mHeartbeat < mNextBucketHeartbeat[bucket]) {
|
||||
// Only skip this job if the app is still waiting for the end of its nominal
|
||||
// bucket interval. Once it's waited that long, we let it go ahead and clear.
|
||||
// The final (NEVER) bucket is special; we never age those apps' jobs into
|
||||
// runnability.
|
||||
final long appLastRan = heartbeatWhenJobsLastRun(job);
|
||||
if (bucket >= mConstants.STANDBY_BEATS.length
|
||||
|| (mHeartbeat > appLastRan
|
||||
&& mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) {
|
||||
if (job.getWhenStandbyDeferred() == 0) {
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
|
||||
+ (appLastRan + mConstants.STANDBY_BEATS[bucket])
|
||||
+ " for " + job);
|
||||
}
|
||||
job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "Bucket deferred job aged into runnability at "
|
||||
+ mHeartbeat + " : " + job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The expensive check: validate that the defined package+service is
|
||||
// still present & viable.
|
||||
return isComponentUsable(job);
|
||||
@@ -2439,9 +2176,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
|
||||
// Job pending/active doesn't affect the readiness of a job.
|
||||
|
||||
// Skipping the heartbeat check as this will only come into play when using the rolling
|
||||
// window quota management system.
|
||||
|
||||
// The expensive check: validate that the defined package+service is
|
||||
// still present & viable.
|
||||
return isComponentUsable(job);
|
||||
@@ -2450,9 +2184,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
/** Returns the maximum amount of time this job could run for. */
|
||||
public long getMaxJobExecutionTimeMs(JobStatus job) {
|
||||
synchronized (mLock) {
|
||||
if (mConstants.USE_HEARTBEATS) {
|
||||
return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
|
||||
}
|
||||
return Math.min(mQuotaController.getMaxJobExecutionTimeMsLocked(job),
|
||||
JobServiceContext.EXECUTING_TIMESLICE_MILLIS);
|
||||
}
|
||||
@@ -2497,56 +2228,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
|
||||
final class LocalService implements JobSchedulerInternal {
|
||||
|
||||
/**
|
||||
* The current bucket heartbeat ordinal
|
||||
*/
|
||||
public long currentHeartbeat() {
|
||||
return getCurrentHeartbeat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Heartbeat ordinal at which the given standby bucket's jobs next become runnable
|
||||
*/
|
||||
public long nextHeartbeatForBucket(int bucket) {
|
||||
synchronized (mLock) {
|
||||
return mNextBucketHeartbeat[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Heartbeat ordinal for the given app. This is typically the heartbeat at which
|
||||
* the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
|
||||
* jobs in a long time is immediately runnable even if the app is bucketed into
|
||||
* an infrequent time allocation.
|
||||
*/
|
||||
public long baseHeartbeatForApp(String packageName, @UserIdInt int userId,
|
||||
final int appStandbyBucket) {
|
||||
if (appStandbyBucket == 0 ||
|
||||
appStandbyBucket >= mConstants.STANDBY_BEATS.length) {
|
||||
// ACTIVE => everything can be run right away
|
||||
// NEVER => we won't run them anyway, so let them go in the future
|
||||
// as soon as the app enters normal use
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "Base heartbeat forced ZERO for new job in "
|
||||
+ packageName + "/" + userId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
final long baseHeartbeat = heartbeatWhenJobsLastRun(packageName, userId);
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.v(TAG, "Base heartbeat " + baseHeartbeat + " for new job in "
|
||||
+ packageName + "/" + userId);
|
||||
}
|
||||
return baseHeartbeat;
|
||||
}
|
||||
|
||||
public void noteJobStart(String packageName, int userId) {
|
||||
synchronized (mLock) {
|
||||
setLastJobHeartbeatLocked(packageName, userId, mHeartbeat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all pending jobs. A running job is not considered pending. Periodic
|
||||
* jobs are always considered pending.
|
||||
@@ -3158,12 +2839,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
}
|
||||
|
||||
long getCurrentHeartbeat() {
|
||||
synchronized (mLock) {
|
||||
return mHeartbeat;
|
||||
}
|
||||
}
|
||||
|
||||
// Shell command infrastructure
|
||||
int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
|
||||
try {
|
||||
@@ -3249,21 +2924,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Shell command infrastructure
|
||||
int executeHeartbeatCommand(PrintWriter pw, int numBeats) {
|
||||
if (numBeats < 1) {
|
||||
pw.println(getCurrentHeartbeat());
|
||||
return 0;
|
||||
}
|
||||
|
||||
pw.print("Advancing standby heartbeat by ");
|
||||
pw.println(numBeats);
|
||||
synchronized (mLock) {
|
||||
advanceHeartbeatLocked(numBeats);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void triggerDockState(boolean idleState) {
|
||||
final Intent dockIntent;
|
||||
if (idleState) {
|
||||
@@ -3319,20 +2979,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
pw.println();
|
||||
|
||||
pw.println(" Heartbeat:");
|
||||
pw.print(" Current: "); pw.println(mHeartbeat);
|
||||
pw.println(" Next");
|
||||
pw.print(" ACTIVE: "); pw.println(mNextBucketHeartbeat[0]);
|
||||
pw.print(" WORKING: "); pw.println(mNextBucketHeartbeat[1]);
|
||||
pw.print(" FREQUENT: "); pw.println(mNextBucketHeartbeat[2]);
|
||||
pw.print(" RARE: "); pw.println(mNextBucketHeartbeat[3]);
|
||||
pw.print(" Last heartbeat: ");
|
||||
TimeUtils.formatDuration(mLastHeartbeatTime, nowElapsed, pw);
|
||||
pw.println();
|
||||
pw.print(" Next heartbeat: ");
|
||||
TimeUtils.formatDuration(mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME,
|
||||
nowElapsed, pw);
|
||||
pw.println();
|
||||
pw.print(" In parole?: ");
|
||||
pw.print(mInParole);
|
||||
pw.println();
|
||||
@@ -3358,9 +3004,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
|
||||
job.dump(pw, " ", true, nowElapsed);
|
||||
pw.print(" Last run heartbeat: ");
|
||||
pw.print(heartbeatWhenJobsLastRun(job));
|
||||
pw.println();
|
||||
|
||||
pw.print(" Ready: ");
|
||||
pw.print(isReadyToBeExecutedLocked(job));
|
||||
@@ -3514,15 +3157,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
proto.end(settingsToken);
|
||||
|
||||
proto.write(JobSchedulerServiceDumpProto.CURRENT_HEARTBEAT, mHeartbeat);
|
||||
proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[0]);
|
||||
proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[1]);
|
||||
proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[2]);
|
||||
proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT, mNextBucketHeartbeat[3]);
|
||||
proto.write(JobSchedulerServiceDumpProto.LAST_HEARTBEAT_TIME_MILLIS,
|
||||
mLastHeartbeatTime - nowUptime);
|
||||
proto.write(JobSchedulerServiceDumpProto.NEXT_HEARTBEAT_TIME_MILLIS,
|
||||
mLastHeartbeatTime + mConstants.STANDBY_HEARTBEAT_TIME - nowUptime);
|
||||
proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
|
||||
proto.write(JobSchedulerServiceDumpProto.IN_THERMAL, mThermalConstraint);
|
||||
|
||||
@@ -3564,7 +3198,6 @@ public class JobSchedulerService extends com.android.server.SystemService
|
||||
}
|
||||
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_COMPONENT_PRESENT,
|
||||
componentPresent);
|
||||
proto.write(RegisteredJob.LAST_RUN_HEARTBEAT, heartbeatWhenJobsLastRun(job));
|
||||
|
||||
proto.end(rjToken);
|
||||
}
|
||||
|
||||
@@ -340,15 +340,8 @@ public final class JobSchedulerShellCommand extends ShellCommand {
|
||||
private int doHeartbeat(PrintWriter pw) throws Exception {
|
||||
checkPermission("manipulate scheduler heartbeat");
|
||||
|
||||
final String arg = getNextArg();
|
||||
final int numBeats = (arg != null) ? Integer.parseInt(arg) : 0;
|
||||
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mInternal.executeHeartbeatCommand(pw, numBeats);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
pw.println("Heartbeat command is no longer supported");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int triggerDockState(PrintWriter pw) throws Exception {
|
||||
@@ -401,8 +394,7 @@ public final class JobSchedulerShellCommand extends ShellCommand {
|
||||
pw.println(" -u or --user: specify which user's job is to be run; the default is");
|
||||
pw.println(" the primary or system user");
|
||||
pw.println(" heartbeat [num]");
|
||||
pw.println(" With no argument, prints the current standby heartbeat. With a positive");
|
||||
pw.println(" argument, advances the standby heartbeat by that number.");
|
||||
pw.println(" No longer used.");
|
||||
pw.println(" monitor-battery [on|off]");
|
||||
pw.println(" Control monitoring of all battery changes. Off by default. Turning");
|
||||
pw.println(" on makes get-battery-seq useful.");
|
||||
|
||||
@@ -285,9 +285,6 @@ public final class JobServiceContext implements ServiceConnection {
|
||||
UsageStatsManagerInternal usageStats =
|
||||
LocalServices.getService(UsageStatsManagerInternal.class);
|
||||
usageStats.setLastJobRunTime(jobPackage, jobUserId, mExecutionStartTimeElapsed);
|
||||
JobSchedulerInternal jobScheduler =
|
||||
LocalServices.getService(JobSchedulerInternal.class);
|
||||
jobScheduler.noteJobStart(jobPackage, jobUserId);
|
||||
mAvailable = false;
|
||||
mStoppedReason = null;
|
||||
mStoppedTime = 0;
|
||||
|
||||
@@ -189,7 +189,7 @@ public final class JobStore {
|
||||
if (utcTimes != null) {
|
||||
Pair<Long, Long> elapsedRuntimes =
|
||||
convertRtcBoundsToElapsed(utcTimes, elapsedNow);
|
||||
JobStatus newJob = new JobStatus(job, job.getBaseHeartbeat(),
|
||||
JobStatus newJob = new JobStatus(job,
|
||||
elapsedRuntimes.first, elapsedRuntimes.second,
|
||||
0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
|
||||
newJob.prepareLocked(am);
|
||||
@@ -944,10 +944,9 @@ public final class JobStore {
|
||||
JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
|
||||
final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
|
||||
sourceUserId, elapsedNow);
|
||||
long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
|
||||
JobStatus js = new JobStatus(
|
||||
jobBuilder.build(), uid, sourcePackageName, sourceUserId,
|
||||
appBucket, currentHeartbeat, sourceTag,
|
||||
appBucket, sourceTag,
|
||||
elapsedRuntimes.first, elapsedRuntimes.second,
|
||||
lastSuccessfulRunTime, lastFailedRunTime,
|
||||
(rtcIsGood) ? null : rtcRuntimes, internalFlags);
|
||||
|
||||
@@ -47,7 +47,6 @@ import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.JobSchedulerService.Constants;
|
||||
import com.android.server.job.JobServiceContext;
|
||||
import com.android.server.job.StateControllerProto;
|
||||
import com.android.server.net.NetworkPolicyManagerInternal;
|
||||
|
||||
@@ -88,8 +87,6 @@ public final class ConnectivityController extends StateController implements
|
||||
@GuardedBy("mLock")
|
||||
private final ArraySet<Network> mAvailableNetworks = new ArraySet<>();
|
||||
|
||||
private boolean mUseQuotaLimit;
|
||||
|
||||
private static final int MSG_DATA_SAVER_TOGGLED = 0;
|
||||
private static final int MSG_UID_RULES_CHANGES = 1;
|
||||
private static final int MSG_REEVALUATE_JOBS = 2;
|
||||
@@ -110,8 +107,6 @@ public final class ConnectivityController extends StateController implements
|
||||
mConnManager.registerNetworkCallback(request, mNetworkCallback);
|
||||
|
||||
mNetPolicyManager.registerListener(mNetPolicyListener);
|
||||
|
||||
mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@@ -142,24 +137,6 @@ public final class ConnectivityController extends StateController implements
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Override
|
||||
public void onConstantsUpdatedLocked() {
|
||||
if (mConstants.USE_HEARTBEATS) {
|
||||
// App idle exceptions are only requested for the rolling quota system.
|
||||
if (DEBUG) Slog.i(TAG, "Revoking all standby exceptions");
|
||||
for (int i = 0; i < mRequestedWhitelistJobs.size(); ++i) {
|
||||
int uid = mRequestedWhitelistJobs.keyAt(i);
|
||||
mNetPolicyManagerInternal.setAppIdleWhitelist(uid, false);
|
||||
}
|
||||
mRequestedWhitelistJobs.clear();
|
||||
}
|
||||
if (mUseQuotaLimit == mConstants.USE_HEARTBEATS) {
|
||||
mUseQuotaLimit = !mConstants.USE_HEARTBEATS;
|
||||
mHandler.obtainMessage(MSG_REEVALUATE_JOBS).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the job's requested network is available. This DOES NOT necesarilly mean
|
||||
* that the UID has been granted access to the network.
|
||||
@@ -237,11 +214,6 @@ public final class ConnectivityController extends StateController implements
|
||||
@GuardedBy("mLock")
|
||||
@Override
|
||||
public void evaluateStateLocked(JobStatus jobStatus) {
|
||||
if (mConstants.USE_HEARTBEATS) {
|
||||
// This should only be used for the rolling quota system.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jobStatus.hasConnectivityConstraint()) {
|
||||
return;
|
||||
}
|
||||
@@ -263,9 +235,6 @@ public final class ConnectivityController extends StateController implements
|
||||
@GuardedBy("mLock")
|
||||
@Override
|
||||
public void reevaluateStateLocked(final int uid) {
|
||||
if (mConstants.USE_HEARTBEATS) {
|
||||
return;
|
||||
}
|
||||
// Check if we still need a connectivity exception in case the JobService was disabled.
|
||||
ArraySet<JobStatus> jobs = mTrackedJobs.get(uid);
|
||||
if (jobs == null) {
|
||||
@@ -329,9 +298,7 @@ public final class ConnectivityController extends StateController implements
|
||||
*/
|
||||
private boolean isInsane(JobStatus jobStatus, Network network,
|
||||
NetworkCapabilities capabilities, Constants constants) {
|
||||
final long maxJobExecutionTimeMs = mUseQuotaLimit
|
||||
? mService.getMaxJobExecutionTimeMs(jobStatus)
|
||||
: JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
|
||||
final long maxJobExecutionTimeMs = mService.getMaxJobExecutionTimeMs(jobStatus);
|
||||
|
||||
final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
|
||||
if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
|
||||
@@ -617,7 +584,6 @@ public final class ConnectivityController extends StateController implements
|
||||
@Override
|
||||
public void dumpControllerStateLocked(IndentingPrintWriter pw,
|
||||
Predicate<JobStatus> predicate) {
|
||||
pw.print("mUseQuotaLimit="); pw.println(mUseQuotaLimit);
|
||||
|
||||
if (mRequestedWhitelistJobs.size() > 0) {
|
||||
pw.print("Requested standby exceptions:");
|
||||
|
||||
@@ -168,12 +168,6 @@ public final class JobStatus {
|
||||
/** How many times this job has failed, used to compute back-off. */
|
||||
private final int numFailures;
|
||||
|
||||
/**
|
||||
* Current standby heartbeat when this job was scheduled or last ran. Used to
|
||||
* pin the runnability check regardless of the job's app moving between buckets.
|
||||
*/
|
||||
private final long baseHeartbeat;
|
||||
|
||||
/**
|
||||
* Which app standby bucket this job's app is in. Updated when the app is moved to a
|
||||
* different bucket.
|
||||
@@ -350,8 +344,6 @@ public final class JobStatus {
|
||||
* @param standbyBucket The standby bucket that the source package is currently assigned to,
|
||||
* cached here for speed of handling during runnability evaluations (and updated when bucket
|
||||
* assignments are changed)
|
||||
* @param heartbeat Timestamp of when the job was created, in the standby-related
|
||||
* timebase.
|
||||
* @param tag A string associated with the job for debugging/logging purposes.
|
||||
* @param numFailures Count of how many times this job has requested a reschedule because
|
||||
* its work was not yet finished.
|
||||
@@ -364,13 +356,12 @@ public final class JobStatus {
|
||||
* @param internalFlags Non-API property flags about this job
|
||||
*/
|
||||
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
|
||||
int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
|
||||
int sourceUserId, int standbyBucket, String tag, int numFailures,
|
||||
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
|
||||
long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags) {
|
||||
this.job = job;
|
||||
this.callingUid = callingUid;
|
||||
this.standbyBucket = standbyBucket;
|
||||
this.baseHeartbeat = heartbeat;
|
||||
|
||||
int tempSourceUid = -1;
|
||||
if (sourceUserId != -1 && sourcePackageName != null) {
|
||||
@@ -440,7 +431,7 @@ public final class JobStatus {
|
||||
public JobStatus(JobStatus jobStatus) {
|
||||
this(jobStatus.getJob(), jobStatus.getUid(),
|
||||
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
|
||||
jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
|
||||
jobStatus.getStandbyBucket(),
|
||||
jobStatus.getSourceTag(), jobStatus.getNumFailures(),
|
||||
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
|
||||
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
|
||||
@@ -462,13 +453,13 @@ public final class JobStatus {
|
||||
* standby bucket is whatever the OS thinks it should be at this moment.
|
||||
*/
|
||||
public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
|
||||
int standbyBucket, long baseHeartbeat, String sourceTag,
|
||||
int standbyBucket, String sourceTag,
|
||||
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
|
||||
long lastSuccessfulRunTime, long lastFailedRunTime,
|
||||
Pair<Long, Long> persistedExecutionTimesUTC,
|
||||
int innerFlags) {
|
||||
this(job, callingUid, sourcePkgName, sourceUserId,
|
||||
standbyBucket, baseHeartbeat,
|
||||
standbyBucket,
|
||||
sourceTag, 0,
|
||||
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
|
||||
lastSuccessfulRunTime, lastFailedRunTime, innerFlags);
|
||||
@@ -486,13 +477,13 @@ public final class JobStatus {
|
||||
}
|
||||
|
||||
/** Create a new job to be rescheduled with the provided parameters. */
|
||||
public JobStatus(JobStatus rescheduling, long newBaseHeartbeat,
|
||||
public JobStatus(JobStatus rescheduling,
|
||||
long newEarliestRuntimeElapsedMillis,
|
||||
long newLatestRuntimeElapsedMillis, int backoffAttempt,
|
||||
long lastSuccessfulRunTime, long lastFailedRunTime) {
|
||||
this(rescheduling.job, rescheduling.getUid(),
|
||||
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
|
||||
rescheduling.getStandbyBucket(), newBaseHeartbeat,
|
||||
rescheduling.getStandbyBucket(),
|
||||
rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
|
||||
newLatestRuntimeElapsedMillis,
|
||||
lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags());
|
||||
@@ -529,11 +520,8 @@ public final class JobStatus {
|
||||
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
|
||||
sourceUserId, elapsedNow);
|
||||
JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
|
||||
long currentHeartbeat = js != null
|
||||
? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
|
||||
: 0;
|
||||
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
|
||||
standbyBucket, currentHeartbeat, tag, 0,
|
||||
standbyBucket, tag, 0,
|
||||
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
|
||||
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
|
||||
/*innerFlags=*/ 0);
|
||||
@@ -714,10 +702,6 @@ public final class JobStatus {
|
||||
return standbyBucket;
|
||||
}
|
||||
|
||||
public long getBaseHeartbeat() {
|
||||
return baseHeartbeat;
|
||||
}
|
||||
|
||||
public void setStandbyBucket(int newBucket) {
|
||||
standbyBucket = newBucket;
|
||||
}
|
||||
@@ -1631,10 +1615,6 @@ public final class JobStatus {
|
||||
}
|
||||
pw.print(prefix); pw.print("Standby bucket: ");
|
||||
pw.println(getBucketName());
|
||||
if (standbyBucket > 0) {
|
||||
pw.print(prefix); pw.print("Base heartbeat: ");
|
||||
pw.println(baseHeartbeat);
|
||||
}
|
||||
if (whenStandbyDeferred != 0) {
|
||||
pw.print(prefix); pw.print(" Deferred since: ");
|
||||
TimeUtils.formatDuration(whenStandbyDeferred, elapsedRealtimeMillis, pw);
|
||||
|
||||
@@ -416,13 +416,6 @@ public final class QuotaController extends StateController {
|
||||
|
||||
private volatile boolean mInParole;
|
||||
|
||||
/**
|
||||
* If the QuotaController should throttle apps based on their standby bucket and job activity.
|
||||
* If false, all jobs will have their CONSTRAINT_WITHIN_QUOTA bit set to true immediately and
|
||||
* indefinitely.
|
||||
*/
|
||||
private boolean mShouldThrottle;
|
||||
|
||||
/** How much time each app will have to run jobs within their standby bucket window. */
|
||||
private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
|
||||
|
||||
@@ -594,8 +587,6 @@ public final class QuotaController extends StateController {
|
||||
} catch (RemoteException e) {
|
||||
// ignored; both services live in system_server
|
||||
}
|
||||
|
||||
mShouldThrottle = !mConstants.USE_HEARTBEATS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -607,8 +598,6 @@ public final class QuotaController extends StateController {
|
||||
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
|
||||
final int userId = jobStatus.getSourceUserId();
|
||||
final String pkgName = jobStatus.getSourcePackageName();
|
||||
// Still need to track jobs even if mShouldThrottle is false in case it's set to true at
|
||||
// some point.
|
||||
ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
|
||||
if (jobs == null) {
|
||||
jobs = new ArraySet<>();
|
||||
@@ -616,16 +605,10 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
jobs.add(jobStatus);
|
||||
jobStatus.setTrackingController(JobStatus.TRACKING_QUOTA);
|
||||
if (mShouldThrottle) {
|
||||
final boolean isWithinQuota = isWithinQuotaLocked(jobStatus);
|
||||
setConstraintSatisfied(jobStatus, isWithinQuota);
|
||||
if (!isWithinQuota) {
|
||||
maybeScheduleStartAlarmLocked(userId, pkgName,
|
||||
getEffectiveStandbyBucket(jobStatus));
|
||||
}
|
||||
} else {
|
||||
// QuotaController isn't throttling, so always set to true.
|
||||
jobStatus.setQuotaConstraintSatisfied(true);
|
||||
final boolean isWithinQuota = isWithinQuotaLocked(jobStatus);
|
||||
setConstraintSatisfied(jobStatus, isWithinQuota);
|
||||
if (!isWithinQuota) {
|
||||
maybeScheduleStartAlarmLocked(userId, pkgName, getEffectiveStandbyBucket(jobStatus));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,20 +656,6 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConstantsUpdatedLocked() {
|
||||
if (mShouldThrottle == mConstants.USE_HEARTBEATS) {
|
||||
mShouldThrottle = !mConstants.USE_HEARTBEATS;
|
||||
|
||||
// Update job bookkeeping out of band.
|
||||
BackgroundThread.getHandler().post(() -> {
|
||||
synchronized (mLock) {
|
||||
maybeUpdateAllConstraintsLocked();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppRemovedLocked(String packageName, int uid) {
|
||||
if (packageName == null) {
|
||||
@@ -780,8 +749,6 @@ public final class QuotaController extends StateController {
|
||||
boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
|
||||
final int standbyBucket) {
|
||||
if (standbyBucket == NEVER_INDEX) return false;
|
||||
// This check is needed in case the flag is toggled after a job has been registered.
|
||||
if (!mShouldThrottle) return true;
|
||||
|
||||
// Quota constraint is not enforced while charging or when parole is on.
|
||||
if (mChargeTracker.isCharging() || mInParole) {
|
||||
@@ -1820,8 +1787,7 @@ public final class QuotaController extends StateController {
|
||||
if (timer != null && timer.isActive()) {
|
||||
timer.rescheduleCutoff();
|
||||
}
|
||||
if (!mShouldThrottle || maybeUpdateConstraintForPkgLocked(userId,
|
||||
packageName)) {
|
||||
if (maybeUpdateConstraintForPkgLocked(userId, packageName)) {
|
||||
mStateChangedListener.onControllerStateChanged();
|
||||
}
|
||||
}
|
||||
@@ -2396,7 +2362,7 @@ public final class QuotaController extends StateController {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed && mShouldThrottle) {
|
||||
if (changed) {
|
||||
// Update job bookkeeping out of band.
|
||||
BackgroundThread.getHandler().post(() -> {
|
||||
synchronized (mLock) {
|
||||
@@ -2561,7 +2527,6 @@ public final class QuotaController extends StateController {
|
||||
@Override
|
||||
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
|
||||
final Predicate<JobStatus> predicate) {
|
||||
pw.println("Is throttling: " + mShouldThrottle);
|
||||
pw.println("Is charging: " + mChargeTracker.isCharging());
|
||||
pw.println("In parole: " + mInParole);
|
||||
pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.server.job;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.job.JobInfo;
|
||||
|
||||
import java.util.List;
|
||||
@@ -27,31 +26,6 @@ import java.util.List;
|
||||
*/
|
||||
public interface JobSchedulerInternal {
|
||||
|
||||
// Bookkeeping about app standby bucket scheduling
|
||||
|
||||
/**
|
||||
* The current bucket heartbeat ordinal
|
||||
*/
|
||||
long currentHeartbeat();
|
||||
|
||||
/**
|
||||
* Heartbeat ordinal at which the given standby bucket's jobs next become runnable
|
||||
*/
|
||||
long nextHeartbeatForBucket(int bucket);
|
||||
|
||||
/**
|
||||
* Heartbeat ordinal for the given app. This is typically the heartbeat at which
|
||||
* the app last ran jobs, so that a newly-scheduled job in an app that hasn't run
|
||||
* jobs in a long time is immediately runnable even if the app is bucketed into
|
||||
* an infrequent time allocation.
|
||||
*/
|
||||
public long baseHeartbeatForApp(String packageName, @UserIdInt int userId, int appBucket);
|
||||
|
||||
/**
|
||||
* Tell the scheduler when a JobServiceContext starts running a job in an app
|
||||
*/
|
||||
void noteJobStart(String packageName, int userId);
|
||||
|
||||
/**
|
||||
* Returns a list of pending jobs scheduled by the system service.
|
||||
*/
|
||||
|
||||
@@ -38,10 +38,10 @@ message JobSchedulerServiceDumpProto {
|
||||
|
||||
optional ConstantsProto settings = 1;
|
||||
|
||||
optional int32 current_heartbeat = 14;
|
||||
repeated int32 next_heartbeat = 15;
|
||||
optional int64 last_heartbeat_time_millis = 16;
|
||||
optional int64 next_heartbeat_time_millis = 17;
|
||||
reserved 14; // current_heartbeat
|
||||
reserved 15; // next_heartbeat
|
||||
reserved 16; // last_heartbeat_time_millis
|
||||
reserved 17; // next_heartbeat_time_millis
|
||||
optional bool in_parole = 18;
|
||||
optional bool in_thermal = 19;
|
||||
|
||||
@@ -64,7 +64,7 @@ message JobSchedulerServiceDumpProto {
|
||||
optional bool is_uid_backing_up = 7;
|
||||
optional bool is_component_present = 8;
|
||||
|
||||
optional int64 last_run_heartbeat = 9;
|
||||
reserved 9; // last_run_heartbeat
|
||||
}
|
||||
repeated RegisteredJob registered_jobs = 3;
|
||||
|
||||
@@ -214,13 +214,13 @@ message ConstantsProto {
|
||||
// assignment. This should be prime relative to common time interval lengths
|
||||
// such as a quarter-hour or day, so that the heartbeat drifts relative to
|
||||
// wall-clock milestones.
|
||||
optional int64 standby_heartbeat_time_ms = 19;
|
||||
reserved 19; // standby_heartbeat_time_ms
|
||||
// Mapping: standby bucket -> number of heartbeats between each sweep of
|
||||
// that bucket's jobs.
|
||||
// Bucket assignments as recorded in the JobStatus objects are normalized to
|
||||
// be indices into this array, rather than the raw constants used by
|
||||
// AppIdleHistory.
|
||||
repeated int32 standby_beats = 20;
|
||||
reserved 20; // standby_beats
|
||||
// The fraction of a job's running window that must pass before we
|
||||
// consider running it when the network is congested.
|
||||
optional double conn_congestion_delay_frac = 21;
|
||||
@@ -229,7 +229,7 @@ message ConstantsProto {
|
||||
optional double conn_prefetch_relax_frac = 22;
|
||||
// Whether to use heartbeats or rolling window for quota management. True
|
||||
// will use heartbeats, false will use a rolling window.
|
||||
optional bool use_heartbeats = 23;
|
||||
reserved 23; // use_heartbeats
|
||||
|
||||
message QuotaController {
|
||||
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
|
||||
|
||||
@@ -399,23 +399,8 @@ public class ConnectivityControllerTest {
|
||||
assertTrue(controller.wouldBeReadyWithConnectivityLocked(red));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluateStateLocked_HeartbeatsOn() {
|
||||
mConstants.USE_HEARTBEATS = true;
|
||||
final ConnectivityController controller = new ConnectivityController(mService);
|
||||
final JobStatus red = createJobStatus(createJob()
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED), UID_RED);
|
||||
|
||||
controller.evaluateStateLocked(red);
|
||||
assertFalse(controller.isStandbyExceptionRequestedLocked(UID_RED));
|
||||
verify(mNetPolicyManagerInternal, never())
|
||||
.setAppIdleWhitelist(eq(UID_RED), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEvaluateStateLocked_JobWithoutConnectivity() {
|
||||
mConstants.USE_HEARTBEATS = false;
|
||||
final ConnectivityController controller = new ConnectivityController(mService);
|
||||
final JobStatus red = createJobStatus(createJob().setMinimumLatency(1));
|
||||
|
||||
@@ -427,7 +412,6 @@ public class ConnectivityControllerTest {
|
||||
|
||||
@Test
|
||||
public void testEvaluateStateLocked_JobWouldBeReady() {
|
||||
mConstants.USE_HEARTBEATS = false;
|
||||
final ConnectivityController controller = spy(new ConnectivityController(mService));
|
||||
doReturn(true).when(controller).wouldBeReadyWithConnectivityLocked(any());
|
||||
final JobStatus red = createJobStatus(createJob()
|
||||
@@ -466,7 +450,6 @@ public class ConnectivityControllerTest {
|
||||
|
||||
@Test
|
||||
public void testEvaluateStateLocked_JobWouldNotBeReady() {
|
||||
mConstants.USE_HEARTBEATS = false;
|
||||
final ConnectivityController controller = spy(new ConnectivityController(mService));
|
||||
doReturn(false).when(controller).wouldBeReadyWithConnectivityLocked(any());
|
||||
final JobStatus red = createJobStatus(createJob()
|
||||
@@ -502,7 +485,6 @@ public class ConnectivityControllerTest {
|
||||
|
||||
@Test
|
||||
public void testReevaluateStateLocked() {
|
||||
mConstants.USE_HEARTBEATS = false;
|
||||
final ConnectivityController controller = spy(new ConnectivityController(mService));
|
||||
final JobStatus redOne = createJobStatus(createJob(1)
|
||||
.setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0)
|
||||
@@ -625,7 +607,7 @@ public class ConnectivityControllerTest {
|
||||
|
||||
private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
|
||||
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
|
||||
return new JobStatus(job.build(), uid, null, -1, 0, 0, null,
|
||||
return new JobStatus(job.build(), uid, null, -1, 0, null,
|
||||
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,7 +573,7 @@ public class JobStatusTest {
|
||||
long latestRunTimeElapsedMillis) {
|
||||
final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
|
||||
return new JobStatus(job, 0, null, -1, 0, 0, null, earliestRunTimeElapsedMillis,
|
||||
return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis,
|
||||
latestRunTimeElapsedMillis, 0, 0, null, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ public class QuotaControllerTest {
|
||||
private static final int SOURCE_USER_ID = 0;
|
||||
|
||||
private BroadcastReceiver mChargingReceiver;
|
||||
private Constants mJsConstants;
|
||||
private QuotaController mQuotaController;
|
||||
private QuotaController.QcConstants mQcConstants;
|
||||
private int mSourceUid;
|
||||
@@ -134,14 +133,11 @@ public class QuotaControllerTest {
|
||||
.strictness(Strictness.LENIENT)
|
||||
.mockStatic(LocalServices.class)
|
||||
.startMocking();
|
||||
// Make sure constants turn on QuotaController.
|
||||
mJsConstants = new Constants();
|
||||
mJsConstants.USE_HEARTBEATS = false;
|
||||
|
||||
// Called in StateController constructor.
|
||||
when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
|
||||
when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
|
||||
when(mJobSchedulerService.getConstants()).thenReturn(mJsConstants);
|
||||
when(mJobSchedulerService.getConstants()).thenReturn(mock(Constants.class));
|
||||
// Called in QuotaController constructor.
|
||||
IActivityManager activityManager = ActivityManager.getService();
|
||||
spyOn(activityManager);
|
||||
@@ -1809,30 +1805,6 @@ public class QuotaControllerTest {
|
||||
.set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
|
||||
}
|
||||
|
||||
/** Tests that QuotaController doesn't throttle if throttling is turned off. */
|
||||
@Test
|
||||
public void testThrottleToggling() throws Exception {
|
||||
setDischarging();
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(
|
||||
JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
|
||||
10 * MINUTE_IN_MILLIS, 4));
|
||||
JobStatus jobStatus = createJobStatus("testThrottleToggling", 1);
|
||||
setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
|
||||
mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
|
||||
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
|
||||
|
||||
mJsConstants.USE_HEARTBEATS = true;
|
||||
mQuotaController.onConstantsUpdatedLocked();
|
||||
Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
|
||||
assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
|
||||
|
||||
mJsConstants.USE_HEARTBEATS = false;
|
||||
mQuotaController.onConstantsUpdatedLocked();
|
||||
Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
|
||||
assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstantsUpdating_ValidValues() {
|
||||
mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
|
||||
|
||||
@@ -273,7 +273,7 @@ public class JobStoreTest {
|
||||
invalidLateRuntimeElapsedMillis - TWO_HOURS; // Early is (late - period).
|
||||
final Pair<Long, Long> persistedExecutionTimesUTC = new Pair<>(rtcNow, rtcNow + ONE_HOUR);
|
||||
final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage",
|
||||
0 /* sourceUserId */, 0, 0, "someTag",
|
||||
0 /* sourceUserId */, 0, "someTag",
|
||||
invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis,
|
||||
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
|
||||
persistedExecutionTimesUTC, 0 /* innerFlagg */);
|
||||
|
||||
Reference in New Issue
Block a user