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();
+ }
+ }
+}