/* * 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.alarm; import static android.Manifest.permission.SCHEDULE_EXACT_ALARM; import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_COMPAT; import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; import static android.app.AlarmManager.FLAG_IDLE_UNTIL; import static android.app.AlarmManager.FLAG_PRIORITIZE; import static android.app.AlarmManager.FLAG_STANDALONE; import static android.app.AlarmManager.FLAG_WAKE_FROM_IDLE; import static android.app.AlarmManager.RTC; import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.AlarmManager.WINDOW_EXACT; import static android.app.AlarmManager.WINDOW_HEURISTIC; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM; 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 android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; 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.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.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_ALLOW_LIST; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_LISTENER; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION; import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_POLICY_PERMISSION; import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED; 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; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_DEVICE_IDLE_FUZZ; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY; 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; import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; import static com.android.server.alarm.AlarmManagerService.RemovedAlarm.REMOVE_REASON_UNDEFINED; import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK; import static com.android.server.alarm.AlarmManagerService.WORKING_INDEX; import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE; import static com.android.server.alarm.Constants.TEST_CALLING_UID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; 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; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.IAlarmCompleteListener; import android.app.IAlarmListener; import android.app.IAlarmManager; import android.app.PendingIntent; import android.app.compat.CompatChanges; import android.app.tare.EconomyManager; import android.app.usage.UsageStatsManagerInternal; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.PermissionChecker; import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.BatteryManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerExemptionManager; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.Settings; import android.text.format.DateFormat; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.MockedVoidMethod; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.util.ArrayUtils; import com.android.server.AlarmManagerInternal; import com.android.server.AppStateTracker; import com.android.server.AppStateTrackerImpl; import com.android.server.DeviceIdleInternal; import com.android.server.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.SystemClockTime.TimeConfidence; import com.android.server.SystemService; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.tare.AlarmManagerEconomicPolicy; import com.android.server.tare.EconomyManagerInternal; import com.android.server.usage.AppStandbyInternal; import libcore.util.EmptyArray; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.LongConsumer; @Presubmit @SuppressWarnings("GuardedBy") // This test enforces synchronous behavior. @RunWith(AndroidJUnit4.class) public final class AlarmManagerServiceTest { private static final String TAG = AlarmManagerServiceTest.class.getSimpleName(); private static final int SYSTEM_UI_UID = 12345; private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID); private static final int TEST_CALLING_UID_2 = TEST_CALLING_UID + 1; private static final int[] ALARM_TYPES = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME}; private long mAppStandbyWindow; private long mAllowWhileIdleWindow; private AlarmManagerService mService; private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener; private AppStateTrackerImpl.Listener mListener; private AlarmManagerService.UninstallReceiver mPackageChangesReceiver; private AlarmManagerService.ChargingReceiver mChargingReceiver; private IAppOpsCallback mIAppOpsCallback; private IAlarmManager mBinder; @Mock private Context mMockContext; @Mock private ContentResolver mContentResolver; @Mock private IActivityManager mIActivityManager; @Mock private IAppOpsService mIAppOpsService; @Mock private AppOpsManager mAppOpsManager; @Mock private BatteryManager mBatteryManager; @Mock private DeviceIdleInternal mDeviceIdleInternal; @Mock private PermissionManagerServiceInternal mPermissionManagerInternal; @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; @Mock private AppStandbyInternal mAppStandbyInternal; @Mock private ActivityManagerInternal mActivityManagerInternal; @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private AppStateTrackerImpl mAppStateTracker; @Mock private AlarmManagerService.ClockReceiver mClockReceiver; @Mock private PowerManager.WakeLock mWakeLock; @Mock private EconomyManagerInternal mEconomyManagerInternal; @Mock DeviceConfig.Properties mDeviceConfigProperties; HashSet mDeviceConfigKeys = new HashSet<>(); private Injector mInjector; private volatile long mNowElapsedTest; private volatile long mNowRtcTest; private volatile int mTestCallingUid = TEST_CALLING_UID; @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 { 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 setKernelTimeZoneOffset(int utcOffsetMillis) { // Do nothing. } @Override void syncKernelTimeZoneOffset() { // Do nothing. } @Override int getCallingUid() { return mTestCallingUid; } @Override void setAlarm(int type, long millis) { mTestTimer.set(type, millis); } @Override int getSystemUiUid(PackageManagerInternal unused) { return SYSTEM_UI_UID; } @Override boolean isAlarmDriverPresent() { // Pretend the driver is present, so code does not fall back to handler return true; } @Override void initializeTimeIfRequired() { // No-op } @Override void setCurrentTimeMillis(long unixEpochMillis, @TimeConfidence int confidence, String logMsg) { mNowRtcTest = unixEpochMillis; } @Override long getElapsedRealtimeMillis() { return mNowElapsedTest; } @Override long getCurrentTimeMillis() { return mNowRtcTest; } @Override AlarmManagerService.ClockReceiver getClockReceiver(AlarmManagerService service) { return mClockReceiver; } @Override PowerManager.WakeLock getAlarmWakeLock() { return mWakeLock; } @Override void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) { // Do nothing. // The tests become flaky with an error message of // "IllegalStateException: Querying activity state off main thread is not allowed." // when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener(). } @Override IAppOpsService getAppOpsService() { return mIAppOpsService; } } @Rule public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) .setStrictness(Strictness.WARN) .spyStatic(ActivityManager.class) .mockStatic(CompatChanges.class) .spyStatic(DateFormat.class) .spyStatic(DeviceConfig.class) .mockStatic(LocalServices.class) .spyStatic(Looper.class) .mockStatic(MetricsHelper.class) .mockStatic(PermissionChecker.class) .mockStatic(PermissionManagerService.class) .mockStatic(ServiceManager.class) .mockStatic(Settings.Global.class) .mockStatic(SystemProperties.class) .spyStatic(UserHandle.class) .afterSessionFinished( () -> LocalServices.removeServiceForTest(AlarmManagerInternal.class)) .build(); @Before public final void setUp() { doReturn(mIActivityManager).when(ActivityManager::getService); doReturn(mDeviceIdleInternal).when( () -> LocalServices.getService(DeviceIdleInternal.class)); doReturn(mEconomyManagerInternal).when( () -> LocalServices.getService(EconomyManagerInternal.class)); doReturn(mPermissionManagerInternal).when( () -> LocalServices.getService(PermissionManagerServiceInternal.class)); doReturn(mActivityManagerInternal).when( () -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mPackageManagerInternal).when( () -> LocalServices.getService(PackageManagerInternal.class)); doReturn(mAppStateTracker).when(() -> LocalServices.getService(AppStateTracker.class)); doReturn(mAppStandbyInternal).when( () -> LocalServices.getService(AppStandbyInternal.class)); doReturn(mUsageStatsManagerInternal).when( () -> LocalServices.getService(UsageStatsManagerInternal.class)); doCallRealMethod().when((MockedVoidMethod) () -> LocalServices.addService(eq(AlarmManagerInternal.class), any())); doCallRealMethod().when(() -> LocalServices.getService(AlarmManagerInternal.class)); doReturn(false).when(() -> UserHandle.isCore(anyInt())); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), eq(TEST_CALLING_USER), anyLong())).thenReturn(STANDBY_BUCKET_ACTIVE); doReturn(Looper.getMainLooper()).when(Looper::myLooper); when(mMockContext.getContentResolver()).thenReturn(mContentResolver); doReturn(mDeviceConfigKeys).when(mDeviceConfigProperties).getKeyset(); when(mDeviceConfigProperties.getLong(anyString(), anyLong())) .thenAnswer((Answer) invocationOnMock -> { Object[] args = invocationOnMock.getArguments(); return (Long) args[1]; }); when(mDeviceConfigProperties.getInt(anyString(), anyInt())) .thenAnswer((Answer) invocationOnMock -> { Object[] args = invocationOnMock.getArguments(); return (Integer) args[1]; }); doAnswer((Answer) invocationOnMock -> null) .when(() -> DeviceConfig.addOnPropertiesChangedListener( anyString(), any(Executor.class), any(DeviceConfig.OnPropertiesChangedListener.class))); doReturn(mDeviceConfigProperties).when( () -> DeviceConfig.getProperties( eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.any())); // Needed to ensure logging doesn't cause tests to fail. doReturn(true) .when(() -> DateFormat.is24HourFormat(eq(mMockContext), anyInt())); doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( () -> PermissionChecker.checkPermissionForPreflight(any(), eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), anyInt(), anyString())); doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when( () -> PermissionChecker.checkPermissionForPreflight(any(), eq(SCHEDULE_EXACT_ALARM), anyInt(), anyInt(), anyString())); when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager); when(mMockContext.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager); registerAppIds(new String[]{TEST_CALLING_PACKAGE}, new Integer[]{UserHandle.getAppId(TEST_CALLING_UID)}); when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(EmptyArray.STRING); mInjector = new Injector(mMockContext); mService = new AlarmManagerService(mMockContext, mInjector); spyOn(mService); mService.onStart(); // Unable to mock mMockContext to return a mock stats manager. // So just mocking the whole MetricsHelper instance. mService.mMetricsHelper = mock(MetricsHelper.class); 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); // Other boot phases don't matter mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); verify(mBatteryManager).isCharging(); setTareEnabled(EconomyManager.ENABLED_MODE_OFF); mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW; mAllowWhileIdleWindow = mService.mConstants.ALLOW_WHILE_IDLE_WINDOW; ArgumentCaptor idleListenerCaptor = ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class); verify(mAppStandbyInternal).addListener(idleListenerCaptor.capture()); mAppStandbyListener = idleListenerCaptor.getValue(); final ArgumentCaptor trackerListenerCaptor = ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class); verify(mAppStateTracker).addListener(trackerListenerCaptor.capture()); mListener = trackerListenerCaptor.getValue(); final ArgumentCaptor chargingReceiverCaptor = ArgumentCaptor.forClass(AlarmManagerService.ChargingReceiver.class); verify(mMockContext).registerReceiver(chargingReceiverCaptor.capture(), argThat((filter) -> filter.hasAction(BatteryManager.ACTION_CHARGING) && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); mChargingReceiver = chargingReceiverCaptor.getValue(); final ArgumentCaptor packageReceiverCaptor = ArgumentCaptor.forClass(AlarmManagerService.UninstallReceiver.class); verify(mMockContext).registerReceiverForAllUsers(packageReceiverCaptor.capture(), argThat((filter) -> filter.hasAction(Intent.ACTION_PACKAGE_ADDED) && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)), isNull(), isNull()); mPackageChangesReceiver = packageReceiverCaptor.getValue(); assertEquals(mService.mExactAlarmCandidates, Collections.emptySet()); ArgumentCaptor binderCaptor = ArgumentCaptor.forClass(IBinder.class); verify(() -> ServiceManager.addService(eq(Context.ALARM_SERVICE), binderCaptor.capture(), anyBoolean(), anyInt())); mBinder = IAlarmManager.Stub.asInterface(binderCaptor.getValue()); ArgumentCaptor appOpsCallbackCaptor = ArgumentCaptor.forClass( IAppOpsCallback.class); try { verify(mIAppOpsService).startWatchingMode(eq(OP_SCHEDULE_EXACT_ALARM), isNull(), appOpsCallbackCaptor.capture()); } catch (RemoteException e) { // Not expected on a mock. } mIAppOpsCallback = appOpsCallbackCaptor.getValue(); setTestableQuotas(); } private void setTestAlarm(int type, long triggerTime, PendingIntent operation) { setTestAlarm(type, triggerTime, operation, 0, FLAG_STANDALONE, TEST_CALLING_UID); } private void setRepeatingTestAlarm(int type, long firstTrigger, long interval, PendingIntent pi) { setTestAlarm(type, firstTrigger, pi, interval, FLAG_STANDALONE, TEST_CALLING_UID); } private void setIdleUntilAlarm(int type, long triggerTime, PendingIntent pi) { setTestAlarm(type, triggerTime, pi, 0, FLAG_IDLE_UNTIL | FLAG_STANDALONE, TEST_CALLING_UID); } private void setWakeFromIdle(int type, long triggerTime, PendingIntent pi) { // Note: Only alarm clock alarms are allowed to include this flag in the actual service. // But this is a unit test so we'll only test the flag for granularity and convenience. setTestAlarm(type, triggerTime, pi, 0, FLAG_WAKE_FROM_IDLE | FLAG_STANDALONE, TEST_CALLING_UID); } private void setPrioritizedAlarm(int type, long triggerTime, long windowLength, IAlarmListener listener) { mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test", FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null, 0); } private void setAllowWhileIdleAlarm(int type, long triggerTime, PendingIntent pi, 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); } private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval, int flags, int callingUid) { setTestAlarm(type, triggerTime, 0, operation, interval, flags, callingUid, null); } private void setTestAlarm(int type, long triggerTime, long windowLength, PendingIntent operation, long interval, int flags, int callingUid, Bundle idleOptions) { setTestAlarm(type, triggerTime, windowLength, operation, interval, flags, callingUid, TEST_CALLING_PACKAGE, idleOptions); } private void setTestAlarm(int type, long triggerTime, long windowLength, PendingIntent operation, long interval, int flags, int callingUid, String callingPackage, Bundle idleOptions) { mService.setImpl(type, triggerTime, windowLength, interval, operation, null, "test", flags, null, null, callingUid, callingPackage, idleOptions, 0); } private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) { setTestAlarmWithListener(type, triggerTime, listener, WINDOW_EXACT, TEST_CALLING_UID); } private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener, long windowLength, int callingUid) { mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test", FLAG_STANDALONE, null, null, callingUid, TEST_CALLING_PACKAGE, null, 0); } private PendingIntent getNewMockPendingIntent() { return getNewMockPendingIntent(false); } private PendingIntent getNewMockPendingIntent(boolean isActivity) { return getNewMockPendingIntent(TEST_CALLING_UID, TEST_CALLING_PACKAGE, isActivity); } private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage) { return getNewMockPendingIntent(creatorUid, creatorPackage, false); } private PendingIntent getNewMockPendingIntent(int creatorUid, String creatorPackage, boolean isActivity) { final PendingIntent mockPi = mock(PendingIntent.class, Answers.RETURNS_DEEP_STUBS); when(mockPi.getCreatorUid()).thenReturn(creatorUid); when(mockPi.getCreatorPackage()).thenReturn(creatorPackage); when(mockPi.isActivity()).thenReturn(isActivity); return mockPi; } private IAlarmListener getNewListener(Runnable onAlarm) { return new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { onAlarm.run(); } }; } private void setDeviceConfigInt(String key, int val) { mDeviceConfigKeys.add(key); doReturn(val).when(mDeviceConfigProperties).getInt(eq(key), anyInt()); mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } private void setDeviceConfigLong(String key, long val) { mDeviceConfigKeys.add(key); doReturn(val).when(mDeviceConfigProperties).getLong(eq(key), anyLong()); mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } private void setDeviceConfigBoolean(String key, boolean val) { mDeviceConfigKeys.add(key); doReturn(val).when(mDeviceConfigProperties).getBoolean(eq(key), anyBoolean()); mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } private void setDeviceConfigString(String key, String val) { mDeviceConfigKeys.add(key); doReturn(val).when(mDeviceConfigProperties).getString(eq(key), anyString()); mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } private void setTareEnabled(int enabledMode) { when(mEconomyManagerInternal.getEnabledMode(eq(AlarmManagerEconomicPolicy.POLICY_ALARM))) .thenReturn(enabledMode); mService.mConstants.onTareEnabledModeChanged(enabledMode); } /** * Lowers quotas to make testing feasible. Careful while calling as this will replace any * existing settings for the calling test. */ private void setTestableQuotas() { setDeviceConfigLong(KEY_MIN_FUTURITY, 0); 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) .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]), anyInt()); doReturn(20).when(mDeviceConfigProperties) .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[FREQUENT_INDEX]), anyInt()); mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } @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 testAlarmBroadcastOption() 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); final ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(Bundle.class); verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), onFinishedCaptor.capture(), any(Handler.class), isNull(), optionsCaptor.capture()); final BroadcastOptions options = new BroadcastOptions(optionsCaptor.getValue()); assertTrue(options.isAlarmBroadcast()); } @Test public void testUpdateConstants() { setDeviceConfigLong(KEY_MIN_FUTURITY, 5); setDeviceConfigLong(KEY_MIN_INTERVAL, 10); setDeviceConfigLong(KEY_MAX_INTERVAL, 15); setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20); setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 25); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WINDOW, 30); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW, 35); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 40); setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 45); setDeviceConfigLong(KEY_MIN_WINDOW, 50); 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); assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA); assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WINDOW); assertEquals(35, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW); assertEquals(40, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION); assertEquals(45, mService.mConstants.LISTENER_TIMEOUT); assertEquals(50, mService.mConstants.MIN_WINDOW); 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 public void updatingExactAlarmDenyList() { ArraySet denyListed = new ArraySet<>(new String[]{ "com.example.package1", "com.example.package2", "com.example.package3", }); setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "com.example.package1,com.example.package2,com.example.package3"); assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); denyListed = new ArraySet<>(new String[]{ "com.example.package1", "com.example.package4", }); setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "com.example.package1,com.example.package4"); assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size()); } @Test public void exactAlarmDenyListMaxSize() { final ArraySet expectedSet = new ArraySet<>(); final StringBuilder sb = new StringBuilder("package1"); expectedSet.add("package1"); for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) { sb.append(",package"); sb.append(i); if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) { expectedSet.add("package" + i); } } assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size()); setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString()); assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST); } @Test public void positiveWhileIdleQuotas() { setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3); assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 0); assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, -8); assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA); setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA, 0); assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_QUOTA); } @Test public void whileIdleWindowsDontExceedAnHour() { setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WINDOW, AlarmManager.INTERVAL_DAY); assertEquals(AlarmManager.INTERVAL_HOUR, mService.mConstants.ALLOW_WHILE_IDLE_WINDOW); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WINDOW, AlarmManager.INTERVAL_HOUR + 1); assertEquals(AlarmManager.INTERVAL_HOUR, mService.mConstants.ALLOW_WHILE_IDLE_WINDOW); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW, AlarmManager.INTERVAL_DAY); assertEquals(AlarmManager.INTERVAL_HOUR, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_COMPAT_WINDOW, AlarmManager.INTERVAL_HOUR + 1); assertEquals(AlarmManager.INTERVAL_HOUR, mService.mConstants.ALLOW_WHILE_IDLE_COMPAT_WINDOW); } @Test public void deviceIdleFuzzRangeNonNegative() { final long newMinFuzz = mService.mConstants.MAX_DEVICE_IDLE_FUZZ + 1542; final long newMaxFuzz = mService.mConstants.MIN_DEVICE_IDLE_FUZZ - 131; setDeviceConfigLong(KEY_MIN_DEVICE_IDLE_FUZZ, newMinFuzz); assertTrue("Negative device-idle fuzz range", mService.mConstants.MAX_DEVICE_IDLE_FUZZ >= mService.mConstants.MIN_DEVICE_IDLE_FUZZ); setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, newMaxFuzz); assertTrue("Negative device-idle fuzz range", mService.mConstants.MAX_DEVICE_IDLE_FUZZ >= mService.mConstants.MIN_DEVICE_IDLE_FUZZ); } @Test public void testMinFuturity() { setDeviceConfigLong(KEY_MIN_FUTURITY, 10L); 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()); } @Test public void testMinFuturityCoreUid() { setDeviceConfigLong(KEY_MIN_FUTURITY, 10L); assertEquals(10, mService.mConstants.MIN_FUTURITY); final long triggerTime = mNowElapsedTest + 1; doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID)); final long expectedTriggerTime = triggerTime; setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, getNewMockPendingIntent()); assertEquals(expectedTriggerTime, mTestTimer.getElapsed()); } @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, REMOVE_REASON_UNDEFINED); assertEquals(mNowElapsedTest + 8, mTestTimer.getElapsed()); mService.removeLocked(pi8, null, REMOVE_REASON_UNDEFINED); assertEquals(mNowElapsedTest + 9, mTestTimer.getElapsed()); } private void testQuotasDeferralOnSet(LongConsumer alarmSetter, int quota, long window) throws Exception { final long firstTrigger = mNowElapsedTest + 10; 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 alarmSetter.accept(firstTrigger + quota); final long expectedNextTrigger = firstTrigger + window; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } private void testQuotasDeferralOnExpiration(LongConsumer alarmSetter, int quota, long window) throws Exception { final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { alarmSetter.accept(firstTrigger + i); } // This one should get deferred after the latest alarm expires. 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; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } private void testQuotasNoDeferral(LongConsumer alarmSetter, int quota, long window) throws Exception { final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { alarmSetter.accept(firstTrigger + i); } // This delivery time maintains the quota invariant. Should not be deferred. final long expectedNextTrigger = firstTrigger + window + 5; 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()); } private void testStandbyQuotasDeferralOnSet(int standbyBucket) throws Exception { final int quota = mService.getQuotaForBucketLocked(standbyBucket); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(standbyBucket); testQuotasDeferralOnSet(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent()), quota, mAppStandbyWindow); } private void testStandbyQuotasDeferralOnExpiration(int standbyBucket) throws Exception { final int quota = mService.getQuotaForBucketLocked(standbyBucket); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(standbyBucket); testQuotasDeferralOnExpiration(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent()), quota, mAppStandbyWindow); } private void testStandbyQuotasNoDeferral(int standbyBucket) throws Exception { final int quota = mService.getQuotaForBucketLocked(standbyBucket); when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(), anyLong())).thenReturn(standbyBucket); testQuotasNoDeferral(trigger -> setTestAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent()), quota, mAppStandbyWindow); } @Test public void testActiveQuota_deferredOnSet() throws Exception { testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_ACTIVE); } @Test public void testActiveQuota_deferredOnExpiration() throws Exception { testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_ACTIVE); } @Test public void testActiveQuota_notDeferred() throws Exception { testStandbyQuotasNoDeferral(STANDBY_BUCKET_ACTIVE); } @Test public void testWorkingQuota_deferredOnSet() throws Exception { testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_WORKING_SET); } @Test public void testWorkingQuota_deferredOnExpiration() throws Exception { testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_WORKING_SET); } @Test public void testWorkingQuota_notDeferred() throws Exception { testStandbyQuotasNoDeferral(STANDBY_BUCKET_WORKING_SET); } @Test public void testFrequentQuota_deferredOnSet() throws Exception { testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_FREQUENT); } @Test public void testFrequentQuota_deferredOnExpiration() throws Exception { testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_FREQUENT); } @Test public void testFrequentQuota_notDeferred() throws Exception { testStandbyQuotasNoDeferral(STANDBY_BUCKET_FREQUENT); } @Test public void testRareQuota_deferredOnSet() throws Exception { testStandbyQuotasDeferralOnSet(STANDBY_BUCKET_RARE); } @Test public void testRareQuota_deferredOnExpiration() throws Exception { testStandbyQuotasDeferralOnExpiration(STANDBY_BUCKET_RARE); } @Test public void testRareQuota_notDeferred() throws Exception { testStandbyQuotasNoDeferral(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); mAppStandbyListener.onAppIdleStateChanged(TEST_CALLING_PACKAGE, UserHandle.getUserId(TEST_CALLING_UID), false, bucket, 0); assertAndHandleMessageSync(APP_STANDBY_BUCKET_CHANGED); } private void assertAndHandleMessageSync(int what) { final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); verify(mService.mHandler, atLeastOnce()).sendMessageAtTime(messageCaptor.capture(), anyLong()); Message expectedMessage = null; final ArrayList sentMessages = new ArrayList<>(); for (Message sentMessage : messageCaptor.getAllValues()) { if (sentMessage.what == what) { expectedMessage = sentMessage; } sentMessages.add(sentMessage.what); } assertNotNull("Unexpected messages sent to handler. Expected: " + what + ", sent: " + sentMessages, expectedMessage); mService.mHandler.handleMessage(expectedMessage); } @Test public void testStandbyQuotaDowngrade() throws Exception { 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; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testStandbyQuotaUpgrade() throws Exception { 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 + 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) { mChargingReceiver.onReceive(mMockContext, new Intent(parole ? BatteryManager.ACTION_CHARGING : BatteryManager.ACTION_DISCHARGING)); assertAndHandleMessageSync(CHARGING_STATUS_CHANGED); assertEquals(parole, mService.mAppStandbyParole); } @Test public void testCharging() throws Exception { 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, 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) + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @Test public void testAlarmRestrictedByFAS() throws Exception { final PendingIntent alarmPi = getNewMockPendingIntent(); when(mAppStateTracker.areAlarmsRestricted(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).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)); mListener.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 alarmsRemovedOnAppStartModeDisabled() { final PendingIntent alarmPi1 = getNewMockPendingIntent(); final PendingIntent alarmPi2 = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, alarmPi1); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 4, alarmPi2); assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); when(mActivityManagerInternal.isAppStartModeDisabled(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); mListener.removeAlarmsForUid(TEST_CALLING_UID); assertEquals(0, mService.mAlarmsPerUid.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(); final ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL), isNull(), optionsCaptor.capture()); verifyTimeTickBroadcastOptions(optionsCaptor.getValue()); } @Test public void sendsTimeTickOnAlarmTrigger() throws Exception { 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()); mService.mTimeTickTrigger.doAlarm(mock(IAlarmCompleteListener.class)); runnableCaptor.getValue().run(); final ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mMockContext).sendBroadcastAsUser(eq(mService.mTimeTickIntent), eq(UserHandle.ALL), isNull(), optionsCaptor.capture()); verifyTimeTickBroadcastOptions(optionsCaptor.getValue()); } private void verifyTimeTickBroadcastOptions(Bundle actualOptionsBundle) { final BroadcastOptions actualOptions = new BroadcastOptions(actualOptionsBundle); assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT, actualOptions.getDeliveryGroupPolicy()); assertEquals(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE, actualOptions.getDeferralPolicy()); } @Test public void alarmCountKeyedOnCallingUid() { final int mockCreatorUid = 431412; setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 5, getNewMockPendingIntent(mockCreatorUid, TEST_CALLING_PACKAGE)); assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); assertEquals(-1, mService.mAlarmsPerUid.get(mockCreatorUid, -1)); } @Test public void alarmCountOnSetPi() { final int numAlarms = 103; for (int i = 1; i <= numAlarms; i++) { setTestAlarm(ALARM_TYPES[i % 4], mNowElapsedTest + i, getNewMockPendingIntent()); assertEquals(i, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } } @Test public void alarmCountOnSetListener() { final int numAlarms = 103; for (int i = 1; i <= numAlarms; i++) { setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i, getNewListener(() -> {})); 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, getNewListener(() -> {})); } 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 = getNewListener(() -> { throw new RuntimeException("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, REMOVE_REASON_UNDEFINED); 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, REMOVE_REASON_UNDEFINED); 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, TEST_CALLING_PACKAGE), 0, FLAG_STANDALONE, mockUid); } assertEquals(numAlarms, mService.mAlarmsPerUid.size()); mService.removeUserLocked(mockUserId); assertEquals(0, mService.mAlarmsPerUid.size()); } @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, REMOVE_REASON_UNDEFINED); assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } } @Test public void alarmTypes() throws Exception { final int[] typesToSet = ALARM_TYPES; 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 alarmCountOnRemoveForCanceled() { final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class); final PendingIntent pi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 12345, pi); assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); ami.remove(pi); assertAndHandleMessageSync(REMOVE_FOR_CANCELED); assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } @Test public void alarmCountOnListenerBinderDied() { final int numAlarms = 10; final IAlarmListener[] listeners = new IAlarmListener[numAlarms]; for (int i = 0; i < numAlarms; i++) { listeners[i] = getNewListener(() -> {}); setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]); } assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); for (int i = 0; i < numAlarms; i++) { mService.mListenerDeathRecipient.binderDied(listeners[i].asBinder()); assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); } } @Test public void nonWakeupAlarmsDeferred() throws Exception { 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]); } doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong()); // Advance time past all expirations. mNowElapsedTest += numAlarms + 5; mTestTimer.expire(); assertEquals(numAlarms, mService.mPendingNonWakeupAlarms.size()); // These alarms should be sent on interactive state change to true mService.interactiveStateChangedLocked(false); mService.interactiveStateChangedLocked(true); for (int i = 0; i < numAlarms; i++) { verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); } } /** * This tests that all non wakeup alarms are sent even when the mPendingNonWakeupAlarms gets * modified before the send is complete. This avoids bugs like b/175701084. */ @Test public void allNonWakeupAlarmsSentAtomically() throws Exception { final int numAlarms = 5; final AtomicInteger alarmsFired = new AtomicInteger(0); for (int i = 0; i < numAlarms; i++) { final IAlarmListener listener = getNewListener(() -> { alarmsFired.incrementAndGet(); mService.mPendingNonWakeupAlarms.clear(); }); setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 5, listener); } doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong()); // Advance time past all expirations. mNowElapsedTest += numAlarms + 5; mTestTimer.expire(); assertEquals(numAlarms, mService.mPendingNonWakeupAlarms.size()); // All of these alarms should be sent on interactive state change to true mService.interactiveStateChangedLocked(false); mService.interactiveStateChangedLocked(true); assertEquals(numAlarms, alarmsFired.get()); assertEquals(0, mService.mPendingNonWakeupAlarms.size()); } @Test public void alarmCountOnPendingNonWakeupAlarmsRemoved() throws Exception { 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]); } doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong()); // Advance time past all expirations. mNowElapsedTest += numAlarms + 5; mTestTimer.expire(); assertEquals(numAlarms, mService.mPendingNonWakeupAlarms.size()); for (int i = 0; i < numAlarms; i++) { mService.removeLocked(pis[i], null, REMOVE_REASON_UNDEFINED); assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0)); } } @Test public void singleIdleUntil() { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); final PendingIntent idleUntilPi6 = getNewMockPendingIntent(); setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, idleUntilPi6); assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi6, null)); assertEquals(mNowElapsedTest + 6, mTestTimer.getElapsed()); assertEquals(mNowElapsedTest + 6, mService.mPendingIdleUntil.getWhenElapsed()); final PendingIntent idleUntilPi2 = getNewMockPendingIntent(); setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, idleUntilPi2); // The same mPendingIdleUntil should get updated, even with a different PendingIntent. assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi2, null)); assertEquals(mNowElapsedTest + 2, mTestTimer.getElapsed()); assertEquals(1, mService.mAlarmStore.size()); final PendingIntent idleUntilPi10 = getNewMockPendingIntent(); setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10, idleUntilPi10); // The same thing should happen even when the new alarm is in farther in the future. assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi10, null)); assertEquals(mNowElapsedTest + 10, mTestTimer.getElapsed()); assertEquals(1, mService.mAlarmStore.size()); } @Test public void nextWakeFromIdle() throws Exception { assertNull(mService.mNextWakeFromIdle); final PendingIntent wakeFromIdle6 = getNewMockPendingIntent(); final long trigger6 = mNowElapsedTest + 6; setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger6, wakeFromIdle6); assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null)); assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed()); assertEquals(trigger6, mTestTimer.getElapsed()); final PendingIntent wakeFromIdle10 = getNewMockPendingIntent(); final long trigger10 = mNowElapsedTest + 10; setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger10, wakeFromIdle10); // mNextWakeFromIdle should not get updated. assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null)); assertEquals(trigger6, mTestTimer.getElapsed()); assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed()); final PendingIntent wakeFromIdle3 = getNewMockPendingIntent(); final long trigger3 = mNowElapsedTest + 3; setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger3, wakeFromIdle3); // mNextWakeFromIdle should always reflect the next earliest wake_from_idle alarm. assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle3, null)); assertEquals(trigger3, mTestTimer.getElapsed()); assertEquals(trigger3, mService.mNextWakeFromIdle.getWhenElapsed()); mNowElapsedTest = trigger3; mTestTimer.expire(); assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null)); assertEquals(trigger6, mTestTimer.getElapsed()); assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed()); mService.removeLocked(wakeFromIdle6, null, REMOVE_REASON_UNDEFINED); assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle10, null)); assertEquals(trigger10, mTestTimer.getElapsed()); assertEquals(trigger10, mService.mNextWakeFromIdle.getWhenElapsed()); mService.removeLocked(wakeFromIdle10, null, REMOVE_REASON_UNDEFINED); assertNull(mService.mNextWakeFromIdle); } private static void assertInRange(String message, long minIncl, long maxIncl, long val) { assertTrue(message, val >= minIncl && val <= maxIncl); } @Test public void idleUntilFuzzedBeforeWakeFromIdle() { final long minFuzz = 6; final long maxFuzz = 17; setDeviceConfigLong(KEY_MIN_DEVICE_IDLE_FUZZ, minFuzz); setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, maxFuzz); mNowElapsedTest = 119; // Arbitrary, just to ensure we are not testing on 0. final PendingIntent idleUntilPi = getNewMockPendingIntent(); final long requestedIdleUntil = mNowElapsedTest + 12; setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, requestedIdleUntil, idleUntilPi); assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed()); final PendingIntent wakeFromIdle5 = getNewMockPendingIntent(); setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, wakeFromIdle5); // Anything before now, gets snapped to now. It is not necessary for it to fire // immediately, just how it is implemented today for simplicity. assertEquals(mNowElapsedTest, mService.mPendingIdleUntil.getWhenElapsed()); final PendingIntent wakeFromIdle8 = getNewMockPendingIntent(); setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 8, wakeFromIdle8); // Next wake from idle is still the same. assertEquals(mNowElapsedTest, mService.mPendingIdleUntil.getWhenElapsed()); final PendingIntent wakeFromIdle19 = getNewMockPendingIntent(); setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 19, wakeFromIdle19); // Next wake from idle is still the same. assertEquals(mNowElapsedTest, mService.mPendingIdleUntil.getWhenElapsed()); mService.removeLocked(wakeFromIdle5, null, REMOVE_REASON_UNDEFINED); // Next wake from idle is at now + 8. long min = mNowElapsedTest; long max = mNowElapsedTest + 8 - minFuzz; assertInRange("Idle until alarm time not in expected range [" + min + ", " + max + "]", min, max, mService.mPendingIdleUntil.getWhenElapsed()); mService.removeLocked(wakeFromIdle8, null, REMOVE_REASON_UNDEFINED); // Next wake from idle is at now + 19, which is > minFuzz distance from // the requested idle until time: now + 12. assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed()); mService.removeLocked(idleUntilPi, null, REMOVE_REASON_UNDEFINED); assertNull(mService.mPendingIdleUntil); setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 21, idleUntilPi); // Next wake from idle is at now + 19, which means this alarm should get pulled back. min = mNowElapsedTest + 19 - maxFuzz; max = mNowElapsedTest + 19 - minFuzz; assertInRange("Idle until alarm time not in expected range [" + min + ", " + max + "]", min, max, mService.mPendingIdleUntil.getWhenElapsed()); } @Test public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + mAllowWhileIdleWindow + 1000, getNewMockPendingIntent()); assertNotNull(mService.mPendingIdleUntil); final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA; final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < quota; i++) { setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent(), false, false); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } // This one should get deferred on set. setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent(), false, false); final long expectedNextTrigger = firstTrigger + mAllowWhileIdleWindow; 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 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); // Both battery saver and doze are on. setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000, getNewMockPendingIntent()); assertNotNull(mService.mPendingIdleUntil); when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); final int numAlarms = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA + 100; final long firstTrigger = mNowElapsedTest + 10; for (int i = 0; i < numAlarms; i++) { setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent(), true, false); } // All of them should fire as expected. for (int i = 0; i < numAlarms; i++) { mNowElapsedTest = mTestTimer.getElapsed(); assertEquals("Incorrect trigger at i=" + i, firstTrigger + i, mNowElapsedTest); mTestTimer.expire(); } } @Test public void deviceIdleDeferralOnSet() throws Exception { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); final long deviceIdleUntil = mNowElapsedTest + 1234; setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, deviceIdleUntil, getNewMockPendingIntent()); assertEquals(deviceIdleUntil, mTestTimer.getElapsed()); final int numAlarms = 10; final PendingIntent[] pis = new PendingIntent[numAlarms]; for (int i = 0; i < numAlarms; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i + 1, pis[i] = getNewMockPendingIntent()); assertEquals(deviceIdleUntil, mTestTimer.getElapsed()); } mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); for (int i = 0; i < numAlarms; i++) { verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); } } @Test public void deviceIdleStateChanges() throws Exception { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); final int numAlarms = 10; final PendingIntent[] pis = new PendingIntent[numAlarms]; for (int i = 0; i < numAlarms; i++) { setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i + 1, pis[i] = getNewMockPendingIntent()); assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed()); } final PendingIntent idleUntil = getNewMockPendingIntent(); setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1234, idleUntil); assertEquals(mNowElapsedTest + 1234, mTestTimer.getElapsed()); mNowElapsedTest += 5; mTestTimer.expire(); // Nothing should happen. verify(pis[0], never()).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); mService.removeLocked(idleUntil, null, REMOVE_REASON_UNDEFINED); mTestTimer.expire(); // Now, the first 5 alarms (upto i = 4) should expire. for (int i = 0; i < 5; i++) { verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), any()); } // Rest should be restored, so the timer should reflect the next alarm. assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed()); } @Test public void batterySaverThrottling() { when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 7, alarmPi); assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(false); mListener.updateAllAlarms(); assertEquals(mNowElapsedTest + 7, mTestTimer.getElapsed()); when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); mListener.updateAlarmsForUid(TEST_CALLING_UID); assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); } @Test public void allowWhileIdleAlarmsInBatterySaver() 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_QUOTA; testQuotasDeferralOnSet(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, getNewMockPendingIntent(), false, false), quota, mAllowWhileIdleWindow); // Refresh the state 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.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); testQuotasNoDeferral(trigger -> setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, trigger, 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 public void prioritizedAlarmsInBatterySaver() throws Exception { when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); final long minDelay = 5; setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay); final long firstTrigger = mNowElapsedTest + 4; final AtomicInteger alarmsFired = new AtomicInteger(0); final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { alarmsFired.incrementAndGet(); } }); } assertEquals(firstTrigger, mTestTimer.getElapsed()); mNowElapsedTest = firstTrigger; mTestTimer.expire(); while (alarmsFired.get() < numAlarms) { assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } assertEquals(numAlarms, alarmsFired.get()); } @Test public void prioritizedAlarmsInDeviceIdle() throws Exception { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); final long minDelay = 5; setDeviceConfigLong(KEY_PRIORITY_ALARM_DELAY, minDelay); final long idleUntil = mNowElapsedTest + 1000; setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil, getNewMockPendingIntent()); assertNotNull(mService.mPendingIdleUntil); final long firstTrigger = mNowElapsedTest + 4; final AtomicInteger alarmsFired = new AtomicInteger(0); final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { alarmsFired.incrementAndGet(); } }); } assertEquals(firstTrigger, mTestTimer.getElapsed()); mNowElapsedTest = firstTrigger; mTestTimer.expire(); while (alarmsFired.get() < numAlarms) { assertEquals(mNowElapsedTest + minDelay, mTestTimer.getElapsed()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } assertEquals(numAlarms, alarmsFired.get()); setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } }); setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } }); assertEquals(idleUntil - 3, mTestTimer.getElapsed()); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); assertEquals(idleUntil, mTestTimer.getElapsed()); } @Test public void tareThrottling() { setTareEnabled(EconomyManager.ENABLED_MODE_ON); final ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(EconomyManagerInternal.AffordabilityChangeListener.class); final ArgumentCaptor billCaptor = ArgumentCaptor.forClass(EconomyManagerInternal.ActionBill.class); when(mEconomyManagerInternal .canPayFor(eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), billCaptor.capture())) .thenReturn(false); final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15, alarmPi); assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); final EconomyManagerInternal.ActionBill bill = billCaptor.getValue(); verify(mEconomyManagerInternal).registerAffordabilityChangeListener( eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), listenerCaptor.capture(), eq(bill)); final EconomyManagerInternal.AffordabilityChangeListener listener = listenerCaptor.getValue(); when(mEconomyManagerInternal .canPayFor(eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), eq(bill))) .thenReturn(true); listener.onAffordabilityChanged(TEST_CALLING_USER, TEST_CALLING_PACKAGE, bill, true); assertAndHandleMessageSync(TARE_AFFORDABILITY_CHANGED); assertEquals(mNowElapsedTest + 15, mTestTimer.getElapsed()); when(mEconomyManagerInternal .canPayFor(eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), eq(bill))) .thenReturn(false); listener.onAffordabilityChanged(TEST_CALLING_USER, TEST_CALLING_PACKAGE, bill, false); assertAndHandleMessageSync(TARE_AFFORDABILITY_CHANGED); assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); } @Test public void dispatchOrder() throws Exception { setDeviceConfigLong(KEY_MAX_DEVICE_IDLE_FUZZ, 0); final long deviceIdleUntil = mNowElapsedTest + 1234; final PendingIntent idleUntilPi = getNewMockPendingIntent(); setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, deviceIdleUntil, idleUntilPi); assertEquals(deviceIdleUntil, mTestTimer.getElapsed()); final PendingIntent pi5wakeup = getNewMockPendingIntent(); final PendingIntent pi4wakeupPackage = getNewMockPendingIntent(); final PendingIntent pi2nonWakeup = getNewMockPendingIntent(57, "test.different.package"); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, pi5wakeup); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 4, pi4wakeupPackage); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 2, pi2nonWakeup); mNowElapsedTest = deviceIdleUntil; mTestTimer.expire(); // The order of the alarms in delivery list should be: // IdleUntil, all alarms of a package with any wakeup alarms, then the rest. // Within a package, alarms should be ordered by requested delivery time. final PendingIntent[] expectedOrder = new PendingIntent[]{ idleUntilPi, pi4wakeupPackage, pi5wakeup, pi2nonWakeup}; ArgumentCaptor> listCaptor = ArgumentCaptor.forClass(ArrayList.class); verify(mService).deliverAlarmsLocked(listCaptor.capture(), anyLong()); final ArrayList deliveryList = listCaptor.getValue(); assertEquals(expectedOrder.length, deliveryList.size()); for (int i = 0; i < expectedOrder.length; i++) { assertTrue("Unexpected alarm: " + deliveryList.get(i) + " at pos: " + i, deliveryList.get(i).matches(expectedOrder[i], null)); } } private void mockChangeEnabled(long changeId, boolean enabled) { doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(), any(UserHandle.class))); doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyInt())); } @Test public void hasScheduleExactAlarmBinderCall() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); mockScheduleExactAlarmState(true); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmState(false); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED); assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); } @Test public void canScheduleExactAlarmsBinderCallChangeDisabled() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); // canScheduleExactAlarms should be true regardless of any permission state. // Both SEA and UEA are denied in setUp. assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); mockUseExactAlarmState(false); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); mockScheduleExactAlarmState(false); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); } @Test public void canScheduleExactAlarmsBinderCallPreT() throws RemoteException { // Policy permission is denied in setUp(). mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); // No permission, no exemption. mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, no exemption. mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Policy permission only, no exemption. mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); mockUseExactAlarmState(false); // User permission only, no exemption. mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // User permission only, no exemption. mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, exemption. mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, exemption. mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false); doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID)); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Both permissions and exemption. mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); } @Test public void canScheduleExactAlarmsBinderCall() throws RemoteException { // Both permissions are denied in setUp(). mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); // No permission, no exemption. assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Policy permission only, no exemption. mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); mockUseExactAlarmState(false); // User permission only, no exemption. mockScheduleExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, exemption. mockScheduleExactAlarmState(false); when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // No permission, core uid exemption. when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false); doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID)); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); // Both permissions and core uid exemption. mockScheduleExactAlarmState(true); mockUseExactAlarmState(true); assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); } @Test public void noPermissionCheckWhenChangeDisabled() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); // alarm clock mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, getNewMockPendingIntent(), null, null, null, mock(AlarmManager.AlarmClockInfo.class)); // exact mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, getNewMockPendingIntent(), null, null, null, null); // exact, allow-while-idle mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null); // inexact, allow-while-idle mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, getNewMockPendingIntent(), null, null, null, null); verify(mService, never()).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mService, never()).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); } @Test public void exactBinderCallWhenChangeDisabled() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), eq(EXACT_ALLOW_REASON_COMPAT)); } @Test public void exactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_COMPAT)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void inexactAllowWhileIdleBinderCallWhenChangeDisabled() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), anyLong(), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_NOT_APPLICABLE)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void alarmClockBinderCallWhenChangeDisabled() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); 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); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_COMPAT)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void alarmClockBinderCallWithSEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); mockScheduleExactAlarmState(true); 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); // Correct permission checks are invoked. verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void alarmClockBinderCallWithUEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); mockUseExactAlarmState(true); mockScheduleExactAlarmState(false); 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); // Correct permission checks are invoked. verify(mService).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_POLICY_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) { String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING; when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM)) .thenReturn(requesters); mService.refreshExactAlarmCandidates(); if (denyList) { setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); } else { setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); } when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(mode); } private void mockScheduleExactAlarmState(boolean granted) { String[] requesters = granted ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING; when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM)) .thenReturn(requesters); mService.refreshExactAlarmCandidates(); final int result = granted ? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED; doReturn(result).when( () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), eq(SCHEDULE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); } private void mockUseExactAlarmState(boolean granted) { final int result = granted ? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED; doReturn(result).when( () -> PermissionChecker.checkPermissionForPreflight(eq(mMockContext), eq(Manifest.permission.USE_EXACT_ALARM), anyInt(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE))); } @Test public void alarmClockBinderCallWithoutPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); 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); // Correct permission checks are invoked. verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE), isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), eq(EXACT_ALLOW_REASON_ALLOW_LIST)); } @Test public void exactBinderCallWithSEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); mockScheduleExactAlarmState(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void exactBinderCallWithUEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); mockUseExactAlarmState(true); mockScheduleExactAlarmState(false); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); verify(mService).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_POLICY_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void exactBinderCallWithAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), eq(EXACT_ALLOW_REASON_ALLOW_LIST)); } @Test public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); verify(mService).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void exactAllowWhileIdleBinderCallWithUEAPermission() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockUseExactAlarmState(true); mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); verify(mService).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE | FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_POLICY_PERMISSION)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // If permission is denied, only then allowlist will be checked. mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); verify(mDeviceIdleInternal).isAppOnWhitelist(UserHandle.getAppId(TEST_CALLING_UID)); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT | FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_ALLOW_LIST)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); // App is on power allowlist, doesn't need explicit FGS grant in broadcast options. assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type); } @Test public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); final PendingIntent alarmPi = getNewMockPendingIntent(); try { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, alarmPi, null, null, null, null); fail("exact binder call succeeded without permission"); } catch (SecurityException se) { // Expected. } try { mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); fail("exact, allow-while-idle binder call succeeded without permission"); } catch (SecurityException se) { // Expected. } verify(mDeviceIdleInternal, times(2)).isAppOnWhitelist(anyInt()); } @Test public void exactListenerBinderCallWithoutPermissionWithoutAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockScheduleExactAlarmState(false); mockUseExactAlarmState(false); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); final IAlarmListener listener = getNewListener(() -> {}); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, 0, null, listener, "test-tag", null, null); verify(mService, never()).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mService, never()).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), isNull(), eq(listener), eq("test-tag"), eq(FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_LISTENER)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void exactAllowWhileIdleListenerBinderCallWithoutPermissionWithoutAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockScheduleExactAlarmState(false); mockUseExactAlarmState(false); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false); final IAlarmListener listener = getNewListener(() -> {}); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, null, listener, "test-tag", null, null); verify(mService, never()).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mService, never()).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), isNull(), eq(listener), eq("test-tag"), eq(FLAG_STANDALONE | FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_LISTENER)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type); } @Test public void inexactAllowWhileIdleBinderCall() throws RemoteException { // Both permission and power exemption status don't matter for these alarms. // We only want to test that the flags and idleOptions are correct. mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 4321, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); verify(mService, never()).hasScheduleExactAlarmInternal(anyString(), anyInt()); verify(mService, never()).hasUseExactAlarmInternal(anyString(), anyInt()); verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt()); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(4321L), anyLong(), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_NOT_APPLICABLE)); final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue()); final int type = idleOptions.getTemporaryAppAllowlistType(); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED, type); } @Test public void binderCallWithUserAllowlist() throws RemoteException { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true); when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true); final PendingIntent alarmPi = getNewMockPendingIntent(); mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L), eq(alarmPi), isNull(), isNull(), eq(FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED | FLAG_STANDALONE), isNull(), isNull(), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(), eq(EXACT_ALLOW_REASON_ALLOW_LIST)); } @Test public void removeAllBinderCall() throws RemoteException { for (int i = 0; i < 10; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, getNewMockPendingIntent()); } final String otherPackage1 = "other.package1"; final String otherPackage2 = "other.package2"; final int otherAppId = 1243; final int otherUser1 = 31; final int otherUser2 = 8; final int otherUid1 = UserHandle.getUid(otherUser1, otherAppId); final int otherUid2 = UserHandle.getUid(otherUser2, otherAppId); registerAppIds( new String[]{TEST_CALLING_PACKAGE, otherPackage1, otherPackage2}, new Integer[]{UserHandle.getAppId(TEST_CALLING_UID), otherAppId, otherAppId} ); for (int i = 0; i < 9; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 11, 0, getNewMockPendingIntent(otherUid1, otherPackage1), 0, 0, otherUid1, otherPackage1, null); } for (int i = 0; i < 8; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 20, 0, getNewMockPendingIntent(otherUid1, otherPackage2), 0, 0, otherUid1, otherPackage2, null); } for (int i = 0; i < 7; i++) { setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 28, 0, getNewMockPendingIntent(otherUid2, otherPackage2), 0, 0, otherUid2, otherPackage2, null); } assertEquals(34, mService.mAlarmStore.size()); try { mBinder.removeAll(otherPackage1); fail("removeAll() for wrong package did not throw SecurityException"); } catch (SecurityException se) { // Expected } try { mBinder.removeAll(otherPackage2); fail("removeAll() for wrong package did not throw SecurityException"); } catch (SecurityException se) { // Expected } mBinder.removeAll(TEST_CALLING_PACKAGE); assertEquals(24, mService.mAlarmStore.size()); assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(TEST_CALLING_PACKAGE))); mTestCallingUid = otherUid1; mBinder.removeAll(otherPackage1); assertEquals(15, mService.mAlarmStore.size()); assertEquals(15, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2))); assertEquals(0, mService.mAlarmStore.getCount(a -> a.matches(otherPackage1))); mBinder.removeAll(otherPackage2); assertEquals(7, mService.mAlarmStore.size()); assertEquals(7, mService.mAlarmStore.getCount(a -> a.matches(otherPackage2))); mTestCallingUid = otherUid2; mBinder.removeAll(otherPackage2); assertEquals(0, mService.mAlarmStore.size()); } @Test public void minWindowChangeEnabled() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); final int minWindow = 73; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); final Random random = new Random(42); // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { final PendingIntent pi = getNewMockPendingIntent(); final long futurity = random.nextInt(minWindow); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, TEST_CALLING_UID, null); final long minAllowed = (long) (futurity * 0.75); // This will always be <= minWindow. assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(Math.max(minAllowed, window), a.windowLength); } for (int window = 1; window <= minWindow; window++) { final PendingIntent pi = getNewMockPendingIntent(); final long futurity = 2 * minWindow + window; // implies (0.75 * futurity) > minWindow setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, TEST_CALLING_UID, null); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(minWindow, a.windowLength); } for (int i = 0; i < 20; i++) { final long window = minWindow + random.nextInt(100); final PendingIntent pi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, 0, window, pi, 0, 0, TEST_CALLING_UID, null); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(window, a.windowLength); } } @Test public void minWindowChangeDisabled() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, false); final long minWindow = 73; final long futurity = 10_000; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { final PendingIntent pi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, TEST_CALLING_UID, null); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(window, a.windowLength); } } @Test public void minWindowExempted() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); final long minWindow = 73; final long futurity = 10_000; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); final int coreUid = 2312; doReturn(true).when(() -> UserHandle.isCore(coreUid)); final int allowlisted = 54239; when(mDeviceIdleInternal.isAppOnWhitelist(UserHandle.getAppId(allowlisted))).thenReturn( true); for (final int callingUid : new int[]{SYSTEM_UI_UID, coreUid, coreUid}) { // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { final PendingIntent pi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, callingUid, null); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(window, a.windowLength); } } // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { final PendingIntent pi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, pi, 0, 0, TEST_CALLING_UID, null); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(minWindow, a.windowLength); } } @Test public void minWindowPriorityAlarm() { mockChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, true); final long minWindow = 73; final long futurity = 10_000; setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. for (int window = 1; window <= minWindow; window++) { setPrioritizedAlarm(ELAPSED_REALTIME, mNowElapsedTest + futurity, window, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } }); assertEquals(1, mService.mAlarmStore.size()); final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); assertEquals(window, a.windowLength); } } @Test public void denyListChanged() { mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT); setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); final ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(), anyLong()); final List messages = messageCaptor.getAllValues(); for (final Message msg : messages) { assertTrue("Unwanted message sent to handler: " + msg.what, msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED); mService.mHandler.handleMessage(msg); } ArraySet added = new ArraySet<>(new String[]{"p4", "p5"}); verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true)); ArraySet removed = new ArraySet<>(new String[]{"p1", "p3"}); verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false)); } @Test public void permissionGrantedDueToDenyList() { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); final String[] packages = {"example.package.1", "example.package.2"}; final int appId1 = 232; final int appId2 = 431; final int userId1 = 42; final int userId2 = 53; registerAppIds(packages, new Integer[]{appId1, appId2}); when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(packages); mService.refreshExactAlarmCandidates(); final long allowListDuration = 53442; when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn( allowListDuration); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); // No permission revoked. verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(), anyBoolean()); // Permission got granted only for (appId1, userId2). final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); final ArgumentCaptor userCaptor = ArgumentCaptor.forClass(UserHandle.class); verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(), isNull(), bundleCaptor.capture()); assertEquals(userId2, userCaptor.getValue().getIdentifier()); // Validate the intent. assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, intentCaptor.getValue().getAction()); assertEquals(packages[0], intentCaptor.getValue().getPackage()); // Validate the options. final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue()); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, bOptions.getTemporaryAppAllowlistType()); assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration()); assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, bOptions.getTemporaryAppAllowlistReasonCode()); } @Test public void permissionRevokedDueToDenyList() { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); final String[] packages = {"example.package.1", "example.package.2"}; final int appId1 = 232; final int appId2 = 431; final int userId1 = 42; final int userId2 = 53; registerAppIds(packages, new Integer[]{appId1, appId2}); when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(packages); mService.refreshExactAlarmCandidates(); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true); // Permission got revoked only for (appId1, userId2) verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); verify(mService, never()).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); verify(mService).removeExactAlarmsOnPermissionRevoked( eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); } @Test public void opChangedPermissionRevoked() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); verify(mService).removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, true); } @Test public void opChangedChangeDisabled() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); verify(mService.mHandler, never()).sendMessageAtTime( argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong()); } @Test public void opChangedNoPermissionChangeDueToDenyList() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); verify(mService.mHandler, never()).sendMessageAtTime( argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong()); } @Test public void opChangedPermissionGranted() throws Exception { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); final long durationMs = 20000L; when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs); mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), bundleCaptor.capture()); // Validate the intent. assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, intentCaptor.getValue().getAction()); assertEquals(TEST_CALLING_PACKAGE, intentCaptor.getValue().getPackage()); // Validate the options. final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue()); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, bOptions.getTemporaryAppAllowlistType()); assertEquals(durationMs, bOptions.getTemporaryAppAllowlistDuration()); assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, bOptions.getTemporaryAppAllowlistReasonCode()); } @Test public void removeExactAlarmsOnPermissionRevoked() { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); // basic exact alarm setTestAlarm(ELAPSED_REALTIME, 0, 0, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null); // exact and allow-while-idle alarm setTestAlarm(ELAPSED_REALTIME, 0, 0, getNewMockPendingIntent(), 0, FLAG_ALLOW_WHILE_IDLE, TEST_CALLING_UID, null); // alarm clock setWakeFromIdle(RTC_WAKEUP, 0, getNewMockPendingIntent()); final PendingIntent inexact = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, 0, 10, inexact, 0, 0, TEST_CALLING_UID, null); final PendingIntent inexactAwi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, 0, 10, inexactAwi, 0, FLAG_ALLOW_WHILE_IDLE, TEST_CALLING_UID, null); final PendingIntent exactButDifferentUid = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, 0, 0, exactButDifferentUid, 0, 0, TEST_CALLING_UID + 5, null); assertEquals(6, mService.mAlarmStore.size()); mService.removeExactAlarmsOnPermissionRevoked(TEST_CALLING_UID, TEST_CALLING_PACKAGE, true); final ArrayList remaining = mService.mAlarmStore.asList(); assertEquals(3, remaining.size()); assertTrue("Basic inexact alarm removed", remaining.removeIf(a -> a.matches(inexact, null))); assertTrue("Inexact allow-while-idle alarm removed", remaining.removeIf(a -> a.matches(inexactAwi, null))); assertTrue("Alarm from different uid removed", remaining.removeIf(a -> a.matches(exactButDifferentUid, null))); // Mock should return false by default. verify(mDeviceIdleInternal, atLeastOnce()).isAppOnWhitelist( UserHandle.getAppId(TEST_CALLING_UID)); verify(() -> PermissionManagerService.killUid(eq(TEST_CALLING_UID), eq(TEST_CALLING_USER), anyString())); } private void optionsSentOnExpiration(boolean isActivity, Bundle idleOptions) throws Exception { final long triggerTime = mNowElapsedTest + 5000; final PendingIntent alarmPi = getNewMockPendingIntent(isActivity); setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, 0, alarmPi, 0, 0, TEST_CALLING_UID, idleOptions); mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), bundleCaptor.capture()); if (idleOptions != null) { assertEquals(idleOptions, bundleCaptor.getValue()); } else { assertFalse("BAL flag needs to be false in alarm manager", bundleCaptor.getValue().getBoolean( ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true)); } } @Test public void activityIdleOptionsSentOnExpiration() throws Exception { final Bundle idleOptions = new Bundle(); idleOptions.putChar("TEST_CHAR_KEY", 'x'); idleOptions.putInt("TEST_INT_KEY", 53); optionsSentOnExpiration(true, idleOptions); } @Test public void broadcastIdleOptionsSentOnExpiration() throws Exception { final Bundle idleOptions = new Bundle(); idleOptions.putChar("TEST_CHAR_KEY", 'x'); idleOptions.putInt("TEST_INT_KEY", 53); optionsSentOnExpiration(false, idleOptions); } @Test public void emptyActivityOptionsSentOnExpiration() throws Exception { optionsSentOnExpiration(true, null); } @Test public void emptyBroadcastOptionsSentOnExpiration() throws Exception { optionsSentOnExpiration(false, null); } private void registerAppIds(String[] packages, Integer[] ids) { assertEquals(packages.length, ids.length); when(mPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt())).thenAnswer( invocation -> { final String pkg = invocation.getArgument(0); final int index = ArrayUtils.indexOf(packages, pkg); if (index < 0) { return index; } final int userId = invocation.getArgument(2); return UserHandle.getUid(userId, ids[index]); }); } @Test public void onLastOpScheduleExactAlarmOnUserStart() { final int userId = 54; SystemService.TargetUser mockTargetUser = mock(SystemService.TargetUser.class); when(mockTargetUser.getUserIdentifier()).thenReturn(userId); final Integer[] appIds = new Integer[]{43, 254, 7731}; final int unknownAppId = 2347; final String[] packageNames = new String[]{"p43", "p254", "p7731"}; final int[] appOpModes = new int[]{MODE_ALLOWED, MODE_IGNORED, MODE_ERRORED}; mService.mExactAlarmCandidates = new ArraySet<>(appIds); mService.mExactAlarmCandidates.add(unknownAppId); for (int i = 0; i < appIds.length; i++) { final int uid = UserHandle.getUid(userId, appIds[i]); final AndroidPackage pkg = mock(AndroidPackage.class); when(pkg.getPackageName()).thenReturn(packageNames[i]); when(mPackageManagerInternal.getPackage(uid)).thenReturn(pkg); when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, uid, packageNames[i])).thenReturn(appOpModes[i]); } final ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); doReturn(true).when(mService.mHandler).post(runnableCaptor.capture()); mService.onUserStarting(mockTargetUser); runnableCaptor.getValue().run(); assertEquals(appIds.length, mService.mLastOpScheduleExactAlarm.size()); for (int i = 0; i < appIds.length; i++) { final int uid = UserHandle.getUid(userId, appIds[i]); assertEquals(appOpModes[i], mService.mLastOpScheduleExactAlarm.get(uid, -1)); } assertTrue(mService.mLastOpScheduleExactAlarm.indexOfKey( UserHandle.getUid(userId, unknownAppId)) < 0); } @Test public void refreshExactAlarmCandidatesOnPackageAdded() { final String[] exactAlarmRequesters = new String[]{"p11", "p2", "p9"}; final Integer[] appIds = new Integer[]{11, 2, 9}; registerAppIds(exactAlarmRequesters, appIds); when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED) .setPackage(TEST_CALLING_PACKAGE); mPackageChangesReceiver.onReceive(mMockContext, packageAdded); assertAndHandleMessageSync(REFRESH_EXACT_ALARM_CANDIDATES); assertEquals(new ArraySet<>(appIds), mService.mExactAlarmCandidates); } @Test public void refreshExactAlarmCandidatesOnPackageReplaced() { final String[] exactAlarmRequesters = new String[]{"p15", "p21", "p3"}; final Integer[] appIds = new Integer[]{15, 21, 3}; registerAppIds(exactAlarmRequesters, appIds); when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); final Intent packageAdded = new Intent(Intent.ACTION_PACKAGE_ADDED) .setData(Uri.fromParts("package", TEST_CALLING_PACKAGE, null)) .putExtra(Intent.EXTRA_REPLACING, true); mPackageChangesReceiver.onReceive(mMockContext, packageAdded); assertAndHandleMessageSync(REFRESH_EXACT_ALARM_CANDIDATES); assertEquals(new ArraySet<>(appIds), mService.mExactAlarmCandidates); } @Test public void refreshExactAlarmCandidatesOnPackageRemoved() { final String[] exactAlarmRequesters = new String[]{"p99", "p1", "p19"}; final Integer[] appIds = new Integer[]{99, 1, 19}; registerAppIds(exactAlarmRequesters, appIds); when(mPermissionManagerInternal.getAppOpPermissionPackages( SCHEDULE_EXACT_ALARM)).thenReturn(exactAlarmRequesters); final Intent packageRemoved = new Intent(Intent.ACTION_PACKAGE_REMOVED) .setPackage(TEST_CALLING_PACKAGE); mPackageChangesReceiver.onReceive(mMockContext, packageRemoved); assertAndHandleMessageSync(REFRESH_EXACT_ALARM_CANDIDATES); assertEquals(new ArraySet<>(appIds), mService.mExactAlarmCandidates); } @Test public void exactAlarmsRemovedIfNeededOnPackageReplaced() { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); final int otherUid = 2313; final String otherPackage = "p1"; final PendingIntent exactAlarm1 = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, 1, 0, exactAlarm1, 0, 0, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); final PendingIntent exactAlarm2 = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, 2, 0, exactAlarm2, 0, 0, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); final PendingIntent otherPackageExactAlarm = getNewMockPendingIntent(otherUid, otherPackage); setTestAlarm(ELAPSED_REALTIME, 0, 0, otherPackageExactAlarm, 0, 0, otherUid, otherPackage, null); final PendingIntent inexactAlarm = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, 0, 23, inexactAlarm, 0, 0, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); final PendingIntent otherPackageInexactAlarm = getNewMockPendingIntent(otherUid, otherPackage); setTestAlarm(ELAPSED_REALTIME, 0, 23, otherPackageInexactAlarm, 0, 0, otherUid, otherPackage, null); assertEquals(5, mService.mAlarmStore.size()); final Intent packageReplacedIntent = new Intent(Intent.ACTION_PACKAGE_ADDED) .setData(Uri.fromParts("package", TEST_CALLING_PACKAGE, null)) .putExtra(Intent.EXTRA_UID, TEST_CALLING_UID) .putExtra(Intent.EXTRA_REPLACING, true); mockUseExactAlarmState(false); mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); // user permission is granted, no alarms should be removed assertEquals(5, mService.mAlarmStore.size()); mockUseExactAlarmState(true); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); // policy permission is granted, no alarms should be removed assertEquals(5, mService.mAlarmStore.size()); mockUseExactAlarmState(false); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent); assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); // no permission is granted, exact alarms should be removed assertEquals(3, mService.mAlarmStore.size()); List remaining = mService.mAlarmStore.asList(); assertTrue("Inexact alarm removed", remaining.removeIf(a -> a.matches(inexactAlarm, null))); assertEquals(2, remaining.size()); assertTrue("Alarms from other package removed", remaining.removeIf(a -> a.matches(otherPackageExactAlarm, null) || a.matches(otherPackageInexactAlarm, null))); assertEquals(0, remaining.size()); } @Test public void alarmScheduledAtomPushed() { for (int i = 0; i < 10; i++) { final PendingIntent pi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i, pi); verify(() -> MetricsHelper.pushAlarmScheduled(argThat(a -> a.matches(pi, null)), anyInt())); } } @Test public void alarmBatchDeliveredAtomPushed() throws InterruptedException { for (int i = 0; i < 10; i++) { final int type = ((i & 1) == 0) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP; setTestAlarm(type, mNowElapsedTest + i, getNewMockPendingIntent()); } for (int i = 0; i < 4; i++) { final int type = ((i & 1) == 0) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP; setTestAlarm( type, mNowElapsedTest + i, getNewMockPendingIntent(), 0, FLAG_STANDALONE, TEST_CALLING_UID_2); } mNowElapsedTest += 100; mTestTimer.expire(); final ArgumentCaptor uidsCaptor = ArgumentCaptor.forClass(int[].class); final ArgumentCaptor alarmsPerUidCaptor = ArgumentCaptor.forClass(int[].class); final ArgumentCaptor wakeupAlarmsPerUidCaptor = ArgumentCaptor.forClass(int[].class); verify(() -> MetricsHelper.pushAlarmBatchDelivered( eq(14), eq(7), uidsCaptor.capture(), alarmsPerUidCaptor.capture(), wakeupAlarmsPerUidCaptor.capture())); assertEquals(2, uidsCaptor.getValue().length); assertEquals(2, alarmsPerUidCaptor.getValue().length); assertEquals(2, wakeupAlarmsPerUidCaptor.getValue().length); final int uid1Idx = uidsCaptor.getValue()[0] == TEST_CALLING_UID ? 0 : 1; final int uid2Idx = 1 - uid1Idx; assertEquals(TEST_CALLING_UID, uidsCaptor.getValue()[uid1Idx]); assertEquals(TEST_CALLING_UID_2, uidsCaptor.getValue()[uid2Idx]); assertEquals(10, alarmsPerUidCaptor.getValue()[uid1Idx]); assertEquals(5, wakeupAlarmsPerUidCaptor.getValue()[uid1Idx]); assertEquals(4, alarmsPerUidCaptor.getValue()[uid2Idx]); assertEquals(2, wakeupAlarmsPerUidCaptor.getValue()[uid2Idx]); } @Test public void tareEventPushed_on() throws Exception { setTareEnabled(EconomyManager.ENABLED_MODE_ON); runTareEventPushed(); } @Test public void tareEventPushed_shadow() throws Exception { setTareEnabled(EconomyManager.ENABLED_MODE_SHADOW); runTareEventPushed(); } private void runTareEventPushed() throws Exception { for (int i = 0; i < 10; i++) { final int type = (i % 2 == 1) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP; setTestAlarm(type, mNowElapsedTest + i, getNewMockPendingIntent()); } final ArrayList alarms = mService.mAlarmStore.remove((alarm) -> { return alarm.creatorUid == TEST_CALLING_UID; }); mService.deliverAlarmsLocked(alarms, mNowElapsedTest); verify(mEconomyManagerInternal, times(10)).noteInstantaneousEvent( eq(TEST_CALLING_USER), eq(TEST_CALLING_PACKAGE), anyInt(), any()); } @Test public void setTimeZoneImpl() { final long durationMs = 20000L; when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs); mService.setTimeZoneImpl("UTC", TIME_ZONE_CONFIDENCE_HIGH, "AlarmManagerServiceTest"); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL), isNull(), bundleCaptor.capture()); assertEquals(Intent.ACTION_TIMEZONE_CHANGED, intentCaptor.getValue().getAction()); assertEquals("UTC", intentCaptor.getValue().getStringExtra(Intent.EXTRA_TIMEZONE)); final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue()); assertEquals(durationMs, bOptions.getTemporaryAppAllowlistDuration()); assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, bOptions.getTemporaryAppAllowlistType()); assertEquals(PowerExemptionManager.REASON_TIMEZONE_CHANGED, bOptions.getTemporaryAppAllowlistReasonCode()); } @Test public void hasUseExactAlarmInternal() { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true); mockUseExactAlarmState(true); assertTrue(mService.hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockUseExactAlarmState(false); assertFalse(mService.hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } @Test public void hasUseExactAlarmInternalChangeDisabled() { mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, false); mockUseExactAlarmState(true); assertFalse(mService.hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockUseExactAlarmState(false); assertFalse(mService.hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } @Test public void hasScheduleExactAlarmInternal() { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, true); mockScheduleExactAlarmState(false); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockScheduleExactAlarmState(true); assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } @Test public void hasScheduleExactAlarmInternalPreT() { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false); mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); } @Test public void hasScheduleExactAlarmInternalPreS() { mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); mockScheduleExactAlarmState(true); mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); mockScheduleExactAlarmState(false); mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); assertFalse(mService.hasScheduleExactAlarmInternal(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, REMOVE_REASON_UNDEFINED); 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, REMOVE_REASON_UNDEFINED); 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); } @Test public void exactListenerAlarmsRemovedOnCached() { mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true); setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID); setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID); setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null); setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null); setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2); setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2); setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null); setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null); assertEquals(8, mService.mAlarmStore.size()); mListener.handleUidCachedChanged(TEST_CALLING_UID, true); assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED); assertEquals(7, mService.mAlarmStore.size()); mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true); assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED); assertEquals(6, mService.mAlarmStore.size()); } @Test public void alarmCountOnListenerCached() { mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true); // Set some alarms for TEST_CALLING_UID. final int numExactListenerUid1 = 14; for (int i = 0; i < numExactListenerUid1; i++) { setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i, getNewListener(() -> {})); } setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID); setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent()); setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null); // Set some alarms for TEST_CALLING_UID_2. final int numExactListenerUid2 = 9; for (int i = 0; i < numExactListenerUid2; i++) { setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i, getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2); } setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2); setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null); assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2)); mListener.handleUidCachedChanged(TEST_CALLING_UID, true); assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED); assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID)); mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true); assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED); assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2)); } @Test public void lookForPackageLocked() throws Exception { final String package2 = "test.package.2"; final int uid2 = 359712; setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 10, getNewMockPendingIntent()); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15, getNewMockPendingIntent(uid2, package2)); doReturn(true).when(mService).checkAllowNonWakeupDelayLocked(anyLong()); assertTrue(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); assertTrue(mService.lookForPackageLocked(package2, uid2)); mNowElapsedTest += 10; // Advance time past the first alarm only. mTestTimer.expire(); assertTrue(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); assertTrue(mService.lookForPackageLocked(package2, uid2)); // The non-wakeup alarm is sent on interactive state change: false -> true. mService.interactiveStateChangedLocked(false); mService.interactiveStateChangedLocked(true); assertFalse(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); assertTrue(mService.lookForPackageLocked(package2, uid2)); mNowElapsedTest += 10; // Advance time past the second alarm. mTestTimer.expire(); assertFalse(mService.lookForPackageLocked(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); assertFalse(mService.lookForPackageLocked(package2, uid2)); } @Test public void onQueryPackageRestart() { final String[] packages = {"p1", "p2", "p3"}; final int uid = 5421; final Intent packageAdded = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART) .setData(Uri.fromParts("package", packages[0], null)) .putExtra(Intent.EXTRA_PACKAGES, packages) .putExtra(Intent.EXTRA_UID, uid); mPackageChangesReceiver.onReceive(mMockContext, packageAdded); for (String p : packages) { verify(mService).lookForPackageLocked(p, uid); } } }