Merge "Introduce JobScheduler quota bumps in lieu of bucket changes." into tm-dev am: d0333d3682
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18204754 Change-Id: I69d35b3cccd491c211911bcbf76cbaca24531d15 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -59,6 +59,13 @@ public interface AppStandbyInternal {
|
||||
public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
|
||||
// No-op by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional callback to inform the listener to give the app a temporary quota bump.
|
||||
*/
|
||||
public void triggerTemporaryQuotaBump(String packageName, @UserIdInt int userId) {
|
||||
// No-op by default
|
||||
}
|
||||
}
|
||||
|
||||
void onBootPhase(int phase);
|
||||
|
||||
@@ -270,13 +270,12 @@ public final class QuotaController extends StateController {
|
||||
private final SparseArrayMap<String, Timer> mEJPkgTimers = new SparseArrayMap<>();
|
||||
|
||||
/** List of all regular timing sessions for a package-userId combo, in chronological order. */
|
||||
private final SparseArrayMap<String, List<TimingSession>> mTimingSessions =
|
||||
new SparseArrayMap<>();
|
||||
private final SparseArrayMap<String, List<TimedEvent>> mTimingEvents = new SparseArrayMap<>();
|
||||
|
||||
/**
|
||||
* List of all expedited job timing sessions for a package-userId combo, in chronological order.
|
||||
*/
|
||||
private final SparseArrayMap<String, List<TimingSession>> mEJTimingSessions =
|
||||
private final SparseArrayMap<String, List<TimedEvent>> mEJTimingSessions =
|
||||
new SparseArrayMap<>();
|
||||
|
||||
/**
|
||||
@@ -519,6 +518,14 @@ public final class QuotaController extends StateController {
|
||||
|
||||
private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
|
||||
|
||||
private long mQuotaBumpAdditionalDurationMs =
|
||||
QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS;
|
||||
private int mQuotaBumpAdditionalJobCount = QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT;
|
||||
private int mQuotaBumpAdditionalSessionCount =
|
||||
QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT;
|
||||
private long mQuotaBumpWindowSizeMs = QcConstants.DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS;
|
||||
private int mQuotaBumpLimit = QcConstants.DEFAULT_QUOTA_BUMP_LIMIT;
|
||||
|
||||
/**
|
||||
* List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission
|
||||
* granted for each user.
|
||||
@@ -707,7 +714,7 @@ public final class QuotaController extends StateController {
|
||||
mTrackedJobs.delete(userId);
|
||||
mPkgTimers.delete(userId);
|
||||
mEJPkgTimers.delete(userId);
|
||||
mTimingSessions.delete(userId);
|
||||
mTimingEvents.delete(userId);
|
||||
mEJTimingSessions.delete(userId);
|
||||
mInQuotaAlarmQueue.removeAlarmsForUserId(userId);
|
||||
mExecutionStatsCache.delete(userId);
|
||||
@@ -738,7 +745,7 @@ public final class QuotaController extends StateController {
|
||||
timer.dropEverythingLocked();
|
||||
}
|
||||
}
|
||||
mTimingSessions.delete(userId, packageName);
|
||||
mTimingEvents.delete(userId, packageName);
|
||||
mEJTimingSessions.delete(userId, packageName);
|
||||
mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
|
||||
mExecutionStatsCache.delete(userId, packageName);
|
||||
@@ -928,7 +935,7 @@ public final class QuotaController extends StateController {
|
||||
(stats.jobRateLimitExpirationTimeElapsed <= now
|
||||
|| stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
|
||||
return isUnderAllowedTimeQuota
|
||||
&& (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]);
|
||||
&& stats.bgJobCountInWindow < stats.jobCountLimit;
|
||||
}
|
||||
|
||||
private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
|
||||
@@ -937,7 +944,7 @@ public final class QuotaController extends StateController {
|
||||
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|
||||
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
|
||||
return isUnderAllowedTimeQuota
|
||||
&& stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket];
|
||||
&& stats.sessionCountInWindow < stats.sessionCountLimit;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -984,12 +991,12 @@ public final class QuotaController extends StateController {
|
||||
long remainingMs = limitMs - quota.getTallyLocked();
|
||||
|
||||
// Stale sessions may still be factored into tally. Make sure they're removed.
|
||||
List<TimingSession> timingSessions = mEJTimingSessions.get(userId, packageName);
|
||||
List<TimedEvent> timingSessions = mEJTimingSessions.get(userId, packageName);
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
final long windowStartTimeElapsed = nowElapsed - mEJLimitWindowSizeMs;
|
||||
if (timingSessions != null) {
|
||||
while (timingSessions.size() > 0) {
|
||||
TimingSession ts = timingSessions.get(0);
|
||||
TimingSession ts = (TimingSession) timingSessions.get(0);
|
||||
if (ts.endTimeElapsed < windowStartTimeElapsed) {
|
||||
final long duration = ts.endTimeElapsed - ts.startTimeElapsed;
|
||||
remainingMs += duration;
|
||||
@@ -1042,9 +1049,9 @@ public final class QuotaController extends StateController {
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
|
||||
List<TimedEvent> events = mTimingEvents.get(userId, packageName);
|
||||
final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
|
||||
if (sessions == null || sessions.size() == 0) {
|
||||
if (events == null || events.size() == 0) {
|
||||
// Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
|
||||
// essentially run until they reach the maximum limit.
|
||||
if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
|
||||
@@ -1064,7 +1071,7 @@ public final class QuotaController extends StateController {
|
||||
// essentially run until they reach the maximum limit.
|
||||
if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
|
||||
return calculateTimeUntilQuotaConsumedLocked(
|
||||
sessions, startMaxElapsed, maxExecutionTimeRemainingMs);
|
||||
events, startMaxElapsed, maxExecutionTimeRemainingMs, false);
|
||||
}
|
||||
|
||||
// Need to check both max time and period time in case one is less than the other.
|
||||
@@ -1073,9 +1080,9 @@ public final class QuotaController extends StateController {
|
||||
// bucket value.
|
||||
return Math.min(
|
||||
calculateTimeUntilQuotaConsumedLocked(
|
||||
sessions, startMaxElapsed, maxExecutionTimeRemainingMs),
|
||||
events, startMaxElapsed, maxExecutionTimeRemainingMs, false),
|
||||
calculateTimeUntilQuotaConsumedLocked(
|
||||
sessions, startWindowElapsed, allowedTimeRemainingMs));
|
||||
events, startWindowElapsed, allowedTimeRemainingMs, true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1084,12 +1091,36 @@ public final class QuotaController extends StateController {
|
||||
* @param windowStartElapsed The start of the window, in the elapsed realtime timebase.
|
||||
* @param deadSpaceMs How much time can be allowed to count towards the quota
|
||||
*/
|
||||
private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions,
|
||||
final long windowStartElapsed, long deadSpaceMs) {
|
||||
private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimedEvent> sessions,
|
||||
final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps) {
|
||||
long timeUntilQuotaConsumedMs = 0;
|
||||
long start = windowStartElapsed;
|
||||
for (int i = 0; i < sessions.size(); ++i) {
|
||||
TimingSession session = sessions.get(i);
|
||||
int numQuotaBumps = 0;
|
||||
final long quotaBumpWindowStartElapsed =
|
||||
sElapsedRealtimeClock.millis() - mQuotaBumpWindowSizeMs;
|
||||
final int numSessions = sessions.size();
|
||||
if (allowQuotaBumps) {
|
||||
for (int i = numSessions - 1; i >= 0; --i) {
|
||||
TimedEvent event = sessions.get(i);
|
||||
|
||||
if (event instanceof QuotaBump) {
|
||||
if (event.getEndTimeElapsed() >= quotaBumpWindowStartElapsed
|
||||
&& numQuotaBumps++ < mQuotaBumpLimit) {
|
||||
deadSpaceMs += mQuotaBumpAdditionalDurationMs;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < numSessions; ++i) {
|
||||
TimedEvent event = sessions.get(i);
|
||||
|
||||
if (event instanceof QuotaBump) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TimingSession session = (TimingSession) event;
|
||||
|
||||
if (session.endTimeElapsed < windowStartElapsed) {
|
||||
// Outside of window. Ignore.
|
||||
@@ -1129,7 +1160,7 @@ public final class QuotaController extends StateController {
|
||||
final long remainingExecutionTimeMs =
|
||||
getRemainingEJExecutionTimeLocked(userId, packageName);
|
||||
|
||||
List<TimingSession> sessions = mEJTimingSessions.get(userId, packageName);
|
||||
List<TimedEvent> sessions = mEJTimingSessions.get(userId, packageName);
|
||||
if (sessions == null || sessions.size() == 0) {
|
||||
return remainingExecutionTimeMs;
|
||||
}
|
||||
@@ -1146,7 +1177,7 @@ public final class QuotaController extends StateController {
|
||||
long phasedOutSessionTimeMs = 0;
|
||||
|
||||
for (int i = 0; i < sessions.size(); ++i) {
|
||||
TimingSession session = sessions.get(i);
|
||||
TimingSession session = (TimingSession) sessions.get(i);
|
||||
if (session.endTimeElapsed < startWindowElapsed) {
|
||||
// Edge case where a session became stale in the time between the call to
|
||||
// getRemainingEJExecutionTimeLocked and this line.
|
||||
@@ -1159,7 +1190,7 @@ public final class QuotaController extends StateController {
|
||||
} else {
|
||||
// Session fully inside window
|
||||
final long timeBetweenSessions = session.startTimeElapsed
|
||||
- (i == 0 ? startWindowElapsed : sessions.get(i - 1).endTimeElapsed);
|
||||
- (i == 0 ? startWindowElapsed : sessions.get(i - 1).getEndTimeElapsed());
|
||||
final long usedDeadSpaceMs = Math.min(remainingDeadSpaceMs, timeBetweenSessions);
|
||||
deadSpaceMs += usedDeadSpaceMs;
|
||||
if (usedDeadSpaceMs == timeBetweenSessions) {
|
||||
@@ -1267,22 +1298,49 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
}
|
||||
|
||||
List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
|
||||
if (sessions == null || sessions.size() == 0) {
|
||||
List<TimedEvent> events = mTimingEvents.get(userId, packageName);
|
||||
if (events == null || events.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
|
||||
final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
|
||||
int sessionCountInWindow = 0;
|
||||
// The minimum time between the start time and the beginning of the sessions that were
|
||||
int numQuotaBumps = 0;
|
||||
final long quotaBumpWindowStartElapsed = nowElapsed - mQuotaBumpWindowSizeMs;
|
||||
// The minimum time between the start time and the beginning of the events that were
|
||||
// looked at --> how much time the stats will be valid for.
|
||||
long emptyTimeMs = Long.MAX_VALUE;
|
||||
// Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
|
||||
// the most recent ones.
|
||||
final int loopStart = sessions.size() - 1;
|
||||
final int loopStart = events.size() - 1;
|
||||
// Process QuotaBumps first to ensure the limits are properly adjusted.
|
||||
for (int i = loopStart; i >= 0; --i) {
|
||||
TimingSession session = sessions.get(i);
|
||||
TimedEvent event = events.get(i);
|
||||
|
||||
if (event.getEndTimeElapsed() < quotaBumpWindowStartElapsed
|
||||
|| numQuotaBumps >= mQuotaBumpLimit) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (event instanceof QuotaBump) {
|
||||
stats.allowedTimePerPeriodMs += mQuotaBumpAdditionalDurationMs;
|
||||
stats.jobCountLimit += mQuotaBumpAdditionalJobCount;
|
||||
stats.sessionCountLimit += mQuotaBumpAdditionalSessionCount;
|
||||
emptyTimeMs = Math.min(emptyTimeMs,
|
||||
event.getEndTimeElapsed() - quotaBumpWindowStartElapsed);
|
||||
numQuotaBumps++;
|
||||
}
|
||||
}
|
||||
TimingSession lastSeenTimingSession = null;
|
||||
for (int i = loopStart; i >= 0; --i) {
|
||||
TimedEvent event = events.get(i);
|
||||
|
||||
if (event instanceof QuotaBump) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TimingSession session = (TimingSession) event;
|
||||
|
||||
// Window management.
|
||||
if (startWindowElapsed < session.endTimeElapsed) {
|
||||
@@ -1309,10 +1367,11 @@ public final class QuotaController extends StateController {
|
||||
final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
|
||||
stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime);
|
||||
}
|
||||
if (i == loopStart
|
||||
|| (sessions.get(i + 1).startTimeElapsed - session.endTimeElapsed)
|
||||
> mTimingSessionCoalescingDurationMs) {
|
||||
// Coalesce sessions if they are very close to each other in time
|
||||
// Coalesce sessions if they are very close to each other in time
|
||||
boolean shouldCoalesce = lastSeenTimingSession != null
|
||||
&& lastSeenTimingSession.startTimeElapsed - session.endTimeElapsed
|
||||
<= mTimingSessionCoalescingDurationMs;
|
||||
if (!shouldCoalesce) {
|
||||
sessionCountInWindow++;
|
||||
|
||||
if (sessionCountInWindow >= stats.sessionCountLimit) {
|
||||
@@ -1348,6 +1407,8 @@ public final class QuotaController extends StateController {
|
||||
// This session ended before the window. No point in going any further.
|
||||
break;
|
||||
}
|
||||
|
||||
lastSeenTimingSession = session;
|
||||
}
|
||||
stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
|
||||
stats.sessionCountInWindow = sessionCountInWindow;
|
||||
@@ -1437,9 +1498,9 @@ public final class QuotaController extends StateController {
|
||||
private void saveTimingSession(final int userId, @NonNull final String packageName,
|
||||
@NonNull final TimingSession session, boolean isExpedited, long debitAdjustment) {
|
||||
synchronized (mLock) {
|
||||
final SparseArrayMap<String, List<TimingSession>> sessionMap =
|
||||
isExpedited ? mEJTimingSessions : mTimingSessions;
|
||||
List<TimingSession> sessions = sessionMap.get(userId, packageName);
|
||||
final SparseArrayMap<String, List<TimedEvent>> sessionMap =
|
||||
isExpedited ? mEJTimingSessions : mTimingEvents;
|
||||
List<TimedEvent> sessions = sessionMap.get(userId, packageName);
|
||||
if (sessions == null) {
|
||||
sessions = new ArrayList<>();
|
||||
sessionMap.add(userId, packageName, sessions);
|
||||
@@ -1460,6 +1521,9 @@ public final class QuotaController extends StateController {
|
||||
|
||||
private void grantRewardForInstantEvent(
|
||||
final int userId, @NonNull final String packageName, final long credit) {
|
||||
if (credit == 0) {
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||
final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
|
||||
@@ -1489,13 +1553,14 @@ public final class QuotaController extends StateController {
|
||||
return changed;
|
||||
}
|
||||
|
||||
private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> {
|
||||
private final class EarliestEndTimeFunctor implements Consumer<List<TimedEvent>> {
|
||||
public long earliestEndElapsed = Long.MAX_VALUE;
|
||||
|
||||
@Override
|
||||
public void accept(List<TimingSession> sessions) {
|
||||
if (sessions != null && sessions.size() > 0) {
|
||||
earliestEndElapsed = Math.min(earliestEndElapsed, sessions.get(0).endTimeElapsed);
|
||||
public void accept(List<TimedEvent> events) {
|
||||
if (events != null && events.size() > 0) {
|
||||
earliestEndElapsed =
|
||||
Math.min(earliestEndElapsed, events.get(0).getEndTimeElapsed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1521,7 +1586,7 @@ public final class QuotaController extends StateController {
|
||||
return;
|
||||
}
|
||||
mEarliestEndTimeFunctor.reset();
|
||||
mTimingSessions.forEach(mEarliestEndTimeFunctor);
|
||||
mTimingEvents.forEach(mEarliestEndTimeFunctor);
|
||||
mEJTimingSessions.forEach(mEarliestEndTimeFunctor);
|
||||
final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed;
|
||||
if (earliestEndElapsed == Long.MAX_VALUE) {
|
||||
@@ -1809,10 +1874,10 @@ public final class QuotaController extends StateController {
|
||||
inEJQuotaTimeElapsed = (nowElapsed - limitMs) + mEJLimitWindowSizeMs;
|
||||
}
|
||||
}
|
||||
List<TimingSession> timingSessions = mEJTimingSessions.get(userId, packageName);
|
||||
List<TimedEvent> timingSessions = mEJTimingSessions.get(userId, packageName);
|
||||
if (timingSessions != null) {
|
||||
for (int i = timingSessions.size() - 1; i >= 0; --i) {
|
||||
TimingSession ts = timingSessions.get(i);
|
||||
TimingSession ts = (TimingSession) timingSessions.get(i);
|
||||
final long durationMs = ts.endTimeElapsed - ts.startTimeElapsed;
|
||||
sumMs += durationMs;
|
||||
if (sumMs >= limitMs) {
|
||||
@@ -1881,7 +1946,14 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static final class TimingSession {
|
||||
interface TimedEvent {
|
||||
long getEndTimeElapsed();
|
||||
|
||||
void dump(IndentingPrintWriter pw);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static final class TimingSession implements TimedEvent {
|
||||
// Start timestamp in elapsed realtime timebase.
|
||||
public final long startTimeElapsed;
|
||||
// End timestamp in elapsed realtime timebase.
|
||||
@@ -1903,6 +1975,11 @@ public final class QuotaController extends StateController {
|
||||
mHashCode = hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEndTimeElapsed() {
|
||||
return endTimeElapsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
|
||||
@@ -1926,6 +2003,7 @@ public final class QuotaController extends StateController {
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.print(startTimeElapsed);
|
||||
pw.print(" -> ");
|
||||
@@ -1952,6 +2030,28 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static final class QuotaBump implements TimedEvent {
|
||||
// Event timestamp in elapsed realtime timebase.
|
||||
public final long eventTimeElapsed;
|
||||
|
||||
QuotaBump(long eventElapsed) {
|
||||
this.eventTimeElapsed = eventElapsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEndTimeElapsed() {
|
||||
return eventTimeElapsed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
pw.print("Quota bump @ ");
|
||||
pw.print(eventTimeElapsed);
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static final class ShrinkableDebits {
|
||||
/** The amount of quota remaining. Can be negative if limit changes. */
|
||||
@@ -2381,6 +2481,21 @@ public final class QuotaController extends StateController {
|
||||
updateStandbyBucket(userId, packageName, bucketIndex);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerTemporaryQuotaBump(String packageName, @UserIdInt int userId) {
|
||||
synchronized (mLock) {
|
||||
List<TimedEvent> events = mTimingEvents.get(userId, packageName);
|
||||
if (events == null || events.size() == 0) {
|
||||
// If the app hasn't run any jobs, there's no point giving it a quota bump.
|
||||
return;
|
||||
}
|
||||
events.add(new QuotaBump(sElapsedRealtimeClock.millis()));
|
||||
invalidateAllExecutionStatsLocked(userId, packageName);
|
||||
}
|
||||
// Update jobs out of band.
|
||||
mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -2476,7 +2591,7 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TimingSessionTooOldPredicate implements Predicate<TimingSession> {
|
||||
private static final class TimedEventTooOldPredicate implements Predicate<TimedEvent> {
|
||||
private long mNowElapsed;
|
||||
|
||||
private void updateNow() {
|
||||
@@ -2484,27 +2599,26 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(TimingSession ts) {
|
||||
return ts.endTimeElapsed <= mNowElapsed - MAX_PERIOD_MS;
|
||||
public boolean test(TimedEvent ts) {
|
||||
return ts.getEndTimeElapsed() <= mNowElapsed - MAX_PERIOD_MS;
|
||||
}
|
||||
}
|
||||
|
||||
private final TimingSessionTooOldPredicate mTimingSessionTooOld =
|
||||
new TimingSessionTooOldPredicate();
|
||||
private final TimedEventTooOldPredicate mTimedEventTooOld = new TimedEventTooOldPredicate();
|
||||
|
||||
private final Consumer<List<TimingSession>> mDeleteOldSessionsFunctor = sessions -> {
|
||||
if (sessions != null) {
|
||||
private final Consumer<List<TimedEvent>> mDeleteOldEventsFunctor = events -> {
|
||||
if (events != null) {
|
||||
// Remove everything older than MAX_PERIOD_MS time ago.
|
||||
sessions.removeIf(mTimingSessionTooOld);
|
||||
events.removeIf(mTimedEventTooOld);
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
void deleteObsoleteSessionsLocked() {
|
||||
mTimingSessionTooOld.updateNow();
|
||||
mTimedEventTooOld.updateNow();
|
||||
|
||||
// Regular sessions
|
||||
mTimingSessions.forEach(mDeleteOldSessionsFunctor);
|
||||
mTimingEvents.forEach(mDeleteOldEventsFunctor);
|
||||
|
||||
// EJ sessions
|
||||
for (int uIdx = 0; uIdx < mEJTimingSessions.numMaps(); ++uIdx) {
|
||||
@@ -2512,14 +2626,14 @@ public final class QuotaController extends StateController {
|
||||
for (int pIdx = 0; pIdx < mEJTimingSessions.numElementsForKey(userId); ++pIdx) {
|
||||
final String packageName = mEJTimingSessions.keyAt(uIdx, pIdx);
|
||||
final ShrinkableDebits debits = getEJDebitsLocked(userId, packageName);
|
||||
final List<TimingSession> sessions = mEJTimingSessions.get(userId, packageName);
|
||||
final List<TimedEvent> sessions = mEJTimingSessions.get(userId, packageName);
|
||||
if (sessions == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
while (sessions.size() > 0) {
|
||||
final TimingSession ts = sessions.get(0);
|
||||
if (mTimingSessionTooOld.test(ts)) {
|
||||
final TimingSession ts = (TimingSession) sessions.get(0);
|
||||
if (mTimedEventTooOld.test(ts)) {
|
||||
// Stale sessions may still be factored into tally. Remove them.
|
||||
final long duration = ts.endTimeElapsed - ts.startTimeElapsed;
|
||||
debits.transactLocked(-duration);
|
||||
@@ -2801,6 +2915,7 @@ public final class QuotaController extends StateController {
|
||||
mQcConstants.mRateLimitingConstantsUpdated = false;
|
||||
mQcConstants.mExecutionPeriodConstantsUpdated = false;
|
||||
mQcConstants.mEJLimitConstantsUpdated = false;
|
||||
mQcConstants.mQuotaBumpConstantsUpdated = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2828,6 +2943,7 @@ public final class QuotaController extends StateController {
|
||||
private boolean mRateLimitingConstantsUpdated = false;
|
||||
private boolean mExecutionPeriodConstantsUpdated = false;
|
||||
private boolean mEJLimitConstantsUpdated = false;
|
||||
private boolean mQuotaBumpConstantsUpdated = false;
|
||||
|
||||
/** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
|
||||
private static final String QC_CONSTANT_PREFIX = "qc_";
|
||||
@@ -2975,6 +3091,21 @@ public final class QuotaController extends StateController {
|
||||
@VisibleForTesting
|
||||
static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
|
||||
QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms";
|
||||
@VisibleForTesting
|
||||
static final String KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS =
|
||||
QC_CONSTANT_PREFIX + "quota_bump_additional_duration_ms";
|
||||
@VisibleForTesting
|
||||
static final String KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT =
|
||||
QC_CONSTANT_PREFIX + "quota_bump_additional_job_count";
|
||||
@VisibleForTesting
|
||||
static final String KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT =
|
||||
QC_CONSTANT_PREFIX + "quota_bump_additional_session_count";
|
||||
@VisibleForTesting
|
||||
static final String KEY_QUOTA_BUMP_WINDOW_SIZE_MS =
|
||||
QC_CONSTANT_PREFIX + "quota_bump_window_size_ms";
|
||||
@VisibleForTesting
|
||||
static final String KEY_QUOTA_BUMP_LIMIT =
|
||||
QC_CONSTANT_PREFIX + "quota_bump_limit";
|
||||
|
||||
private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
|
||||
10 * 60 * 1000L; // 10 minutes
|
||||
@@ -3046,6 +3177,11 @@ public final class QuotaController extends StateController {
|
||||
private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0;
|
||||
private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS;
|
||||
private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS;
|
||||
private static final long DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS = 1 * MINUTE_IN_MILLIS;
|
||||
private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = 2;
|
||||
private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 1;
|
||||
private static final long DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS = 8 * HOUR_IN_MILLIS;
|
||||
private static final int DEFAULT_QUOTA_BUMP_LIMIT = 8;
|
||||
|
||||
/**
|
||||
* How much time each app in the exempted bucket will have to run jobs within their standby
|
||||
@@ -3347,6 +3483,33 @@ public final class QuotaController extends StateController {
|
||||
*/
|
||||
public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS;
|
||||
|
||||
/**
|
||||
* How much additional session duration to give an app for each accepted quota bump.
|
||||
*/
|
||||
public long QUOTA_BUMP_ADDITIONAL_DURATION_MS = DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS;
|
||||
|
||||
/**
|
||||
* How many additional regular jobs to give an app for each accepted quota bump.
|
||||
*/
|
||||
public int QUOTA_BUMP_ADDITIONAL_JOB_COUNT = DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT;
|
||||
|
||||
/**
|
||||
* How many additional sessions to give an app for each accepted quota bump.
|
||||
*/
|
||||
public int QUOTA_BUMP_ADDITIONAL_SESSION_COUNT =
|
||||
DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT;
|
||||
|
||||
/**
|
||||
* The rolling window size within which to accept and apply quota bump events.
|
||||
*/
|
||||
public long QUOTA_BUMP_WINDOW_SIZE_MS = DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS;
|
||||
|
||||
/**
|
||||
* The maximum number of quota bumps to accept and apply within the
|
||||
* {@link #QUOTA_BUMP_WINDOW_SIZE_MS window}.
|
||||
*/
|
||||
public int QUOTA_BUMP_LIMIT = DEFAULT_QUOTA_BUMP_LIMIT;
|
||||
|
||||
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
|
||||
@NonNull String key) {
|
||||
switch (key) {
|
||||
@@ -3383,6 +3546,14 @@ public final class QuotaController extends StateController {
|
||||
updateEJLimitConstantsLocked();
|
||||
break;
|
||||
|
||||
case KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS:
|
||||
case KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT:
|
||||
case KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT:
|
||||
case KEY_QUOTA_BUMP_WINDOW_SIZE_MS:
|
||||
case KEY_QUOTA_BUMP_LIMIT:
|
||||
updateQuotaBumpConstantsLocked();
|
||||
break;
|
||||
|
||||
case KEY_MAX_JOB_COUNT_EXEMPTED:
|
||||
MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED);
|
||||
int newExemptedMaxJobCount =
|
||||
@@ -3881,6 +4052,65 @@ public final class QuotaController extends StateController {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateQuotaBumpConstantsLocked() {
|
||||
if (mQuotaBumpConstantsUpdated) {
|
||||
return;
|
||||
}
|
||||
mQuotaBumpConstantsUpdated = true;
|
||||
|
||||
// Query the values as an atomic set.
|
||||
final DeviceConfig.Properties properties = DeviceConfig.getProperties(
|
||||
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
|
||||
KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
|
||||
KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT,
|
||||
KEY_QUOTA_BUMP_WINDOW_SIZE_MS, KEY_QUOTA_BUMP_LIMIT);
|
||||
QUOTA_BUMP_ADDITIONAL_DURATION_MS = properties.getLong(
|
||||
KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
|
||||
DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS);
|
||||
QUOTA_BUMP_ADDITIONAL_JOB_COUNT = properties.getInt(
|
||||
KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT);
|
||||
QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = properties.getInt(
|
||||
KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT,
|
||||
DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT);
|
||||
QUOTA_BUMP_WINDOW_SIZE_MS = properties.getLong(
|
||||
KEY_QUOTA_BUMP_WINDOW_SIZE_MS, DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS);
|
||||
QUOTA_BUMP_LIMIT = properties.getInt(
|
||||
KEY_QUOTA_BUMP_LIMIT, DEFAULT_QUOTA_BUMP_LIMIT);
|
||||
|
||||
// The window must be in the range [1 hour, 24 hours].
|
||||
long newWindowSizeMs = Math.max(HOUR_IN_MILLIS,
|
||||
Math.min(MAX_PERIOD_MS, QUOTA_BUMP_WINDOW_SIZE_MS));
|
||||
if (mQuotaBumpWindowSizeMs != newWindowSizeMs) {
|
||||
mQuotaBumpWindowSizeMs = newWindowSizeMs;
|
||||
mShouldReevaluateConstraints = true;
|
||||
}
|
||||
// The limit must be nonnegative.
|
||||
int newLimit = Math.max(0, QUOTA_BUMP_LIMIT);
|
||||
if (mQuotaBumpLimit != newLimit) {
|
||||
mQuotaBumpLimit = newLimit;
|
||||
mShouldReevaluateConstraints = true;
|
||||
}
|
||||
// The job count must be nonnegative.
|
||||
int newJobAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_JOB_COUNT);
|
||||
if (mQuotaBumpAdditionalJobCount != newJobAddition) {
|
||||
mQuotaBumpAdditionalJobCount = newJobAddition;
|
||||
mShouldReevaluateConstraints = true;
|
||||
}
|
||||
// The session count must be nonnegative.
|
||||
int newSessionAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_SESSION_COUNT);
|
||||
if (mQuotaBumpAdditionalSessionCount != newSessionAddition) {
|
||||
mQuotaBumpAdditionalSessionCount = newSessionAddition;
|
||||
mShouldReevaluateConstraints = true;
|
||||
}
|
||||
// The additional duration must be in the range [0, 10 minutes].
|
||||
long newAdditionalDuration = Math.max(0,
|
||||
Math.min(10 * MINUTE_IN_MILLIS, QUOTA_BUMP_ADDITIONAL_DURATION_MS));
|
||||
if (mQuotaBumpAdditionalDurationMs != newAdditionalDuration) {
|
||||
mQuotaBumpAdditionalDurationMs = newAdditionalDuration;
|
||||
mShouldReevaluateConstraints = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void dump(IndentingPrintWriter pw) {
|
||||
pw.println();
|
||||
pw.println("QuotaController:");
|
||||
@@ -3943,6 +4173,15 @@ public final class QuotaController extends StateController {
|
||||
EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS).println();
|
||||
pw.print(KEY_EJ_GRACE_PERIOD_TOP_APP_MS, EJ_GRACE_PERIOD_TOP_APP_MS).println();
|
||||
|
||||
pw.print(KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
|
||||
QUOTA_BUMP_ADDITIONAL_DURATION_MS).println();
|
||||
pw.print(KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT,
|
||||
QUOTA_BUMP_ADDITIONAL_JOB_COUNT).println();
|
||||
pw.print(KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT,
|
||||
QUOTA_BUMP_ADDITIONAL_SESSION_COUNT).println();
|
||||
pw.print(KEY_QUOTA_BUMP_WINDOW_SIZE_MS, QUOTA_BUMP_WINDOW_SIZE_MS).println();
|
||||
pw.print(KEY_QUOTA_BUMP_LIMIT, QUOTA_BUMP_LIMIT).println();
|
||||
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
@@ -4103,7 +4342,7 @@ public final class QuotaController extends StateController {
|
||||
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
List<TimingSession> getEJTimingSessions(int userId, String packageName) {
|
||||
List<TimedEvent> getEJTimingSessions(int userId, String packageName) {
|
||||
return mEJTimingSessions.get(userId, packageName);
|
||||
}
|
||||
|
||||
@@ -4150,8 +4389,8 @@ public final class QuotaController extends StateController {
|
||||
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
List<TimingSession> getTimingSessions(int userId, String packageName) {
|
||||
return mTimingSessions.get(userId, packageName);
|
||||
List<TimedEvent> getTimingSessions(int userId, String packageName) {
|
||||
return mTimingEvents.get(userId, packageName);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -4160,6 +4399,31 @@ public final class QuotaController extends StateController {
|
||||
return mQcConstants;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
long getQuotaBumpAdditionDurationMs() {
|
||||
return mQuotaBumpAdditionalDurationMs;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int getQuotaBumpAdditionJobCount() {
|
||||
return mQuotaBumpAdditionalJobCount;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int getQuotaBumpAdditionSessionCount() {
|
||||
return mQuotaBumpAdditionalSessionCount;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int getQuotaBumpLimit() {
|
||||
return mQuotaBumpLimit;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
long getQuotaBumpWindowSizeMs() {
|
||||
return mQuotaBumpWindowSizeMs;
|
||||
}
|
||||
|
||||
//////////////////////////// DATA DUMP //////////////////////////////
|
||||
|
||||
@NeverCompile // Avoid size overhead of debugging code.
|
||||
@@ -4250,14 +4514,14 @@ public final class QuotaController extends StateController {
|
||||
final String pkgName = mPkgTimers.keyAt(u, p);
|
||||
mPkgTimers.valueAt(u, p).dump(pw, predicate);
|
||||
pw.println();
|
||||
List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
|
||||
if (sessions != null) {
|
||||
List<TimedEvent> events = mTimingEvents.get(userId, pkgName);
|
||||
if (events != null) {
|
||||
pw.increaseIndent();
|
||||
pw.println("Saved sessions:");
|
||||
pw.println("Saved events:");
|
||||
pw.increaseIndent();
|
||||
for (int j = sessions.size() - 1; j >= 0; j--) {
|
||||
TimingSession session = sessions.get(j);
|
||||
session.dump(pw);
|
||||
for (int j = events.size() - 1; j >= 0; j--) {
|
||||
TimedEvent event = events.get(j);
|
||||
event.dump(pw);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
pw.decreaseIndent();
|
||||
@@ -4273,13 +4537,13 @@ public final class QuotaController extends StateController {
|
||||
final String pkgName = mEJPkgTimers.keyAt(u, p);
|
||||
mEJPkgTimers.valueAt(u, p).dump(pw, predicate);
|
||||
pw.println();
|
||||
List<TimingSession> sessions = mEJTimingSessions.get(userId, pkgName);
|
||||
List<TimedEvent> sessions = mEJTimingSessions.get(userId, pkgName);
|
||||
if (sessions != null) {
|
||||
pw.increaseIndent();
|
||||
pw.println("Saved sessions:");
|
||||
pw.increaseIndent();
|
||||
for (int j = sessions.size() - 1; j >= 0; j--) {
|
||||
TimingSession session = sessions.get(j);
|
||||
TimedEvent session = sessions.get(j);
|
||||
session.dump(pw);
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
@@ -4398,10 +4662,14 @@ public final class QuotaController extends StateController {
|
||||
predicate);
|
||||
}
|
||||
|
||||
List<TimingSession> sessions = mTimingSessions.get(userId, pkgName);
|
||||
if (sessions != null) {
|
||||
for (int j = sessions.size() - 1; j >= 0; j--) {
|
||||
TimingSession session = sessions.get(j);
|
||||
List<TimedEvent> events = mTimingEvents.get(userId, pkgName);
|
||||
if (events != null) {
|
||||
for (int j = events.size() - 1; j >= 0; j--) {
|
||||
TimedEvent event = events.get(j);
|
||||
if (!(event instanceof TimingSession)) {
|
||||
continue;
|
||||
}
|
||||
TimingSession session = (TimingSession) event;
|
||||
session.dump(proto,
|
||||
StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS);
|
||||
}
|
||||
|
||||
@@ -289,6 +289,7 @@ public class AppStandbyController
|
||||
static final int MSG_INFORM_LISTENERS = 3;
|
||||
static final int MSG_FORCE_IDLE_STATE = 4;
|
||||
static final int MSG_CHECK_IDLE_STATES = 5;
|
||||
static final int MSG_TRIGGER_LISTENER_QUOTA_BUMP = 7;
|
||||
static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
|
||||
static final int MSG_PAROLE_STATE_CHANGED = 9;
|
||||
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
|
||||
@@ -318,6 +319,12 @@ public class AppStandbyController
|
||||
/** The standby bucket that an app will be promoted on a notification-seen event */
|
||||
int mNotificationSeenPromotedBucket =
|
||||
ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET;
|
||||
/**
|
||||
* If true, tell each {@link AppIdleStateChangeListener} to give quota bump for each
|
||||
* notification seen event.
|
||||
*/
|
||||
private boolean mTriggerQuotaBumpOnNotificationSeen =
|
||||
ConstantsObserver.DEFAULT_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN;
|
||||
/** Minimum time a system update event should keep the buckets elevated. */
|
||||
long mSystemUpdateUsageTimeoutMillis = ConstantsObserver.DEFAULT_SYSTEM_UPDATE_TIMEOUT;
|
||||
/** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
|
||||
@@ -767,6 +774,17 @@ public class AppStandbyController
|
||||
appUsage.bucketingReason, false);
|
||||
}
|
||||
|
||||
/** Trigger a quota bump in the listeners. */
|
||||
private void triggerListenerQuotaBump(String packageName, int userId) {
|
||||
if (!mAppIdleEnabled) return;
|
||||
|
||||
synchronized (mPackageAccessListeners) {
|
||||
for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
|
||||
listener.triggerTemporaryQuotaBump(packageName, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setChargingState(boolean isCharging) {
|
||||
if (mIsCharging != isCharging) {
|
||||
@@ -1080,6 +1098,10 @@ public class AppStandbyController
|
||||
final int subReason = usageEventToSubReason(eventType);
|
||||
final int reason = REASON_MAIN_USAGE | subReason;
|
||||
if (eventType == UsageEvents.Event.NOTIFICATION_SEEN) {
|
||||
if (mTriggerQuotaBumpOnNotificationSeen) {
|
||||
mHandler.obtainMessage(MSG_TRIGGER_LISTENER_QUOTA_BUMP, userId, -1, pkg)
|
||||
.sendToTarget();
|
||||
}
|
||||
// Notification-seen elevates to a higher bucket (depending on
|
||||
// {@link ConstantsObserver#KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET}) but doesn't
|
||||
// change usage time.
|
||||
@@ -2201,6 +2223,9 @@ public class AppStandbyController
|
||||
pw.print(" mNotificationSeenPromotedBucket=");
|
||||
pw.print(standbyBucketToString(mNotificationSeenPromotedBucket));
|
||||
pw.println();
|
||||
pw.print(" mTriggerQuotaBumpOnNotificationSeen=");
|
||||
pw.print(mTriggerQuotaBumpOnNotificationSeen);
|
||||
pw.println();
|
||||
pw.print(" mSlicePinnedTimeoutMillis=");
|
||||
TimeUtils.formatDuration(mSlicePinnedTimeoutMillis, pw);
|
||||
pw.println();
|
||||
@@ -2597,6 +2622,10 @@ public class AppStandbyController
|
||||
checkIdleStates(UserHandle.USER_ALL);
|
||||
break;
|
||||
|
||||
case MSG_TRIGGER_LISTENER_QUOTA_BUMP:
|
||||
triggerListenerQuotaBump((String) msg.obj, msg.arg1);
|
||||
break;
|
||||
|
||||
case MSG_REPORT_CONTENT_PROVIDER_USAGE:
|
||||
ContentProviderUsageRecord record = (ContentProviderUsageRecord) msg.obj;
|
||||
reportContentProviderUsage(record.name, record.packageName, record.userId);
|
||||
@@ -2683,6 +2712,8 @@ public class AppStandbyController
|
||||
"notification_seen_duration";
|
||||
private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET =
|
||||
"notification_seen_promoted_bucket";
|
||||
private static final String KEY_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN =
|
||||
"trigger_quota_bump_on_notification_seen";
|
||||
private static final String KEY_SLICE_PINNED_HOLD_DURATION =
|
||||
"slice_pinned_duration";
|
||||
private static final String KEY_SYSTEM_UPDATE_HOLD_DURATION =
|
||||
@@ -2742,6 +2773,7 @@ public class AppStandbyController
|
||||
COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
|
||||
public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET =
|
||||
STANDBY_BUCKET_WORKING_SET;
|
||||
public static final boolean DEFAULT_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN = false;
|
||||
public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT =
|
||||
COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR;
|
||||
public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT =
|
||||
@@ -2842,6 +2874,11 @@ public class AppStandbyController
|
||||
KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET,
|
||||
DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET);
|
||||
break;
|
||||
case KEY_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN:
|
||||
mTriggerQuotaBumpOnNotificationSeen = properties.getBoolean(
|
||||
KEY_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN,
|
||||
DEFAULT_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN);
|
||||
break;
|
||||
case KEY_SLICE_PINNED_HOLD_DURATION:
|
||||
mSlicePinnedTimeoutMillis = properties.getLong(
|
||||
KEY_SLICE_PINNED_HOLD_DURATION,
|
||||
|
||||
@@ -90,7 +90,9 @@ import com.android.server.job.JobSchedulerService;
|
||||
import com.android.server.job.JobStore;
|
||||
import com.android.server.job.controllers.QuotaController.ExecutionStats;
|
||||
import com.android.server.job.controllers.QuotaController.QcConstants;
|
||||
import com.android.server.job.controllers.QuotaController.QuotaBump;
|
||||
import com.android.server.job.controllers.QuotaController.ShrinkableDebits;
|
||||
import com.android.server.job.controllers.QuotaController.TimedEvent;
|
||||
import com.android.server.job.controllers.QuotaController.TimingSession;
|
||||
import com.android.server.usage.AppStandbyInternal;
|
||||
|
||||
@@ -128,6 +130,7 @@ public class QuotaControllerTest {
|
||||
private QuotaController.QcConstants mQcConstants;
|
||||
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
|
||||
private int mSourceUid;
|
||||
private AppStandbyInternal.AppIdleStateChangeListener mAppIdleStateChangeListener;
|
||||
private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
|
||||
private IUidObserver mUidObserver;
|
||||
private UsageStatsManagerInternal.UsageEventListener mUsageEventListener;
|
||||
@@ -178,7 +181,8 @@ public class QuotaControllerTest {
|
||||
when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
|
||||
doReturn(mActivityMangerInternal)
|
||||
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
|
||||
doReturn(mock(AppStandbyInternal.class))
|
||||
final AppStandbyInternal appStandbyInternal = mock(AppStandbyInternal.class);
|
||||
doReturn(appStandbyInternal)
|
||||
.when(() -> LocalServices.getService(AppStandbyInternal.class));
|
||||
doReturn(mock(BatteryManagerInternal.class))
|
||||
.when(() -> LocalServices.getService(BatteryManagerInternal.class));
|
||||
@@ -222,6 +226,8 @@ public class QuotaControllerTest {
|
||||
|
||||
// Initialize real objects.
|
||||
// Capture the listeners.
|
||||
ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> aiscListenerCaptor =
|
||||
ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
|
||||
ArgumentCaptor<IUidObserver> uidObserverCaptor =
|
||||
ArgumentCaptor.forClass(IUidObserver.class);
|
||||
ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
|
||||
@@ -231,6 +237,8 @@ public class QuotaControllerTest {
|
||||
mQuotaController = new QuotaController(mJobSchedulerService,
|
||||
mock(BackgroundJobsController.class), mock(ConnectivityController.class));
|
||||
|
||||
verify(appStandbyInternal).addListener(aiscListenerCaptor.capture());
|
||||
mAppIdleStateChangeListener = aiscListenerCaptor.getValue();
|
||||
verify(mPowerAllowlistInternal)
|
||||
.registerTempAllowlistChangeListener(taChangeCaptor.capture());
|
||||
mTempAllowlistListener = taChangeCaptor.getValue();
|
||||
@@ -457,26 +465,31 @@ public class QuotaControllerTest {
|
||||
now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
|
||||
TimingSession two = createTimingSession(
|
||||
now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
|
||||
QuotaBump bump1 = new QuotaBump(now - 2 * HOUR_IN_MILLIS);
|
||||
TimingSession thr = createTimingSession(
|
||||
now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
|
||||
// Overlaps 24 hour boundary.
|
||||
TimingSession fou = createTimingSession(
|
||||
now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
|
||||
// Way past the 24 hour boundary.
|
||||
QuotaBump bump2 = new QuotaBump(now - 24 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
|
||||
TimingSession fiv = createTimingSession(
|
||||
now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
|
||||
List<TimingSession> expectedRegular = new ArrayList<>();
|
||||
List<TimingSession> expectedEJ = new ArrayList<>();
|
||||
List<TimedEvent> expectedRegular = new ArrayList<>();
|
||||
List<TimedEvent> expectedEJ = new ArrayList<>();
|
||||
// Added in correct (chronological) order.
|
||||
expectedRegular.add(fou);
|
||||
expectedRegular.add(thr);
|
||||
expectedRegular.add(bump1);
|
||||
expectedRegular.add(two);
|
||||
expectedRegular.add(one);
|
||||
expectedEJ.add(fou);
|
||||
expectedEJ.add(one);
|
||||
mQuotaController.saveTimingSession(0, "com.android.test", fiv, false);
|
||||
mQuotaController.getTimingSessions(0, "com.android.test").add(bump2);
|
||||
mQuotaController.saveTimingSession(0, "com.android.test", fou, false);
|
||||
mQuotaController.saveTimingSession(0, "com.android.test", thr, false);
|
||||
mQuotaController.getTimingSessions(0, "com.android.test").add(bump1);
|
||||
mQuotaController.saveTimingSession(0, "com.android.test", two, false);
|
||||
mQuotaController.saveTimingSession(0, "com.android.test", one, false);
|
||||
mQuotaController.saveTimingSession(0, "com.android.test", fiv, true);
|
||||
@@ -1773,6 +1786,129 @@ public class QuotaControllerTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
|
||||
* window and there are valid QuotaBumps in the history.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeUntilQuotaConsumedLocked_QuotaBump() {
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
|
||||
|
||||
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
// Close to RARE boundary.
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
|
||||
30 * SECOND_IN_MILLIS, 5), false);
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 16 * HOUR_IN_MILLIS));
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 12 * HOUR_IN_MILLIS));
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 8 * HOUR_IN_MILLIS));
|
||||
// Far away from FREQUENT boundary.
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
|
||||
// Overlap WORKING_SET boundary.
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 2 * HOUR_IN_MILLIS));
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
|
||||
3 * MINUTE_IN_MILLIS, 5), false);
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS));
|
||||
// Close to ACTIVE boundary.
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
|
||||
|
||||
setStandbyBucket(RARE_INDEX);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(3 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
|
||||
mQuotaController.getRemainingExecutionTimeLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
assertEquals(4 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getTimeUntilQuotaConsumedLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
}
|
||||
|
||||
setStandbyBucket(FREQUENT_INDEX);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(4 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getRemainingExecutionTimeLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
assertEquals(4 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getTimeUntilQuotaConsumedLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
}
|
||||
|
||||
setStandbyBucket(WORKING_INDEX);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(8 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getRemainingExecutionTimeLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
assertEquals(10 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getTimeUntilQuotaConsumedLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
}
|
||||
|
||||
// ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
|
||||
// max execution time.
|
||||
setStandbyBucket(ACTIVE_INDEX);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(10 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getRemainingExecutionTimeLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getTimeUntilQuotaConsumedLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getTimeUntilQuotaConsumedLocked when there are valid QuotaBumps in recent history that
|
||||
* provide enough additional quota to bridge gaps between sessions.
|
||||
*/
|
||||
@Test
|
||||
public void testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps() {
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
|
||||
|
||||
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (25 * HOUR_IN_MILLIS),
|
||||
30 * MINUTE_IN_MILLIS, 25), false);
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 16 * HOUR_IN_MILLIS));
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 12 * HOUR_IN_MILLIS));
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 8 * HOUR_IN_MILLIS));
|
||||
// Without the valid quota bumps, the app would only 3 minutes until the quota was consumed.
|
||||
// The quota bumps provide enough quota to bridge the gap between the two earliest sessions.
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (8 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (8 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS),
|
||||
2 * MINUTE_IN_MILLIS, 5), false);
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
|
||||
3 * MINUTE_IN_MILLIS, 1), false);
|
||||
mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
|
||||
.add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS));
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (9 * MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 1), false);
|
||||
|
||||
setStandbyBucket(FREQUENT_INDEX);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertEquals(2 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getRemainingExecutionTimeLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
assertEquals(7 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getTimeUntilQuotaConsumedLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsWithinQuotaLocked_NeverApp() {
|
||||
synchronized (mQuotaController.mLock) {
|
||||
@@ -2026,6 +2162,271 @@ public class QuotaControllerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsWithinQuotaLocked_WithQuotaBump_Duration() {
|
||||
setDischarging();
|
||||
int standbyBucket = WORKING_INDEX;
|
||||
setStandbyBucket(standbyBucket);
|
||||
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
|
||||
5 * MINUTE_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
|
||||
|
||||
long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(
|
||||
now - (HOUR_IN_MILLIS - 2 * MINUTE_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1),
|
||||
false);
|
||||
final ExecutionStats stats;
|
||||
synchronized (mQuotaController.mLock) {
|
||||
stats = mQuotaController.getExecutionStatsLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
|
||||
mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1);
|
||||
assertFalse(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(5 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
|
||||
}
|
||||
mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
|
||||
}
|
||||
|
||||
advanceElapsedClock(HOUR_IN_MILLIS);
|
||||
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
|
||||
}
|
||||
|
||||
// Emulate a quota bump while some jobs are executing
|
||||
JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 1);
|
||||
JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 2);
|
||||
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStartTrackingJobLocked(job1, null);
|
||||
mQuotaController.prepareForExecutionLocked(job1);
|
||||
}
|
||||
|
||||
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||
mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
|
||||
mQuotaController.maybeStartTrackingJobLocked(job2, null);
|
||||
mQuotaController.prepareForExecutionLocked(job2);
|
||||
}
|
||||
|
||||
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
|
||||
mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
|
||||
assertFalse(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
|
||||
}
|
||||
|
||||
// Phase out the first session
|
||||
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
|
||||
}
|
||||
|
||||
// Phase out the first quota bump
|
||||
advanceElapsedClock(7 * HOUR_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsWithinQuotaLocked_WithQuotaBump_JobCount() {
|
||||
setDischarging();
|
||||
int standbyBucket = WORKING_INDEX;
|
||||
setStandbyBucket(standbyBucket);
|
||||
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
|
||||
20 * MINUTE_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 1);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
|
||||
|
||||
long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 10), false);
|
||||
final ExecutionStats stats;
|
||||
synchronized (mQuotaController.mLock) {
|
||||
stats = mQuotaController.getExecutionStatsLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
|
||||
mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 10);
|
||||
assertFalse(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(10, stats.jobCountLimit);
|
||||
}
|
||||
mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(11, stats.jobCountLimit);
|
||||
}
|
||||
|
||||
advanceElapsedClock(HOUR_IN_MILLIS);
|
||||
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(11, stats.jobCountLimit);
|
||||
}
|
||||
|
||||
// Emulate a quota bump while some jobs are executing
|
||||
JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1);
|
||||
JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2);
|
||||
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStartTrackingJobLocked(job1, null);
|
||||
mQuotaController.prepareForExecutionLocked(job1);
|
||||
}
|
||||
|
||||
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||
mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(12, stats.jobCountLimit);
|
||||
mQuotaController.maybeStartTrackingJobLocked(job2, null);
|
||||
mQuotaController.prepareForExecutionLocked(job2);
|
||||
}
|
||||
|
||||
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
|
||||
mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
|
||||
assertFalse(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(12, stats.jobCountLimit);
|
||||
}
|
||||
|
||||
// Phase out the first session
|
||||
advanceElapsedClock(3 * MINUTE_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(12, stats.jobCountLimit);
|
||||
}
|
||||
|
||||
// Phase out the first quota bump
|
||||
advanceElapsedClock(7 * HOUR_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(11, stats.jobCountLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsWithinQuotaLocked_WithQuotaBump_SessionCount() {
|
||||
setDischarging();
|
||||
int standbyBucket = WORKING_INDEX;
|
||||
setStandbyBucket(standbyBucket);
|
||||
setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
|
||||
20 * MINUTE_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 2);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 1);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
|
||||
|
||||
long now = JobSchedulerService.sElapsedRealtimeClock.millis();
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1), false);
|
||||
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
|
||||
createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
|
||||
final ExecutionStats stats;
|
||||
synchronized (mQuotaController.mLock) {
|
||||
stats = mQuotaController.getExecutionStatsLocked(
|
||||
SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
|
||||
assertFalse(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(2, stats.sessionCountLimit);
|
||||
}
|
||||
mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(3, stats.sessionCountLimit);
|
||||
}
|
||||
|
||||
advanceElapsedClock(HOUR_IN_MILLIS);
|
||||
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(3, stats.sessionCountLimit);
|
||||
}
|
||||
|
||||
// Emulate a quota bump while some jobs are executing
|
||||
JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1);
|
||||
JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2);
|
||||
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStartTrackingJobLocked(job1, null);
|
||||
mQuotaController.prepareForExecutionLocked(job1);
|
||||
}
|
||||
|
||||
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||
mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(4, stats.sessionCountLimit);
|
||||
}
|
||||
|
||||
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStartTrackingJobLocked(job2, null);
|
||||
mQuotaController.prepareForExecutionLocked(job2);
|
||||
}
|
||||
|
||||
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
|
||||
assertFalse(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(4, stats.sessionCountLimit);
|
||||
}
|
||||
|
||||
// Phase out the first session
|
||||
advanceElapsedClock(2 * MINUTE_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(4, stats.sessionCountLimit);
|
||||
}
|
||||
|
||||
// Phase out the first quota bump
|
||||
advanceElapsedClock(7 * HOUR_IN_MILLIS);
|
||||
synchronized (mQuotaController.mLock) {
|
||||
assertTrue(mQuotaController
|
||||
.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
|
||||
assertEquals(3, stats.sessionCountLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsWithinEJQuotaLocked_NeverApp() {
|
||||
JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1);
|
||||
@@ -3012,6 +3413,12 @@ public class QuotaControllerTest {
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS,
|
||||
84 * SECOND_IN_MILLIS);
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
|
||||
93 * SECOND_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 92);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 91);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 90 * MINUTE_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 89);
|
||||
|
||||
assertEquals(8 * MINUTE_IN_MILLIS,
|
||||
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
|
||||
@@ -3069,6 +3476,11 @@ public class QuotaControllerTest {
|
||||
assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
|
||||
assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
|
||||
assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
|
||||
assertEquals(93 * SECOND_IN_MILLIS, mQuotaController.getQuotaBumpAdditionDurationMs());
|
||||
assertEquals(92, mQuotaController.getQuotaBumpAdditionJobCount());
|
||||
assertEquals(91, mQuotaController.getQuotaBumpAdditionSessionCount());
|
||||
assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
|
||||
assertEquals(89, mQuotaController.getQuotaBumpLimit());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -3121,6 +3533,11 @@ public class QuotaControllerTest {
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1);
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1);
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, -1);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, -1);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, -1);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 59 * MINUTE_IN_MILLIS);
|
||||
setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, -1);
|
||||
|
||||
assertEquals(MINUTE_IN_MILLIS,
|
||||
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
|
||||
@@ -3171,6 +3588,11 @@ public class QuotaControllerTest {
|
||||
assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs());
|
||||
assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs());
|
||||
assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs());
|
||||
assertEquals(0, mQuotaController.getQuotaBumpAdditionDurationMs());
|
||||
assertEquals(0, mQuotaController.getQuotaBumpAdditionJobCount());
|
||||
assertEquals(0, mQuotaController.getQuotaBumpAdditionSessionCount());
|
||||
assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
|
||||
assertEquals(0, mQuotaController.getQuotaBumpLimit());
|
||||
|
||||
// Invalid configurations.
|
||||
// In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
|
||||
@@ -3228,6 +3650,7 @@ public class QuotaControllerTest {
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS);
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS);
|
||||
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
|
||||
setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS);
|
||||
|
||||
assertEquals(24 * HOUR_IN_MILLIS,
|
||||
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
|
||||
@@ -3268,6 +3691,7 @@ public class QuotaControllerTest {
|
||||
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
|
||||
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
|
||||
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
|
||||
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
|
||||
}
|
||||
|
||||
/** Tests that TimingSessions aren't saved when the device is charging. */
|
||||
|
||||
@@ -179,7 +179,9 @@ public class AppStandbyControllerTests {
|
||||
private AppStandbyController mController;
|
||||
|
||||
private CountDownLatch mStateChangedLatch = new CountDownLatch(1);
|
||||
private CountDownLatch mQuotaBumpLatch = new CountDownLatch(1);
|
||||
private String mLatchPkgName = null;
|
||||
private int mLatchUserId = -1;
|
||||
private AppIdleStateChangeListener mListener = new AppIdleStateChangeListener() {
|
||||
@Override
|
||||
public void onAppIdleStateChanged(String packageName, int userId,
|
||||
@@ -188,6 +190,16 @@ public class AppStandbyControllerTests {
|
||||
if (mLatchPkgName != null && !mLatchPkgName.equals(packageName)) return;
|
||||
mStateChangedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerTemporaryQuotaBump(String packageName, int userId) {
|
||||
// Ignore events not related to mLatchPkgName, if set.
|
||||
if ((mLatchPkgName != null && !mLatchPkgName.equals(packageName))
|
||||
|| (mLatchUserId != -1 && mLatchUserId != userId)) {
|
||||
return;
|
||||
}
|
||||
mQuotaBumpLatch.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
static class MyContextWrapper extends ContextWrapper {
|
||||
@@ -880,20 +892,27 @@ public class AppStandbyControllerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationEvent() throws Exception {
|
||||
public void testNotificationEvent_bucketPromotion() throws Exception {
|
||||
mInjector.mPropertiesChangedListener
|
||||
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
|
||||
|
||||
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
|
||||
mInjector.mElapsedRealtime = 1;
|
||||
rearmQuotaBumpLatch(PACKAGE_1, USER_ID);
|
||||
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
|
||||
|
||||
mController.forceIdleState(PACKAGE_1, USER_ID, true);
|
||||
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
|
||||
assertFalse(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationEvent_changePromotedBucket() throws Exception {
|
||||
public void testNotificationEvent_bucketPromotion_changePromotedBucket() throws Exception {
|
||||
mInjector.mPropertiesChangedListener
|
||||
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
|
||||
mController.forceIdleState(PACKAGE_1, USER_ID, true);
|
||||
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
|
||||
@@ -908,6 +927,28 @@ public class AppStandbyControllerTests {
|
||||
assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotificationEvent_quotaBump() throws Exception {
|
||||
mInjector.mSettingsBuilder
|
||||
.setBoolean("trigger_quota_bump_on_notification_seen", true);
|
||||
mInjector.mSettingsBuilder
|
||||
.setInt("notification_seen_promoted_bucket", STANDBY_BUCKET_NEVER);
|
||||
mInjector.mPropertiesChangedListener
|
||||
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
|
||||
|
||||
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
|
||||
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
|
||||
mInjector.mElapsedRealtime = RARE_THRESHOLD * 2;
|
||||
setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
|
||||
|
||||
rearmQuotaBumpLatch(PACKAGE_1, USER_ID);
|
||||
mInjector.mElapsedRealtime += 1;
|
||||
|
||||
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
|
||||
assertTrue(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
|
||||
assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@FlakyTest(bugId = 185169504)
|
||||
public void testSlicePinnedEvent() throws Exception {
|
||||
@@ -1384,6 +1425,9 @@ public class AppStandbyControllerTests {
|
||||
@Test
|
||||
@FlakyTest(bugId = 185169504)
|
||||
public void testCascadingTimeouts() throws Exception {
|
||||
mInjector.mPropertiesChangedListener
|
||||
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
|
||||
|
||||
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
|
||||
assertBucket(STANDBY_BUCKET_ACTIVE);
|
||||
|
||||
@@ -1408,6 +1452,9 @@ public class AppStandbyControllerTests {
|
||||
@Test
|
||||
@FlakyTest(bugId = 185169504)
|
||||
public void testOverlappingTimeouts() throws Exception {
|
||||
mInjector.mPropertiesChangedListener
|
||||
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
|
||||
|
||||
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
|
||||
assertBucket(STANDBY_BUCKET_ACTIVE);
|
||||
|
||||
@@ -1498,6 +1545,9 @@ public class AppStandbyControllerTests {
|
||||
@Test
|
||||
@FlakyTest(bugId = 185169504)
|
||||
public void testPredictionNotOverridden() throws Exception {
|
||||
mInjector.mPropertiesChangedListener
|
||||
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
|
||||
|
||||
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
|
||||
assertBucket(STANDBY_BUCKET_ACTIVE);
|
||||
|
||||
@@ -2096,7 +2146,7 @@ public class AppStandbyControllerTests {
|
||||
private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception {
|
||||
rearmLatch(pkg);
|
||||
mController.setAppStandbyBucket(pkg, user, bucket, reason);
|
||||
mStateChangedLatch.await(100, TimeUnit.MILLISECONDS);
|
||||
mStateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertEquals("Failed to set package bucket", bucket,
|
||||
getStandbyBucket(mController, PACKAGE_1));
|
||||
}
|
||||
@@ -2109,4 +2159,10 @@ public class AppStandbyControllerTests {
|
||||
private void rearmLatch() {
|
||||
rearmLatch(null);
|
||||
}
|
||||
|
||||
private void rearmQuotaBumpLatch(String pkgName, int userId) {
|
||||
mLatchPkgName = pkgName;
|
||||
mLatchUserId = userId;
|
||||
mQuotaBumpLatch = new CountDownLatch(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user