/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.RTC; 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_WORKING_SET; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED; import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME; import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME; import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.AlarmManagerService.Constants.KEY_APP_STANDBY_QUOTAS_ENABLED; import static com.android.server.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT; 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.TIME_CHANGED_MASK; import static com.android.server.AlarmManagerService.WORKING_INDEX; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.atLeastOnce; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.IActivityManager; import android.app.IAlarmCompleteListener; import android.app.IAlarmListener; import android.app.IUidObserver; import android.app.PendingIntent; import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.annotations.GuardedBy; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import java.util.ArrayList; @Presubmit @RunWith(AndroidJUnit4.class) public class AlarmManagerServiceTest { private static final String TAG = AlarmManagerServiceTest.class.getSimpleName(); 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 long mAppStandbyWindow; private AlarmManagerService mService; private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener; @Mock private ContentResolver mMockResolver; @Mock private Context mMockContext; @Mock private IActivityManager mIActivityManager; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @Mock private AppStateTracker mAppStateTracker; @Mock private AlarmManagerService.ClockReceiver mClockReceiver; @Mock private PowerManager.WakeLock mWakeLock; private MockitoSession mMockingSession; private Injector mInjector; private volatile long mNowElapsedTest; private volatile long mNowRtcTest; @GuardedBy("mTestTimer") private TestTimer mTestTimer = new TestTimer(); static class TestTimer { private long mElapsed; boolean mExpired; private int mType; private int mFlags; // Flags used to decide what needs to be evaluated. synchronized long getElapsed() { return mElapsed; } synchronized void set(int type, long millisElapsed) { mType = type; mElapsed = millisElapsed; } synchronized int getType() { return mType; } synchronized int getFlags() { return mFlags; } synchronized void expire() throws InterruptedException { expire(IS_WAKEUP_MASK); // Default: evaluate eligibility of all alarms } synchronized void expire(int flags) throws InterruptedException { mFlags = flags; mExpired = true; notifyAll(); // Now wait for the alarm thread to finish execution. wait(); } } public class Injector extends AlarmManagerService.Injector { boolean mIsAutomotiveOverride; Injector(Context context) { super(context); } @Override void init() { // Do nothing. } @Override int waitForAlarm() { synchronized (mTestTimer) { mTestTimer.notifyAll(); if (!mTestTimer.mExpired) { try { mTestTimer.wait(); } catch (InterruptedException ie) { Log.e(TAG, "Wait interrupted!", ie); return 0; } } mTestTimer.mExpired = false; } return mTestTimer.getFlags(); } @Override void setKernelTimezone(int minutesWest) { // Do nothing. } @Override void setAlarm(int type, long millis) { mTestTimer.set(type, millis); } @Override void setKernelTime(long millis) { } @Override int getSystemUiUid() { return SYSTEM_UI_UID; } @Override boolean isAlarmDriverPresent() { // Pretend the driver is present, so code does not fall back to handler return true; } @Override long getElapsedRealtime() { return mNowElapsedTest; } @Override long getCurrentTimeMillis() { return mNowRtcTest; } @Override AlarmManagerService.ClockReceiver getClockReceiver(AlarmManagerService service) { return mClockReceiver; } @Override PowerManager.WakeLock getAlarmWakeLock() { return mWakeLock; } } @Before public final void setUp() throws Exception { mMockingSession = mockitoSession() .initMocks(this) .spyStatic(ActivityManager.class) .mockStatic(LocalServices.class) .spyStatic(Looper.class) .spyStatic(Settings.Global.class) .strictness(Strictness.WARN) .startMocking(); doReturn(mIActivityManager).when(ActivityManager::getService); doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class)); doReturn(null) .when(() -> LocalServices.getService(DeviceIdleController.LocalService.class)); doReturn(mUsageStatsManagerInternal).when( () -> LocalServices.getService(UsageStatsManagerInternal.class)); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong())) .thenReturn(STANDBY_BUCKET_ACTIVE); doReturn(Looper.getMainLooper()).when(Looper::myLooper); when(mMockContext.getContentResolver()).thenReturn(mMockResolver); doReturn("min_futurity=0,min_interval=0").when(() -> Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS)); mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); spyOn(mService); doNothing().when(mService).publishBinderService(any(), any()); mService.onStart(); spyOn(mService.mHandler); // Stubbing the handler. Test should simulate any handling of messages synchronously. doReturn(true).when(mService.mHandler).sendMessageAtTime(any(Message.class), anyLong()); assertEquals(mService.mSystemUiUid, SYSTEM_UI_UID); assertEquals(mService.mClockReceiver, mClockReceiver); assertEquals(mService.mWakeLock, mWakeLock); verify(mIActivityManager).registerUidObserver(any(IUidObserver.class), anyInt(), anyInt(), isNull()); // Other boot phases don't matter mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); assertEquals(0, mService.mConstants.MIN_FUTURITY); assertEquals(0, mService.mConstants.MIN_INTERVAL); mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; ArgumentCaptor captor = ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class); verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture()); mAppStandbyListener = captor.getValue(); } private void setTestAlarm(int type, long triggerTime, PendingIntent operation) { setTestAlarm(type, triggerTime, operation, 0, TEST_CALLING_UID); } private void setRepeatingTestAlarm(int type, long firstTrigger, long interval, PendingIntent pi) { setTestAlarm(type, firstTrigger, pi, interval, TEST_CALLING_UID); } private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval, int callingUid) { mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval, operation, null, "test", AlarmManager.FLAG_STANDALONE, null, null, callingUid, TEST_CALLING_PACKAGE); } private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, 0, null, listener, "test", AlarmManager.FLAG_STANDALONE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE); } private PendingIntent getNewMockPendingIntent() { return getNewMockPendingIntent(TEST_CALLING_UID); } private PendingIntent getNewMockPendingIntent(int mockUid) { final PendingIntent mockPi = mock(PendingIntent.class, Answers.RETURNS_DEEP_STUBS); when(mockPi.getCreatorUid()).thenReturn(mockUid); when(mockPi.getCreatorPackage()).thenReturn(TEST_CALLING_PACKAGE); return mockPi; } /** * Careful while calling as this will replace any existing settings for the calling test. */ private void setQuotasEnabled(boolean enabled) { final StringBuilder constantsBuilder = new StringBuilder(); constantsBuilder.append(KEY_MIN_FUTURITY); constantsBuilder.append("=0,"); // Capping active and working quotas to make testing feasible. constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]); constantsBuilder.append("=8,"); constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]); constantsBuilder.append("=5,"); if (!enabled) { constantsBuilder.append(KEY_APP_STANDBY_QUOTAS_ENABLED); constantsBuilder.append("=false,"); } doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS)); mService.mConstants.onChange(false, null); assertEquals(mService.mConstants.APP_STANDBY_QUOTAS_ENABLED, enabled); } @Test public void singleElapsedAlarmSet() { final long triggerTime = mNowElapsedTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi); assertEquals(triggerTime, mTestTimer.getElapsed()); } @Test public void singleRtcAlarmSet() { mNowElapsedTest = 54; mNowRtcTest = 1243; // arbitrary values of time final long triggerRtc = mNowRtcTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(RTC_WAKEUP, triggerRtc, alarmPi); final long triggerElapsed = triggerRtc - (mNowRtcTest - mNowElapsedTest); assertEquals(triggerElapsed, mTestTimer.getElapsed()); } @Test public void timeChangeMovesRtcAlarm() throws Exception { mNowElapsedTest = 42; mNowRtcTest = 4123; // arbitrary values of time final long triggerRtc = mNowRtcTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(RTC_WAKEUP, triggerRtc, alarmPi); final long triggerElapsed1 = mTestTimer.getElapsed(); final long timeDelta = -123; mNowRtcTest += timeDelta; mTestTimer.expire(TIME_CHANGED_MASK); final long triggerElapsed2 = mTestTimer.getElapsed(); assertEquals("Invalid movement of triggerElapsed following time change", triggerElapsed2, triggerElapsed1 - timeDelta); } @Test public void testSingleAlarmExpiration() throws Exception { final long triggerTime = mNowElapsedTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); final ArgumentCaptor onFinishedCaptor = ArgumentCaptor.forClass(PendingIntent.OnFinished.class); verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), onFinishedCaptor.capture(), any(Handler.class), isNull(), any()); verify(mWakeLock).acquire(); onFinishedCaptor.getValue().onSendFinished(alarmPi, null, 0, null, null); verify(mWakeLock).release(); } @Test public void testUpdateConstants() { final StringBuilder constantsBuilder = new StringBuilder(); constantsBuilder.append(KEY_MIN_FUTURITY); constantsBuilder.append("=5,"); constantsBuilder.append(KEY_MIN_INTERVAL); constantsBuilder.append("=10,"); constantsBuilder.append(KEY_MAX_INTERVAL); constantsBuilder.append("=15,"); constantsBuilder.append(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); constantsBuilder.append("=20,"); constantsBuilder.append(KEY_ALLOW_WHILE_IDLE_LONG_TIME); constantsBuilder.append("=25,"); constantsBuilder.append(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); constantsBuilder.append("=30,"); constantsBuilder.append(KEY_LISTENER_TIMEOUT); constantsBuilder.append("=35,"); doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS)); mService.mConstants.onChange(false, null); assertEquals(5, mService.mConstants.MIN_FUTURITY); assertEquals(10, mService.mConstants.MIN_INTERVAL); assertEquals(15, mService.mConstants.MAX_INTERVAL); assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_SHORT_TIME); assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_LONG_TIME); assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION); assertEquals(35, mService.mConstants.LISTENER_TIMEOUT); } @Test public void testMinFuturity() { doReturn("min_futurity=10").when(() -> Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS)); mService.mConstants.onChange(false, null); assertEquals(10, mService.mConstants.MIN_FUTURITY); final long triggerTime = mNowElapsedTest + 1; final long expectedTriggerTime = mNowElapsedTest + mService.mConstants.MIN_FUTURITY; setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, getNewMockPendingIntent()); assertEquals(expectedTriggerTime, mTestTimer.getElapsed()); } @FlakyTest(bugId = 130313408) @Test public void testEarliestAlarmSet() { final PendingIntent pi6 = getNewMockPendingIntent(); final PendingIntent pi8 = getNewMockPendingIntent(); final PendingIntent pi9 = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 8, pi8); assertEquals(mNowElapsedTest + 8, mTestTimer.getElapsed()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 9, pi9); assertEquals(mNowElapsedTest + 8, mTestTimer.getElapsed()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, pi6); assertEquals(mNowElapsedTest + 6, mTestTimer.getElapsed()); mService.removeLocked(pi6, null); assertEquals(mNowElapsedTest + 8, mTestTimer.getElapsed()); mService.removeLocked(pi8, null); assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed()); } @Test public void testStandbyBucketDelay_workingSet() throws Exception { setQuotasEnabled(false); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent()); assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed()); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); verify(mUsageStatsManagerInternal, atLeastOnce()) .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong()); final long expectedNextTrigger = mNowElapsedTest + mService.getMinDelayForBucketLocked(STANDBY_BUCKET_WORKING_SET); assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testStandbyBucketDelay_frequent() throws Exception { setQuotasEnabled(false); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent()); assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed()); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); verify(mUsageStatsManagerInternal, atLeastOnce()) .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong()); final long expectedNextTrigger = mNowElapsedTest + mService.getMinDelayForBucketLocked(STANDBY_BUCKET_FREQUENT); assertEquals("Incorrect next alarm trigger.", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testStandbyBucketDelay_rare() throws Exception { setQuotasEnabled(false); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, getNewMockPendingIntent()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, getNewMockPendingIntent()); assertEquals(mNowElapsedTest + 5, mTestTimer.getElapsed()); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_RARE); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); verify(mUsageStatsManagerInternal, atLeastOnce()) .getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong()); final long expectedNextTrigger = mNowElapsedTest + mService.getMinDelayForBucketLocked(STANDBY_BUCKET_RARE); assertEquals("Incorrect next alarm trigger.", expectedNextTrigger, mTestTimer.getElapsed()); } private void testQuotasDeferralOnSet(int standbyBucket) throws Exception { final int quota = mService.getQuotaForBucketLocked(standbyBucket); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(standbyBucket); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, getNewMockPendingIntent()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } // This one should get deferred on set setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, getNewMockPendingIntent()); final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } private void testQuotasDeferralOnExpiration(int standbyBucket) throws Exception { final int quota = mService.getQuotaForBucketLocked(standbyBucket); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(standbyBucket); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, getNewMockPendingIntent()); } // This one should get deferred after the latest alarm expires setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + quota + 10, getNewMockPendingIntent()); for (int i = 0; i < quota; i++) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } private void testQuotasNoDeferral(int standbyBucket) throws Exception { final int quota = mService.getQuotaForBucketLocked(standbyBucket); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(standbyBucket); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10 + i, getNewMockPendingIntent()); } // This delivery time maintains the quota invariant. Should not be deferred. final long expectedNextTrigger = firstTrigger + mAppStandbyWindow + 5; setTestAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent()); for (int i = 0; i < quota; i++) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testActiveQuota_deferredOnSet() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE); } @Test public void testActiveQuota_deferredOnExpiration() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE); } @Test public void testActiveQuota_notDeferred() throws Exception { setQuotasEnabled(true); testQuotasNoDeferral(STANDBY_BUCKET_ACTIVE); } @Test public void testWorkingQuota_deferredOnSet() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET); } @Test public void testWorkingQuota_deferredOnExpiration() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET); } @Test public void testWorkingQuota_notDeferred() throws Exception { setQuotasEnabled(true); testQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET); } @Test public void testFrequentQuota_deferredOnSet() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT); } @Test public void testFrequentQuota_deferredOnExpiration() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT); } @Test public void testFrequentQuota_notDeferred() throws Exception { setQuotasEnabled(true); testQuotasNoDeferral(STANDBY_BUCKET_FREQUENT); } @Test public void testRareQuota_deferredOnSet() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnSet(STANDBY_BUCKET_RARE); } @Test public void testRareQuota_deferredOnExpiration() throws Exception { setQuotasEnabled(true); testQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE); } @Test public void testRareQuota_notDeferred() throws Exception { setQuotasEnabled(true); testQuotasNoDeferral(STANDBY_BUCKET_RARE); } private void assertAndHandleBucketChanged(int bucket) { when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(bucket); mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE, UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0); final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture()); final Message lastMessage = messageCaptor.getValue(); assertEquals("Unexpected message send to handler", lastMessage.what, APP_STANDBY_BUCKET_CHANGED); mService.mHandler.handleMessage(lastMessage); } @Test public void testQuotaDowngrade() throws Exception { setQuotasEnabled(true); final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < workingQuota; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); } // No deferrals now. for (int i = 0; i < workingQuota - 1; i++) { mNowElapsedTest = mTestTimer.getElapsed(); assertEquals(firstTrigger + i, mNowElapsedTest); mTestTimer.expire(); } // The next upcoming alarm in queue should also be set as expected. assertEquals(firstTrigger + workingQuota - 1, mTestTimer.getElapsed()); // Downgrading the bucket now assertAndHandleBucketChanged(STANDBY_BUCKET_RARE); final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE); // The last alarm should now be deferred. final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota) + mAppStandbyWindow + 1; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testQuotaUpgrade() throws Exception { setQuotasEnabled(true); final int frequentQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_FREQUENT); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_FREQUENT); final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < frequentQuota + 1; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); if (i < frequentQuota) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } } // The last alarm should be deferred due to exceeding the quota final long deferredTrigger = firstTrigger + 1 + mAppStandbyWindow; assertEquals(deferredTrigger, mTestTimer.getElapsed()); // Upgrading the bucket now assertAndHandleBucketChanged(STANDBY_BUCKET_ACTIVE); // The last alarm should now be rescheduled to go as per original expectations final long originalTrigger = firstTrigger + frequentQuota; assertEquals("Incorrect next alarm trigger", originalTrigger, mTestTimer.getElapsed()); } private void assertAndHandleParoleChanged(boolean parole) { mAppStandbyListener.onParoleStateChanged(parole); final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture()); final Message lastMessage = messageCaptor.getValue(); assertEquals("Unexpected message send to handler", lastMessage.what, APP_STANDBY_PAROLE_CHANGED); mService.mHandler.handleMessage(lastMessage); } @Test public void testParole() throws Exception { setQuotasEnabled(true); final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(STANDBY_BUCKET_WORKING_SET); final long firstTrigger = mNowElapsedTest + 10; final int totalAlarms = workingQuota + 10; for (int i = 0; i < totalAlarms; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent()); } // Use up the quota, no deferrals expected. for (int i = 0; i < workingQuota; i++) { mNowElapsedTest = mTestTimer.getElapsed(); assertEquals(firstTrigger + i, mNowElapsedTest); mTestTimer.expire(); } // Any subsequent alarms in queue should all be deferred assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed()); // Paroling now assertAndHandleParoleChanged(true); // Subsequent alarms should now go off as per original expectations. for (int i = 0; i < 5; i++) { mNowElapsedTest = mTestTimer.getElapsed(); assertEquals(firstTrigger + workingQuota + i, mNowElapsedTest); mTestTimer.expire(); } // Come out of parole assertAndHandleParoleChanged(false); // Subsequent alarms should again get deferred final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testAlarmRestrictedInBatterSaver() throws Exception { final ArgumentCaptor listenerArgumentCaptor = ArgumentCaptor.forClass(AppStateTracker.Listener.class); verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture()); final PendingIntent alarmPi = getNewMockPendingIntent(); when(mAppStateTracker.areAlarmsRestricted(TEST_CALLING_UID, TEST_CALLING_PACKAGE, false)).thenReturn(true); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, alarmPi); assertEquals(mNowElapsedTest + 2, mTestTimer.getElapsed()); final SparseArray> restrictedAlarms = mService.mPendingBackgroundAlarms; assertNull(restrictedAlarms.get(TEST_CALLING_UID)); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); assertNotNull(restrictedAlarms.get(TEST_CALLING_UID)); listenerArgumentCaptor.getValue().unblockAlarmsForUid(TEST_CALLING_UID); verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); assertNull(restrictedAlarms.get(TEST_CALLING_UID)); } @Test public void sendsTimeTickOnInteractive() { final ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); // Stubbing so the handler doesn't actually run the runnable. doReturn(true).when(mService.mHandler).post(runnableCaptor.capture()); // change interactive state: false -> true mService.interactiveStateChangedLocked(false); mService.interactiveStateChangedLocked(true); runnableCaptor.getValue().run(); verify(mMockContext).sendBroadcastAsUser(mService.mTimeTickIntent, UserHandle.ALL); } @Test public void alarmCountKeyedOnCallingUid() { final int mockCreatorUid = 431412; setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 5, getNewMockPendingIntent(mockCreatorUid)); assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); assertEquals(-1, mService.mAlarmsPerUid.get(mockCreatorUid, -1)); } @Test public void alarmCountOnSetPi() { final int numAlarms = 103; final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; for (int i = 1; i <= numAlarms; i++) { setTestAlarm(types[i % 4], mNowElapsedTest + i, getNewMockPendingIntent()); assertEquals(i, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } } @Test public void alarmCountOnSetListener() { final int numAlarms = 103; final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; for (int i = 1; i <= numAlarms; i++) { setTestAlarmWithListener(types[i % 4], mNowElapsedTest + i, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } }); assertEquals(i, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } } @Test public void alarmCountOnExpirationPi() throws InterruptedException { final int numAlarms = 8; // This test is slow for (int i = 0; i < numAlarms; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent()); } int expired = 0; while (expired < numAlarms) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); expired++; assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } } @Test public void alarmCountOnExpirationListener() throws InterruptedException { final int numAlarms = 8; // This test is slow for (int i = 0; i < numAlarms; i++) { setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } }); } int expired = 0; while (expired < numAlarms) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); expired++; assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } } @Test public void alarmCountOnExceptionWhileSendingPi() throws Exception { final int numAlarms = 5; // This test is slow for (int i = 0; i < numAlarms; i++) { final PendingIntent pi = getNewMockPendingIntent(); doThrow(PendingIntent.CanceledException.class).when(pi).send(eq(mMockContext), eq(0), any(), any(), any(), any(), any()); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, pi); } int expired = 0; while (expired < numAlarms) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); expired++; assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } } @Test public void alarmCountOnExceptionWhileCallingListener() throws Exception { final int numAlarms = 5; // This test is slow for (int i = 0; i < numAlarms; i++) { final IAlarmListener listener = new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { throw new RemoteException("For testing behavior on exception"); } }; setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10, listener); } int expired = 0; while (expired < numAlarms) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); expired++; assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } } @Test public void alarmCountForRepeatingAlarms() throws Exception { final long interval = 1231; final long firstTrigger = mNowElapsedTest + 321; final PendingIntent pi = getNewMockPendingIntent(); setRepeatingTestAlarm(ELAPSED_REALTIME, firstTrigger, interval, pi); assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); for (int i = 0; i < 5; i++) { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } doThrow(PendingIntent.CanceledException.class).when(pi).send(eq(mMockContext), eq(0), any(), any(), any(), any(), any()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); assertEquals(-1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, -1)); } @Test public void alarmCountOnUidRemoved() { final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent()); } assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); mService.removeLocked(TEST_CALLING_UID); assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } @Test public void alarmCountOnPackageRemoved() { final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent()); } assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); mService.removeLocked(TEST_CALLING_PACKAGE); assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } @Test public void alarmCountOnUserRemoved() { final int mockUserId = 15; final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { int mockUid = UserHandle.getUid(mockUserId, 1234 + i); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent(mockUid), 0, mockUid); } assertEquals(numAlarms, mService.mAlarmsPerUid.size()); mService.removeUserLocked(mockUserId); assertEquals(0, mService.mAlarmsPerUid.size()); } @Test public void alarmCountOnRemoveFromPendingWhileIdle() { mService.mPendingIdleUntil = mock(AlarmManagerService.Alarm.class); final int numAlarms = 15; final PendingIntent[] pis = new PendingIntent[numAlarms]; for (int i = 0; i < numAlarms; i++) { pis[i] = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 5, pis[i]); } assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); assertEquals(numAlarms, mService.mPendingWhileIdleAlarms.size()); final int toRemove = 8; for (int i = 0; i < toRemove; i++) { mService.removeLocked(pis[i], null); assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } mService.removeLocked(TEST_CALLING_UID); assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } @Test public void alarmCountOnAlarmRemoved() { final int numAlarms = 10; final PendingIntent[] pis = new PendingIntent[numAlarms]; for (int i = 0; i < numAlarms; i++) { pis[i] = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 5, pis[i]); } assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); for (int i = 0; i < numAlarms; i++) { mService.removeLocked(pis[i], null); assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } } @Test public void alarmTypes() throws Exception { final int[] typesToSet = {ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME, RTC_WAKEUP, RTC}; final int[] typesExpected = {ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; assertAlarmTypeConversion(typesToSet, typesExpected); } private void assertAlarmTypeConversion(int[] typesToSet, int[] typesExpected) throws Exception { for (int i = 0; i < typesToSet.length; i++) { setTestAlarm(typesToSet[i], 1234, getNewMockPendingIntent()); final int typeSet = mTestTimer.getType(); assertEquals("Alarm of type " + typesToSet[i] + " was set to type " + typeSet, typesExpected[i], typeSet); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } } @Test public void alarmCountOnInvalidSet() { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 12345, null); assertEquals(-1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, -1)); } @Test public void alarmCountOnPendingIntentCancel() { final PendingIntent pi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 123, pi); verify(pi).registerCancelListener(mService.mOperationCancelListener); assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); mService.mOperationCancelListener.onCancelled(pi); assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } @After public void tearDown() { if (mMockingSession != null) { mMockingSession.finishMocking(); } } }