Merge "Introduce JobScheduler quota bumps in lieu of bucket changes." into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
d0333d3682
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user