Decrement alarm count on delivery failures

Alarm delivery may fail for various reasons. We should still decrement
alarm count.
Added tests that simulate failure for both listener and pending intent
alarms.

Test: atest FrameworksMockingServicesTests:\
com.android.server.AlarmManagerServiceTest
atest CtsAlarmManagerTestCases

Bug: 129240711
Fixes: 129299739

Change-Id: I503b5b0275eb502a148019c29e5a0562681e0015
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 3d918fc..0f39029 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -3825,6 +3825,7 @@
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
+            decrementAlarmCount(alarm.uid);
         }
     }
 
@@ -4148,6 +4149,10 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
+                    // This code is used when the kernel timer driver is not available, which
+                    // shouldn't happen. Here, we try our best to simulate it, which may be useful
+                    // when porting Android to a new device. Note that we can't wake up a device
+                    // this way, so WAKE_UP alarms will be delivered only when the device is awake.
                     ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
                     synchronized (mLock) {
                         final long nowELAPSED = mInjector.getElapsedRealtime();
@@ -4167,6 +4172,7 @@
                                 removeImpl(alarm.operation, null);
                             }
                         }
+                        decrementAlarmCount(alarm.uid);
                     }
                     break;
                 }
@@ -4760,7 +4766,6 @@
                 mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
                         UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
             }
-            decrementAlarmCount(alarm.uid);
             final BroadcastStats bs = inflight.mBroadcastStats;
             bs.count++;
             if (bs.nesting == 0) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 7c91b64..7a40e44 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -26,6 +26,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -57,6 +58,8 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
 import android.app.IUidObserver;
 import android.app.PendingIntent;
 import android.app.usage.UsageStatsManagerInternal;
@@ -67,6 +70,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
@@ -231,7 +235,7 @@
         doReturn(Looper.getMainLooper()).when(Looper::myLooper);
 
         when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
-        doReturn("min_futurity=0").when(() ->
+        doReturn("min_futurity=0,min_interval=0").when(() ->
                 Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS));
         mInjector = new Injector(mMockContext);
         mService = new AlarmManagerService(mMockContext, mInjector);
@@ -249,6 +253,7 @@
         // Other boot phases don't matter
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         assertEquals(0, mService.mConstants.MIN_FUTURITY);
+        assertEquals(0, mService.mConstants.MIN_INTERVAL);
         mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
         ArgumentCaptor<UsageStatsManagerInternal.AppIdleStateChangeListener> captor =
                 ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class);
@@ -257,15 +262,28 @@
     }
 
     private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
-        setTestAlarm(type, triggerTime, operation, TEST_CALLING_UID);
+        setTestAlarm(type, triggerTime, operation, 0, TEST_CALLING_UID);
     }
 
-    private void setTestAlarm(int type, long triggerTime, PendingIntent operation, int callingUid) {
-        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, 0,
+    private void setRepeatingTestAlarm(int type, long firstTrigger, long interval,
+            PendingIntent pi) {
+        setTestAlarm(type, firstTrigger, pi, interval, TEST_CALLING_UID);
+    }
+
+    private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
+            int callingUid) {
+        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval,
                 operation, null, "test", AlarmManager.FLAG_STANDALONE, null, null,
                 callingUid, TEST_CALLING_PACKAGE);
     }
 
+    private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
+        mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, 0,
+                null, listener, "test", AlarmManager.FLAG_STANDALONE, null, null,
+                TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+    }
+
+
     private PendingIntent getNewMockPendingIntent() {
         return getNewMockPendingIntent(TEST_CALLING_UID);
     }
@@ -738,14 +756,14 @@
     @Test
     public void alarmCountKeyedOnCallingUid() {
         final int mockCreatorUid = 431412;
-        final PendingIntent pi = getNewMockPendingIntent(mockCreatorUid);
-        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 5, pi);
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 5,
+                getNewMockPendingIntent(mockCreatorUid));
         assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
         assertEquals(-1, mService.mAlarmsPerUid.get(mockCreatorUid, -1));
     }
 
     @Test
