Merge "Make sure PendingIntent cancel listener is unregistered" into rvc-dev am: 0e3d9d31a2
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/12037679 Change-Id: I8252074649fbdbe1f7cb1ac0b611b82807a87b01
This commit is contained in:
@@ -124,8 +124,26 @@ class Bubble implements BubbleViewProvider {
|
|||||||
private int mNotificationId;
|
private int mNotificationId;
|
||||||
private int mAppUid = -1;
|
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
|
@Nullable
|
||||||
private PendingIntent mIntent;
|
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
|
@Nullable
|
||||||
private PendingIntent mDeleteIntent;
|
private PendingIntent mDeleteIntent;
|
||||||
|
|
||||||
@@ -150,13 +168,19 @@ class Bubble implements BubbleViewProvider {
|
|||||||
mShowBubbleUpdateDot = false;
|
mShowBubbleUpdateDot = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used in tests when no UI is required. */
|
|
||||||
@VisibleForTesting(visibility = PRIVATE)
|
@VisibleForTesting(visibility = PRIVATE)
|
||||||
Bubble(@NonNull final NotificationEntry e,
|
Bubble(@NonNull final NotificationEntry e,
|
||||||
@Nullable final BubbleController.NotificationSuppressionChangedListener listener) {
|
@Nullable final BubbleController.NotificationSuppressionChangedListener listener,
|
||||||
|
final BubbleController.PendingIntentCanceledListener intentCancelListener) {
|
||||||
Objects.requireNonNull(e);
|
Objects.requireNonNull(e);
|
||||||
mKey = e.getKey();
|
mKey = e.getKey();
|
||||||
mSuppressionListener = listener;
|
mSuppressionListener = listener;
|
||||||
|
mIntentCancelListener = intent -> {
|
||||||
|
if (mIntent != null) {
|
||||||
|
mIntent.unregisterCancelListener(mIntentCancelListener);
|
||||||
|
}
|
||||||
|
intentCancelListener.onPendingIntentCanceled(this);
|
||||||
|
};
|
||||||
setEntry(e);
|
setEntry(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +262,10 @@ class Bubble implements BubbleViewProvider {
|
|||||||
mExpandedView = null;
|
mExpandedView = null;
|
||||||
}
|
}
|
||||||
mIconView = null;
|
mIconView = null;
|
||||||
|
if (mIntent != null) {
|
||||||
|
mIntent.unregisterCancelListener(mIntentCancelListener);
|
||||||
|
}
|
||||||
|
mIntentActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPendingIntentCanceled() {
|
void setPendingIntentCanceled() {
|
||||||
@@ -371,11 +399,24 @@ class Bubble implements BubbleViewProvider {
|
|||||||
mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
|
mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
|
||||||
mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
|
mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
|
||||||
mIcon = entry.getBubbleMetadata().getIcon();
|
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();
|
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
|
||||||
}
|
}
|
||||||
mIsImportantConversation =
|
mIsImportantConversation =
|
||||||
entry.getChannel() == null ? false : entry.getChannel().isImportantConversation();
|
entry.getChannel() != null && entry.getChannel().isImportantConversation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@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() {
|
void setIntentActive() {
|
||||||
return mLastAccessed != 0L;
|
mIntentActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isIntentActive() {
|
||||||
|
return mIntentActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -262,6 +262,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
|||||||
void onBubbleNotificationSuppressionChange(Bubble bubble);
|
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:
|
* Callback for when the BubbleController wants to interact with the notification pipeline to:
|
||||||
* - Remove a previously bubbled notification
|
* - 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;
|
mNotificationEntryManager = entryManager;
|
||||||
mNotificationGroupManager = groupManager;
|
mNotificationGroupManager = groupManager;
|
||||||
@@ -1101,23 +1123,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
|||||||
// Lazy init stack view when a bubble is created
|
// Lazy init stack view when a bubble is created
|
||||||
ensureStackViewCreated();
|
ensureStackViewCreated();
|
||||||
bubble.setInflateSynchronously(mInflateSynchronously);
|
bubble.setInflateSynchronously(mInflateSynchronously);
|
||||||
bubble.inflate(
|
bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
|
||||||
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));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
|
mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ import javax.inject.Singleton;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class BubbleData {
|
public class BubbleData {
|
||||||
|
|
||||||
BubbleLogger mLogger = new BubbleLoggerImpl();
|
private BubbleLogger mLogger = new BubbleLoggerImpl();
|
||||||
|
|
||||||
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
|
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
|
||||||
|
|
||||||
@@ -137,6 +137,7 @@ public class BubbleData {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
|
private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
|
||||||
|
private BubbleController.PendingIntentCanceledListener mCancelledListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We track groups with summaries that aren't visibly displayed but still kept around because
|
* We track groups with summaries that aren't visibly displayed but still kept around because
|
||||||
@@ -167,6 +168,11 @@ public class BubbleData {
|
|||||||
mSuppressionListener = listener;
|
mSuppressionListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPendingIntentCancelledListener(
|
||||||
|
BubbleController.PendingIntentCanceledListener listener) {
|
||||||
|
mCancelledListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasBubbles() {
|
public boolean hasBubbles() {
|
||||||
return !mBubbles.isEmpty();
|
return !mBubbles.isEmpty();
|
||||||
}
|
}
|
||||||
@@ -236,7 +242,7 @@ public class BubbleData {
|
|||||||
bubbleToReturn = mPendingBubbles.get(key);
|
bubbleToReturn = mPendingBubbles.get(key);
|
||||||
} else if (entry != null) {
|
} else if (entry != null) {
|
||||||
// New bubble
|
// New bubble
|
||||||
bubbleToReturn = new Bubble(entry, mSuppressionListener);
|
bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener);
|
||||||
} else {
|
} else {
|
||||||
// Persisted bubble being promoted
|
// Persisted bubble being promoted
|
||||||
bubbleToReturn = persistedBubble;
|
bubbleToReturn = persistedBubble;
|
||||||
|
|||||||
@@ -183,6 +183,9 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
// Apply flags to make behaviour match documentLaunchMode=always.
|
// Apply flags to make behaviour match documentLaunchMode=always.
|
||||||
fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
|
fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||||
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
|
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||||
|
if (mBubble != null) {
|
||||||
|
mBubble.setIntentActive();
|
||||||
|
}
|
||||||
mActivityView.startActivity(mPendingIntent, fillInIntent, options);
|
mActivityView.startActivity(mPendingIntent, fillInIntent, options);
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ public class BubbleDataTest extends SysuiTestCase {
|
|||||||
@Mock
|
@Mock
|
||||||
private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
|
private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BubbleController.PendingIntentCanceledListener mPendingIntentCanceledListener;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
mNotificationTestHelper = new NotificationTestHelper(
|
mNotificationTestHelper = new NotificationTestHelper(
|
||||||
@@ -127,20 +130,20 @@ public class BubbleDataTest extends SysuiTestCase {
|
|||||||
modifyRanking(mEntryInterruptive)
|
modifyRanking(mEntryInterruptive)
|
||||||
.setVisuallyInterruptive(true)
|
.setVisuallyInterruptive(true)
|
||||||
.build();
|
.build();
|
||||||
mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener);
|
mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null);
|
||||||
|
|
||||||
ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
|
ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
|
||||||
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
|
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
|
||||||
mEntryDismissed.setRow(row);
|
mEntryDismissed.setRow(row);
|
||||||
mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener);
|
mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null);
|
||||||
|
|
||||||
mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener);
|
mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener);
|
||||||
mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener);
|
mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener);
|
||||||
mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener);
|
mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener);
|
||||||
mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener);
|
mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener);
|
||||||
mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener);
|
mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener);
|
||||||
mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener);
|
mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
|
||||||
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener);
|
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
|
||||||
|
|
||||||
mBubbleData = new BubbleData(getContext());
|
mBubbleData = new BubbleData(getContext());
|
||||||
|
|
||||||
@@ -847,14 +850,6 @@ public class BubbleDataTest extends SysuiTestCase {
|
|||||||
when(entry.getSbn().getPostTime()).thenReturn(postTime);
|
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
|
* No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
|
||||||
* required for BubbleData functionality and verification. NotificationTestHelper is used only
|
* required for BubbleData functionality and verification. NotificationTestHelper is used only
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class BubbleTest extends SysuiTestCase {
|
|||||||
.setNotification(mNotif)
|
.setNotification(mNotif)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
mBubble = new Bubble(mEntry, mSuppressionListener);
|
mBubble = new Bubble(mEntry, mSuppressionListener, null);
|
||||||
|
|
||||||
Intent target = new Intent(mContext, BubblesTestActivity.class);
|
Intent target = new Intent(mContext, BubblesTestActivity.class);
|
||||||
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
|
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
|
||||||
|
|||||||
Reference in New Issue
Block a user