Remove cancel listeners from pending intent alarms

The cancel listeners are created per PendingIntent instance and were
spamming the callback list stored inside PendingIntentRecord. In cases
where there is even a single live PendingIntent backed by this
PendingIntentRecord, all PendingIntent instances backed by this
PendingIntentRecord for which a callback was ever registered will leak.

Test: atest FrameworksMockingServicesTests:\
com.android.server.am.PendingIntentControllerTest
atest FrameworksMockingServicesTests:\
com.android.server.AlarmManagerServiceTest

Bug: 143091024
Change-Id: I65df12da0c437064e6e3719911926738c677c4eb
Merged-In: I65df12da0c437064e6e3719911926738c677c4eb
(cherry picked from commit 0d51a8bcc0d5ac6dadbf242db5ce9404ca369fb2)
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 6f7a060..7b29bcd 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1257,7 +1257,12 @@
         return b != null ? new PendingIntent(b, in.getClassCookie(PendingIntent.class)) : null;
     }
 
-    /*package*/ PendingIntent(IIntentSender target) {
+    /**
+     * Creates a PendingIntent with the given target.
+     * @param target the backing IIntentSender
+     * @hide
+     */
+    public PendingIntent(IIntentSender target) {
         mTarget = target;
     }
 
diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java
index 5b0de5e..0a73502 100644
--- a/services/core/java/com/android/server/AlarmManagerInternal.java
+++ b/services/core/java/com/android/server/AlarmManagerInternal.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import android.app.PendingIntent;
+
 public interface AlarmManagerInternal {
     // Some other components in the system server need to know about
     // broadcast alarms currently in flight
@@ -30,4 +32,10 @@
     boolean isIdling();
     public void removeAlarmsForUid(int uid);
     public void registerInFlightListener(InFlightListener callback);
+
+    /**
+     * Removes any alarm with the given pending intent with equality determined using
+     * {@link android.app.PendingIntent#equals(java.lang.Object) PendingIntent.equals}
+     */
+    void remove(PendingIntent rec);
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index d162441..386b49e 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -210,7 +210,6 @@
     IAlarmListener mTimeTickTrigger;
     PendingIntent mDateChangeSender;
     Random mRandom;
-    PendingIntent.CancelListener mOperationCancelListener;
     boolean mInteractive = true;
     long mNonInteractiveStartTime;
     long mNonInteractiveTime;
@@ -1498,7 +1497,6 @@
 
         synchronized (mLock) {
             mHandler = new AlarmHandler();
-            mOperationCancelListener = (intent) -> removeImpl(intent, null);
             mConstants = new Constants(mHandler);
             mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
 
@@ -1750,9 +1748,6 @@
         } else {
             maxElapsed = triggerElapsed + windowLength;
         }
-        if (operation != null) {
-            operation.registerCancelListener(mOperationCancelListener);
-        }
         synchronized (mLock) {
             if (DEBUG_BATCH) {
                 Slog.v(TAG, "set(" + operation + ") : type=" + type
@@ -1765,8 +1760,6 @@
                         "Maximum limit of concurrent alarms " + mConstants.MAX_ALARMS_PER_UID
                                 + " reached for uid: " + UserHandle.formatUid(callingUid)
                                 + ", callingPackage: " + callingPackage;
-                mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
-                        operation).sendToTarget();
                 Slog.w(TAG, errorMsg);
                 throw new IllegalStateException(errorMsg);
             }
@@ -1787,8 +1780,6 @@
             if (ActivityManager.getService().isAppStartModeDisabled(callingUid, callingPackage)) {
                 Slog.w(TAG, "Not setting alarm from " + callingUid + ":" + a
                         + " -- package not allowed to start");
-                mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
-                        operation).sendToTarget();
                 return;
             }
         } catch (RemoteException e) {
@@ -2042,6 +2033,11 @@
         }
 
         @Override
+        public void remove(PendingIntent pi) {
+            mHandler.obtainMessage(AlarmHandler.REMOVE_FOR_CANCELED, pi).sendToTarget();
+        }
+
+        @Override
         public void registerInFlightListener(InFlightListener callback) {
             synchronized (mLock) {
                 mInFlightListeners.add(callback);
@@ -2146,8 +2142,6 @@
             synchronized (mLock) {
                 removeLocked(operation, listener);
             }
-            mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
-                    operation).sendToTarget();
         }
 
         @Override
@@ -4151,7 +4145,7 @@
         public static final int APP_STANDBY_BUCKET_CHANGED = 5;
         public static final int APP_STANDBY_PAROLE_CHANGED = 6;
         public static final int REMOVE_FOR_STOPPED = 7;
-        public static final int UNREGISTER_CANCEL_LISTENER = 8;
+        public static final int REMOVE_FOR_CANCELED = 8;
 
         AlarmHandler() {
             super(Looper.myLooper());
@@ -4234,10 +4228,10 @@
                     }
                     break;
 
-                case UNREGISTER_CANCEL_LISTENER:
-                    final PendingIntent pi = (PendingIntent) msg.obj;
-                    if (pi != null) {
-                        pi.unregisterCancelListener(mOperationCancelListener);
+                case REMOVE_FOR_CANCELED:
+                    final PendingIntent operation = (PendingIntent) msg.obj;
+                    synchronized (mLock) {
+                        removeLocked(operation, null);
                     }
                     break;
 
@@ -4696,11 +4690,6 @@
                                         Intent.EXTRA_ALARM_COUNT, alarm.count),
                                 mDeliveryTracker, mHandler, null,
                                 allowWhileIdle ? mIdleOptions : null);
