Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +17 −21 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Retention(SOURCE) @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED, DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION}) DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE}) @interface DismissReason {} static final int DISMISS_USER_GESTURE = 1; Loading @@ -95,6 +95,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi static final int DISMISS_BLOCKED = 4; static final int DISMISS_NOTIF_CANCEL = 5; static final int DISMISS_ACCESSIBILITY_ACTION = 6; static final int DISMISS_NO_LONGER_BUBBLE = 7; static final int MAX_BUBBLES = 5; // TODO: actually enforce this Loading Loading @@ -129,8 +130,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final StatusBarWindowController mStatusBarWindowController; private StatusBarStateListener mStatusBarStateListener; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = Dependency.get(NotificationInterruptionStateProvider.class); private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private INotificationManager mNotificationManagerService; Loading Loading @@ -189,15 +189,19 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController) { BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { this(context, statusBarWindowController, data, null /* synchronizer */, configurationController); configurationController, interruptionStateProvider); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, ConfigurationController configurationController) { ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { mContext = context; mNotificationInterruptionStateProvider = interruptionStateProvider; configurationController.addCallback(this /* configurationListener */); mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); Loading Loading @@ -394,7 +398,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (shouldAutoBubbleForFlags(mContext, entry) || shouldBubble(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { // TODO: handle group summaries? updateShowInShadeForSuppressNotification(entry); } Loading @@ -405,7 +409,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { updateBubble(entry); } } Loading @@ -415,8 +419,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && alertAgain(entry, entry.notification.getNotification())) { boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble && alertAgain(entry, entry.notification.getNotification())) { updateShowInShadeForSuppressNotification(entry); entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed updateBubble(entry); Loading Loading @@ -560,17 +567,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return mStackView; } /** * Whether the notification has been developer configured to bubble and is allowed by the user. */ @VisibleForTesting protected boolean shouldBubble(NotificationEntry entry) { StatusBarNotification n = entry.notification; boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null && n.getNotification().getBubbleMetadata().getIntent() != null; return hasOverlayIntent && entry.canBubble; } /** * Whether the notification should automatically bubble or not. Gated by secure settings flags. */ Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java +17 −1 Original line number Diff line number Diff line Loading @@ -147,7 +147,14 @@ public class NotificationInterruptionStateProvider { * @return true if the entry should bubble up, false otherwise */ public boolean shouldBubbleUp(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; final StatusBarNotification sbn = entry.notification; if (!entry.canBubble) { if (DEBUG) { Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey()); } return false; } if (!entry.isBubble()) { if (DEBUG) { Log.d(TAG, "No bubble up: notification " + sbn.getKey() Loading @@ -156,6 +163,15 @@ public class NotificationInterruptionStateProvider { return false; } final Notification n = sbn.getNotification(); if (n.getBubbleMetadata() == null || n.getBubbleMetadata().getIntent() == null) { if (DEBUG) { Log.d(TAG, "No bubble up: notification: " + sbn.getKey() + " doesn't have valid metadata"); } return false; } if (!canHeadsUpCommon(entry)) { return false; } Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +39 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; import static android.app.Notification.FLAG_BUBBLE; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static com.google.common.truth.Truth.assertThat; Loading @@ -25,6 +26,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; Loading @@ -47,15 +49,18 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; import org.junit.Test; Loading Loading @@ -138,7 +143,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Some bubbles want to suppress notifs Notification.BubbleMetadata suppressNotifMetadata = getBuilder().setSuppressInitialNotification(true).build(); getBuilder().setSuppressNotification(true).build(); mSuppressNotifRow = mNotificationTestHelper.createBubble(suppressNotifMetadata, FOREGROUND_TEST_PKG_NAME); Loading @@ -146,9 +151,15 @@ public class BubbleControllerTest extends SysuiTestCase { when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel); TestableNotificationInterruptionStateProvider interruptionStateProvider = new TestableNotificationInterruptionStateProvider(mContext); interruptionStateProvider.setUpWithPresenter( mock(NotificationPresenter.class), mock(HeadsUpManager.class), mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController, mBubbleData, mConfigurationController); mBubbleData, mConfigurationController, interruptionStateProvider); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); Loading Loading @@ -487,12 +498,27 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mDeleteIntent, times(2)).send(); } @Test public void testRemoveBubble_noLongerBubbleAfterUpdate() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); mRow.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE; mEntryListener.onPreEntryUpdated(mRow.getEntry()); assertFalse(mBubbleController.hasBubbles()); verify(mDeleteIntent, never()).send(); } static class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController) { super(context, statusBarWindowController, data, Runnable::run, configurationController); ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { super(context, statusBarWindowController, data, Runnable::run, configurationController, interruptionStateProvider); } @Override Loading @@ -501,6 +527,15 @@ public class BubbleControllerTest extends SysuiTestCase { } } public static class TestableNotificationInterruptionStateProvider extends NotificationInterruptionStateProvider { public TestableNotificationInterruptionStateProvider(Context context) { super(context); mUseHeadsUp = true; } } /** * @return basic {@link android.app.Notification.BubbleMetadata.Builder} */ Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +4 −1 Original line number Diff line number Diff line Loading @@ -178,7 +178,10 @@ public class NotificationTestHelper { Notification n = createNotification(false /* isGroupSummary */, null /* groupKey */, bubbleMetadata); n.flags |= FLAG_BUBBLE; return generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); row.getEntry().canBubble = true; return row; } /** Loading Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +17 −21 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Retention(SOURCE) @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED, DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION}) DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE}) @interface DismissReason {} static final int DISMISS_USER_GESTURE = 1; Loading @@ -95,6 +95,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi static final int DISMISS_BLOCKED = 4; static final int DISMISS_NOTIF_CANCEL = 5; static final int DISMISS_ACCESSIBILITY_ACTION = 6; static final int DISMISS_NO_LONGER_BUBBLE = 7; static final int MAX_BUBBLES = 5; // TODO: actually enforce this Loading Loading @@ -129,8 +130,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final StatusBarWindowController mStatusBarWindowController; private StatusBarStateListener mStatusBarStateListener; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = Dependency.get(NotificationInterruptionStateProvider.class); private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private INotificationManager mNotificationManagerService; Loading Loading @@ -189,15 +189,19 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Inject public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController) { BubbleData data, ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { this(context, statusBarWindowController, data, null /* synchronizer */, configurationController); configurationController, interruptionStateProvider); } public BubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, ConfigurationController configurationController) { ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { mContext = context; mNotificationInterruptionStateProvider = interruptionStateProvider; configurationController.addCallback(this /* configurationListener */); mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); Loading Loading @@ -394,7 +398,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (shouldAutoBubbleForFlags(mContext, entry) || shouldBubble(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { // TODO: handle group summaries? updateShowInShadeForSuppressNotification(entry); } Loading @@ -405,7 +409,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) { updateBubble(entry); } } Loading @@ -415,8 +419,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (!areBubblesEnabled(mContext)) { return; } if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) && alertAgain(entry, entry.notification.getNotification())) { boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry); if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble && alertAgain(entry, entry.notification.getNotification())) { updateShowInShadeForSuppressNotification(entry); entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed updateBubble(entry); Loading Loading @@ -560,17 +567,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return mStackView; } /** * Whether the notification has been developer configured to bubble and is allowed by the user. */ @VisibleForTesting protected boolean shouldBubble(NotificationEntry entry) { StatusBarNotification n = entry.notification; boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null && n.getNotification().getBubbleMetadata().getIntent() != null; return hasOverlayIntent && entry.canBubble; } /** * Whether the notification should automatically bubble or not. Gated by secure settings flags. */ Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java +17 −1 Original line number Diff line number Diff line Loading @@ -147,7 +147,14 @@ public class NotificationInterruptionStateProvider { * @return true if the entry should bubble up, false otherwise */ public boolean shouldBubbleUp(NotificationEntry entry) { StatusBarNotification sbn = entry.notification; final StatusBarNotification sbn = entry.notification; if (!entry.canBubble) { if (DEBUG) { Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey()); } return false; } if (!entry.isBubble()) { if (DEBUG) { Log.d(TAG, "No bubble up: notification " + sbn.getKey() Loading @@ -156,6 +163,15 @@ public class NotificationInterruptionStateProvider { return false; } final Notification n = sbn.getNotification(); if (n.getBubbleMetadata() == null || n.getBubbleMetadata().getIntent() == null) { if (DEBUG) { Log.d(TAG, "No bubble up: notification: " + sbn.getKey() + " doesn't have valid metadata"); } return false; } if (!canHeadsUpCommon(entry)) { return false; } Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +39 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; import static android.app.Notification.FLAG_BUBBLE; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static com.google.common.truth.Truth.assertThat; Loading @@ -25,6 +26,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; Loading @@ -47,15 +49,18 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; import org.junit.Test; Loading Loading @@ -138,7 +143,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Some bubbles want to suppress notifs Notification.BubbleMetadata suppressNotifMetadata = getBuilder().setSuppressInitialNotification(true).build(); getBuilder().setSuppressNotification(true).build(); mSuppressNotifRow = mNotificationTestHelper.createBubble(suppressNotifMetadata, FOREGROUND_TEST_PKG_NAME); Loading @@ -146,9 +151,15 @@ public class BubbleControllerTest extends SysuiTestCase { when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel); TestableNotificationInterruptionStateProvider interruptionStateProvider = new TestableNotificationInterruptionStateProvider(mContext); interruptionStateProvider.setUpWithPresenter( mock(NotificationPresenter.class), mock(HeadsUpManager.class), mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class)); mBubbleData = new BubbleData(mContext); mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController, mBubbleData, mConfigurationController); mBubbleData, mConfigurationController, interruptionStateProvider); mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener); mBubbleController.setExpandListener(mBubbleExpandListener); Loading Loading @@ -487,12 +498,27 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mDeleteIntent, times(2)).send(); } @Test public void testRemoveBubble_noLongerBubbleAfterUpdate() throws PendingIntent.CanceledException { mBubbleController.updateBubble(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); mRow.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE; mEntryListener.onPreEntryUpdated(mRow.getEntry()); assertFalse(mBubbleController.hasBubbles()); verify(mDeleteIntent, never()).send(); } static class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, StatusBarWindowController statusBarWindowController, BubbleData data, ConfigurationController configurationController) { super(context, statusBarWindowController, data, Runnable::run, configurationController); ConfigurationController configurationController, NotificationInterruptionStateProvider interruptionStateProvider) { super(context, statusBarWindowController, data, Runnable::run, configurationController, interruptionStateProvider); } @Override Loading @@ -501,6 +527,15 @@ public class BubbleControllerTest extends SysuiTestCase { } } public static class TestableNotificationInterruptionStateProvider extends NotificationInterruptionStateProvider { public TestableNotificationInterruptionStateProvider(Context context) { super(context); mUseHeadsUp = true; } } /** * @return basic {@link android.app.Notification.BubbleMetadata.Builder} */ Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +4 −1 Original line number Diff line number Diff line Loading @@ -178,7 +178,10 @@ public class NotificationTestHelper { Notification n = createNotification(false /* isGroupSummary */, null /* groupKey */, bubbleMetadata); n.flags |= FLAG_BUBBLE; return generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH); row.getEntry().canBubble = true; return row; } /** Loading