Loading services/core/java/com/android/server/notification/BubbleExtractor.java +2 −1 Original line number Diff line number Diff line Loading @@ -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 { Loading services/core/java/com/android/server/notification/NotificationManagerService.java +3 −0 Original line number Diff line number Diff line Loading @@ -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)); } Loading Loading @@ -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(); Loading services/core/java/com/android/server/notification/NotificationRecord.java +14 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; Loading services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java +30 −0 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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()); } } services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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()); Loading Loading @@ -5591,6 +5601,67 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0); } @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! Loading Loading
services/core/java/com/android/server/notification/BubbleExtractor.java +2 −1 Original line number Diff line number Diff line Loading @@ -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 { Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +3 −0 Original line number Diff line number Diff line Loading @@ -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)); } Loading Loading @@ -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(); Loading
services/core/java/com/android/server/notification/NotificationRecord.java +14 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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; Loading
services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java +30 −0 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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()); } }
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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()); Loading Loading @@ -5591,6 +5601,67 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0); } @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! Loading