-    public void alarmCountOnSet() {
+    public void alarmCountOnSetPi() {
         final int numAlarms = 103;
         final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME};
         for (int i = 1; i <= numAlarms; i++) {
@@ -755,7 +773,21 @@
     }
 
     @Test
-    public void alarmCountOnExpiration() throws InterruptedException {
+    public void alarmCountOnSetListener() {
+        final int numAlarms = 103;
+        final int[] types = {RTC_WAKEUP, RTC, ELAPSED_REALTIME_WAKEUP, ELAPSED_REALTIME};
+        for (int i = 1; i <= numAlarms; i++) {
+            setTestAlarmWithListener(types[i % 4], mNowElapsedTest + i, new IAlarmListener.Stub() {
+                @Override
+                public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+                }
+            });
+            assertEquals(i, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+        }
+    }
+
+    @Test
+    public void alarmCountOnExpirationPi() throws InterruptedException {
         final int numAlarms = 8; // This test is slow
         for (int i = 0; i < numAlarms; i++) {
             setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, getNewMockPendingIntent());
@@ -770,6 +802,86 @@
     }
 
     @Test
+    public void alarmCountOnExpirationListener() throws InterruptedException {
+        final int numAlarms = 8; // This test is slow
+        for (int i = 0; i < numAlarms; i++) {
+            setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10,
+                    new IAlarmListener.Stub() {
+                        @Override
+                        public void doAlarm(IAlarmCompleteListener callback)
+                                throws RemoteException {
+                        }
+                    });
+        }
+        int expired = 0;
+        while (expired < numAlarms) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+            expired++;
+            assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
+        }
+    }
+
+    @Test
+    public void alarmCountOnExceptionWhileSendingPi() throws Exception {
+        final int numAlarms = 5; // This test is slow
+        for (int i = 0; i < numAlarms; i++) {
+            final PendingIntent pi = getNewMockPendingIntent();
+            doThrow(PendingIntent.CanceledException.class).when(pi).send(eq(mMockContext), eq(0),
+                    any(), any(), any(), any(), any());
+            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10, pi);
+        }
+        int expired = 0;
+        while (expired < numAlarms) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+            expired++;
+            assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
+        }
+    }
+
+    @Test
+    public void alarmCountOnExceptionWhileCallingListener() throws Exception {
+        final int numAlarms = 5; // This test is slow
+        for (int i = 0; i < numAlarms; i++) {
+            final IAlarmListener listener = new IAlarmListener.Stub() {
+                @Override
+                public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+                    throw new RemoteException("For testing behavior on exception");
+                }
+            };
+            setTestAlarmWithListener(ELAPSED_REALTIME, mNowElapsedTest + i + 10, listener);
+        }
+        int expired = 0;
+        while (expired < numAlarms) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+            expired++;
+            assertEquals(numAlarms - expired, mService.mAlarmsPerUid.get(TEST_CALLING_UID, 0));
+        }
+    }
+
+    @Test
+    public void alarmCountForRepeatingAlarms() throws Exception {
+        final long interval = 1231;
+        final long firstTrigger = mNowElapsedTest + 321;
+        final PendingIntent pi = getNewMockPendingIntent();
+        setRepeatingTestAlarm(ELAPSED_REALTIME, firstTrigger, interval, pi);
+        assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+
+        for (int i = 0; i < 5; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+            assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+        }
+        doThrow(PendingIntent.CanceledException.class).when(pi).send(eq(mMockContext), eq(0),
+                any(), any(), any(), any(), any());
+        mNowElapsedTest = mTestTimer.getElapsed();
+        mTestTimer.expire();
+        assertEquals(-1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, -1));
+    }
+
+    @Test
     public void alarmCountOnUidRemoved() {
         final int numAlarms = 10;
         for (int i = 0; i < numAlarms; i++) {
@@ -798,7 +910,7 @@
         for (int i = 0; i < numAlarms; i++) {
             int mockUid = UserHandle.getUid(mockUserId, 1234 + i);
             setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10,
-                    getNewMockPendingIntent(mockUid), mockUid);
+                    getNewMockPendingIntent(mockUid), 0, mockUid);
         }
         assertEquals(numAlarms, mService.mAlarmsPerUid.size());
         mService.removeUserLocked(mockUserId);
@@ -820,6 +932,12 @@
         }
     }
 
+    @Test
+    public void alarmCountOnInvalidSet() {
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 12345, null);
+        assertEquals(-1, mService.mAlarmsPerUid.get(TEST_CALLING_UID, -1));
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {