diff options
7 files changed, 411 insertions, 6 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtender.java new file mode 100644 index 000000000000..1c1f3887e7ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtender.java @@ -0,0 +1,87 @@ +/* + * 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.systemui.statusbar; + +import android.annotation.NonNull; +import android.app.Notification; +import android.os.Handler; +import android.os.Looper; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.statusbar.NotificationData; + +/** + * Extends the lifetime of foreground notification services such that they show for at least + * five seconds + */ +public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender { + private static final String TAG = "FGSLifetimeExtender"; + + @VisibleForTesting + public static final int MIN_FGS_TIME_MS = 5000; + + private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback; + private ArraySet<NotificationData.Entry> mManagedEntries = new ArraySet<>(); + private Handler mHandler = new Handler(Looper.getMainLooper()); + + public ForegroundServiceLifetimeExtender() { + } + + @Override + public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) { + mNotificationSafeToRemoveCallback = callback; + } + + @Override + public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) { + if ((entry.notification.getNotification().flags + & Notification.FLAG_FOREGROUND_SERVICE) == 0) { + return false; + } + long currentTime = System.currentTimeMillis(); + return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS; + } + + @Override + public boolean shouldExtendLifetimeForPendingNotification( + @NonNull NotificationData.Entry entry) { + return shouldExtendLifetime(entry); + } + + @Override + public void setShouldManageLifetime( + @NonNull NotificationData.Entry entry, boolean shouldManage) { + android.util.Log.d("FGSExtender", "setShouldManageLifetime " + shouldManage); + if (!shouldManage) { + mManagedEntries.remove(entry); + return; + } + mManagedEntries.add(entry); + Runnable r = () -> { + if (mManagedEntries.contains(entry)) { + mManagedEntries.remove(entry); + if (mNotificationSafeToRemoveCallback != null) { + mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key); + } + } + }; + long delayAmt = MIN_FGS_TIME_MS + - (System.currentTimeMillis() - entry.notification.getPostTime()); + mHandler.postDelayed(r, delayAmt); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java new file mode 100644 index 000000000000..19edfaac18e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java @@ -0,0 +1,81 @@ +/* + * 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.systemui.statusbar; + +import android.annotation.NonNull; + +import com.android.systemui.statusbar.NotificationData; + +/** + * Interface for anything that may need to keep notifications managed even after + * {@link NotificationListener} removes it. The lifetime extender is in charge of performing the + * callback when the notification is then safe to remove. + */ +public interface NotificationLifetimeExtender { + + /** + * Set the handler to callback to when the notification is safe to remove. + * + * @param callback the handler to callback + */ + void setCallback(@NonNull NotificationSafeToRemoveCallback callback); + + /** + * Determines whether or not the extender needs the notification kept after removal. + * + * @param entry the entry containing the notification to check + * @return true if the notification lifetime should be extended + */ + boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry); + + /** + * It's possible that a notification was canceled before it ever became visible. This callback + * gives lifetime extenders a chance to make sure it shows up. For example if a foreground + * service is canceled too quickly but we still want to make sure a FGS notification shows. + * @param pendingEntry the canceled (but pending) entry + * @return true if the notification lifetime should be extended + */ + default boolean shouldExtendLifetimeForPendingNotification( + @NonNull NotificationData.Entry pendingEntry) { + return false; + } + + /** + * Sets whether or not the lifetime should be managed by the extender. In practice, if + * shouldManage is true, this is where the extender starts managing the entry internally and is + * now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)} + * when the entry is safe to remove. If shouldManage is false, the extender no longer needs to + * worry about it (either because we will be removing it anyway or the entry is no longer + * removed due to an update). + * + * @param entry the entry that needs an extended lifetime + * @param shouldManage true if the extender should manage the entry now, false otherwise + */ + void setShouldManageLifetime(@NonNull NotificationData.Entry entry, boolean shouldManage); + + /** + * The callback for when the notification is now safe to remove (i.e. its lifetime has ended). + */ + interface NotificationSafeToRemoveCallback { + /** + * Called when the lifetime extender determines it's safe to remove. + * + * @param key key of the entry that is now safe to remove + */ + void onSafeToRemove(String key); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index d2994bd4fca1..74d96b1f24f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -104,6 +104,7 @@ import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; import android.text.TextUtils; import android.util.ArraySet; +import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -134,6 +135,7 @@ import android.widget.RemoteViews; import android.widget.TextView; import android.widget.Toast; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -191,6 +193,7 @@ import com.android.systemui.statusbar.DismissView; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ForegroundServiceLifetimeExtender; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -198,6 +201,7 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.NotificationGuts; import com.android.systemui.statusbar.NotificationInfo; +import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationSnooze; import com.android.systemui.statusbar.RemoteInputController; @@ -715,7 +719,7 @@ public class StatusBar extends SystemUI implements DemoMode, private ConfigurationListener mConfigurationListener; private boolean mReinflateNotificationsOnUserSwitched; private HashMap<String, Entry> mPendingNotifications = new HashMap<>(); - private ForegroundServiceController mForegroundServiceController; + protected ForegroundServiceController mForegroundServiceController; private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { final int N = array.size(); @@ -760,6 +764,9 @@ public class StatusBar extends SystemUI implements DemoMode, mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); mForegroundServiceController = Dependency.get(ForegroundServiceController.class); + mFGSExtender = new ForegroundServiceLifetimeExtender(); + mFGSExtender.setCallback(key -> removeNotification(key, mLatestRankingMap)); + mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); mDisplay = mWindowManager.getDefaultDisplay(); @@ -1733,6 +1740,11 @@ public class StatusBar extends SystemUI implements DemoMode, } Entry entry = mNotificationData.get(key); + if (entry != null && mFGSExtender.shouldExtendLifetime(entry)) { + extendLifetime(entry, mFGSExtender); + return; + } + if (entry != null && mRemoteInputController.isRemoteInputActive(entry) && (entry.row != null && !entry.row.isDismissed())) { mLatestRankingMap = ranking; @@ -1763,9 +1775,35 @@ public class StatusBar extends SystemUI implements DemoMode, } } } + // Make sure no lifetime extension is happening anymore + cancelLifetimeExtension(entry); setAreThereNotifications(); } + /** Lifetime extension keeps entries around after they would've otherwise been canceled */ + private void extendLifetime(Entry entry, NotificationLifetimeExtender extender) { + // Cancel any other extender which might be holding on to this notification entry + NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry); + if (activeExtender != null && activeExtender != extender) { + activeExtender.setShouldManageLifetime(entry, false); + } + mRetainedNotifications.put(entry, extender); + extender.setShouldManageLifetime(entry, true); + } + + /** Tells the current extender (if any) to stop extending the entry's lifetime */ + private void cancelLifetimeExtension(NotificationData.Entry entry) { + NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry); + if (activeExtender != null) { + activeExtender.setShouldManageLifetime(entry, false); + } + } + + @VisibleForTesting + public Map<Entry, NotificationLifetimeExtender> getRetainedNotificationMap() { + return mRetainedNotifications; + } + /** * Ensures that the group children are cancelled immediately when the group summary is cancelled * instead of waiting for the notification manager to send all cancels. Otherwise this could @@ -3369,6 +3407,17 @@ public class StatusBar extends SystemUI implements DemoMode, } } + pw.println(" Lifetime-extended notifications:"); + if (mRetainedNotifications.isEmpty()) { + pw.println(" None"); + } else { + for (Map.Entry<NotificationData.Entry, NotificationLifetimeExtender> entry + : mRetainedNotifications.entrySet()) { + pw.println(" " + entry.getKey().notification + " retained by " + + entry.getValue().getClass().getName()); + } + } + pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); pw.print(" mStatusBarWindowState="); pw.println(windowStateToString(mStatusBarWindowState)); @@ -5212,6 +5261,11 @@ public class StatusBar extends SystemUI implements DemoMode, protected RemoteInputController mRemoteInputController; + // A lifetime extender that watches for foreground service notifications + @VisibleForTesting protected NotificationLifetimeExtender mFGSExtender; + private final Map<Entry, NotificationLifetimeExtender> mRetainedNotifications = + new ArrayMap<>(); + // for heads up notifications protected HeadsUpManager mHeadsUpManager; @@ -6825,6 +6879,9 @@ public class StatusBar extends SystemUI implements DemoMode, mRemoteInputEntriesToRemoveOnCollapse.remove(entry); } + // No need to keep the lifetime extension around if an update comes in for it + cancelLifetimeExtension(entry); + Notification n = notification.getNotification(); mNotificationData.updateRanking(ranking); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtenderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtenderTest.java new file mode 100644 index 000000000000..153efde9c8d8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtenderTest.java @@ -0,0 +1,85 @@ +/* + * 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.systemui.statusbar; + +import static com.android.systemui.statusbar.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationData.Entry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ForegroundServiceLifetimeExtenderTest extends SysuiTestCase { + private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender(); + private StatusBarNotification mSbn; + private NotificationData.Entry mEntry; + private Notification mNotif; + + @Before + public void setup() { + mNotif = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text") + .build(); + mSbn = mock(StatusBarNotification.class); + when(mSbn.getNotification()).thenReturn(mNotif); + mEntry = new NotificationData.Entry(mSbn); + } + + /** + * ForegroundServiceLifetimeExtenderTest + */ + @Test + public void testShouldExtendLifetime_should_foreground() { + // Extend the lifetime of a FGS notification iff it has not been visible + // for the minimum time + mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE; + when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis()); + assertTrue(mExtender.shouldExtendLifetime(mEntry)); + } + + @Test + public void testShouldExtendLifetime_shouldNot_foreground() { + mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE; + when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1); + assertFalse(mExtender.shouldExtendLifetime(mEntry)); + } + + @Test + public void testShouldExtendLifetime_shouldNot_notForeground() { + mNotif.flags = 0; + when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1); + assertFalse(mExtender.shouldExtendLifetime(mEntry)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 0e3ea7a07e7d..d47612319ba8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static com.android.systemui.statusbar.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.fail; @@ -37,6 +39,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; +import android.app.ActivityManager; import android.app.Notification; import android.metrics.LogMaker; import android.os.Handler; @@ -47,6 +50,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import android.service.notification.NotificationListenerService.RankingMap; import android.support.test.filters.SmallTest; import android.support.test.metricshelper.MetricsAsserts; import android.support.test.runner.AndroidJUnit4; @@ -61,6 +65,11 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.keyguard.KeyguardHostView.OnDismissAction; +import com.android.systemui.ForegroundServiceController; +import com.android.systemui.R; +import com.android.systemui.statusbar.ForegroundServiceLifetimeExtender; +import com.android.systemui.statusbar.NotificationLifetimeExtender; +import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.SysuiTestCase; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.statusbar.ActivatableNotificationView; @@ -75,13 +84,19 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import junit.framework.Assert; + import java.util.ArrayList; +import java.util.Map; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper public class StatusBarTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 123; + StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; UnlockMethodCache mUnlockMethodCache; KeyguardIndicationController mKeyguardIndicationController; @@ -94,8 +109,12 @@ public class StatusBarTest extends SysuiTestCase { SystemServicesProxy mSystemServicesProxy; NotificationPanelView mNotificationPanelView; IStatusBarService mBarService; + RemoteInputController mRemoteInputController; + ForegroundServiceController mForegroundServiceController; ArrayList<Entry> mNotificationList; private DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private ForegroundServiceLifetimeExtender mFGSExtender = + new ForegroundServiceLifetimeExtender(); @Before public void setup() throws Exception { @@ -110,6 +129,8 @@ public class StatusBarTest extends SysuiTestCase { mNotificationPanelView = mock(NotificationPanelView.class); mNotificationList = mock(ArrayList.class); IPowerManager powerManagerService = mock(IPowerManager.class); + mRemoteInputController = mock(RemoteInputController.class); + mForegroundServiceController = mock(ForegroundServiceController.class); HandlerThread handlerThread = new HandlerThread("TestThread"); handlerThread.start(); mPowerManager = new PowerManager(mContext, powerManagerService, @@ -118,10 +139,11 @@ public class StatusBarTest extends SysuiTestCase { mBarService = mock(IStatusBarService.class); mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); + mFGSExtender.setCallback(key -> mStatusBar.removeNotification(key, mock(RankingMap.class))); mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache, mKeyguardIndicationController, mStackScroller, mHeadsUpManager, mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView, - mBarService); + mBarService, mFGSExtender, mRemoteInputController, mForegroundServiceController); doAnswer(invocation -> { OnDismissAction onDismissAction = (OnDismissAction) invocation.getArguments()[0]; onDismissAction.onDismiss(); @@ -318,6 +340,53 @@ public class StatusBarTest extends SysuiTestCase { } @Test + public void testForegroundServiceNotificationKeptForFiveSeconds() throws Exception { + RankingMap rm = mock(RankingMap.class); + + // sbn posted "just now" + Notification n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text") + .build(); + n.flags |= Notification.FLAG_FOREGROUND_SERVICE; + StatusBarNotification sbn = + new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, n, new UserHandle(ActivityManager.getCurrentUser()), null, + System.currentTimeMillis()); + NotificationData.Entry entry = new NotificationData.Entry(sbn); + when(mNotificationData.get(any())).thenReturn(entry); + mStatusBar.removeNotification(sbn.getKey(), rm); + Map<NotificationData.Entry, NotificationLifetimeExtender> map = + mStatusBar.getRetainedNotificationMap(); + Assert.assertTrue(map.containsKey(entry)); + } + @Test + public void testForegroundServiceNotification_notRetainedIfShownForFiveSeconds() + throws Exception { + + RankingMap rm = mock(RankingMap.class); + + // sbn posted "more than 5 seconds ago" + Notification n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text") + .build(); + n.flags |= Notification.FLAG_FOREGROUND_SERVICE; + StatusBarNotification sbn = + new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, n, new UserHandle(ActivityManager.getCurrentUser()), null, + System.currentTimeMillis() - MIN_FGS_TIME_MS - 1); + NotificationData.Entry entry = new NotificationData.Entry(sbn); + when(mNotificationData.get(any())).thenReturn(entry); + mStatusBar.removeNotification(sbn.getKey(), rm); + Map<NotificationData.Entry, NotificationLifetimeExtender> map = + mStatusBar.getRetainedNotificationMap(); + Assert.assertFalse(map.containsKey(entry)); + } + + @Test public void testLogHidden() { try { mStatusBar.handleVisibleToUserChanged(false); @@ -390,7 +459,8 @@ public class StatusBarTest extends SysuiTestCase { UnlockMethodCache unlock, KeyguardIndicationController key, NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd, PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView, - IStatusBarService barService) { + IStatusBarService barService, ForegroundServiceLifetimeExtender fgsExtender, + RemoteInputController ric, ForegroundServiceController fsc) { mStatusBarKeyguardViewManager = man; mUnlockMethodCache = unlock; mKeyguardIndicationController = key; @@ -402,10 +472,13 @@ public class StatusBarTest extends SysuiTestCase { mSystemServicesProxy = ssp; mNotificationPanel = panelView; mBarService = barService; + mFGSExtender = fgsExtender; + mRemoteInputController = ric; + mForegroundServiceController = fsc; } public void setBarStateForTest(int state) { mState = state; } } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8a7d6000dd01..1a730383b5bb 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4385,8 +4385,15 @@ public class NotificationManagerService extends SystemService { userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName); synchronized (mNotificationLock) { - // Look for the notification, searching both the posted and enqueued lists. - NotificationRecord r = findNotificationLocked(pkg, tag, id, userId); + // If the notification is currently enqueued, repost this runnable so it has a + // chance to notify listeners + if ((findNotificationByListLocked( + mEnqueuedNotifications, pkg, tag, id, userId)) != null) { + mHandler.post(this); + } + // Look for the notification in the posted list, since we already checked enq + NotificationRecord r = findNotificationByListLocked( + mNotificationList, pkg, tag, id, userId); if (r != null) { // The notification was found, check if it should be removed. diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index d7815a282d17..d25721e7ec3f 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -319,6 +319,21 @@ public class NotificationManagerServiceTest extends NotificationTestCase { assertEquals(0, mNotificationManagerService.getNotificationRecordCount()); } + + @Test + public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag() + throws Exception { + final StatusBarNotification sbn = generateNotificationRecord(null).sbn; + sbn.getNotification().flags = + Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE; + mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId()); + waitForIdle(); + verify(mNotificationListeners, times(1)).notifyPostedLocked(any(), any()); + verify(mNotificationListeners, times(1)).notifyRemovedLocked(any(), anyInt()); + } + @Test public void testCancelNotificationWhilePostedAndEnqueued() throws Exception { mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0, |