Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 266afba3 authored by Mady Mellor's avatar Mady Mellor
Browse files

Make sure PendingIntent cancel listener is unregistered

Test: manual
Fixes: 160177818
Fixes: 149918957
Change-Id: I429fc3bcd409cf57276617bd8c9aab26194d3af9
parent 31a89937
Loading
Loading
Loading
Loading
+53 −7
Original line number Diff line number Diff line
@@ -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();

            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;
    }

    /**
+23 −17
Original line number Diff line number Diff line
@@ -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
@@ -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 */);
    }

+8 −2
Original line number Diff line number Diff line
@@ -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;
+3 −0
Original line number Diff line number Diff line
@@ -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) {
+12 −17
Original line number Diff line number Diff line
@@ -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
Loading