-                        if (alarm.repeatInterval == 0) {
-                            // Keep the listener for repeating alarms until they get cancelled
-                            mHandler.obtainMessage(AlarmHandler.UNREGISTER_CANCEL_LISTENER,
-                                    alarm.operation).sendToTarget();
-                        }
                     } catch (PendingIntent.CanceledException e) {
                         if (alarm.repeatInterval > 0) {
                             // This IntentSender is no longer valid, but this
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index d75591c..df76713 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -43,6 +43,7 @@
 
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.AlarmManagerInternal;
 import com.android.server.LocalServices;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.SafeActivityOptions;
@@ -293,6 +294,8 @@
                     PendingIntentController::handlePendingIntentCancelled, this, callbacks);
             mH.sendMessage(m);
         }
+        final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
+        ami.remove(new PendingIntent(rec));
     }
 
     private void handlePendingIntentCancelled(RemoteCallbackList<IResultReceiver> callbacks) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 1e29ed6..fafd4e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -24,6 +24,7 @@
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
 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;
@@ -35,6 +36,7 @@
 import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
 import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
 import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
 import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
 import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -79,9 +81,9 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
 import com.android.internal.annotations.GuardedBy;
 
 import org.junit.After;
@@ -166,7 +168,6 @@
     }
 
     public class Injector extends AlarmManagerService.Injector {
-        boolean mIsAutomotiveOverride;
 
         Injector(Context context) {
             super(context);
@@ -256,6 +257,9 @@
                 .when(() -> LocalServices.getService(DeviceIdleController.LocalService.class));
         doReturn(mUsageStatsManagerInternal).when(
                 () -> LocalServices.getService(UsageStatsManagerInternal.class));
+        doCallRealMethod().when((MockedVoidMethod) () ->
+                LocalServices.addService(eq(AlarmManagerInternal.class), any()));
+        doCallRealMethod().when(() -> LocalServices.getService(AlarmManagerInternal.class));
         when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE),
                 eq(UserHandle.getUserId(TEST_CALLING_UID)), anyLong()))
                 .thenReturn(STANDBY_BUCKET_ACTIVE);
@@ -443,7 +447,6 @@
         assertEquals(expectedTriggerTime, mTestTimer.getElapsed());
     }
 
-    @FlakyTest(bugId = 130313408)
     @Test
     public void testEarliestAlarmSet() {
         final PendingIntent pi6 = getNewMockPendingIntent();
@@ -661,11 +664,15 @@
                 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<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
         final Message lastMessage = messageCaptor.getValue();
         assertEquals("Unexpected message send to handler", lastMessage.what,
-                APP_STANDBY_BUCKET_CHANGED);
+                what);
         mService.mHandler.handleMessage(lastMessage);
     }
 
