Merge "Make sure PendingIntent cancel listener is unregistered" into rvc-dev

This commit is contained in:
Mady Mellor
2020-07-01 16:09:29 +00:00
committed by Android (Google) Code Review
6 changed files with 100 additions and 44 deletions

View File

@@ -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;
}
/**

View File

@@ -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 */);
}

View File

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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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(