Merge "Separating alarm history for lower quota" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-07-14 20:09:15 +00:00
committed by Android (Google) Code Review
2 changed files with 174 additions and 16 deletions

View File

@@ -256,6 +256,7 @@ public class AlarmManagerService extends SystemService {
AlarmHandler mHandler;
AppWakeupHistory mAppWakeupHistory;
AppWakeupHistory mAllowWhileIdleHistory;
AppWakeupHistory mAllowWhileIdleCompatHistory;
private final SparseLongArray mLastPriorityAlarmDispatch = new SparseLongArray();
private final SparseArray<RingBuffer<RemovedAlarm>> mRemovalHistory = new SparseArray<>();
ClockReceiver mClockReceiver;
@@ -1633,6 +1634,7 @@ public class AlarmManagerService extends SystemService {
mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
mAllowWhileIdleHistory = new AppWakeupHistory(INTERVAL_HOUR);
mAllowWhileIdleCompatHistory = new AppWakeupHistory(INTERVAL_HOUR);
mNextWakeup = mNextNonWakeup = 0;
@@ -2142,20 +2144,23 @@ public class AlarmManagerService extends SystemService {
final int userId = UserHandle.getUserId(alarm.creatorUid);
final int quota;
final long window;
final AppWakeupHistory history;
if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) {
quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
window = mConstants.ALLOW_WHILE_IDLE_WINDOW;
history = mAllowWhileIdleHistory;
} else {
quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
history = mAllowWhileIdleCompatHistory;
}
final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
final int dispatchesInHistory = history.getTotalWakeupsInWindow(
alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) {
if (dispatchesInHistory < quota) {
// fine to go out immediately.
batterySaverPolicyElapsed = nowElapsed;
} else {
batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
batterySaverPolicyElapsed = history.getNthLastWakeupForPackage(
alarm.sourcePackage, userId, quota) + window;
}
} else if ((alarm.flags & FLAG_PRIORITIZE) != 0) {
@@ -2201,20 +2206,23 @@ public class AlarmManagerService extends SystemService {
final int userId = UserHandle.getUserId(alarm.creatorUid);
final int quota;
final long window;
final AppWakeupHistory history;
if ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0) {
quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
window = mConstants.ALLOW_WHILE_IDLE_WINDOW;
history = mAllowWhileIdleHistory;
} else {
quota = mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
window = mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
history = mAllowWhileIdleCompatHistory;
}
final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
final int dispatchesInHistory = history.getTotalWakeupsInWindow(
alarm.sourcePackage, userId);
if (dispatchesInWindow < quota) {
if (dispatchesInHistory < quota) {
// fine to go out immediately.
deviceIdlePolicyTime = nowElapsed;
} else {
final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
final long whenInQuota = history.getNthLastWakeupForPackage(
alarm.sourcePackage, userId, quota) + window;
deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
}
@@ -2502,6 +2510,7 @@ public class AlarmManagerService extends SystemService {
Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
// The API doesn't allow using both together.
flags &= ~FLAG_ALLOW_WHILE_IDLE;
// Prioritized alarms don't need any extra permission to be exact.
} else if (exact || allowWhileIdle) {
final boolean needsPermission;
boolean lowerQuota;
@@ -2992,6 +3001,10 @@ public class AlarmManagerService extends SystemService {
mAllowWhileIdleHistory.dump(pw, nowELAPSED);
pw.println();
pw.println("Allow while idle compat history:");
mAllowWhileIdleCompatHistory.dump(pw, nowELAPSED);
pw.println();
if (mLastPriorityAlarmDispatch.size() > 0) {
pw.println("Last priority alarm dispatches:");
pw.increaseIndent();
@@ -4553,6 +4566,7 @@ public class AlarmManagerService extends SystemService {
removeUserLocked(userHandle);
mAppWakeupHistory.removeForUser(userHandle);
mAllowWhileIdleHistory.removeForUser(userHandle);
mAllowWhileIdleCompatHistory.removeForUser(userHandle);
}
return;
case Intent.ACTION_UID_REMOVED:
@@ -4588,6 +4602,8 @@ public class AlarmManagerService extends SystemService {
// package-removed and package-restarted case
mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
mAllowWhileIdleCompatHistory.removeForPackage(pkg,
UserHandle.getUserId(uid));
removeLocked(uid, REMOVE_REASON_UNDEFINED);
} else {
// external-applications-unavailable case
@@ -4965,7 +4981,10 @@ public class AlarmManagerService extends SystemService {
if (isAllowedWhileIdleRestricted(alarm)) {
// Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the
// device was in doze or battery saver.
mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
final AppWakeupHistory history = ((alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0)
? mAllowWhileIdleHistory
: mAllowWhileIdleCompatHistory;
history.recordAlarmForPackage(alarm.sourcePackage,
UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
mAlarmStore.updateAlarmDeliveries(a -> {
if (a.creatorUid != alarm.creatorUid || !isAllowedWhileIdleRestricted(a)) {

View File

@@ -514,8 +514,16 @@ public class AlarmManagerServiceTest {
}
private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi,
boolean unrestricted) {
final int flags = unrestricted ? FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED : FLAG_ALLOW_WHILE_IDLE;
boolean unrestricted, boolean compat) {
assertFalse("Alarm cannot be compat and unrestricted", unrestricted && compat);
final int flags;
if (unrestricted) {
flags = FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
} else if (compat) {
flags = FLAG_ALLOW_WHILE_IDLE_COMPAT;
} else {
flags = FLAG_ALLOW_WHILE_IDLE;
}
setTestAlarm(type, triggerTime, pi, 0, flags, TEST_CALLING_UID);
}
@@ -1600,13 +1608,13 @@ public class AlarmManagerServiceTest {
final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
getNewMockPendingIntent(), false);
getNewMockPendingIntent(), false, false);
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
// This one should get deferred on set.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent(), false);
getNewMockPendingIntent(), false, false);
final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow;
assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
mTestTimer.getElapsed());
@@ -1618,6 +1626,108 @@ public class AlarmManagerServiceTest {
assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed());
}
@Test
public void allowWhileIdleCompatAlarmsWhileDeviceIdle() throws Exception {
setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0);
final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + window + 1000,
getNewMockPendingIntent());
assertNotNull(mService.mPendingIdleUntil);
final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < quota; i++) {
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
getNewMockPendingIntent(), false, true);
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
// This one should get deferred on set.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
getNewMockPendingIntent(), false, true);
final long expectedNextTrigger = firstTrigger + window;
assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
mTestTimer.getElapsed());
// Bring the idle until alarm back.
setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger - 50,
getNewMockPendingIntent());
assertEquals(expectedNextTrigger - 50, mService.mPendingIdleUntil.getWhenElapsed());
assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed());
}
@Test
public void allowWhileIdleCompatHistorySeparate() throws Exception {
when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
TEST_CALLING_PACKAGE)).thenReturn(true);
when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
final int fullQuota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
final int compatQuota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
final long fullWindow = mAllowWhileIdleWindow;
final long compatWindow = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
final long firstFullTrigger = mNowElapsedTest + 10;
for (int i = 0; i < fullQuota; i++) {
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + i,
getNewMockPendingIntent(), false, false);
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
// This one should get deferred on set, as full quota is not available.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstFullTrigger + fullQuota,
getNewMockPendingIntent(), false, false);
final long expectedNextFullTrigger = firstFullTrigger + fullWindow;
assertEquals("Incorrect trigger when no quota left", expectedNextFullTrigger,
mTestTimer.getElapsed());
mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
// The following should be allowed, as compat quota should be free.
for (int i = 0; i < compatQuota; i++) {
final long trigger = mNowElapsedTest + 1;
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(),
false, true);
assertEquals(trigger, mTestTimer.getElapsed());
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
// Refresh the state
mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
TEST_CALLING_USER);
// Now test with flipped order
final long firstCompatTrigger = mNowElapsedTest + 10;
for (int i = 0; i < compatQuota; i++) {
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + i,
getNewMockPendingIntent(), false, true);
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
// This one should get deferred on set, as full quota is not available.
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstCompatTrigger + compatQuota,
getNewMockPendingIntent(), false, true);
final long expectedNextCompatTrigger = firstCompatTrigger + compatWindow;
assertEquals("Incorrect trigger when no quota left", expectedNextCompatTrigger,
mTestTimer.getElapsed());
mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
// The following should be allowed, as full quota should be free.
for (int i = 0; i < fullQuota; i++) {
final long trigger = mNowElapsedTest + 1;
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(),
false, false);
assertEquals(trigger, mTestTimer.getElapsed());
mNowElapsedTest = mTestTimer.getElapsed();
mTestTimer.expire();
}
}
@Test
public void allowWhileIdleUnrestricted() throws Exception {
setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0);
@@ -1634,7 +1744,7 @@ public class AlarmManagerServiceTest {
final long firstTrigger = mNowElapsedTest + 10;
for (int i = 0; i < numAlarms; i++) {
setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
getNewMockPendingIntent(), true);
getNewMockPendingIntent(), true, false);
}
// All of them should fire as expected.
for (int i = 0; i < numAlarms; i++) {
@@ -1736,7 +1846,7 @@ public class AlarmManagerServiceTest {
final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
// Refresh the state
mService.removeLocked(TEST_CALLING_UID,
@@ -1744,7 +1854,7 @@ public class AlarmManagerServiceTest {
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
trigger, getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
trigger, getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
// Refresh the state
mService.removeLocked(TEST_CALLING_UID,
@@ -1752,7 +1862,36 @@ public class AlarmManagerServiceTest {
mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent(), false), quota, mAllowWhileIdleWindow);
getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow);
}
@Test
public void allowWhileIdleCompatAlarmsInBatterySaver() throws Exception {
when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
TEST_CALLING_PACKAGE)).thenReturn(true);
when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
final int quota = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA;
final long window = mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW;
testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent(), false, true), quota, window);
// Refresh the state
mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
TEST_CALLING_USER);
testQuotasDeferralOnExpiration(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP,
trigger, getNewMockPendingIntent(), false, true), quota, window);
// Refresh the state
mService.removeLocked(TEST_CALLING_UID, REMOVE_REASON_UNDEFINED);
mService.mAllowWhileIdleCompatHistory.removeForPackage(TEST_CALLING_PACKAGE,
TEST_CALLING_USER);
testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger,
getNewMockPendingIntent(), false, true), quota, window);
}
@Test
@@ -2123,7 +2262,7 @@ public class AlarmManagerServiceTest {
final PendingIntent alarmPi = getNewMockPendingIntent();
final AlarmManager.AlarmClockInfo alarmClock = mock(AlarmManager.AlarmClockInfo.class);
mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
alarmPi, null, null, null, alarmClock);
alarmPi, null, null, null, alarmClock);
// Correct permission checks are invoked.
verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);