@@ -725,12 +732,7 @@
 
     private void assertAndHandleParoleChanged(boolean parole) {
         mAppStandbyListener.onParoleStateChanged(parole);
-        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
-        final Message lastMessage = messageCaptor.getValue();
-        assertEquals("Unexpected message send to handler", lastMessage.what,
-                APP_STANDBY_PAROLE_CHANGED);
-        mService.mHandler.handleMessage(lastMessage);
+        assertAndHandleMessageSync(APP_STANDBY_PAROLE_CHANGED);
     }
 
     @Test
@@ -1033,12 +1035,13 @@
     }
 
     @Test
-    public void alarmCountOnPendingIntentCancel() {
+    public void alarmCountOnRemoveForCanceled() {
+        final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);
         final PendingIntent pi = getNewMockPendingIntent();
-        setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 123, pi);
-        verify(pi).registerCancelListener(mService.mOperationCancelListener);
+        setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + 12345, pi);
         assertEquals(1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
-        mService.mOperationCancelListener.onCancelled(pi);
+        ami.remove(pi);
+        assertAndHandleMessageSync(REMOVE_FOR_CANCELED);
         assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
     }
 
@@ -1047,5 +1050,6 @@
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
         }
+        LocalServices.removeServiceForTest(AlarmManagerInternal.class);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
new file mode 100644
index 0000000..3975f0b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.am;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.os.Looper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AlarmManagerInternal;
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class PendingIntentControllerTest {
+    private static final String TEST_PACKAGE_NAME = "test-package-1";
+    private static final int TEST_CALLING_UID = android.os.Process.myUid();
+    private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")};
+
+    @Mock
+    private UserController mUserController;
+    @Mock
+    private AlarmManagerInternal mAlarmManagerInternal;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternal;
+    @Mock
+    private IPackageManager mIPackageManager;
+
+    private MockitoSession mMockingSession;
+    private PendingIntentController mPendingIntentController;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(LocalServices.class)
+                .mockStatic(AppGlobals.class)
+                .strictness(Strictness.LENIENT) // Needed to stub LocalServices.getService twice
+                .startMocking();
+        doReturn(mAlarmManagerInternal).when(
+                () -> LocalServices.getService(AlarmManagerInternal.class));
+        doReturn(mActivityManagerInternal).when(
+                () -> LocalServices.getService(ActivityManagerInternal.class));
+        doReturn(mIPackageManager).when(() -> AppGlobals.getPackageManager());
+        when(mIPackageManager.getPackageUid(eq(TEST_PACKAGE_NAME), anyInt(), anyInt())).thenReturn(
+                TEST_CALLING_UID);
+        mPendingIntentController = new PendingIntentController(Looper.getMainLooper(),
+                mUserController);
+        mPendingIntentController.onActivityManagerInternalAdded();
+    }
+
+    private PendingIntentRecord createPendingIntentRecord(int flags) {
+        return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST,
+                TEST_PACKAGE_NAME, TEST_CALLING_UID, 0, null, null, 0, TEST_INTENTS, null, flags,
+                null);
+    }
+
+    @Test
+    public void alarmsRemovedOnCancel() {
+        final PendingIntentRecord pir = createPendingIntentRecord(0);
+        mPendingIntentController.cancelIntentSender(pir);
+        final ArgumentCaptor<PendingIntent> piCaptor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAlarmManagerInternal).remove(piCaptor.capture());
+        assertEquals("Wrong target for pending intent passed to alarm manager", pir,
+                piCaptor.getValue().getTarget());
+    }
+
+    @Test
+    public void alarmsRemovedOnRecreateWithCancelCurrent() {
+        final PendingIntentRecord pir = createPendingIntentRecord(0);
+        createPendingIntentRecord(PendingIntent.FLAG_CANCEL_CURRENT);
+        final ArgumentCaptor<PendingIntent> piCaptor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAlarmManagerInternal).remove(piCaptor.capture());
+        assertEquals("Wrong target for pending intent passed to alarm manager", pir,
+                piCaptor.getValue().getTarget());
+    }
+
+    @Test
+    public void alarmsRemovedOnSendingOneShot() {
+        final PendingIntentRecord pir = createPendingIntentRecord(PendingIntent.FLAG_ONE_SHOT);
+        pir.send(0, null, null, null, null, null, null);
+        final ArgumentCaptor<PendingIntent> piCaptor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAlarmManagerInternal).remove(piCaptor.capture());
+        assertEquals("Wrong target for pending intent passed to alarm manager", pir,
+                piCaptor.getValue().getTarget());
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+}