Merge "Adding a reserve of temporary quota for alarms" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
735ebc1f79
@@ -123,6 +123,7 @@ class Alarm {
|
||||
public AlarmManagerService.PriorityClass priorityClass;
|
||||
/** Broadcast options to use when delivering this alarm */
|
||||
public Bundle mIdleOptions;
|
||||
public boolean mUsingReserveQuota;
|
||||
|
||||
Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
|
||||
PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
|
||||
@@ -151,6 +152,7 @@ class Alarm {
|
||||
mExactAllowReason = exactAllowReason;
|
||||
sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
|
||||
creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
|
||||
mUsingReserveQuota = false;
|
||||
}
|
||||
|
||||
public static String makeTag(PendingIntent pi, String tag, int type) {
|
||||
@@ -340,6 +342,9 @@ class Alarm {
|
||||
TimeUtils.formatDuration(getWhenElapsed(), nowELAPSED, ipw);
|
||||
ipw.print(" maxWhenElapsed=");
|
||||
TimeUtils.formatDuration(mMaxWhenElapsed, nowELAPSED, ipw);
|
||||
if (mUsingReserveQuota) {
|
||||
ipw.print(" usingReserveQuota=true");
|
||||
}
|
||||
ipw.println();
|
||||
|
||||
if (alarmClock != null) {
|
||||
|
||||
@@ -213,6 +213,8 @@ public class AlarmManagerService extends SystemService {
|
||||
static final int RARE_INDEX = 3;
|
||||
static final int NEVER_INDEX = 4;
|
||||
|
||||
private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
|
||||
|
||||
private final Intent mBackgroundIntent
|
||||
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
|
||||
|
||||
@@ -282,6 +284,7 @@ public class AlarmManagerService extends SystemService {
|
||||
AppWakeupHistory mAppWakeupHistory;
|
||||
AppWakeupHistory mAllowWhileIdleHistory;
|
||||
AppWakeupHistory mAllowWhileIdleCompatHistory;
|
||||
TemporaryQuotaReserve mTemporaryQuotaReserve;
|
||||
private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray();
|
||||
private final SparseArray<RingBuffer<RemovedAlarm>> mRemovalHistory = new SparseArray<>();
|
||||
ClockReceiver mClockReceiver;
|
||||
@@ -358,6 +361,133 @@ public class AlarmManagerService extends SystemService {
|
||||
@VisibleForTesting
|
||||
boolean mAppStandbyParole;
|
||||
|
||||
/**
|
||||
* Holds information about temporary quota that can be allotted to apps to use as a "reserve"
|
||||
* when they run out of their standard app-standby quota.
|
||||
* This reserve only lasts for a fixed duration of time from when it was last replenished.
|
||||
*/
|
||||
static class TemporaryQuotaReserve {
|
||||
|
||||
private static class QuotaInfo {
|
||||
public int remainingQuota;
|
||||
public long expirationTime;
|
||||
public long lastUsage;
|
||||
}
|
||||
/** Map of {package, user} -> {quotaInfo} */
|
||||
private final ArrayMap<Pair<String, Integer>, QuotaInfo> mQuotaBuffer = new ArrayMap<>();
|
||||
|
||||
private long mMaxDuration;
|
||||
|
||||
TemporaryQuotaReserve(long maxDuration) {
|
||||
mMaxDuration = maxDuration;
|
||||
}
|
||||
|
||||
void replenishQuota(String packageName, int userId, int quota, long nowElapsed) {
|
||||
if (quota <= 0) {
|
||||
return;
|
||||
}
|
||||
final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
|
||||
QuotaInfo currentQuotaInfo = mQuotaBuffer.get(packageUser);
|
||||
if (currentQuotaInfo == null) {
|
||||
currentQuotaInfo = new QuotaInfo();
|
||||
mQuotaBuffer.put(packageUser, currentQuotaInfo);
|
||||
}
|
||||
currentQuotaInfo.remainingQuota = quota;
|
||||
currentQuotaInfo.expirationTime = nowElapsed + mMaxDuration;
|
||||
}
|
||||
|
||||
/** Returns if the supplied package has reserve quota to fire at the given time. */
|
||||
boolean hasQuota(String packageName, int userId, long triggerElapsed) {
|
||||
final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
|
||||
final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
|
||||
|
||||
return quotaInfo != null && quotaInfo.remainingQuota > 0
|
||||
&& triggerElapsed <= quotaInfo.expirationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records quota usage of the given package at the given time and subtracts quota if
|
||||
* required.
|
||||
*/
|
||||
void recordUsage(String packageName, int userId, long nowElapsed) {
|
||||
final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
|
||||
final QuotaInfo quotaInfo = mQuotaBuffer.get(packageUser);
|
||||
|
||||
if (quotaInfo == null) {
|
||||
Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
|
||||
+ " but not found for package: " + packageName + ", user: " + userId);
|
||||
return;
|
||||
}
|
||||
// Only consume quota if this usage is later than the last one recorded. This is
|
||||
// needed as this can be called multiple times when a batch of alarms is delivered.
|
||||
if (nowElapsed > quotaInfo.lastUsage) {
|
||||
if (quotaInfo.remainingQuota <= 0) {
|
||||
Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
|
||||
+ " but remaining only " + quotaInfo.remainingQuota
|
||||
+ " for package: " + packageName + ", user: " + userId);
|
||||
} else if (quotaInfo.expirationTime < nowElapsed) {
|
||||
Slog.wtf(TAG, "Temporary quota being consumed at " + nowElapsed
|
||||
+ " but expired at " + quotaInfo.expirationTime
|
||||
+ " for package: " + packageName + ", user: " + userId);
|
||||
} else {
|
||||
quotaInfo.remainingQuota--;
|
||||
// We keep the quotaInfo entry even if remaining quota reduces to 0 as
|
||||
// following calls can be made with nowElapsed <= lastUsage. The object will
|
||||
// eventually be removed in cleanUpExpiredQuotas or reused in replenishQuota.
|
||||
}
|
||||
quotaInfo.lastUsage = nowElapsed;
|
||||
}
|
||||
}
|
||||
|
||||
/** Clean up any quotas that have expired before the given time. */
|
||||
void cleanUpExpiredQuotas(long nowElapsed) {
|
||||
for (int i = mQuotaBuffer.size() - 1; i >= 0; i--) {
|
||||
final QuotaInfo quotaInfo = mQuotaBuffer.valueAt(i);
|
||||
if (quotaInfo.expirationTime < nowElapsed) {
|
||||
mQuotaBuffer.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeForUser(int userId) {
|
||||
for (int i = mQuotaBuffer.size() - 1; i >= 0; i--) {
|
||||
final Pair<String, Integer> packageUserKey = mQuotaBuffer.keyAt(i);
|
||||
if (packageUserKey.second == userId) {
|
||||
mQuotaBuffer.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeForPackage(String packageName, int userId) {
|
||||
final Pair<String, Integer> packageUser = Pair.create(packageName, userId);
|
||||
mQuotaBuffer.remove(packageUser);
|
||||
}
|
||||
|
||||
void dump(IndentingPrintWriter pw, long nowElapsed) {
|
||||
pw.increaseIndent();
|
||||
for (int i = 0; i < mQuotaBuffer.size(); i++) {
|
||||
final Pair<String, Integer> packageUser = mQuotaBuffer.keyAt(i);
|
||||
final QuotaInfo quotaInfo = mQuotaBuffer.valueAt(i);
|
||||
pw.print(packageUser.first);
|
||||
pw.print(", u");
|
||||
pw.print(packageUser.second);
|
||||
pw.print(": ");
|
||||
if (quotaInfo == null) {
|
||||
pw.print("--");
|
||||
} else {
|
||||
pw.print("quota: ");
|
||||
pw.print(quotaInfo.remainingQuota);
|
||||
pw.print(", expiration: ");
|
||||
TimeUtils.formatDuration(quotaInfo.expirationTime, nowElapsed, pw);
|
||||
pw.print(" last used: ");
|
||||
TimeUtils.formatDuration(quotaInfo.lastUsage, nowElapsed, pw);
|
||||
}
|
||||
pw.println();
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A container to keep rolling window history of previous times when an alarm was sent to
|
||||
* a package.
|
||||
@@ -569,6 +699,8 @@ public class AlarmManagerService extends SystemService {
|
||||
@VisibleForTesting
|
||||
static final String KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED =
|
||||
"kill_on_schedule_exact_alarm_revoked";
|
||||
@VisibleForTesting
|
||||
static final String KEY_TEMPORARY_QUOTA_BUMP = "temporary_quota_bump";
|
||||
|
||||
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
|
||||
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
|
||||
@@ -613,6 +745,8 @@ public class AlarmManagerService extends SystemService {
|
||||
|
||||
private static final boolean DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED = true;
|
||||
|
||||
private static final int DEFAULT_TEMPORARY_QUOTA_BUMP = 0;
|
||||
|
||||
// Minimum futurity of a new alarm
|
||||
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
|
||||
|
||||
@@ -702,6 +836,17 @@ public class AlarmManagerService extends SystemService {
|
||||
|
||||
public boolean USE_TARE_POLICY = Settings.Global.DEFAULT_ENABLE_TARE == 1;
|
||||
|
||||
/**
|
||||
* The amount of temporary reserve quota to give apps on receiving the
|
||||
* {@link AppIdleStateChangeListener#triggerTemporaryQuotaBump(String, int)} callback
|
||||
* from {@link com.android.server.usage.AppStandbyController}.
|
||||
* <p> This quota adds on top of the standard standby bucket quota available to the app, and
|
||||
* works the same way, i.e. each count of quota denotes one point in time when the app can
|
||||
* receive any number of alarms together.
|
||||
* This quota is tracked per package and expires after {@link #TEMPORARY_QUOTA_DURATION}.
|
||||
*/
|
||||
public int TEMPORARY_QUOTA_BUMP = DEFAULT_TEMPORARY_QUOTA_BUMP;
|
||||
|
||||
private long mLastAllowWhileIdleWhitelistDuration = -1;
|
||||
private int mVersion = 0;
|
||||
|
||||
@@ -886,6 +1031,10 @@ public class AlarmManagerService extends SystemService {
|
||||
KEY_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED,
|
||||
DEFAULT_KILL_ON_SCHEDULE_EXACT_ALARM_REVOKED);
|
||||
break;
|
||||
case KEY_TEMPORARY_QUOTA_BUMP:
|
||||
TEMPORARY_QUOTA_BUMP = properties.getInt(KEY_TEMPORARY_QUOTA_BUMP,
|
||||
DEFAULT_TEMPORARY_QUOTA_BUMP);
|
||||
break;
|
||||
default:
|
||||
if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
|
||||
// The quotas need to be updated in order, so we can't just rely
|
||||
@@ -1136,6 +1285,9 @@ public class AlarmManagerService extends SystemService {
|
||||
pw.print(Settings.Global.ENABLE_TARE, USE_TARE_POLICY);
|
||||
pw.println();
|
||||
|
||||
pw.print(KEY_TEMPORARY_QUOTA_BUMP, TEMPORARY_QUOTA_BUMP);
|
||||
pw.println();
|
||||
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
@@ -1748,6 +1900,8 @@ public class AlarmManagerService extends SystemService {
|
||||
mAllowWhileIdleHistory = new AppWakeupHistory(INTERVAL_HOUR);
|
||||
mAllowWhileIdleCompatHistory = new AppWakeupHistory(INTERVAL_HOUR);
|
||||
|
||||
mTemporaryQuotaReserve = new TemporaryQuotaReserve(TEMPORARY_QUOTA_DURATION);
|
||||
|
||||
mNextWakeup = mNextNonWakeup = 0;
|
||||
|
||||
// We have to set current TimeZone info to kernel
|
||||
@@ -2391,6 +2545,12 @@ public class AlarmManagerService extends SystemService {
|
||||
final int quotaForBucket = getQuotaForBucketLocked(standbyBucket);
|
||||
if (wakeupsInWindow >= quotaForBucket) {
|
||||
final long minElapsed;
|
||||
if (mTemporaryQuotaReserve.hasQuota(sourcePackage, sourceUserId, nowElapsed)) {
|
||||
// We will let this alarm go out as usual, but mark it so it consumes the quota
|
||||
// at the time of delivery.
|
||||
alarm.mUsingReserveQuota = true;
|
||||
return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
|
||||
}
|
||||
if (quotaForBucket <= 0) {
|
||||
// Just keep deferring indefinitely till the quota changes.
|
||||
minElapsed = nowElapsed + INDEFINITE_DELAY;
|
||||
@@ -2405,6 +2565,7 @@ public class AlarmManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
// wakeupsInWindow are less than the permitted quota, hence no deferring is needed.
|
||||
alarm.mUsingReserveQuota = false;
|
||||
return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
|
||||
}
|
||||
|
||||
@@ -3165,6 +3326,10 @@ public class AlarmManagerService extends SystemService {
|
||||
pw.println("App Alarm history:");
|
||||
mAppWakeupHistory.dump(pw, nowELAPSED);
|
||||
|
||||
pw.println();
|
||||
pw.println("Temporary Quota Reserves:");
|
||||
mTemporaryQuotaReserve.dump(pw, nowELAPSED);
|
||||
|
||||
if (mPendingIdleUntil != null) {
|
||||
pw.println();
|
||||
pw.println("Idle mode state:");
|
||||
@@ -4573,6 +4738,7 @@ public class AlarmManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
deliverAlarmsLocked(triggerList, nowELAPSED);
|
||||
mTemporaryQuotaReserve.cleanUpExpiredQuotas(nowELAPSED);
|
||||
if (mConstants.USE_TARE_POLICY) {
|
||||
reorderAlarmsBasedOnTare(triggerPackages);
|
||||
} else {
|
||||
@@ -4682,6 +4848,7 @@ public class AlarmManagerService extends SystemService {
|
||||
public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;
|
||||
public static final int TARE_AFFORDABILITY_CHANGED = 12;
|
||||
public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13;
|
||||
public static final int TEMPORARY_QUOTA_CHANGED = 14;
|
||||
|
||||
AlarmHandler() {
|
||||
super(Looper.myLooper());
|
||||
@@ -4747,6 +4914,7 @@ public class AlarmManagerService extends SystemService {
|
||||
}
|
||||
break;
|
||||
|
||||
case TEMPORARY_QUOTA_CHANGED:
|
||||
case APP_STANDBY_BUCKET_CHANGED:
|
||||
synchronized (mLock) {
|
||||
final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
|
||||
@@ -4958,6 +5126,7 @@ public class AlarmManagerService extends SystemService {
|
||||
mAppWakeupHistory.removeForUser(userHandle);
|
||||
mAllowWhileIdleHistory.removeForUser(userHandle);
|
||||
mAllowWhileIdleCompatHistory.removeForUser(userHandle);
|
||||
mTemporaryQuotaReserve.removeForUser(userHandle);
|
||||
}
|
||||
return;
|
||||
case Intent.ACTION_UID_REMOVED:
|
||||
@@ -5006,6 +5175,7 @@ public class AlarmManagerService extends SystemService {
|
||||
mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
|
||||
mAllowWhileIdleCompatHistory.removeForPackage(pkg,
|
||||
UserHandle.getUserId(uid));
|
||||
mTemporaryQuotaReserve.removeForPackage(pkg, UserHandle.getUserId(uid));
|
||||
removeLocked(uid, REMOVE_REASON_UNDEFINED);
|
||||
} else {
|
||||
// external-applications-unavailable case
|
||||
@@ -5040,6 +5210,30 @@ public class AlarmManagerService extends SystemService {
|
||||
mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName)
|
||||
.sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerTemporaryQuotaBump(String packageName, int userId) {
|
||||
final int quotaBump;
|
||||
synchronized (mLock) {
|
||||
quotaBump = mConstants.TEMPORARY_QUOTA_BUMP;
|
||||
}
|
||||
if (quotaBump <= 0) {
|
||||
return;
|
||||
}
|
||||
final int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
|
||||
if (uid < 0 || UserHandle.isCore(uid)) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG_STANDBY) {
|
||||
Slog.d(TAG, "Bumping quota temporarily for " + packageName + " for user " + userId);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mTemporaryQuotaReserve.replenishQuota(packageName, userId, quotaBump,
|
||||
mInjector.getElapsedRealtime());
|
||||
}
|
||||
mHandler.obtainMessage(AlarmHandler.TEMPORARY_QUOTA_CHANGED, userId, -1,
|
||||
packageName).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private final EconomyManagerInternal.AffordabilityChangeListener mAffordabilityChangeListener =
|
||||
@@ -5448,8 +5642,13 @@ public class AlarmManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
if (!isExemptFromAppStandby(alarm)) {
|
||||
mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
|
||||
UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
|
||||
final int userId = UserHandle.getUserId(alarm.creatorUid);
|
||||
if (alarm.mUsingReserveQuota) {
|
||||
mTemporaryQuotaReserve.recordUsage(alarm.sourcePackage, userId, nowELAPSED);
|
||||
} else {
|
||||
mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage, userId,
|
||||
nowELAPSED);
|
||||
}
|
||||
}
|
||||
final BroadcastStats bs = inflight.mBroadcastStats;
|
||||
bs.count++;
|
||||
|
||||
@@ -67,6 +67,7 @@ import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_
|
||||
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
|
||||
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
|
||||
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TARE_AFFORDABILITY_CHANGED;
|
||||
import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TEMPORARY_QUOTA_CHANGED;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
|
||||
@@ -83,6 +84,7 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUT
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP;
|
||||
import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;
|
||||
import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
|
||||
import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
|
||||
@@ -110,6 +112,7 @@ import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ActivityManager;
|
||||
@@ -654,6 +657,7 @@ public class AlarmManagerServiceTest {
|
||||
setDeviceConfigLong(KEY_MIN_INTERVAL, 0);
|
||||
mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
|
||||
mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
|
||||
mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[FREQUENT_INDEX]);
|
||||
doReturn(50).when(mDeviceConfigProperties)
|
||||
.getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]), anyInt());
|
||||
doReturn(35).when(mDeviceConfigProperties)
|
||||
@@ -732,6 +736,7 @@ public class AlarmManagerServiceTest {
|
||||
setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, 55);
|
||||
setDeviceConfigLong(KEY_MIN_DEVICE_IDLE_FUZZ, 60);
|
||||
setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 65);
|
||||
setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 70);
|
||||
assertEquals(5, mService.mConstants.MIN_FUTURITY);
|
||||
assertEquals(10, mService.mConstants.MIN_INTERVAL);
|
||||
assertEquals(15, mService.mConstants.MAX_INTERVAL);
|
||||
@@ -745,6 +750,7 @@ public class AlarmManagerServiceTest {
|
||||
assertEquals(55, mService.mConstants.PRIORITY_ALARM_DELAY);
|
||||
assertEquals(60, mService.mConstants.MIN_DEVICE_IDLE_FUZZ);
|
||||
assertEquals(65, mService.mConstants.MAX_DEVICE_IDLE_FUZZ);
|
||||
assertEquals(70, mService.mConstants.TEMPORARY_QUOTA_BUMP);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -879,6 +885,7 @@ public class AlarmManagerServiceTest {
|
||||
for (int i = 0; i < quota; i++) {
|
||||
alarmSetter.accept(firstTrigger + i);
|
||||
mNowElapsedTest = mTestTimer.getElapsed();
|
||||
assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
|
||||
mTestTimer.expire();
|
||||
}
|
||||
// This one should get deferred on set
|
||||
@@ -897,6 +904,7 @@ public class AlarmManagerServiceTest {
|
||||
alarmSetter.accept(firstTrigger + quota);
|
||||
for (int i = 0; i < quota; i++) {
|
||||
mNowElapsedTest = mTestTimer.getElapsed();
|
||||
assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
|
||||
mTestTimer.expire();
|
||||
}
|
||||
final long expectedNextTrigger = firstTrigger + window;
|
||||
@@ -914,6 +922,7 @@ public class AlarmManagerServiceTest {
|
||||
alarmSetter.accept(expectedNextTrigger);
|
||||
for (int i = 0; i < quota; i++) {
|
||||
mNowElapsedTest = mTestTimer.getElapsed();
|
||||
assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
|
||||
mTestTimer.expire();
|
||||
}
|
||||
assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed());
|
||||
@@ -1919,16 +1928,14 @@ public class AlarmManagerServiceTest {
|
||||
getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
|
||||
|
||||
// Refresh the state
|
||||
mService.removeLocked(TEST_CALLING_UID,
|
||||
REMOVE_REASON_UNDEFINED);
|
||||
mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
|
||||
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
|
||||
testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
|
||||
trigger, getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
|
||||
|
||||
// Refresh the state
|
||||
mService.removeLocked(TEST_CALLING_UID,
|
||||
REMOVE_REASON_UNDEFINED);
|
||||
mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
|
||||
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
|
||||
testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
|
||||
@@ -3303,7 +3310,7 @@ public class AlarmManagerServiceTest {
|
||||
Arrays.asList(package4));
|
||||
|
||||
mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false);
|
||||
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] {
|
||||
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
|
||||
package1,
|
||||
package3,
|
||||
});
|
||||
@@ -3315,7 +3322,7 @@ public class AlarmManagerServiceTest {
|
||||
assertTrue(mService.isScheduleExactAlarmAllowedByDefault(package4, uid4));
|
||||
|
||||
mockChangeEnabled(SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true);
|
||||
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[] {
|
||||
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{
|
||||
package1,
|
||||
package3,
|
||||
});
|
||||
@@ -3407,6 +3414,218 @@ public class AlarmManagerServiceTest {
|
||||
assertFalse(mService.hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuotaReserve_hasQuota() {
|
||||
final int quotaToFill = 5;
|
||||
final String package1 = "package1";
|
||||
final int user1 = 123;
|
||||
final long startTime = 54;
|
||||
final long quotaDuration = 17;
|
||||
|
||||
final AlarmManagerService.TemporaryQuotaReserve quotaReserve =
|
||||
new AlarmManagerService.TemporaryQuotaReserve(quotaDuration);
|
||||
quotaReserve.replenishQuota(package1, user1, quotaToFill, startTime);
|
||||
|
||||
for (long time = startTime; time <= startTime + quotaDuration; time++) {
|
||||
assertTrue(quotaReserve.hasQuota(package1, user1, time));
|
||||
assertFalse(quotaReserve.hasQuota("some.other.package", 21, time));
|
||||
assertFalse(quotaReserve.hasQuota(package1, 321, time));
|
||||
}
|
||||
|
||||
assertFalse(quotaReserve.hasQuota(package1, user1, startTime + quotaDuration + 1));
|
||||
assertFalse(quotaReserve.hasQuota(package1, user1, startTime + quotaDuration + 435421));
|
||||
|
||||
for (int i = 0; i < quotaToFill - 1; i++) {
|
||||
assertTrue(i < quotaDuration);
|
||||
// Use record usage multiple times with the same timestamp.
|
||||
quotaReserve.recordUsage(package1, user1, startTime + i);
|
||||
quotaReserve.recordUsage(package1, user1, startTime + i);
|
||||
quotaReserve.recordUsage(package1, user1, startTime + i);
|
||||
quotaReserve.recordUsage(package1, user1, startTime + i);
|
||||
|
||||
// Quota should not run out in this loop.
|
||||
assertTrue(quotaReserve.hasQuota(package1, user1, startTime + i));
|
||||
}
|
||||
quotaReserve.recordUsage(package1, user1, startTime + quotaDuration);
|
||||
|
||||
// Should be out of quota now.
|
||||
for (long time = startTime; time <= startTime + quotaDuration; time++) {
|
||||
assertFalse(quotaReserve.hasQuota(package1, user1, time));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuotaReserve_removeForPackage() {
|
||||
final String[] packages = new String[]{"package1", "test.package2"};
|
||||
final int userId = 472;
|
||||
final long startTime = 59;
|
||||
final long quotaDuration = 100;
|
||||
|
||||
final AlarmManagerService.TemporaryQuotaReserve quotaReserve =
|
||||
new AlarmManagerService.TemporaryQuotaReserve(quotaDuration);
|
||||
|
||||
quotaReserve.replenishQuota(packages[0], userId, 10, startTime);
|
||||
quotaReserve.replenishQuota(packages[1], userId, 10, startTime);
|
||||
|
||||
assertTrue(quotaReserve.hasQuota(packages[0], userId, startTime + 1));
|
||||
assertTrue(quotaReserve.hasQuota(packages[1], userId, startTime + 1));
|
||||
|
||||
quotaReserve.removeForPackage(packages[0], userId);
|
||||
|
||||
assertFalse(quotaReserve.hasQuota(packages[0], userId, startTime + 1));
|
||||
assertTrue(quotaReserve.hasQuota(packages[1], userId, startTime + 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuotaReserve_removeForUser() {
|
||||
final String[] packagesUser1 = new String[]{"test1.package1", "test1.package2"};
|
||||
final String[] packagesUser2 = new String[]{"test2.p1", "test2.p2", "test2.p3"};
|
||||
final int user1 = 3201;
|
||||
final int user2 = 5409;
|
||||
final long startTime = 59;
|
||||
final long quotaDuration = 100;
|
||||
|
||||
final AlarmManagerService.TemporaryQuotaReserve quotaReserve =
|
||||
new AlarmManagerService.TemporaryQuotaReserve(quotaDuration);
|
||||
|
||||
for (String packageUser1 : packagesUser1) {
|
||||
quotaReserve.replenishQuota(packageUser1, user1, 10, startTime);
|
||||
}
|
||||
for (String packageUser2 : packagesUser2) {
|
||||
quotaReserve.replenishQuota(packageUser2, user2, 10, startTime);
|
||||
}
|
||||
|
||||
for (String packageUser1 : packagesUser1) {
|
||||
assertTrue(quotaReserve.hasQuota(packageUser1, user1, startTime));
|
||||
}
|
||||
for (String packageUser2 : packagesUser2) {
|
||||
assertTrue(quotaReserve.hasQuota(packageUser2, user2, startTime));
|
||||
}
|
||||
|
||||
quotaReserve.removeForUser(user2);
|
||||
|
||||
for (String packageUser1 : packagesUser1) {
|
||||
assertTrue(quotaReserve.hasQuota(packageUser1, user1, startTime));
|
||||
}
|
||||
for (String packageUser2 : packagesUser2) {
|
||||
assertFalse(quotaReserve.hasQuota(packageUser2, user2, startTime));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggerTemporaryQuotaBump_zeroQuota() {
|
||||
setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, 0);
|
||||
|
||||
mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
verifyZeroInteractions(mPackageManagerInternal);
|
||||
verifyZeroInteractions(mService.mHandler);
|
||||
}
|
||||
|
||||
private void testTemporaryQuota_bumpedAfterDeferral(int standbyBucket) throws Exception {
|
||||
final int temporaryQuota = 31;
|
||||
setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, temporaryQuota);
|
||||
|
||||
final int standbyQuota = mService.getQuotaForBucketLocked(standbyBucket);
|
||||
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
|
||||
anyLong())).thenReturn(standbyBucket);
|
||||
|
||||
final long firstTrigger = mNowElapsedTest + 10;
|
||||
for (int i = 0; i < standbyQuota + 1; i++) {
|
||||
setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent());
|
||||
}
|
||||
|
||||
for (int i = 0; i < standbyQuota; i++) {
|
||||
mNowElapsedTest = mTestTimer.getElapsed();
|
||||
assertEquals("Incorrect trigger time at i=" + i, firstTrigger + i, mNowElapsedTest);
|
||||
mTestTimer.expire();
|
||||
}
|
||||
|
||||
// The last alarm should be deferred due to exceeding the quota
|
||||
final long deferredTrigger = firstTrigger + mAppStandbyWindow;
|
||||
assertEquals(deferredTrigger, mTestTimer.getElapsed());
|
||||
|
||||
// Triggering temporary quota now.
|
||||
mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
assertAndHandleMessageSync(TEMPORARY_QUOTA_CHANGED);
|
||||
// The last alarm should now be rescheduled to go as per original expectations
|
||||
final long originalTrigger = firstTrigger + standbyQuota;
|
||||
assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedAfterDeferral_active() throws Exception {
|
||||
testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_ACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedAfterDeferral_working() throws Exception {
|
||||
testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_WORKING_SET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedAfterDeferral_frequent() throws Exception {
|
||||
testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_FREQUENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedAfterDeferral_rare() throws Exception {
|
||||
testTemporaryQuota_bumpedAfterDeferral(STANDBY_BUCKET_RARE);
|
||||
}
|
||||
|
||||
private void testTemporaryQuota_bumpedBeforeDeferral(int standbyBucket) throws Exception {
|
||||
final int temporaryQuota = 7;
|
||||
setDeviceConfigInt(KEY_TEMPORARY_QUOTA_BUMP, temporaryQuota);
|
||||
|
||||
final int standbyQuota = mService.getQuotaForBucketLocked(standbyBucket);
|
||||
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
|
||||
anyLong())).thenReturn(standbyBucket);
|
||||
|
||||
mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
// No need to handle message TEMPORARY_QUOTA_CHANGED, as the quota change doesn't need to
|
||||
// trigger a re-evaluation in this test.
|
||||
testQuotasDeferralOnExpiration(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
|
||||
getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
|
||||
|
||||
// refresh the state.
|
||||
mService.removeLocked(TEST_CALLING_PACKAGE);
|
||||
mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
|
||||
mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
testQuotasDeferralOnSet(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
|
||||
getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
|
||||
|
||||
// refresh the state.
|
||||
mService.removeLocked(TEST_CALLING_PACKAGE);
|
||||
mService.mAppWakeupHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
mService.mTemporaryQuotaReserve.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
|
||||
mAppStandbyListener.triggerTemporaryQuotaBump(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
|
||||
testQuotasNoDeferral(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
|
||||
getNewMockPendingIntent()), standbyQuota + temporaryQuota, mAppStandbyWindow);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedBeforeDeferral_active() throws Exception {
|
||||
testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_ACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedBeforeDeferral_working() throws Exception {
|
||||
testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_WORKING_SET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedBeforeDeferral_frequent() throws Exception {
|
||||
testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_FREQUENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void temporaryQuota_bumpedBeforeDeferral_rare() throws Exception {
|
||||
testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_RARE);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (mMockingSession != null) {
|
||||
|
||||
Reference in New Issue
Block a user