diff options
5 files changed, 120 insertions, 1 deletions
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java index f2eb176aa676..2fa80cd8e4e4 100644 --- a/services/core/java/com/android/server/notification/BubbleExtractor.java +++ b/services/core/java/com/android/server/notification/BubbleExtractor.java @@ -76,7 +76,8 @@ public class BubbleExtractor implements NotificationSignalExtractor { record.setAllowBubble(appCanShowBubble); } } - final boolean applyFlag = mBubbleChecker.isNotificationAppropriateToBubble(record); + final boolean applyFlag = mBubbleChecker.isNotificationAppropriateToBubble(record) + && !record.isFlagBubbleRemoved(); if (applyFlag) { record.getNotification().flags |= FLAG_BUBBLE; } else { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 87061e1d488c..ebc1bc4938d6 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1214,10 +1214,12 @@ public class NotificationManagerService extends SystemService { // apps querying noMan will know that their notification is not showing // as a bubble. r.getNotification().flags &= ~FLAG_BUBBLE; + r.setFlagBubbleRemoved(true); } else { // Enqueue will trigger resort & if the flag is allowed to be true it'll // be applied there. r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; + r.setFlagBubbleRemoved(false); mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, isAppForeground)); } @@ -5637,6 +5639,7 @@ public class NotificationManagerService extends SystemService { final NotificationRecord r = new NotificationRecord(getContext(), n, channel); r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid)); r.setPostSilently(postSilently); + r.setFlagBubbleRemoved(false); if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { final boolean fgServiceShown = channel.isFgServiceShown(); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 3f24b38161c3..54a0f9f46892 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -187,6 +187,7 @@ public final class NotificationRecord { private boolean mSuggestionsGeneratedByAssistant; private boolean mEditChoicesBeforeSending; private boolean mHasSeenSmartReplies; + private boolean mFlagBubbleRemoved; private boolean mPostSilently; /** * Whether this notification (and its channels) should be considered user locked. Used in @@ -1201,6 +1202,19 @@ public final class NotificationRecord { return stats.hasBeenVisiblyExpanded(); } + /** + * When the bubble state on a notif changes due to user action (e.g. dismiss a bubble) then + * this value is set until an update or bubble change event due to user action (e.g. create + * bubble from sysui) + **/ + public boolean isFlagBubbleRemoved() { + return mFlagBubbleRemoved; + } + + public void setFlagBubbleRemoved(boolean flagBubbleRemoved) { + mFlagBubbleRemoved = flagBubbleRemoved; + } + public void setSystemGeneratedSmartActions( ArrayList<Notification.Action> systemGeneratedSmartActions) { mSystemGeneratedSmartActions = systemGeneratedSmartActions; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java index c7cef05ce442..0dbbbaa9cdd6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java @@ -47,6 +47,7 @@ import org.mockito.MockitoAnnotations; public class BubbleExtractorTest extends UiServiceTestCase { @Mock RankingConfig mConfig; + @Mock BubbleExtractor.BubbleChecker mBubbleChecker; BubbleExtractor mBubbleExtractor; private String mPkg = "com.android.server.notification"; @@ -141,4 +142,33 @@ public class BubbleExtractorTest extends UiServiceTestCase { assertFalse(r.canBubble()); } + + @Test + public void testFlagBubble_true() { + when(mConfig.bubblesEnabled()).thenReturn(true); + when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true); + NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED); + + mBubbleExtractor.setBubbleChecker(mBubbleChecker); + when(mBubbleChecker.isNotificationAppropriateToBubble(r)).thenReturn(true); + mBubbleExtractor.process(r); + + assertTrue(r.canBubble()); + assertTrue(r.getNotification().isBubbleNotification()); + } + + @Test + public void testFlagBubble_noFlag_previouslyRemoved() { + when(mConfig.bubblesEnabled()).thenReturn(true); + when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true); + NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED); + r.setFlagBubbleRemoved(true); + + mBubbleExtractor.setBubbleChecker(mBubbleChecker); + when(mBubbleChecker.isNotificationAppropriateToBubble(r)).thenReturn(true); + mBubbleExtractor.process(r); + + assertTrue(r.canBubble()); + assertFalse(r.getNotification().isBubbleNotification()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index d2417f9d10c5..702642045eeb 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -78,6 +78,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.AutomaticZenRule; import android.app.IActivityManager; @@ -154,6 +155,7 @@ import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.InstanceIdSequenceFake; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.FastXmlSerializer; +import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiServiceTestCase; @@ -380,12 +382,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { MockitoAnnotations.initMocks(this); + DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class); + when(deviceIdleInternal.getNotificationWhitelistDuration()).thenReturn(3000L); + ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class); + LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); LocalServices.removeServiceForTest(StatusBarManagerInternal.class); LocalServices.addService(StatusBarManagerInternal.class, mStatusBar); + LocalServices.removeServiceForTest(DeviceIdleInternal.class); + LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal); + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal); doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); @@ -5592,6 +5602,67 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testNotificationBubbleIsFlagRemoved_resetOnUpdate() throws Exception { + // Bubbles are allowed! + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); + // Notif with bubble metadata + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testNotificationBubbleIsFlagRemoved_resetOnUpdate"); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + // Flag shouldn't be modified + NotificationRecord recordToCheck = mService.getNotificationRecord(nr.getSbn().getKey()); + assertFalse(recordToCheck.isFlagBubbleRemoved()); + + // Notify we're not a bubble + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false); + waitForIdle(); + // Flag should be modified + recordToCheck = mService.getNotificationRecord(nr.getSbn().getKey()); + assertTrue(recordToCheck.isFlagBubbleRemoved()); + + + // Update the notif + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + // And the flag is reset + recordToCheck = mService.getNotificationRecord(nr.getSbn().getKey()); + assertFalse(recordToCheck.isFlagBubbleRemoved()); + } + + @Test + public void testNotificationBubbleIsFlagRemoved_resetOnBubbleChangedTrue() throws Exception { + // Bubbles are allowed! + setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); + // Notif with bubble metadata + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testNotificationBubbleIsFlagRemoved_trueOnBubbleChangedTrue"); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + // Flag shouldn't be modified + NotificationRecord recordToCheck = mService.getNotificationRecord(nr.getSbn().getKey()); + assertFalse(recordToCheck.isFlagBubbleRemoved()); + + // Notify we're not a bubble + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), false); + waitForIdle(); + // Flag should be modified + recordToCheck = mService.getNotificationRecord(nr.getSbn().getKey()); + assertTrue(recordToCheck.isFlagBubbleRemoved()); + + // Notify we are a bubble + mService.mNotificationDelegate.onNotificationBubbleChanged(nr.getKey(), true); + waitForIdle(); + // And the flag is reset + assertFalse(recordToCheck.isFlagBubbleRemoved()); + } + + @Test public void testOnBubbleNotificationSuppressionChanged() throws Exception { // Bubbles are allowed! setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); |