Loading packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +53 −7 Original line number Diff line number Diff line Loading @@ -124,8 +124,26 @@ class Bubble implements BubbleViewProvider { private int mNotificationId; private int mAppUid = -1; /** * A bubble is created and can be updated. This intent is updated until the user first * expands the bubble. Once the user has expanded the contents, we ignore the intent updates * to prevent restarting the intent & possibly altering UI state in the activity in front of * the user. * * Once the bubble is overflowed, the activity is finished and updates to the * notification are respected. Typically an update to an overflowed bubble would result in * that bubble being added back to the stack anyways. */ @Nullable private PendingIntent mIntent; private boolean mIntentActive; @Nullable private PendingIntent.CancelListener mIntentCancelListener; /** * Sent when the bubble & notification are no longer visible to the user (i.e. no * notification in the shade, no bubble in the stack or overflow). */ @Nullable private PendingIntent mDeleteIntent; Loading @@ -150,13 +168,19 @@ class Bubble implements BubbleViewProvider { mShowBubbleUpdateDot = false; } /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) Bubble(@NonNull final NotificationEntry e, @Nullable final BubbleController.NotificationSuppressionChangedListener listener) { @Nullable final BubbleController.NotificationSuppressionChangedListener listener, final BubbleController.PendingIntentCanceledListener intentCancelListener) { Objects.requireNonNull(e); mKey = e.getKey(); mSuppressionListener = listener; mIntentCancelListener = intent -> { if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); } intentCancelListener.onPendingIntentCanceled(this); }; setEntry(e); } Loading Loading @@ -238,6 +262,10 @@ class Bubble implements BubbleViewProvider { mExpandedView = null; } mIconView = null; if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); } mIntentActive = false; } void setPendingIntentCanceled() { Loading Loading @@ -371,11 +399,24 @@ class Bubble implements BubbleViewProvider { mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); mIcon = entry.getBubbleMetadata().getIcon(); if (!mIntentActive || mIntent == null) { if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); } mIntent = entry.getBubbleMetadata().getIntent(); if (mIntent != null) { mIntent.registerCancelListener(mIntentCancelListener); } } else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) { // Was an intent bubble now it's a shortcut bubble... still unregister the listener mIntent.unregisterCancelListener(mIntentCancelListener); mIntent = null; } mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } mIsImportantConversation = entry.getChannel() == null ? false : entry.getChannel().isImportantConversation(); entry.getChannel() != null && entry.getChannel().isImportantConversation(); } @Nullable Loading @@ -395,10 +436,15 @@ class Bubble implements BubbleViewProvider { } /** * @return if the bubble was ever expanded * Sets if the intent used for this bubble is currently active (i.e. populating an * expanded view, expanded or not). */ boolean getWasAccessed() { return mLastAccessed != 0L; void setIntentActive() { mIntentActive = true; } boolean isIntentActive() { return mIntentActive; } /** Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +23 −17 Original line number Diff line number Diff line Loading @@ -262,6 +262,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi void onBubbleNotificationSuppressionChange(Bubble bubble); } /** * Listener to be notified when a pending intent has been canceled for a bubble. */ public interface PendingIntentCanceledListener { /** * Called when the pending intent for a bubble has been canceled. */ void onPendingIntentCanceled(Bubble bubble); } /** * Callback for when the BubbleController wants to interact with the notification pipeline to: * - Remove a previously bubbled notification Loading Loading @@ -390,6 +400,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } }); mBubbleData.setPendingIntentCancelledListener(bubble -> { if (bubble.getBubbleIntent() == null) { return; } if (bubble.isIntentActive()) { bubble.setPendingIntentCanceled(); return; } mHandler.post( () -> removeBubble(bubble.getKey(), BubbleController.DISMISS_INVALID_INTENT)); }); mNotificationEntryManager = entryManager; mNotificationGroupManager = groupManager; Loading Loading @@ -1101,23 +1123,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Lazy init stack view when a bubble is created ensureStackViewCreated(); bubble.setInflateSynchronously(mInflateSynchronously); bubble.inflate( b -> { mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade); if (bubble.getBubbleIntent() == null) { return; } bubble.getBubbleIntent().registerCancelListener(pendingIntent -> { if (bubble.getWasAccessed()) { bubble.setPendingIntentCanceled(); return; } mHandler.post( () -> removeBubble(bubble.getKey(), BubbleController.DISMISS_INVALID_INTENT)); }); }, bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade), mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +8 −2 Original line number Diff line number Diff line Loading @@ -59,7 +59,7 @@ import javax.inject.Singleton; @Singleton public class BubbleData { BubbleLogger mLogger = new BubbleLoggerImpl(); private BubbleLogger mLogger = new BubbleLoggerImpl(); private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES; Loading Loading @@ -137,6 +137,7 @@ public class BubbleData { @Nullable private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; private BubbleController.PendingIntentCanceledListener mCancelledListener; /** * We track groups with summaries that aren't visibly displayed but still kept around because Loading Loading @@ -167,6 +168,11 @@ public class BubbleData { mSuppressionListener = listener; } public void setPendingIntentCancelledListener( BubbleController.PendingIntentCanceledListener listener) { mCancelledListener = listener; } public boolean hasBubbles() { return !mBubbles.isEmpty(); } Loading Loading @@ -236,7 +242,7 @@ public class BubbleData { bubbleToReturn = mPendingBubbles.get(key); } else if (entry != null) { // New bubble bubbleToReturn = new Bubble(entry, mSuppressionListener); bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener); } else { // Persisted bubble being promoted bubbleToReturn = persistedBubble; Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +3 −0 Original line number Diff line number Diff line Loading @@ -183,6 +183,9 @@ public class BubbleExpandedView extends LinearLayout { // Apply flags to make behaviour match documentLaunchMode=always. fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); if (mBubble != null) { mBubble.setIntentActive(); } mActivityView.startActivity(mPendingIntent, fillInIntent, options); } } catch (RuntimeException e) { Loading packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +12 −17 Original line number Diff line number Diff line Loading @@ -107,6 +107,9 @@ public class BubbleDataTest extends SysuiTestCase { @Mock private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; @Mock private BubbleController.PendingIntentCanceledListener mPendingIntentCanceledListener; @Before public void setUp() throws Exception { mNotificationTestHelper = new NotificationTestHelper( Loading @@ -127,20 +130,20 @@ public class BubbleDataTest extends SysuiTestCase { modifyRanking(mEntryInterruptive) .setVisuallyInterruptive(true) .build(); mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener); mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null); ExpandableNotificationRow row = mNotificationTestHelper.createBubble(); mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d"); mEntryDismissed.setRow(row); mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener); mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null); mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener); mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener); mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener); mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener); mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener); mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener); mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener); mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener); mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener); mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener); mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener); mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener); mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener); mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener); mBubbleData = new BubbleData(getContext()); Loading Loading @@ -847,14 +850,6 @@ public class BubbleDataTest extends SysuiTestCase { when(entry.getSbn().getPostTime()).thenReturn(postTime); } private void setOngoing(NotificationEntry entry, boolean ongoing) { if (ongoing) { entry.getSbn().getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; } else { entry.getSbn().getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE; } } /** * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is * required for BubbleData functionality and verification. NotificationTestHelper is used only Loading Loading
packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +53 −7 Original line number Diff line number Diff line Loading @@ -124,8 +124,26 @@ class Bubble implements BubbleViewProvider { private int mNotificationId; private int mAppUid = -1; /** * A bubble is created and can be updated. This intent is updated until the user first * expands the bubble. Once the user has expanded the contents, we ignore the intent updates * to prevent restarting the intent & possibly altering UI state in the activity in front of * the user. * * Once the bubble is overflowed, the activity is finished and updates to the * notification are respected. Typically an update to an overflowed bubble would result in * that bubble being added back to the stack anyways. */ @Nullable private PendingIntent mIntent; private boolean mIntentActive; @Nullable private PendingIntent.CancelListener mIntentCancelListener; /** * Sent when the bubble & notification are no longer visible to the user (i.e. no * notification in the shade, no bubble in the stack or overflow). */ @Nullable private PendingIntent mDeleteIntent; Loading @@ -150,13 +168,19 @@ class Bubble implements BubbleViewProvider { mShowBubbleUpdateDot = false; } /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) Bubble(@NonNull final NotificationEntry e, @Nullable final BubbleController.NotificationSuppressionChangedListener listener) { @Nullable final BubbleController.NotificationSuppressionChangedListener listener, final BubbleController.PendingIntentCanceledListener intentCancelListener) { Objects.requireNonNull(e); mKey = e.getKey(); mSuppressionListener = listener; mIntentCancelListener = intent -> { if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); } intentCancelListener.onPendingIntentCanceled(this); }; setEntry(e); } Loading Loading @@ -238,6 +262,10 @@ class Bubble implements BubbleViewProvider { mExpandedView = null; } mIconView = null; if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); } mIntentActive = false; } void setPendingIntentCanceled() { Loading Loading @@ -371,11 +399,24 @@ class Bubble implements BubbleViewProvider { mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); mIcon = entry.getBubbleMetadata().getIcon(); if (!mIntentActive || mIntent == null) { if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); } mIntent = entry.getBubbleMetadata().getIntent(); if (mIntent != null) { mIntent.registerCancelListener(mIntentCancelListener); } } else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) { // Was an intent bubble now it's a shortcut bubble... still unregister the listener mIntent.unregisterCancelListener(mIntentCancelListener); mIntent = null; } mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } mIsImportantConversation = entry.getChannel() == null ? false : entry.getChannel().isImportantConversation(); entry.getChannel() != null && entry.getChannel().isImportantConversation(); } @Nullable Loading @@ -395,10 +436,15 @@ class Bubble implements BubbleViewProvider { } /** * @return if the bubble was ever expanded * Sets if the intent used for this bubble is currently active (i.e. populating an * expanded view, expanded or not). */ boolean getWasAccessed() { return mLastAccessed != 0L; void setIntentActive() { mIntentActive = true; } boolean isIntentActive() { return mIntentActive; } /** Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +23 −17 Original line number Diff line number Diff line Loading @@ -262,6 +262,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi void onBubbleNotificationSuppressionChange(Bubble bubble); } /** * Listener to be notified when a pending intent has been canceled for a bubble. */ public interface PendingIntentCanceledListener { /** * Called when the pending intent for a bubble has been canceled. */ void onPendingIntentCanceled(Bubble bubble); } /** * Callback for when the BubbleController wants to interact with the notification pipeline to: * - Remove a previously bubbled notification Loading Loading @@ -390,6 +400,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } }); mBubbleData.setPendingIntentCancelledListener(bubble -> { if (bubble.getBubbleIntent() == null) { return; } if (bubble.isIntentActive()) { bubble.setPendingIntentCanceled(); return; } mHandler.post( () -> removeBubble(bubble.getKey(), BubbleController.DISMISS_INVALID_INTENT)); }); mNotificationEntryManager = entryManager; mNotificationGroupManager = groupManager; Loading Loading @@ -1101,23 +1123,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Lazy init stack view when a bubble is created ensureStackViewCreated(); bubble.setInflateSynchronously(mInflateSynchronously); bubble.inflate( b -> { mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade); if (bubble.getBubbleIntent() == null) { return; } bubble.getBubbleIntent().registerCancelListener(pendingIntent -> { if (bubble.getWasAccessed()) { bubble.setPendingIntentCanceled(); return; } mHandler.post( () -> removeBubble(bubble.getKey(), BubbleController.DISMISS_INVALID_INTENT)); }); }, bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade), mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +8 −2 Original line number Diff line number Diff line Loading @@ -59,7 +59,7 @@ import javax.inject.Singleton; @Singleton public class BubbleData { BubbleLogger mLogger = new BubbleLoggerImpl(); private BubbleLogger mLogger = new BubbleLoggerImpl(); private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES; Loading Loading @@ -137,6 +137,7 @@ public class BubbleData { @Nullable private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; private BubbleController.PendingIntentCanceledListener mCancelledListener; /** * We track groups with summaries that aren't visibly displayed but still kept around because Loading Loading @@ -167,6 +168,11 @@ public class BubbleData { mSuppressionListener = listener; } public void setPendingIntentCancelledListener( BubbleController.PendingIntentCanceledListener listener) { mCancelledListener = listener; } public boolean hasBubbles() { return !mBubbles.isEmpty(); } Loading Loading @@ -236,7 +242,7 @@ public class BubbleData { bubbleToReturn = mPendingBubbles.get(key); } else if (entry != null) { // New bubble bubbleToReturn = new Bubble(entry, mSuppressionListener); bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener); } else { // Persisted bubble being promoted bubbleToReturn = persistedBubble; Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +3 −0 Original line number Diff line number Diff line Loading @@ -183,6 +183,9 @@ public class BubbleExpandedView extends LinearLayout { // Apply flags to make behaviour match documentLaunchMode=always. fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); if (mBubble != null) { mBubble.setIntentActive(); } mActivityView.startActivity(mPendingIntent, fillInIntent, options); } } catch (RuntimeException e) { Loading
packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +12 −17 Original line number Diff line number Diff line Loading @@ -107,6 +107,9 @@ public class BubbleDataTest extends SysuiTestCase { @Mock private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; @Mock private BubbleController.PendingIntentCanceledListener mPendingIntentCanceledListener; @Before public void setUp() throws Exception { mNotificationTestHelper = new NotificationTestHelper( Loading @@ -127,20 +130,20 @@ public class BubbleDataTest extends SysuiTestCase { modifyRanking(mEntryInterruptive) .setVisuallyInterruptive(true) .build(); mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener); mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null); ExpandableNotificationRow row = mNotificationTestHelper.createBubble(); mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d"); mEntryDismissed.setRow(row); mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener); mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null); mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener); mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener); mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener); mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener); mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener); mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener); mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener); mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener); mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener); mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener); mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener); mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener); mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener); mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener); mBubbleData = new BubbleData(getContext()); Loading Loading @@ -847,14 +850,6 @@ public class BubbleDataTest extends SysuiTestCase { when(entry.getSbn().getPostTime()).thenReturn(postTime); } private void setOngoing(NotificationEntry entry, boolean ongoing) { if (ongoing) { entry.getSbn().getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE; } else { entry.getSbn().getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE; } } /** * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is * required for BubbleData functionality and verification. NotificationTestHelper is used only Loading