Loading services/core/java/com/android/server/AlarmManagerService.java +67 −19 Original line number Diff line number Diff line Loading @@ -331,12 +331,16 @@ class AlarmManagerService extends SystemService { return (history == null) ? 0 : history.size(); } long getLastWakeupForPackage(String packageName, int userId, int positionFromEnd) { /** * @param n The desired nth-last wakeup * (1=1st-last=the ultimate wakeup and 2=2nd-last=the penultimate wakeup) */ long getNthLastWakeupForPackage(String packageName, int userId, int n) { final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId)); if (history == null) { return 0; } final int i = history.size() - positionFromEnd; final int i = history.size() - n; return (i < 0) ? 0 : history.get(i); } Loading Loading @@ -400,6 +404,11 @@ class AlarmManagerService extends SystemService { "standby_rare_quota", "standby_never_quota", }; // Not putting this in the KEYS_APP_STANDBY_QUOTAS array because this uses a different // window size. private static final String KEY_APP_STANDBY_RESTRICTED_QUOTA = "standby_restricted_quota"; private static final String KEY_APP_STANDBY_RESTRICTED_WINDOW = "app_standby_restricted_window"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; Loading @@ -420,6 +429,8 @@ class AlarmManagerService extends SystemService { 1, // Rare 0 // Never }; private static final int DEFAULT_APP_STANDBY_RESTRICTED_QUOTA = 1; private static final long DEFAULT_APP_STANDBY_RESTRICTED_WINDOW = MILLIS_IN_DAY; // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; Loading @@ -446,6 +457,8 @@ class AlarmManagerService extends SystemService { public long APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW; public int[] APP_STANDBY_QUOTAS = new int[DEFAULT_APP_STANDBY_QUOTAS.length]; public int APP_STANDBY_RESTRICTED_QUOTA = DEFAULT_APP_STANDBY_RESTRICTED_QUOTA; public long APP_STANDBY_RESTRICTED_WINDOW = DEFAULT_APP_STANDBY_RESTRICTED_WINDOW; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -520,6 +533,14 @@ class AlarmManagerService extends SystemService { Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i])); } APP_STANDBY_RESTRICTED_QUOTA = Math.max(1, mParser.getInt(KEY_APP_STANDBY_RESTRICTED_QUOTA, DEFAULT_APP_STANDBY_RESTRICTED_QUOTA)); APP_STANDBY_RESTRICTED_WINDOW = Math.max(APP_STANDBY_WINDOW, mParser.getLong(KEY_APP_STANDBY_RESTRICTED_WINDOW, DEFAULT_APP_STANDBY_RESTRICTED_WINDOW)); MAX_ALARMS_PER_UID = mParser.getInt(KEY_MAX_ALARMS_PER_UID, DEFAULT_MAX_ALARMS_PER_UID); if (MAX_ALARMS_PER_UID < DEFAULT_MAX_ALARMS_PER_UID) { Loading Loading @@ -581,6 +602,14 @@ class AlarmManagerService extends SystemService { pw.println(APP_STANDBY_QUOTAS[i]); } pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA); pw.print("="); TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_QUOTA, pw); pw.println(); pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW); pw.print("="); TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw); pw.println(); pw.decreaseIndent(); } Loading Loading @@ -1814,10 +1843,28 @@ class AlarmManagerService extends SystemService { sourcePackage, sourceUserId, mInjector.getElapsedRealtime()); // Quota deferring implementation: boolean deferred = false; final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage, sourceUserId); if (standbyBucket == UsageStatsManager.STANDBY_BUCKET_RESTRICTED) { // Special case because it's 1/day instead of 1/hour. // AppWakeupHistory doesn't delete old wakeup times until a new one is logged, so we // should always have the last wakeup available. if (wakeupsInWindow > 0) { final long lastWakeupTime = mAppWakeupHistory.getNthLastWakeupForPackage( sourcePackage, sourceUserId, mConstants.APP_STANDBY_RESTRICTED_QUOTA); if (mInjector.getElapsedRealtime() - lastWakeupTime < mConstants.APP_STANDBY_RESTRICTED_WINDOW) { final long minElapsed = lastWakeupTime + mConstants.APP_STANDBY_RESTRICTED_WINDOW; if (alarm.expectedWhenElapsed < minElapsed) { alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; deferred = true; } } } } else { final int quotaForBucket = getQuotaForBucketLocked(standbyBucket); boolean deferred = false; if (wakeupsInWindow >= quotaForBucket) { final long minElapsed; if (quotaForBucket <= 0) { Loading @@ -1826,8 +1873,8 @@ class AlarmManagerService extends SystemService { } else { // Suppose the quota for window was q, and the qth last delivery time for this // package was t(q) then the next delivery must be after t(q) + <window_size> final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage, sourceUserId, quotaForBucket); final long t = mAppWakeupHistory.getNthLastWakeupForPackage( sourcePackage, sourceUserId, quotaForBucket); minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW; } if (alarm.expectedWhenElapsed < minElapsed) { Loading @@ -1835,6 +1882,7 @@ class AlarmManagerService extends SystemService { deferred = true; } } } if (!deferred) { // Restore original requirements in case they were changed earlier. alarm.whenElapsed = alarm.expectedWhenElapsed; Loading services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +44 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; Loading @@ -45,6 +46,7 @@ import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL; import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY; import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL; import static com.android.server.AlarmManagerService.IS_WAKEUP_MASK; import static com.android.server.AlarmManagerService.MILLIS_IN_DAY; import static com.android.server.AlarmManagerService.TIME_CHANGED_MASK; import static com.android.server.AlarmManagerService.WORKING_INDEX; Loading Loading @@ -107,6 +109,7 @@ public class AlarmManagerServiceTest { private static final String TEST_CALLING_PACKAGE = "com.android.framework.test-package"; private static final int SYSTEM_UI_UID = 123456789; private static final int TEST_CALLING_UID = 12345; private static final long RESTRICTED_WINDOW_MS = MILLIS_IN_DAY; private long mAppStandbyWindow; private AlarmManagerService mService; Loading Loading @@ -485,13 +488,13 @@ public class AlarmManagerServiceTest { anyLong())).thenReturn(standbyBucket); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } // This one should get deferred on set setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent()); final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); Loading @@ -503,11 +506,11 @@ public class AlarmManagerServiceTest { anyLong())).thenReturn(standbyBucket); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); } // This one should get deferred after the latest alarm expires setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent()); for (int i = 0; i < quota; i++) { mNowElapsedTest = mTestTimer.getElapsed(); Loading Loading @@ -596,6 +599,43 @@ public class AlarmManagerServiceTest { testQuotasNoDeferral(STANDBY_BUCKET_RARE); } @Test public void testRestrictedBucketAlarmsDeferredOnSet() throws Exception { when(mUsageStatsManagerInternal .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())) .thenReturn(STANDBY_BUCKET_RESTRICTED); // This one should go off final long firstTrigger = mNowElapsedTest + 10; setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger, getNewMockPendingIntent()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); // This one should get deferred on set setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + 1, getNewMockPendingIntent()); final long expectedNextTrigger = firstTrigger + mService.mConstants.APP_STANDBY_RESTRICTED_WINDOW; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testRestrictedBucketAlarmsDeferredOnExpiration() throws Exception { when(mUsageStatsManagerInternal .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())) .thenReturn(STANDBY_BUCKET_RESTRICTED); // This one should go off final long firstTrigger = mNowElapsedTest + 10; setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger, getNewMockPendingIntent()); // This one should get deferred after the latest alarm expires setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + 1, getNewMockPendingIntent()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); final long expectedNextTrigger = firstTrigger + mService.mConstants.APP_STANDBY_RESTRICTED_WINDOW; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } private void assertAndHandleBucketChanged(int bucket) { when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(bucket); Loading Loading
services/core/java/com/android/server/AlarmManagerService.java +67 −19 Original line number Diff line number Diff line Loading @@ -331,12 +331,16 @@ class AlarmManagerService extends SystemService { return (history == null) ? 0 : history.size(); } long getLastWakeupForPackage(String packageName, int userId, int positionFromEnd) { /** * @param n The desired nth-last wakeup * (1=1st-last=the ultimate wakeup and 2=2nd-last=the penultimate wakeup) */ long getNthLastWakeupForPackage(String packageName, int userId, int n) { final LongArrayQueue history = mPackageHistory.get(Pair.create(packageName, userId)); if (history == null) { return 0; } final int i = history.size() - positionFromEnd; final int i = history.size() - n; return (i < 0) ? 0 : history.get(i); } Loading Loading @@ -400,6 +404,11 @@ class AlarmManagerService extends SystemService { "standby_rare_quota", "standby_never_quota", }; // Not putting this in the KEYS_APP_STANDBY_QUOTAS array because this uses a different // window size. private static final String KEY_APP_STANDBY_RESTRICTED_QUOTA = "standby_restricted_quota"; private static final String KEY_APP_STANDBY_RESTRICTED_WINDOW = "app_standby_restricted_window"; private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; Loading @@ -420,6 +429,8 @@ class AlarmManagerService extends SystemService { 1, // Rare 0 // Never }; private static final int DEFAULT_APP_STANDBY_RESTRICTED_QUOTA = 1; private static final long DEFAULT_APP_STANDBY_RESTRICTED_WINDOW = MILLIS_IN_DAY; // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; Loading @@ -446,6 +457,8 @@ class AlarmManagerService extends SystemService { public long APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW; public int[] APP_STANDBY_QUOTAS = new int[DEFAULT_APP_STANDBY_QUOTAS.length]; public int APP_STANDBY_RESTRICTED_QUOTA = DEFAULT_APP_STANDBY_RESTRICTED_QUOTA; public long APP_STANDBY_RESTRICTED_WINDOW = DEFAULT_APP_STANDBY_RESTRICTED_WINDOW; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -520,6 +533,14 @@ class AlarmManagerService extends SystemService { Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i])); } APP_STANDBY_RESTRICTED_QUOTA = Math.max(1, mParser.getInt(KEY_APP_STANDBY_RESTRICTED_QUOTA, DEFAULT_APP_STANDBY_RESTRICTED_QUOTA)); APP_STANDBY_RESTRICTED_WINDOW = Math.max(APP_STANDBY_WINDOW, mParser.getLong(KEY_APP_STANDBY_RESTRICTED_WINDOW, DEFAULT_APP_STANDBY_RESTRICTED_WINDOW)); MAX_ALARMS_PER_UID = mParser.getInt(KEY_MAX_ALARMS_PER_UID, DEFAULT_MAX_ALARMS_PER_UID); if (MAX_ALARMS_PER_UID < DEFAULT_MAX_ALARMS_PER_UID) { Loading Loading @@ -581,6 +602,14 @@ class AlarmManagerService extends SystemService { pw.println(APP_STANDBY_QUOTAS[i]); } pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA); pw.print("="); TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_QUOTA, pw); pw.println(); pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW); pw.print("="); TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw); pw.println(); pw.decreaseIndent(); } Loading Loading @@ -1814,10 +1843,28 @@ class AlarmManagerService extends SystemService { sourcePackage, sourceUserId, mInjector.getElapsedRealtime()); // Quota deferring implementation: boolean deferred = false; final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage, sourceUserId); if (standbyBucket == UsageStatsManager.STANDBY_BUCKET_RESTRICTED) { // Special case because it's 1/day instead of 1/hour. // AppWakeupHistory doesn't delete old wakeup times until a new one is logged, so we // should always have the last wakeup available. if (wakeupsInWindow > 0) { final long lastWakeupTime = mAppWakeupHistory.getNthLastWakeupForPackage( sourcePackage, sourceUserId, mConstants.APP_STANDBY_RESTRICTED_QUOTA); if (mInjector.getElapsedRealtime() - lastWakeupTime < mConstants.APP_STANDBY_RESTRICTED_WINDOW) { final long minElapsed = lastWakeupTime + mConstants.APP_STANDBY_RESTRICTED_WINDOW; if (alarm.expectedWhenElapsed < minElapsed) { alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; deferred = true; } } } } else { final int quotaForBucket = getQuotaForBucketLocked(standbyBucket); boolean deferred = false; if (wakeupsInWindow >= quotaForBucket) { final long minElapsed; if (quotaForBucket <= 0) { Loading @@ -1826,8 +1873,8 @@ class AlarmManagerService extends SystemService { } else { // Suppose the quota for window was q, and the qth last delivery time for this // package was t(q) then the next delivery must be after t(q) + <window_size> final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage, sourceUserId, quotaForBucket); final long t = mAppWakeupHistory.getNthLastWakeupForPackage( sourcePackage, sourceUserId, quotaForBucket); minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW; } if (alarm.expectedWhenElapsed < minElapsed) { Loading @@ -1835,6 +1882,7 @@ class AlarmManagerService extends SystemService { deferred = true; } } } if (!deferred) { // Restore original requirements in case they were changed earlier. alarm.whenElapsed = alarm.expectedWhenElapsed; Loading
services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +44 −4 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; Loading @@ -45,6 +46,7 @@ import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL; import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY; import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL; import static com.android.server.AlarmManagerService.IS_WAKEUP_MASK; import static com.android.server.AlarmManagerService.MILLIS_IN_DAY; import static com.android.server.AlarmManagerService.TIME_CHANGED_MASK; import static com.android.server.AlarmManagerService.WORKING_INDEX; Loading Loading @@ -107,6 +109,7 @@ public class AlarmManagerServiceTest { private static final String TEST_CALLING_PACKAGE = "com.android.framework.test-package"; private static final int SYSTEM_UI_UID = 123456789; private static final int TEST_CALLING_UID = 12345; private static final long RESTRICTED_WINDOW_MS = MILLIS_IN_DAY; private long mAppStandbyWindow; private AlarmManagerService mService; Loading Loading @@ -485,13 +488,13 @@ public class AlarmManagerServiceTest { anyLong())).thenReturn(standbyBucket); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } // This one should get deferred on set setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent()); final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); Loading @@ -503,11 +506,11 @@ public class AlarmManagerServiceTest { anyLong())).thenReturn(standbyBucket); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); } // This one should get deferred after the latest alarm expires setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent()); for (int i = 0; i < quota; i++) { mNowElapsedTest = mTestTimer.getElapsed(); Loading Loading @@ -596,6 +599,43 @@ public class AlarmManagerServiceTest { testQuotasNoDeferral(STANDBY_BUCKET_RARE); } @Test public void testRestrictedBucketAlarmsDeferredOnSet() throws Exception { when(mUsageStatsManagerInternal .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())) .thenReturn(STANDBY_BUCKET_RESTRICTED); // This one should go off final long firstTrigger = mNowElapsedTest + 10; setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger, getNewMockPendingIntent()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); // This one should get deferred on set setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + 1, getNewMockPendingIntent()); final long expectedNextTrigger = firstTrigger + mService.mConstants.APP_STANDBY_RESTRICTED_WINDOW; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testRestrictedBucketAlarmsDeferredOnExpiration() throws Exception { when(mUsageStatsManagerInternal .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())) .thenReturn(STANDBY_BUCKET_RESTRICTED); // This one should go off final long firstTrigger = mNowElapsedTest + 10; setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger, getNewMockPendingIntent()); // This one should get deferred after the latest alarm expires setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + 1, getNewMockPendingIntent()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); final long expectedNextTrigger = firstTrigger + mService.mConstants.APP_STANDBY_RESTRICTED_WINDOW; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } private void assertAndHandleBucketChanged(int bucket) { when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(bucket); Loading