Merge "Make sure EJ rewarding quota works." into sc-dev
This commit is contained in:
@@ -828,8 +828,8 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private ShrinkableDebits getEJQuotaLocked(final int userId,
|
@VisibleForTesting
|
||||||
@NonNull final String packageName) {
|
ShrinkableDebits getEJDebitsLocked(final int userId, @NonNull final String packageName) {
|
||||||
ShrinkableDebits debits = mEJStats.get(userId, packageName);
|
ShrinkableDebits debits = mEJStats.get(userId, packageName);
|
||||||
if (debits == null) {
|
if (debits == null) {
|
||||||
debits = new ShrinkableDebits(
|
debits = new ShrinkableDebits(
|
||||||
@@ -931,7 +931,7 @@ public final class QuotaController extends StateController {
|
|||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
long getRemainingEJExecutionTimeLocked(final int userId, @NonNull final String packageName) {
|
long getRemainingEJExecutionTimeLocked(final int userId, @NonNull final String packageName) {
|
||||||
ShrinkableDebits quota = getEJQuotaLocked(userId, packageName);
|
ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
|
||||||
if (quota.getStandbyBucketLocked() == NEVER_INDEX) {
|
if (quota.getStandbyBucketLocked() == NEVER_INDEX) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -948,7 +948,7 @@ public final class QuotaController extends StateController {
|
|||||||
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;
|
||||||
quota.transactOnDebitsLocked(-duration);
|
quota.transactLocked(-duration);
|
||||||
timingSessions.remove(0);
|
timingSessions.remove(0);
|
||||||
} else if (ts.startTimeElapsed < windowStartTimeElapsed) {
|
} else if (ts.startTimeElapsed < windowStartTimeElapsed) {
|
||||||
remainingMs += windowStartTimeElapsed - ts.startTimeElapsed;
|
remainingMs += windowStartTimeElapsed - ts.startTimeElapsed;
|
||||||
@@ -960,15 +960,16 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TopAppTimer topAppTimer = mTopAppTrackers.get(userId, packageName);
|
||||||
|
if (topAppTimer != null && topAppTimer.isActive()) {
|
||||||
|
remainingMs += topAppTimer.getPendingReward(nowElapsed);
|
||||||
|
}
|
||||||
|
|
||||||
Timer timer = mEJPkgTimers.get(userId, packageName);
|
Timer timer = mEJPkgTimers.get(userId, packageName);
|
||||||
if (timer == null) {
|
if (timer == null) {
|
||||||
return remainingMs;
|
return remainingMs;
|
||||||
}
|
}
|
||||||
// There's a case where the debits tally is 0 but a currently running HPJ still counts
|
|
||||||
// towards quota. If the app gets a reward in this case, the reward is lost and the HPJ
|
|
||||||
// run is still fully counted.
|
|
||||||
// TODO(171305774)/STOPSHIP: make sure getting rewards while HPJ currently executing isn't
|
|
||||||
// treated negatively
|
|
||||||
return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis());
|
return remainingMs - timer.getCurrentDuration(sElapsedRealtimeClock.millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1081,7 +1082,7 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
ShrinkableDebits quota = getEJQuotaLocked(userId, packageName);
|
ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
|
||||||
final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked());
|
final long limitMs = getEJLimitMsLocked(packageName, quota.getStandbyBucketLocked());
|
||||||
final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs);
|
final long startWindowElapsed = Math.max(0, nowElapsed - mEJLimitWindowSizeMs);
|
||||||
long remainingDeadSpaceMs = remainingExecutionTimeMs;
|
long remainingDeadSpaceMs = remainingExecutionTimeMs;
|
||||||
@@ -1372,6 +1373,11 @@ public final class QuotaController extends StateController {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void saveTimingSession(final int userId, @NonNull final String packageName,
|
void saveTimingSession(final int userId, @NonNull final String packageName,
|
||||||
@NonNull final TimingSession session, boolean isExpedited) {
|
@NonNull final TimingSession session, boolean isExpedited) {
|
||||||
|
saveTimingSession(userId, packageName, session, isExpedited, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveTimingSession(final int userId, @NonNull final String packageName,
|
||||||
|
@NonNull final TimingSession session, boolean isExpedited, long debitAdjustment) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
final SparseArrayMap<String, List<TimingSession>> sessionMap =
|
final SparseArrayMap<String, List<TimingSession>> sessionMap =
|
||||||
isExpedited ? mEJTimingSessions : mTimingSessions;
|
isExpedited ? mEJTimingSessions : mTimingSessions;
|
||||||
@@ -1382,8 +1388,9 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
sessions.add(session);
|
sessions.add(session);
|
||||||
if (isExpedited) {
|
if (isExpedited) {
|
||||||
final ShrinkableDebits quota = getEJQuotaLocked(userId, packageName);
|
final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
|
||||||
quota.transactOnDebitsLocked(session.endTimeElapsed - session.startTimeElapsed);
|
quota.transactLocked(session.endTimeElapsed - session.startTimeElapsed
|
||||||
|
+ debitAdjustment);
|
||||||
} else {
|
} else {
|
||||||
// Adding a new session means that the current stats are now incorrect.
|
// Adding a new session means that the current stats are now incorrect.
|
||||||
invalidateAllExecutionStatsLocked(userId, packageName);
|
invalidateAllExecutionStatsLocked(userId, packageName);
|
||||||
@@ -1396,15 +1403,34 @@ 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) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
final ShrinkableDebits quota = getEJQuotaLocked(userId, packageName);
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
quota.transactOnDebitsLocked(-credit);
|
final ShrinkableDebits quota = getEJDebitsLocked(userId, packageName);
|
||||||
if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
|
if (transactQuotaLocked(userId, packageName, nowElapsed, quota, credit)
|
||||||
userId, packageName)) {
|
&& maybeUpdateConstraintForPkgLocked(nowElapsed, userId, packageName)) {
|
||||||
mStateChangedListener.onControllerStateChanged();
|
mStateChangedListener.onControllerStateChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean transactQuotaLocked(final int userId, @NonNull final String packageName,
|
||||||
|
final long nowElapsed, @NonNull ShrinkableDebits debits, final long credit) {
|
||||||
|
final long oldTally = debits.getTallyLocked();
|
||||||
|
final long leftover = debits.transactLocked(-credit);
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "debits overflowed by " + leftover);
|
||||||
|
}
|
||||||
|
boolean changed = oldTally != debits.getTallyLocked();
|
||||||
|
if (leftover != 0) {
|
||||||
|
// Only adjust timer if its active.
|
||||||
|
final Timer ejTimer = mEJPkgTimers.get(userId, packageName);
|
||||||
|
if (ejTimer != null && ejTimer.isActive()) {
|
||||||
|
ejTimer.updateDebitAdjustment(nowElapsed, leftover);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> {
|
private final class EarliestEndTimeFunctor implements Consumer<List<TimingSession>> {
|
||||||
public long earliestEndElapsed = Long.MAX_VALUE;
|
public long earliestEndElapsed = Long.MAX_VALUE;
|
||||||
|
|
||||||
@@ -1875,7 +1901,8 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ShrinkableDebits {
|
@VisibleForTesting
|
||||||
|
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. */
|
||||||
private long mDebitTally;
|
private long mDebitTally;
|
||||||
private int mStandbyBucket;
|
private int mStandbyBucket;
|
||||||
@@ -1893,8 +1920,11 @@ public final class QuotaController extends StateController {
|
|||||||
* Negative if the tally should decrease (therefore increasing available quota);
|
* Negative if the tally should decrease (therefore increasing available quota);
|
||||||
* or positive if the tally should increase (therefore decreasing available quota).
|
* or positive if the tally should increase (therefore decreasing available quota).
|
||||||
*/
|
*/
|
||||||
void transactOnDebitsLocked(final long amount) {
|
long transactLocked(final long amount) {
|
||||||
|
final long leftover = amount < 0 && Math.abs(amount) > mDebitTally
|
||||||
|
? mDebitTally + amount : 0;
|
||||||
mDebitTally = Math.max(0, mDebitTally + amount);
|
mDebitTally = Math.max(0, mDebitTally + amount);
|
||||||
|
return leftover;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStandbyBucketLocked(int standbyBucket) {
|
void setStandbyBucketLocked(int standbyBucket) {
|
||||||
@@ -1927,6 +1957,7 @@ public final class QuotaController extends StateController {
|
|||||||
private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
|
private final ArraySet<JobStatus> mRunningBgJobs = new ArraySet<>();
|
||||||
private long mStartTimeElapsed;
|
private long mStartTimeElapsed;
|
||||||
private int mBgJobCount;
|
private int mBgJobCount;
|
||||||
|
private long mDebitAdjustment;
|
||||||
|
|
||||||
Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
|
Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
|
||||||
mPkg = new Package(userId, packageName);
|
mPkg = new Package(userId, packageName);
|
||||||
@@ -1957,6 +1988,7 @@ public final class QuotaController extends StateController {
|
|||||||
if (mRunningBgJobs.size() == 1) {
|
if (mRunningBgJobs.size() == 1) {
|
||||||
// Started tracking the first job.
|
// Started tracking the first job.
|
||||||
mStartTimeElapsed = sElapsedRealtimeClock.millis();
|
mStartTimeElapsed = sElapsedRealtimeClock.millis();
|
||||||
|
mDebitAdjustment = 0;
|
||||||
if (mRegularJobTimer) {
|
if (mRegularJobTimer) {
|
||||||
// Starting the timer means that all cached execution stats are now
|
// Starting the timer means that all cached execution stats are now
|
||||||
// incorrect.
|
// incorrect.
|
||||||
@@ -1988,6 +2020,11 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateDebitAdjustment(long nowElapsed, long debit) {
|
||||||
|
// Make sure we don't have a credit larger than the expected session.
|
||||||
|
mDebitAdjustment = Math.max(mDebitAdjustment + debit, mStartTimeElapsed - nowElapsed);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops tracking all jobs and cancels any pending alarms. This should only be called if
|
* Stops tracking all jobs and cancels any pending alarms. This should only be called if
|
||||||
* the Timer is not going to be used anymore.
|
* the Timer is not going to be used anymore.
|
||||||
@@ -2003,7 +2040,8 @@ public final class QuotaController extends StateController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount);
|
TimingSession ts = new TimingSession(mStartTimeElapsed, nowElapsed, mBgJobCount);
|
||||||
saveTimingSession(mPkg.userId, mPkg.packageName, ts, !mRegularJobTimer);
|
saveTimingSession(mPkg.userId, mPkg.packageName, ts, !mRegularJobTimer,
|
||||||
|
mDebitAdjustment);
|
||||||
mBgJobCount = 0;
|
mBgJobCount = 0;
|
||||||
// Don't reset the tracked jobs list as we need to keep tracking the current number
|
// Don't reset the tracked jobs list as we need to keep tracking the current number
|
||||||
// of jobs.
|
// of jobs.
|
||||||
@@ -2030,7 +2068,7 @@ public final class QuotaController extends StateController {
|
|||||||
|
|
||||||
long getCurrentDuration(long nowElapsed) {
|
long getCurrentDuration(long nowElapsed) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
return !isActive() ? 0 : nowElapsed - mStartTimeElapsed;
|
return !isActive() ? 0 : nowElapsed - mStartTimeElapsed + mDebitAdjustment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2059,6 +2097,7 @@ public final class QuotaController extends StateController {
|
|||||||
// Start timing from unplug.
|
// Start timing from unplug.
|
||||||
if (mRunningBgJobs.size() > 0) {
|
if (mRunningBgJobs.size() > 0) {
|
||||||
mStartTimeElapsed = nowElapsed;
|
mStartTimeElapsed = nowElapsed;
|
||||||
|
mDebitAdjustment = 0;
|
||||||
// NOTE: this does have the unfortunate consequence that if the device is
|
// NOTE: this does have the unfortunate consequence that if the device is
|
||||||
// repeatedly plugged in and unplugged, or an app changes foreground state
|
// repeatedly plugged in and unplugged, or an app changes foreground state
|
||||||
// very frequently, the job count for a package may be artificially high.
|
// very frequently, the job count for a package may be artificially high.
|
||||||
@@ -2128,6 +2167,11 @@ public final class QuotaController extends StateController {
|
|||||||
pw.print(", ");
|
pw.print(", ");
|
||||||
pw.print(mBgJobCount);
|
pw.print(mBgJobCount);
|
||||||
pw.print(" running bg jobs");
|
pw.print(" running bg jobs");
|
||||||
|
if (!mRegularJobTimer) {
|
||||||
|
pw.print(" (debit adj=");
|
||||||
|
pw.print(mDebitAdjustment);
|
||||||
|
pw.print(")");
|
||||||
|
}
|
||||||
pw.println();
|
pw.println();
|
||||||
pw.increaseIndent();
|
pw.increaseIndent();
|
||||||
for (int i = 0; i < mRunningBgJobs.size(); i++) {
|
for (int i = 0; i < mRunningBgJobs.size(); i++) {
|
||||||
@@ -2171,6 +2215,21 @@ public final class QuotaController extends StateController {
|
|||||||
mPkg = new Package(userId, packageName);
|
mPkg = new Package(userId, packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int calculateTimeChunks(final long nowElapsed) {
|
||||||
|
final long totalTopTimeMs = nowElapsed - mStartTimeElapsed;
|
||||||
|
int numTimeChunks = (int) (totalTopTimeMs / mEJTopAppTimeChunkSizeMs);
|
||||||
|
final long remainderMs = totalTopTimeMs % mEJTopAppTimeChunkSizeMs;
|
||||||
|
if (remainderMs >= SECOND_IN_MILLIS) {
|
||||||
|
// "Round up"
|
||||||
|
numTimeChunks++;
|
||||||
|
}
|
||||||
|
return numTimeChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getPendingReward(final long nowElapsed) {
|
||||||
|
return mEJRewardTopAppMs * calculateTimeChunks(nowElapsed);
|
||||||
|
}
|
||||||
|
|
||||||
void processEventLocked(@NonNull UsageEvents.Event event) {
|
void processEventLocked(@NonNull UsageEvents.Event event) {
|
||||||
final long nowElapsed = sElapsedRealtimeClock.millis();
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
switch (event.getEventType()) {
|
switch (event.getEventType()) {
|
||||||
@@ -2186,21 +2245,16 @@ public final class QuotaController extends StateController {
|
|||||||
final UsageEvents.Event existingEvent =
|
final UsageEvents.Event existingEvent =
|
||||||
mActivities.removeReturnOld(event.mInstanceId);
|
mActivities.removeReturnOld(event.mInstanceId);
|
||||||
if (existingEvent != null && mActivities.size() == 0) {
|
if (existingEvent != null && mActivities.size() == 0) {
|
||||||
final long totalTopTimeMs = nowElapsed - mStartTimeElapsed;
|
final long pendingReward = getPendingReward(nowElapsed);
|
||||||
int numTimeChunks = (int) (totalTopTimeMs / mEJTopAppTimeChunkSizeMs);
|
|
||||||
final long remainderMs = totalTopTimeMs % mEJTopAppTimeChunkSizeMs;
|
|
||||||
if (remainderMs >= SECOND_IN_MILLIS) {
|
|
||||||
// "Round up"
|
|
||||||
numTimeChunks++;
|
|
||||||
}
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG,
|
Slog.d(TAG, "Crediting " + mPkg + " " + pendingReward + "ms"
|
||||||
"Crediting " + mPkg + " for " + numTimeChunks + " time chunks");
|
+ " for " + calculateTimeChunks(nowElapsed) + " time chunks");
|
||||||
}
|
}
|
||||||
final ShrinkableDebits quota =
|
final ShrinkableDebits debits =
|
||||||
getEJQuotaLocked(mPkg.userId, mPkg.packageName);
|
getEJDebitsLocked(mPkg.userId, mPkg.packageName);
|
||||||
quota.transactOnDebitsLocked(-mEJRewardTopAppMs * numTimeChunks);
|
if (transactQuotaLocked(mPkg.userId, mPkg.packageName,
|
||||||
if (maybeUpdateConstraintForPkgLocked(nowElapsed,
|
nowElapsed, debits, pendingReward)
|
||||||
|
&& maybeUpdateConstraintForPkgLocked(nowElapsed,
|
||||||
mPkg.userId, mPkg.packageName)) {
|
mPkg.userId, mPkg.packageName)) {
|
||||||
mStateChangedListener.onControllerStateChanged();
|
mStateChangedListener.onControllerStateChanged();
|
||||||
}
|
}
|
||||||
@@ -2321,7 +2375,7 @@ public final class QuotaController extends StateController {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
|
public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
|
||||||
mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event);
|
mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2408,7 +2462,6 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class QcHandler extends Handler {
|
private class QcHandler extends Handler {
|
||||||
private boolean mIsProcessing;
|
|
||||||
|
|
||||||
QcHandler(Looper looper) {
|
QcHandler(Looper looper) {
|
||||||
super(looper);
|
super(looper);
|
||||||
@@ -2417,8 +2470,6 @@ public final class QuotaController extends StateController {
|
|||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
mIsProcessing = true;
|
|
||||||
|
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MSG_REACHED_QUOTA: {
|
case MSG_REACHED_QUOTA: {
|
||||||
Package pkg = (Package) msg.obj;
|
Package pkg = (Package) msg.obj;
|
||||||
@@ -2539,6 +2590,10 @@ public final class QuotaController extends StateController {
|
|||||||
final int userId = msg.arg1;
|
final int userId = msg.arg1;
|
||||||
final UsageEvents.Event event = (UsageEvents.Event) msg.obj;
|
final UsageEvents.Event event = (UsageEvents.Event) msg.obj;
|
||||||
final String pkgName = event.getPackageName();
|
final String pkgName = event.getPackageName();
|
||||||
|
if (DEBUG) {
|
||||||
|
Slog.d(TAG, "Processing event " + event.getEventType()
|
||||||
|
+ " for " + string(userId, pkgName));
|
||||||
|
}
|
||||||
switch (event.getEventType()) {
|
switch (event.getEventType()) {
|
||||||
case UsageEvents.Event.ACTIVITY_RESUMED:
|
case UsageEvents.Event.ACTIVITY_RESUMED:
|
||||||
case UsageEvents.Event.ACTIVITY_PAUSED:
|
case UsageEvents.Event.ACTIVITY_PAUSED:
|
||||||
@@ -2604,8 +2659,6 @@ public final class QuotaController extends StateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mIsProcessing = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3883,11 +3936,6 @@ public final class QuotaController extends StateController {
|
|||||||
return mQcConstants;
|
return mQcConstants;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
boolean isActiveBackgroundProcessing() {
|
|
||||||
return mHandler.mIsProcessing;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////// DATA DUMP //////////////////////////////
|
//////////////////////////// DATA DUMP //////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX;
|
|||||||
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
|
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
|
||||||
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
|
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
|
||||||
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
|
||||||
|
import static com.android.server.job.JobSchedulerService.sSystemClock;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -58,6 +59,7 @@ import android.app.AppGlobals;
|
|||||||
import android.app.IActivityManager;
|
import android.app.IActivityManager;
|
||||||
import android.app.IUidObserver;
|
import android.app.IUidObserver;
|
||||||
import android.app.job.JobInfo;
|
import android.app.job.JobInfo;
|
||||||
|
import android.app.usage.UsageEvents;
|
||||||
import android.app.usage.UsageStatsManager;
|
import android.app.usage.UsageStatsManager;
|
||||||
import android.app.usage.UsageStatsManagerInternal;
|
import android.app.usage.UsageStatsManagerInternal;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
@@ -85,6 +87,7 @@ 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.ShrinkableDebits;
|
||||||
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;
|
||||||
|
|
||||||
@@ -125,6 +128,7 @@ public class QuotaControllerTest {
|
|||||||
private int mSourceUid;
|
private int mSourceUid;
|
||||||
private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
|
private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
|
||||||
private IUidObserver mUidObserver;
|
private IUidObserver mUidObserver;
|
||||||
|
private UsageStatsManagerInternal.UsageEventListener mUsageEventListener;
|
||||||
DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
|
DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
|
||||||
|
|
||||||
private MockitoSession mMockingSession;
|
private MockitoSession mMockingSession;
|
||||||
@@ -218,6 +222,8 @@ public class QuotaControllerTest {
|
|||||||
ArgumentCaptor.forClass(IUidObserver.class);
|
ArgumentCaptor.forClass(IUidObserver.class);
|
||||||
ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
|
ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
|
||||||
ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class);
|
ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class);
|
||||||
|
ArgumentCaptor<UsageStatsManagerInternal.UsageEventListener> ueListenerCaptor =
|
||||||
|
ArgumentCaptor.forClass(UsageStatsManagerInternal.UsageEventListener.class);
|
||||||
mQuotaController = new QuotaController(mJobSchedulerService,
|
mQuotaController = new QuotaController(mJobSchedulerService,
|
||||||
mock(BackgroundJobsController.class), mock(ConnectivityController.class));
|
mock(BackgroundJobsController.class), mock(ConnectivityController.class));
|
||||||
|
|
||||||
@@ -229,6 +235,8 @@ public class QuotaControllerTest {
|
|||||||
verify(mPowerAllowlistInternal)
|
verify(mPowerAllowlistInternal)
|
||||||
.registerTempAllowlistChangeListener(taChangeCaptor.capture());
|
.registerTempAllowlistChangeListener(taChangeCaptor.capture());
|
||||||
mTempAllowlistListener = taChangeCaptor.getValue();
|
mTempAllowlistListener = taChangeCaptor.getValue();
|
||||||
|
verify(mUsageStatsManager).registerListener(ueListenerCaptor.capture());
|
||||||
|
mUsageEventListener = ueListenerCaptor.getValue();
|
||||||
try {
|
try {
|
||||||
verify(activityManager).registerUidObserver(
|
verify(activityManager).registerUidObserver(
|
||||||
uidObserverCaptor.capture(),
|
uidObserverCaptor.capture(),
|
||||||
@@ -288,7 +296,7 @@ public class QuotaControllerTest {
|
|||||||
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid));
|
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid));
|
||||||
assertFalse(foregroundUids.get(uid));
|
assertFalse(foregroundUids.get(uid));
|
||||||
}
|
}
|
||||||
waitForQuietBackground();
|
waitForNonDelayedMessagesProcessed();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
fail("exception encountered: " + e.getMessage());
|
fail("exception encountered: " + e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -385,13 +393,8 @@ public class QuotaControllerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForQuietBackground() throws Exception {
|
private void waitForNonDelayedMessagesProcessed() {
|
||||||
for (int i = 0; i < 5; ++i) {
|
mQuotaController.getHandler().runWithScissors(() -> {}, 15_000);
|
||||||
if (!mQuotaController.isActiveBackgroundProcessing()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Thread.sleep(500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1330,7 +1333,9 @@ public class QuotaControllerTest {
|
|||||||
timeUsedMs, 5), true);
|
timeUsedMs, 5), true);
|
||||||
JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0);
|
JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0);
|
||||||
setStandbyBucket(RARE_INDEX, job);
|
setStandbyBucket(RARE_INDEX, job);
|
||||||
mQuotaController.maybeStartTrackingJobLocked(job, null);
|
synchronized (mQuotaController.mLock) {
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(job, null);
|
||||||
|
}
|
||||||
|
|
||||||
setCharging();
|
setCharging();
|
||||||
synchronized (mQuotaController.mLock) {
|
synchronized (mQuotaController.mLock) {
|
||||||
@@ -5158,4 +5163,257 @@ public class QuotaControllerTest {
|
|||||||
eq(10 * SECOND_IN_MILLIS));
|
eq(10 * SECOND_IN_MILLIS));
|
||||||
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
|
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEJDebitTallying() {
|
||||||
|
setStandbyBucket(RARE_INDEX);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
|
||||||
|
// 15 seconds for each 30 second chunk.
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS);
|
||||||
|
|
||||||
|
// No history. Debits should be 0.
|
||||||
|
ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(10 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// Regular job shouldn't affect EJ tally.
|
||||||
|
JobStatus regJob = createJobStatus("testEJDebitTallying", 1);
|
||||||
|
synchronized (mQuotaController.mLock) {
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(regJob, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(regJob);
|
||||||
|
}
|
||||||
|
advanceElapsedClock(5000);
|
||||||
|
synchronized (mQuotaController.mLock) {
|
||||||
|
mQuotaController.maybeStopTrackingJobLocked(regJob, null, false);
|
||||||
|
}
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(10 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// EJ job should affect EJ tally.
|
||||||
|
JobStatus eJob = createExpeditedJobStatus("testEJDebitTallying", 2);
|
||||||
|
synchronized (mQuotaController.mLock) {
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(eJob, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(eJob);
|
||||||
|
}
|
||||||
|
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
|
||||||
|
synchronized (mQuotaController.mLock) {
|
||||||
|
mQuotaController.maybeStopTrackingJobLocked(eJob, null, false);
|
||||||
|
}
|
||||||
|
assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(5 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// Instantaneous event for a different user shouldn't affect tally.
|
||||||
|
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS);
|
||||||
|
|
||||||
|
UsageEvents.Event event =
|
||||||
|
new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID + 10, event);
|
||||||
|
assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
|
||||||
|
// Instantaneous event for correct user should reduce tally.
|
||||||
|
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
|
||||||
|
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(6 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// Activity start shouldn't reduce tally, but duration with activity started should affect
|
||||||
|
// remaining EJ time.
|
||||||
|
advanceElapsedClock(5 * MINUTE_IN_MILLIS);
|
||||||
|
event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
advanceElapsedClock(30 * SECOND_IN_MILLIS);
|
||||||
|
assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(6 * MINUTE_IN_MILLIS + 15 * SECOND_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
advanceElapsedClock(30 * SECOND_IN_MILLIS);
|
||||||
|
assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(6 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// With activity pausing/stopping/destroying, tally should be updated.
|
||||||
|
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||||
|
event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
assertEquals(3 * MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(7 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEJDebitTallying_StaleSession() {
|
||||||
|
setStandbyBucket(RARE_INDEX);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
|
||||||
|
|
||||||
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
|
TimingSession ts = new TimingSession(nowElapsed, nowElapsed + 10 * MINUTE_IN_MILLIS, 5);
|
||||||
|
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true);
|
||||||
|
|
||||||
|
// Make the session stale.
|
||||||
|
advanceElapsedClock(12 * MINUTE_IN_MILLIS + mQcConstants.EJ_WINDOW_SIZE_MS);
|
||||||
|
|
||||||
|
// With lazy deletion, we don't update the tally until getRemainingEJExecutionTimeLocked()
|
||||||
|
// is called, so call that first.
|
||||||
|
assertEquals(10 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that rewards are properly accounted when there's no EJ running and the rewards exceed
|
||||||
|
* the accumulated debits.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEJDebitTallying_RewardExceedDebits_NoActiveSession() {
|
||||||
|
setStandbyBucket(WORKING_INDEX);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS);
|
||||||
|
|
||||||
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
|
TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS,
|
||||||
|
nowElapsed - 4 * MINUTE_IN_MILLIS, 2);
|
||||||
|
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true);
|
||||||
|
|
||||||
|
ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
|
||||||
|
assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(29 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
advanceElapsedClock(30 * SECOND_IN_MILLIS);
|
||||||
|
UsageEvents.Event event =
|
||||||
|
new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(30 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(30 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// Excessive rewards don't increase maximum quota.
|
||||||
|
event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(30 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that rewards are properly accounted when there's an active EJ running and the rewards
|
||||||
|
* exceed the accumulated debits.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEJDebitTallying_RewardExceedDebits_ActiveSession() {
|
||||||
|
setStandbyBucket(WORKING_INDEX);
|
||||||
|
setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS);
|
||||||
|
// 15 seconds for each 30 second chunk.
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS);
|
||||||
|
setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS);
|
||||||
|
|
||||||
|
final long nowElapsed = sElapsedRealtimeClock.millis();
|
||||||
|
TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS,
|
||||||
|
nowElapsed - 4 * MINUTE_IN_MILLIS, 2);
|
||||||
|
mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true);
|
||||||
|
|
||||||
|
ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
|
||||||
|
assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(29 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// With rewards coming in while an EJ is running, the remaining execution time should be
|
||||||
|
// adjusted accordingly (decrease due to EJ running + increase from reward).
|
||||||
|
JobStatus eJob =
|
||||||
|
createExpeditedJobStatus("testEJDebitTallying_RewardExceedDebits_ActiveSession", 1);
|
||||||
|
synchronized (mQuotaController.mLock) {
|
||||||
|
mQuotaController.maybeStartTrackingJobLocked(eJob, null);
|
||||||
|
mQuotaController.prepareForExecutionLocked(eJob);
|
||||||
|
}
|
||||||
|
advanceElapsedClock(30 * SECOND_IN_MILLIS);
|
||||||
|
assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked());
|
||||||
|
assertEquals(28 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
advanceElapsedClock(30 * SECOND_IN_MILLIS);
|
||||||
|
UsageEvents.Event event =
|
||||||
|
new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(29 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(28 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// Activity start shouldn't reduce tally, but duration with activity started should affect
|
||||||
|
// remaining EJ time.
|
||||||
|
event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
advanceElapsedClock(30 * SECOND_IN_MILLIS);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
// Decrease by 30 seconds for running EJ, increase by 15 seconds due to ongoing activity.
|
||||||
|
assertEquals(27 * MINUTE_IN_MILLIS + 45 * SECOND_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
advanceElapsedClock(30 * SECOND_IN_MILLIS);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(27 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(28 * MINUTE_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
advanceElapsedClock(MINUTE_IN_MILLIS);
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
|
||||||
|
// At this point, with activity pausing/stopping/destroying, since we're giving a reward,
|
||||||
|
// tally should remain 0, and time remaining shouldn't change since it was accounted for
|
||||||
|
// at every step.
|
||||||
|
event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis());
|
||||||
|
event.mPackage = SOURCE_PACKAGE;
|
||||||
|
mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
|
||||||
|
waitForNonDelayedMessagesProcessed();
|
||||||
|
assertEquals(0, debit.getTallyLocked());
|
||||||
|
assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
|
||||||
|
mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user