Merge "Make sure PendingIntent cancel listener is unregistered" into rvc-dev
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user