Merge "Introduce JobScheduler quota bumps in lieu of bucket changes." into tm-dev

This commit is contained in:
TreeHugger Robot
2022-05-17 11:41:14 +00:00
committed by Android (Google) Code Review
5 changed files with 867 additions and 75 deletions

View File

@@ -59,6 +59,13 @@ public interface AppStandbyInternal {
public void onUserInteractionStarted(String packageName, @UserIdInt int userId) { public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
// No-op by default // 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); void onBootPhase(int phase);

View File

@@ -270,13 +270,12 @@ public final class QuotaController extends StateController {
private final SparseArrayMap<String, Timer> mEJPkgTimers = new SparseArrayMap<>(); private final SparseArrayMap<String, Timer> mEJPkgTimers = new SparseArrayMap<>();
/** List of all regular timing sessions for a package-userId combo, in chronological order. */ /** List of all regular timing sessions for a package-userId combo, in chronological order. */
private final SparseArrayMap<String, List<TimingSession>> mTimingSessions = private final SparseArrayMap<String, List<TimedEvent>> mTimingEvents = new SparseArrayMap<>();
new SparseArrayMap<>();
/** /**
* List of all expedited job timing sessions for a package-userId combo, in chronological order. * 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<>(); 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 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 * List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission
* granted for each user. * granted for each user.
@@ -707,7 +714,7 @@ public final class QuotaController extends StateController {
mTrackedJobs.delete(userId); mTrackedJobs.delete(userId);
mPkgTimers.delete(userId); mPkgTimers.delete(userId);
mEJPkgTimers.delete(userId); mEJPkgTimers.delete(userId);
mTimingSessions.delete(userId); mTimingEvents.delete(userId);
mEJTimingSessions.delete(userId); mEJTimingSessions.delete(userId);
mInQuotaAlarmQueue.removeAlarmsForUserId(userId); mInQuotaAlarmQueue.removeAlarmsForUserId(userId);
mExecutionStatsCache.delete(userId); mExecutionStatsCache.delete(userId);
@@ -738,7 +745,7 @@ public final class QuotaController extends StateController {
timer.dropEverythingLocked(); timer.dropEverythingLocked();
} }
} }
mTimingSessions.delete(userId, packageName); mTimingEvents.delete(userId, packageName);
mEJTimingSessions.delete(userId, packageName); mEJTimingSessions.delete(userId, packageName);
mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
mExecutionStatsCache.delete(userId, packageName); mExecutionStatsCache.delete(userId, packageName);
@@ -928,7 +935,7 @@ public final class QuotaController extends StateController {
(stats.jobRateLimitExpirationTimeElapsed <= now (stats.jobRateLimitExpirationTimeElapsed <= now
|| stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow); || stats.jobCountInRateLimitingWindow < mMaxJobCountPerRateLimitingWindow);
return isUnderAllowedTimeQuota return isUnderAllowedTimeQuota
&& (stats.bgJobCountInWindow < mMaxBucketJobCounts[standbyBucket]); && stats.bgJobCountInWindow < stats.jobCountLimit;
} }
private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats, private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
@@ -937,7 +944,7 @@ public final class QuotaController extends StateController {
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow); || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
return isUnderAllowedTimeQuota return isUnderAllowedTimeQuota
&& stats.sessionCountInWindow < mMaxBucketSessionCounts[standbyBucket]; && stats.sessionCountInWindow < stats.sessionCountLimit;
} }
@VisibleForTesting @VisibleForTesting
@@ -984,12 +991,12 @@ public final class QuotaController extends StateController {
long remainingMs = limitMs - quota.getTallyLocked(); long remainingMs = limitMs - quota.getTallyLocked();
// Stale sessions may still be factored into tally. Make sure they're removed. // 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 nowElapsed = sElapsedRealtimeClock.millis();
final long windowStartTimeElapsed = nowElapsed - mEJLimitWindowSizeMs; final long windowStartTimeElapsed = nowElapsed - mEJLimitWindowSizeMs;
if (timingSessions != null) { if (timingSessions != null) {
while (timingSessions.size() > 0) { while (timingSessions.size() > 0) {
TimingSession ts = timingSessions.get(0); TimingSession ts = (TimingSession) timingSessions.get(0);
if (ts.endTimeElapsed < windowStartTimeElapsed) { if (ts.endTimeElapsed < windowStartTimeElapsed) {
final long duration = ts.endTimeElapsed - ts.startTimeElapsed; final long duration = ts.endTimeElapsed - ts.startTimeElapsed;
remainingMs += duration; remainingMs += duration;
@@ -1042,9 +1049,9 @@ public final class QuotaController extends StateController {
return 0; return 0;
} }
List<TimingSession> sessions = mTimingSessions.get(userId, packageName); List<TimedEvent> events = mTimingEvents.get(userId, packageName);
final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); 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 // Regular ACTIVE case. Since the bucket size equals the allowed time, the app jobs can
// essentially run until they reach the maximum limit. // essentially run until they reach the maximum limit.
if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
@@ -1064,7 +1071,7 @@ public final class QuotaController extends StateController {
// essentially run until they reach the maximum limit. // essentially run until they reach the maximum limit.
if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) {
return calculateTimeUntilQuotaConsumedLocked( 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. // 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. // bucket value.
return Math.min( return Math.min(
calculateTimeUntilQuotaConsumedLocked( calculateTimeUntilQuotaConsumedLocked(
sessions, startMaxElapsed, maxExecutionTimeRemainingMs), events, startMaxElapsed, maxExecutionTimeRemainingMs, false),
calculateTimeUntilQuotaConsumedLocked( 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 windowStartElapsed The start of the window, in the elapsed realtime timebase.
* @param deadSpaceMs How much time can be allowed to count towards the quota * @param deadSpaceMs How much time can be allowed to count towards the quota
*/ */
private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimingSession> sessions, private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimedEvent> sessions,
final long windowStartElapsed, long deadSpaceMs) { final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps) {
long timeUntilQuotaConsumedMs = 0; long timeUntilQuotaConsumedMs = 0;
long start = windowStartElapsed; long start = windowStartElapsed;
for (int i = 0; i < sessions.size(); ++i) { int numQuotaBumps = 0;
TimingSession session = sessions.get(i); 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) { if (session.endTimeElapsed < windowStartElapsed) {
// Outside of window. Ignore. // Outside of window. Ignore.
@@ -1129,7 +1160,7 @@ public final class QuotaController extends StateController {
final long remainingExecutionTimeMs = final long remainingExecutionTimeMs =
getRemainingEJExecutionTimeLocked(userId, packageName); getRemainingEJExecutionTimeLocked(userId, packageName);
List<TimingSession> sessions = mEJTimingSessions.get(userId, packageName); List<TimedEvent> sessions = mEJTimingSessions.get(userId, packageName);
if (sessions == null || sessions.size() == 0) { if (sessions == null || sessions.size() == 0) {
return remainingExecutionTimeMs; return remainingExecutionTimeMs;
} }
@@ -1146,7 +1177,7 @@ public final class QuotaController extends StateController {
long phasedOutSessionTimeMs = 0; long phasedOutSessionTimeMs = 0;
for (int i = 0; i < sessions.size(); ++i) { for (int i = 0; i < sessions.size(); ++i) {
TimingSession session = sessions.get(i); TimingSession session = (TimingSession) sessions.get(i);
if (session.endTimeElapsed < startWindowElapsed) { if (session.endTimeElapsed < startWindowElapsed) {
// Edge case where a session became stale in the time between the call to // Edge case where a session became stale in the time between the call to
// getRemainingEJExecutionTimeLocked and this line. // getRemainingEJExecutionTimeLocked and this line.
@@ -1159,7 +1190,7 @@ public final class QuotaController extends StateController {
} else { } else {
// Session fully inside window // Session fully inside window
final long timeBetweenSessions = session.startTimeElapsed 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); final long usedDeadSpaceMs = Math.min(remainingDeadSpaceMs, timeBetweenSessions);
deadSpaceMs += usedDeadSpaceMs; deadSpaceMs += usedDeadSpaceMs;
if (usedDeadSpaceMs == timeBetweenSessions) { if (usedDeadSpaceMs == timeBetweenSessions) {
@@ -1267,22 +1298,49 @@ public final class QuotaController extends StateController {
} }
} }
List<TimingSession> sessions = mTimingSessions.get(userId, packageName); List<TimedEvent> events = mTimingEvents.get(userId, packageName);
if (sessions == null || sessions.size() == 0) { if (events == null || events.size() == 0) {
return; return;
} }
final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
int sessionCountInWindow = 0; 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. // looked at --> how much time the stats will be valid for.
long emptyTimeMs = Long.MAX_VALUE; long emptyTimeMs = Long.MAX_VALUE;
// Sessions are non-overlapping and in order of occurrence, so iterating backwards will get // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
// the most recent ones. // 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) { 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. // Window management.
if (startWindowElapsed < session.endTimeElapsed) { if (startWindowElapsed < session.endTimeElapsed) {
@@ -1309,10 +1367,11 @@ public final class QuotaController extends StateController {
final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs; final long inQuotaTime = session.endTimeElapsed + stats.windowSizeMs;
stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, inQuotaTime); 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++; sessionCountInWindow++;
if (sessionCountInWindow >= stats.sessionCountLimit) { 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. // This session ended before the window. No point in going any further.
break; break;
} }
lastSeenTimingSession = session;
} }
stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
stats.sessionCountInWindow = sessionCountInWindow; stats.sessionCountInWindow = sessionCountInWindow;
@@ -1437,9 +1498,9 @@ public final class QuotaController extends StateController {
private void saveTimingSession(final int userId, @NonNull final String packageName, private void saveTimingSession(final int userId, @NonNull final String packageName,
@NonNull final TimingSession session, boolean isExpedited, long debitAdjustment) { @NonNull final TimingSession session, boolean isExpedited, long debitAdjustment) {
synchronized (mLock) { synchronized (mLock) {
final SparseArrayMap<String, List<TimingSession>> sessionMap = final SparseArrayMap<String, List<TimedEvent>> sessionMap =
isExpedited ? mEJTimingSessions : mTimingSessions; isExpedited ? mEJTimingSessions : mTimingEvents;
List<TimingSession> sessions = sessionMap.get(userId, packageName); List<TimedEvent> sessions = sessionMap.get(userId, packageName);
if (sessions == null) { if (sessions == null) {
sessions = new ArrayList<>(); sessions = new ArrayList<>();
sessionMap.add(userId, packageName, sessions); sessionMap.add(userId, packageName, sessions);
@@ -1460,6 +1521,9 @@ public final class QuotaController extends StateController {
private void grantRewardForInstantEvent( private void grantRewardForInstantEvent(
final int userId, @NonNull final String packageName, final long credit) { final int userId, @NonNull final String packageName, final long credit) {
if (credit == 0) {
return;
}
synchronized (mLock) { synchronized (mLock) {
final long nowElapsed = sElapsedRealtimeClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis();
final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName); final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
@@ -1489,13 +1553,14 @@ public final class QuotaController extends StateController {
return changed; return changed;
} }
private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> { private final class EarliestEndTimeFunctor implements Consumer<List<TimedEvent>> {
public long earliestEndElapsed = Long.MAX_VALUE; public long earliestEndElapsed = Long.MAX_VALUE;
@Override @Override
public void accept(List<TimingSession> sessions) { public void accept(List<TimedEvent> events) {
if (sessions != null && sessions.size() > 0) { if (events != null && events.size() > 0) {
earliestEndElapsed = Math.min(earliestEndElapsed, sessions.get(0).endTimeElapsed); earliestEndElapsed =
Math.min(earliestEndElapsed, events.get(0).getEndTimeElapsed());
} }
} }
@@ -1521,7 +1586,7 @@ public final class QuotaController extends StateController {
return; return;
} }
mEarliestEndTimeFunctor.reset(); mEarliestEndTimeFunctor.reset();
mTimingSessions.forEach(mEarliestEndTimeFunctor); mTimingEvents.forEach(mEarliestEndTimeFunctor);
mEJTimingSessions.forEach(mEarliestEndTimeFunctor); mEJTimingSessions.forEach(mEarliestEndTimeFunctor);
final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed; final long earliestEndElapsed = mEarliestEndTimeFunctor.earliestEndElapsed;
if (earliestEndElapsed == Long.MAX_VALUE) { if (earliestEndElapsed == Long.MAX_VALUE) {
@@ -1809,10 +1874,10 @@ public final class QuotaController extends StateController {
inEJQuotaTimeElapsed = (nowElapsed - limitMs) + mEJLimitWindowSizeMs; inEJQuotaTimeElapsed = (nowElapsed - limitMs) + mEJLimitWindowSizeMs;
} }
} }
List<TimingSession> timingSessions = mEJTimingSessions.get(userId, packageName); List<TimedEvent> timingSessions = mEJTimingSessions.get(userId, packageName);
if (timingSessions != null) { if (timingSessions != null) {
for (int i = timingSessions.size() - 1; i >= 0; --i) { 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; final long durationMs = ts.endTimeElapsed - ts.startTimeElapsed;
sumMs += durationMs; sumMs += durationMs;
if (sumMs >= limitMs) { if (sumMs >= limitMs) {
@@ -1881,7 +1946,14 @@ public final class QuotaController extends StateController {
} }
@VisibleForTesting @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. // Start timestamp in elapsed realtime timebase.
public final long startTimeElapsed; public final long startTimeElapsed;
// End timestamp in elapsed realtime timebase. // End timestamp in elapsed realtime timebase.
@@ -1903,6 +1975,11 @@ public final class QuotaController extends StateController {
mHashCode = hashCode; mHashCode = hashCode;
} }
@Override
public long getEndTimeElapsed() {
return endTimeElapsed;
}
@Override @Override
public String toString() { public String toString() {
return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount return "TimingSession{" + startTimeElapsed + "->" + endTimeElapsed + ", " + bgJobCount
@@ -1926,6 +2003,7 @@ public final class QuotaController extends StateController {
return mHashCode; return mHashCode;
} }
@Override
public void dump(IndentingPrintWriter pw) { public void dump(IndentingPrintWriter pw) {
pw.print(startTimeElapsed); pw.print(startTimeElapsed);
pw.print(" -> "); 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 @VisibleForTesting
static final class ShrinkableDebits { static final class ShrinkableDebits {
/** The amount of quota remaining. Can be negative if limit changes. */ /** 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); 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 @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 long mNowElapsed;
private void updateNow() { private void updateNow() {
@@ -2484,27 +2599,26 @@ public final class QuotaController extends StateController {
} }
@Override @Override
public boolean test(TimingSession ts) { public boolean test(TimedEvent ts) {
return ts.endTimeElapsed <= mNowElapsed - MAX_PERIOD_MS; return ts.getEndTimeElapsed() <= mNowElapsed - MAX_PERIOD_MS;
} }
} }
private final TimingSessionTooOldPredicate mTimingSessionTooOld = private final TimedEventTooOldPredicate mTimedEventTooOld = new TimedEventTooOldPredicate();
new TimingSessionTooOldPredicate();
private final Consumer<List<TimingSession>> mDeleteOldSessionsFunctor = sessions -> { private final Consumer<List<TimedEvent>> mDeleteOldEventsFunctor = events -> {
if (sessions != null) { if (events != null) {
// Remove everything older than MAX_PERIOD_MS time ago. // Remove everything older than MAX_PERIOD_MS time ago.
sessions.removeIf(mTimingSessionTooOld); events.removeIf(mTimedEventTooOld);
} }
}; };
@VisibleForTesting @VisibleForTesting
void deleteObsoleteSessionsLocked() { void deleteObsoleteSessionsLocked() {
mTimingSessionTooOld.updateNow(); mTimedEventTooOld.updateNow();
// Regular sessions // Regular sessions
mTimingSessions.forEach(mDeleteOldSessionsFunctor); mTimingEvents.forEach(mDeleteOldEventsFunctor);
// EJ sessions // EJ sessions
for (int uIdx = 0; uIdx < mEJTimingSessions.numMaps(); ++uIdx) { 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) { for (int pIdx = 0; pIdx < mEJTimingSessions.numElementsForKey(userId); ++pIdx) {
final String packageName = mEJTimingSessions.keyAt(uIdx, pIdx); final String packageName = mEJTimingSessions.keyAt(uIdx, pIdx);
final ShrinkableDebits debits = getEJDebitsLocked(userId, packageName); 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) { if (sessions == null) {
continue; continue;
} }
while (sessions.size() > 0) { while (sessions.size() > 0) {
final TimingSession ts = sessions.get(0); final TimingSession ts = (TimingSession) sessions.get(0);
if (mTimingSessionTooOld.test(ts)) { if (mTimedEventTooOld.test(ts)) {
// Stale sessions may still be factored into tally. Remove them. // Stale sessions may still be factored into tally. Remove them.
final long duration = ts.endTimeElapsed - ts.startTimeElapsed; final long duration = ts.endTimeElapsed - ts.startTimeElapsed;
debits.transactLocked(-duration); debits.transactLocked(-duration);
@@ -2801,6 +2915,7 @@ public final class QuotaController extends StateController {
mQcConstants.mRateLimitingConstantsUpdated = false; mQcConstants.mRateLimitingConstantsUpdated = false;
mQcConstants.mExecutionPeriodConstantsUpdated = false; mQcConstants.mExecutionPeriodConstantsUpdated = false;
mQcConstants.mEJLimitConstantsUpdated = false; mQcConstants.mEJLimitConstantsUpdated = false;
mQcConstants.mQuotaBumpConstantsUpdated = false;
} }
@Override @Override
@@ -2828,6 +2943,7 @@ public final class QuotaController extends StateController {
private boolean mRateLimitingConstantsUpdated = false; private boolean mRateLimitingConstantsUpdated = false;
private boolean mExecutionPeriodConstantsUpdated = false; private boolean mExecutionPeriodConstantsUpdated = false;
private boolean mEJLimitConstantsUpdated = false; private boolean mEJLimitConstantsUpdated = false;
private boolean mQuotaBumpConstantsUpdated = false;
/** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
private static final String QC_CONSTANT_PREFIX = "qc_"; private static final String QC_CONSTANT_PREFIX = "qc_";
@@ -2975,6 +3091,21 @@ public final class QuotaController extends StateController {
@VisibleForTesting @VisibleForTesting
static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS =
QC_CONSTANT_PREFIX + "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 = private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS =
10 * 60 * 1000L; // 10 minutes 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_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_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_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 * 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; 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, public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) { @NonNull String key) {
switch (key) { switch (key) {
@@ -3383,6 +3546,14 @@ public final class QuotaController extends StateController {
updateEJLimitConstantsLocked(); updateEJLimitConstantsLocked();
break; 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: case KEY_MAX_JOB_COUNT_EXEMPTED:
MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED); MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED);
int newExemptedMaxJobCount = 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) { private void dump(IndentingPrintWriter pw) {
pw.println(); pw.println();
pw.println("QuotaController:"); pw.println("QuotaController:");
@@ -3943,6 +4173,15 @@ public final class QuotaController extends StateController {
EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS).println(); 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_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(); pw.decreaseIndent();
} }
@@ -4103,7 +4342,7 @@ public final class QuotaController extends StateController {
@VisibleForTesting @VisibleForTesting
@Nullable @Nullable
List<TimingSession> getEJTimingSessions(int userId, String packageName) { List<TimedEvent> getEJTimingSessions(int userId, String packageName) {
return mEJTimingSessions.get(userId, packageName); return mEJTimingSessions.get(userId, packageName);
} }
@@ -4150,8 +4389,8 @@ public final class QuotaController extends StateController {
@VisibleForTesting @VisibleForTesting
@Nullable @Nullable
List<TimingSession> getTimingSessions(int userId, String packageName) { List<TimedEvent> getTimingSessions(int userId, String packageName) {
return mTimingSessions.get(userId, packageName); return mTimingEvents.get(userId, packageName);
} }
@VisibleForTesting @VisibleForTesting
@@ -4160,6 +4399,31 @@ public final class QuotaController extends StateController {
return mQcConstants; 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 ////////////////////////////// //////////////////////////// DATA DUMP //////////////////////////////
@NeverCompile // Avoid size overhead of debugging code. @NeverCompile // Avoid size overhead of debugging code.
@@ -4250,14 +4514,14 @@ public final class QuotaController extends StateController {
final String pkgName = mPkgTimers.keyAt(u, p); final String pkgName = mPkgTimers.keyAt(u, p);
mPkgTimers.valueAt(u, p).dump(pw, predicate); mPkgTimers.valueAt(u, p).dump(pw, predicate);
pw.println(); pw.println();
List<TimingSession> sessions = mTimingSessions.get(userId, pkgName); List<TimedEvent> events = mTimingEvents.get(userId, pkgName);
if (sessions != null) { if (events != null) {
pw.increaseIndent(); pw.increaseIndent();
pw.println("Saved sessions:"); pw.println("Saved events:");
pw.increaseIndent(); pw.increaseIndent();
for (int j = sessions.size() - 1; j >= 0; j--) { for (int j = events.size() - 1; j >= 0; j--) {
TimingSession session = sessions.get(j); TimedEvent event = events.get(j);
session.dump(pw); event.dump(pw);
} }
pw.decreaseIndent(); pw.decreaseIndent();
pw.decreaseIndent(); pw.decreaseIndent();
@@ -4273,13 +4537,13 @@ public final class QuotaController extends StateController {
final String pkgName = mEJPkgTimers.keyAt(u, p); final String pkgName = mEJPkgTimers.keyAt(u, p);
mEJPkgTimers.valueAt(u, p).dump(pw, predicate); mEJPkgTimers.valueAt(u, p).dump(pw, predicate);
pw.println(); pw.println();
List<TimingSession> sessions = mEJTimingSessions.get(userId, pkgName); List<TimedEvent> sessions = mEJTimingSessions.get(userId, pkgName);
if (sessions != null) { if (sessions != null) {
pw.increaseIndent(); pw.increaseIndent();
pw.println("Saved sessions:"); pw.println("Saved sessions:");
pw.increaseIndent(); pw.increaseIndent();
for (int j = sessions.size() - 1; j >= 0; j--) { for (int j = sessions.size() - 1; j >= 0; j--) {
TimingSession session = sessions.get(j); TimedEvent session = sessions.get(j);
session.dump(pw); session.dump(pw);
} }
pw.decreaseIndent(); pw.decreaseIndent();
@@ -4398,10 +4662,14 @@ public final class QuotaController extends StateController {
predicate); predicate);
} }
List<TimingSession> sessions = mTimingSessions.get(userId, pkgName); List<TimedEvent> events = mTimingEvents.get(userId, pkgName);
if (sessions != null) { if (events != null) {
for (int j = sessions.size() - 1; j >= 0; j--) { for (int j = events.size() - 1; j >= 0; j--) {
TimingSession session = sessions.get(j); TimedEvent event = events.get(j);
if (!(event instanceof TimingSession)) {
continue;
}
TimingSession session = (TimingSession) event;
session.dump(proto, session.dump(proto,
StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS); StateControllerProto.QuotaController.PackageStats.SAVED_SESSIONS);
} }

View File

@@ -289,6 +289,7 @@ public class AppStandbyController
static final int MSG_INFORM_LISTENERS = 3; static final int MSG_INFORM_LISTENERS = 3;
static final int MSG_FORCE_IDLE_STATE = 4; static final int MSG_FORCE_IDLE_STATE = 4;
static final int MSG_CHECK_IDLE_STATES = 5; 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_REPORT_CONTENT_PROVIDER_USAGE = 8;
static final int MSG_PAROLE_STATE_CHANGED = 9; static final int MSG_PAROLE_STATE_CHANGED = 9;
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10; 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 */ /** The standby bucket that an app will be promoted on a notification-seen event */
int mNotificationSeenPromotedBucket = int mNotificationSeenPromotedBucket =
ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET; 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. */ /** Minimum time a system update event should keep the buckets elevated. */
long mSystemUpdateUsageTimeoutMillis = ConstantsObserver.DEFAULT_SYSTEM_UPDATE_TIMEOUT; long mSystemUpdateUsageTimeoutMillis = ConstantsObserver.DEFAULT_SYSTEM_UPDATE_TIMEOUT;
/** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */ /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
@@ -767,6 +774,17 @@ public class AppStandbyController
appUsage.bucketingReason, false); 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 @VisibleForTesting
void setChargingState(boolean isCharging) { void setChargingState(boolean isCharging) {
if (mIsCharging != isCharging) { if (mIsCharging != isCharging) {
@@ -1080,6 +1098,10 @@ public class AppStandbyController
final int subReason = usageEventToSubReason(eventType); final int subReason = usageEventToSubReason(eventType);
final int reason = REASON_MAIN_USAGE | subReason; final int reason = REASON_MAIN_USAGE | subReason;
if (eventType == UsageEvents.Event.NOTIFICATION_SEEN) { 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 // Notification-seen elevates to a higher bucket (depending on
// {@link ConstantsObserver#KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET}) but doesn't // {@link ConstantsObserver#KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET}) but doesn't
// change usage time. // change usage time.
@@ -2201,6 +2223,9 @@ public class AppStandbyController
pw.print(" mNotificationSeenPromotedBucket="); pw.print(" mNotificationSeenPromotedBucket=");
pw.print(standbyBucketToString(mNotificationSeenPromotedBucket)); pw.print(standbyBucketToString(mNotificationSeenPromotedBucket));
pw.println(); pw.println();
pw.print(" mTriggerQuotaBumpOnNotificationSeen=");
pw.print(mTriggerQuotaBumpOnNotificationSeen);
pw.println();
pw.print(" mSlicePinnedTimeoutMillis="); pw.print(" mSlicePinnedTimeoutMillis=");
TimeUtils.formatDuration(mSlicePinnedTimeoutMillis, pw); TimeUtils.formatDuration(mSlicePinnedTimeoutMillis, pw);
pw.println(); pw.println();
@@ -2597,6 +2622,10 @@ public class AppStandbyController
checkIdleStates(UserHandle.USER_ALL); checkIdleStates(UserHandle.USER_ALL);
break; break;
case MSG_TRIGGER_LISTENER_QUOTA_BUMP:
triggerListenerQuotaBump((String) msg.obj, msg.arg1);
break;
case MSG_REPORT_CONTENT_PROVIDER_USAGE: case MSG_REPORT_CONTENT_PROVIDER_USAGE:
ContentProviderUsageRecord record = (ContentProviderUsageRecord) msg.obj; ContentProviderUsageRecord record = (ContentProviderUsageRecord) msg.obj;
reportContentProviderUsage(record.name, record.packageName, record.userId); reportContentProviderUsage(record.name, record.packageName, record.userId);
@@ -2683,6 +2712,8 @@ public class AppStandbyController
"notification_seen_duration"; "notification_seen_duration";
private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET = private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET =
"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 = private static final String KEY_SLICE_PINNED_HOLD_DURATION =
"slice_pinned_duration"; "slice_pinned_duration";
private static final String KEY_SYSTEM_UPDATE_HOLD_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; COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET = public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET =
STANDBY_BUCKET_WORKING_SET; STANDBY_BUCKET_WORKING_SET;
public static final boolean DEFAULT_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN = false;
public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT =
COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR; COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR;
public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT = public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT =
@@ -2842,6 +2874,11 @@ public class AppStandbyController
KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET, KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET,
DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET); DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET);
break; 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: case KEY_SLICE_PINNED_HOLD_DURATION:
mSlicePinnedTimeoutMillis = properties.getLong( mSlicePinnedTimeoutMillis = properties.getLong(
KEY_SLICE_PINNED_HOLD_DURATION, KEY_SLICE_PINNED_HOLD_DURATION,

View File

@@ -90,7 +90,9 @@ import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore; import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats; import com.android.server.job.controllers.QuotaController.ExecutionStats;
import com.android.server.job.controllers.QuotaController.QcConstants; 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.ShrinkableDebits;
import com.android.server.job.controllers.QuotaController.TimedEvent;
import com.android.server.job.controllers.QuotaController.TimingSession; import com.android.server.job.controllers.QuotaController.TimingSession;
import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal;
@@ -128,6 +130,7 @@ public class QuotaControllerTest {
private QuotaController.QcConstants mQcConstants; private QuotaController.QcConstants mQcConstants;
private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
private int mSourceUid; private int mSourceUid;
private AppStandbyInternal.AppIdleStateChangeListener mAppIdleStateChangeListener;
private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener; private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
private IUidObserver mUidObserver; private IUidObserver mUidObserver;
private UsageStatsManagerInternal.UsageEventListener mUsageEventListener; private UsageStatsManagerInternal.UsageEventListener mUsageEventListener;
@@ -178,7 +181,8 @@ public class QuotaControllerTest {
when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
doReturn(mActivityMangerInternal) doReturn(mActivityMangerInternal)
.when(() -> LocalServices.getService(ActivityManagerInternal.class)); .when(() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mock(AppStandbyInternal.class)) final AppStandbyInternal appStandbyInternal = mock(AppStandbyInternal.class);
doReturn(appStandbyInternal)
.when(() -> LocalServices.getService(AppStandbyInternal.class)); .when(() -> LocalServices.getService(AppStandbyInternal.class));
doReturn(mock(BatteryManagerInternal.class)) doReturn(mock(BatteryManagerInternal.class))
.when(() -> LocalServices.getService(BatteryManagerInternal.class)); .when(() -> LocalServices.getService(BatteryManagerInternal.class));
@@ -222,6 +226,8 @@ public class QuotaControllerTest {
// Initialize real objects. // Initialize real objects.
// Capture the listeners. // Capture the listeners.
ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> aiscListenerCaptor =
ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
ArgumentCaptor<IUidObserver> uidObserverCaptor = ArgumentCaptor<IUidObserver> uidObserverCaptor =
ArgumentCaptor.forClass(IUidObserver.class); ArgumentCaptor.forClass(IUidObserver.class);
ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor = ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
@@ -231,6 +237,8 @@ public class QuotaControllerTest {
mQuotaController = new QuotaController(mJobSchedulerService, mQuotaController = new QuotaController(mJobSchedulerService,
mock(BackgroundJobsController.class), mock(ConnectivityController.class)); mock(BackgroundJobsController.class), mock(ConnectivityController.class));
verify(appStandbyInternal).addListener(aiscListenerCaptor.capture());
mAppIdleStateChangeListener = aiscListenerCaptor.getValue();
verify(mPowerAllowlistInternal) verify(mPowerAllowlistInternal)
.registerTempAllowlistChangeListener(taChangeCaptor.capture()); .registerTempAllowlistChangeListener(taChangeCaptor.capture());
mTempAllowlistListener = taChangeCaptor.getValue(); mTempAllowlistListener = taChangeCaptor.getValue();
@@ -457,26 +465,31 @@ public class QuotaControllerTest {
now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
TimingSession two = createTimingSession( TimingSession two = createTimingSession(
now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
QuotaBump bump1 = new QuotaBump(now - 2 * HOUR_IN_MILLIS);
TimingSession thr = createTimingSession( TimingSession thr = createTimingSession(
now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
// Overlaps 24 hour boundary. // Overlaps 24 hour boundary.
TimingSession fou = createTimingSession( TimingSession fou = createTimingSession(
now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1); now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
// Way past the 24 hour boundary. // Way past the 24 hour boundary.
QuotaBump bump2 = new QuotaBump(now - 24 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
TimingSession fiv = createTimingSession( TimingSession fiv = createTimingSession(
now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4); now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
List<TimingSession> expectedRegular = new ArrayList<>(); List<TimedEvent> expectedRegular = new ArrayList<>();
List<TimingSession> expectedEJ = new ArrayList<>(); List<TimedEvent> expectedEJ = new ArrayList<>();
// Added in correct (chronological) order. // Added in correct (chronological) order.
expectedRegular.add(fou); expectedRegular.add(fou);
expectedRegular.add(thr); expectedRegular.add(thr);
expectedRegular.add(bump1);
expectedRegular.add(two); expectedRegular.add(two);
expectedRegular.add(one); expectedRegular.add(one);
expectedEJ.add(fou); expectedEJ.add(fou);
expectedEJ.add(one); expectedEJ.add(one);
mQuotaController.saveTimingSession(0, "com.android.test", fiv, false); 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", fou, false);
mQuotaController.saveTimingSession(0, "com.android.test", thr, 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", two, false);
mQuotaController.saveTimingSession(0, "com.android.test", one, false); mQuotaController.saveTimingSession(0, "com.android.test", one, false);
mQuotaController.saveTimingSession(0, "com.android.test", fiv, true); 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 @Test
public void testIsWithinQuotaLocked_NeverApp() { public void testIsWithinQuotaLocked_NeverApp() {
synchronized (mQuotaController.mLock) { 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 @Test
public void testIsWithinEJQuotaLocked_NeverApp() { public void testIsWithinEJQuotaLocked_NeverApp() {
JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1); JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1);
@@ -3012,6 +3413,12 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS,
84 * SECOND_IN_MILLIS); 84 * SECOND_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * 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, assertEquals(8 * MINUTE_IN_MILLIS,
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
@@ -3069,6 +3476,11 @@ public class QuotaControllerTest {
assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); 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 @Test
@@ -3121,6 +3533,11 @@ public class QuotaControllerTest {
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1); 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_TEMP_ALLOWLIST_MS, -1);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_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, assertEquals(MINUTE_IN_MILLIS,
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
@@ -3171,6 +3588,11 @@ public class QuotaControllerTest {
assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs());
assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs());
assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs()); 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. // Invalid configurations.
// In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD // 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_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_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS);
setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_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, assertEquals(24 * HOUR_IN_MILLIS,
mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
@@ -3268,6 +3691,7 @@ public class QuotaControllerTest {
assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
} }
/** Tests that TimingSessions aren't saved when the device is charging. */ /** Tests that TimingSessions aren't saved when the device is charging. */

View File

@@ -179,7 +179,9 @@ public class AppStandbyControllerTests {
private AppStandbyController mController; private AppStandbyController mController;
private CountDownLatch mStateChangedLatch = new CountDownLatch(1); private CountDownLatch mStateChangedLatch = new CountDownLatch(1);
private CountDownLatch mQuotaBumpLatch = new CountDownLatch(1);
private String mLatchPkgName = null; private String mLatchPkgName = null;
private int mLatchUserId = -1;
private AppIdleStateChangeListener mListener = new AppIdleStateChangeListener() { private AppIdleStateChangeListener mListener = new AppIdleStateChangeListener() {
@Override @Override
public void onAppIdleStateChanged(String packageName, int userId, public void onAppIdleStateChanged(String packageName, int userId,
@@ -188,6 +190,16 @@ public class AppStandbyControllerTests {
if (mLatchPkgName != null && !mLatchPkgName.equals(packageName)) return; if (mLatchPkgName != null && !mLatchPkgName.equals(packageName)) return;
mStateChangedLatch.countDown(); 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 { static class MyContextWrapper extends ContextWrapper {
@@ -880,20 +892,27 @@ public class AppStandbyControllerTests {
} }
@Test @Test
public void testNotificationEvent() throws Exception { public void testNotificationEvent_bucketPromotion() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
mInjector.mElapsedRealtime = 1; mInjector.mElapsedRealtime = 1;
rearmQuotaBumpLatch(PACKAGE_1, USER_ID);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
mController.forceIdleState(PACKAGE_1, USER_ID, true); mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1)); assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
assertFalse(mQuotaBumpLatch.await(1, TimeUnit.SECONDS));
} }
@Test @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); mController.forceIdleState(PACKAGE_1, USER_ID, true);
reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1); reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime, PACKAGE_1);
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, 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)); 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 @Test
@FlakyTest(bugId = 185169504) @FlakyTest(bugId = 185169504)
public void testSlicePinnedEvent() throws Exception { public void testSlicePinnedEvent() throws Exception {
@@ -1384,6 +1425,9 @@ public class AppStandbyControllerTests {
@Test @Test
@FlakyTest(bugId = 185169504) @FlakyTest(bugId = 185169504)
public void testCascadingTimeouts() throws Exception { public void testCascadingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
assertBucket(STANDBY_BUCKET_ACTIVE); assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1408,6 +1452,9 @@ public class AppStandbyControllerTests {
@Test @Test
@FlakyTest(bugId = 185169504) @FlakyTest(bugId = 185169504)
public void testOverlappingTimeouts() throws Exception { public void testOverlappingTimeouts() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
assertBucket(STANDBY_BUCKET_ACTIVE); assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1498,6 +1545,9 @@ public class AppStandbyControllerTests {
@Test @Test
@FlakyTest(bugId = 185169504) @FlakyTest(bugId = 185169504)
public void testPredictionNotOverridden() throws Exception { public void testPredictionNotOverridden() throws Exception {
mInjector.mPropertiesChangedListener
.onPropertiesChanged(mInjector.getDeviceConfigProperties());
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
assertBucket(STANDBY_BUCKET_ACTIVE); assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -2096,7 +2146,7 @@ public class AppStandbyControllerTests {
private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception { private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception {
rearmLatch(pkg); rearmLatch(pkg);
mController.setAppStandbyBucket(pkg, user, bucket, reason); mController.setAppStandbyBucket(pkg, user, bucket, reason);
mStateChangedLatch.await(100, TimeUnit.MILLISECONDS); mStateChangedLatch.await(1, TimeUnit.SECONDS);
assertEquals("Failed to set package bucket", bucket, assertEquals("Failed to set package bucket", bucket,
getStandbyBucket(mController, PACKAGE_1)); getStandbyBucket(mController, PACKAGE_1));
} }
@@ -2109,4 +2159,10 @@ public class AppStandbyControllerTests {
private void rearmLatch() { private void rearmLatch() {
rearmLatch(null); rearmLatch(null);
} }
private void rearmQuotaBumpLatch(String pkgName, int userId) {
mLatchPkgName = pkgName;
mLatchUserId = userId;
mQuotaBumpLatch = new CountDownLatch(1);
}
} }