diff options
6 files changed, 100 insertions, 44 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 6dc8322a5cf3..c6d128631930 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -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; @@ -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); } @@ -238,6 +262,10 @@ class Bubble implements BubbleViewProvider { mExpandedView = null; } mIconView = null; + if (mIntent != null) { + mIntent.unregisterCancelListener(mIntentCancelListener); + } + mIntentActive = false; } void setPendingIntentCanceled() { @@ -371,11 +399,24 @@ class Bubble implements BubbleViewProvider { mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); mIcon = entry.getBubbleMetadata().getIcon(); - mIntent = entry.getBubbleMetadata().getIntent(); + + 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 @@ -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; } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 6ea0cde44282..6f103b020814 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -263,6 +263,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** + * 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 * - Update the notification shade since bubbled notification should/shouldn't be showing @@ -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; @@ -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 */); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index ec4304f184f9..d2dc506c8e5c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -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; @@ -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 @@ -167,6 +168,11 @@ public class BubbleData { mSuppressionListener = listener; } + public void setPendingIntentCancelledListener( + BubbleController.PendingIntentCanceledListener listener) { + mCancelledListener = listener; + } + public boolean hasBubbles() { return !mBubbles.isEmpty(); } @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 1211fb491ced..3d3171208b15 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -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) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index ed4e6865e508..315caeebe0e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -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( @@ -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()); @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java index be03923e7264..2bcc22c4b99e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java @@ -71,7 +71,7 @@ public class BubbleTest extends SysuiTestCase { .setNotification(mNotif) .build(); - mBubble = new Bubble(mEntry, mSuppressionListener); + mBubble = new Bubble(mEntry, mSuppressionListener, null); Intent target = new Intent(mContext, BubblesTestActivity.class); Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder( |