Merge "Refactor parts of BubbleController"
This commit is contained in:
@@ -190,6 +190,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
|
||||
private boolean mInflateSynchronously;
|
||||
|
||||
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
|
||||
private final List<NotifCallback> mCallbacks = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Listener to be notified when some states of the bubbles change.
|
||||
*/
|
||||
@@ -231,7 +234,35 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
* Called when the notification suppression state of a bubble changes.
|
||||
*/
|
||||
void onBubbleNotificationSuppressionChange(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
|
||||
*/
|
||||
public interface NotifCallback {
|
||||
/**
|
||||
* Called when the BubbleController wants to remove an entry that it was previously hiding
|
||||
* from the shade. See {@link BubbleController#isBubbleNotificationSuppressedFromShade}.
|
||||
*/
|
||||
void removeNotification(NotificationEntry entry);
|
||||
|
||||
/**
|
||||
* Called when a bubbled notification has changed whether it should be
|
||||
* filtered from the shade.
|
||||
*/
|
||||
void invalidateNotificationFilter(String reason);
|
||||
|
||||
/**
|
||||
* Called on a bubbled entry that has been removed when there are no longer
|
||||
* bubbled entries in its group.
|
||||
*
|
||||
* Checks whether its group has any other (non-bubbled) children. If it doesn't,
|
||||
* removes all remnants of the group's summary from the notification pipeline.
|
||||
* TODO: (b/145659174) Only old pipeline needs this - delete post-migration.
|
||||
*/
|
||||
void maybeCancelSummary(NotificationEntry entry);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,26 +363,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
});
|
||||
|
||||
mNotificationEntryManager = entryManager;
|
||||
mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
|
||||
mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
|
||||
mNotificationGroupManager = groupManager;
|
||||
mNotificationGroupManager.addOnGroupChangeListener(
|
||||
new NotificationGroupManager.OnGroupChangeListener() {
|
||||
@Override
|
||||
public void onGroupSuppressionChanged(
|
||||
NotificationGroupManager.NotificationGroup group,
|
||||
boolean suppressed) {
|
||||
// More notifications could be added causing summary to no longer
|
||||
// be suppressed -- in this case need to remove the key.
|
||||
final String groupKey = group.summary != null
|
||||
? group.summary.getSbn().getGroupKey()
|
||||
: null;
|
||||
if (!suppressed && groupKey != null
|
||||
&& mBubbleData.isSummarySuppressed(groupKey)) {
|
||||
mBubbleData.removeSuppressedSummary(groupKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
setupNEM();
|
||||
|
||||
mNotificationShadeWindowController = notificationShadeWindowController;
|
||||
mStatusBarStateListener = new StatusBarStateListener();
|
||||
@@ -390,6 +403,104 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
mBubbleIconFactory = new BubbleIconFactory(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link NotifCallback}.
|
||||
*/
|
||||
public void addNotifCallback(NotifCallback callback) {
|
||||
mCallbacks.add(callback);
|
||||
}
|
||||
|
||||
private void setupNEM() {
|
||||
mNotificationEntryManager.addNotificationEntryListener(
|
||||
new NotificationEntryListener() {
|
||||
@Override
|
||||
public void onNotificationAdded(NotificationEntry entry) {
|
||||
onEntryAdded(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreEntryUpdated(NotificationEntry entry) {
|
||||
onEntryUpdated(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRankingUpdated(RankingMap rankingMap) {
|
||||
onRankingUpdated(rankingMap);
|
||||
}
|
||||
});
|
||||
|
||||
mNotificationEntryManager.setNotificationRemoveInterceptor(
|
||||
new NotificationRemoveInterceptor() {
|
||||
@Override
|
||||
public boolean onNotificationRemoveRequested(String key, int reason) {
|
||||
NotificationEntry entry =
|
||||
mNotificationEntryManager.getActiveNotificationUnfiltered(key);
|
||||
return shouldInterceptDismissal(entry, reason);
|
||||
}
|
||||
});
|
||||
|
||||
mNotificationGroupManager.addOnGroupChangeListener(
|
||||
new NotificationGroupManager.OnGroupChangeListener() {
|
||||
@Override
|
||||
public void onGroupSuppressionChanged(
|
||||
NotificationGroupManager.NotificationGroup group,
|
||||
boolean suppressed) {
|
||||
// More notifications could be added causing summary to no longer
|
||||
// be suppressed -- in this case need to remove the key.
|
||||
final String groupKey = group.summary != null
|
||||
? group.summary.getSbn().getGroupKey()
|
||||
: null;
|
||||
if (!suppressed && groupKey != null
|
||||
&& mBubbleData.isSummarySuppressed(groupKey)) {
|
||||
mBubbleData.removeSuppressedSummary(groupKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addNotifCallback(new NotifCallback() {
|
||||
@Override
|
||||
public void removeNotification(NotificationEntry entry) {
|
||||
mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
|
||||
UNDEFINED_DISMISS_REASON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNotificationFilter(String reason) {
|
||||
mNotificationEntryManager.updateNotifications(reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeCancelSummary(NotificationEntry entry) {
|
||||
// Check if removed bubble has an associated suppressed group summary that needs
|
||||
// to be removed now.
|
||||
final String groupKey = entry.getSbn().getGroup();
|
||||
if (mBubbleData.isSummarySuppressed(groupKey)) {
|
||||
mBubbleData.removeSuppressedSummary(entry.getSbn().getGroupKey());
|
||||
|
||||
final NotificationEntry summary =
|
||||
mNotificationEntryManager.getActiveNotificationUnfiltered(
|
||||
mBubbleData.getSummaryKey(groupKey));
|
||||
mNotificationEntryManager.performRemoveNotification(summary.getSbn(),
|
||||
UNDEFINED_DISMISS_REASON);
|
||||
}
|
||||
|
||||
// Check if summary should be removed from NoManGroup
|
||||
NotificationEntry summary =
|
||||
mNotificationGroupManager.getLogicalGroupSummary(entry.getSbn());
|
||||
if (summary != null) {
|
||||
ArrayList<NotificationEntry> summaryChildren =
|
||||
mNotificationGroupManager.getLogicalChildren(summary.getSbn());
|
||||
boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey());
|
||||
if (!isSummaryThisNotif && (summaryChildren == null
|
||||
|| summaryChildren.isEmpty())) {
|
||||
mNotificationEntryManager.performRemoveNotification(summary.getSbn(),
|
||||
UNDEFINED_DISMISS_REASON);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to perform inflation on the same thread as the caller. This method should only
|
||||
* be used in tests, not in production.
|
||||
@@ -562,13 +673,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
*
|
||||
* False otherwise.
|
||||
*/
|
||||
public boolean isBubbleNotificationSuppressedFromShade(String key) {
|
||||
public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
|
||||
String key = entry.getKey();
|
||||
boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
|
||||
&& !mBubbleData.getBubbleWithKey(key).showInShade();
|
||||
NotificationEntry entry = mNotificationEntryManager.getActiveNotificationUnfiltered(key);
|
||||
String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
|
||||
|
||||
String groupKey = entry.getSbn().getGroupKey();
|
||||
boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
|
||||
boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
|
||||
|
||||
return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
|
||||
}
|
||||
|
||||
@@ -702,160 +815,46 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final NotificationRemoveInterceptor mRemoveInterceptor =
|
||||
new NotificationRemoveInterceptor() {
|
||||
@Override
|
||||
public boolean onNotificationRemoveRequested(String key, int reason) {
|
||||
NotificationEntry entry =
|
||||
mNotificationEntryManager.getActiveNotificationUnfiltered(key);
|
||||
String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
|
||||
ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
|
||||
private void onEntryAdded(NotificationEntry entry) {
|
||||
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
|
||||
boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
|
||||
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
|
||||
mContext, entry, previouslyUserCreated, userBlocked);
|
||||
|
||||
boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
|
||||
boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
|
||||
&& mBubbleData.getSummaryKey(groupKey).equals(key));
|
||||
boolean isSummary = entry != null
|
||||
&& entry.getSbn().getNotification().isGroupSummary();
|
||||
boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
|
||||
&& bubbleChildren != null && !bubbleChildren.isEmpty();
|
||||
|
||||
if (!inBubbleData && !isSummaryOfBubbles) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean isClearAll = reason == REASON_CANCEL_ALL;
|
||||
final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK;
|
||||
final boolean isAppCancel = reason == REASON_APP_CANCEL
|
||||
|| reason == REASON_APP_CANCEL_ALL;
|
||||
final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED;
|
||||
|
||||
// Need to check for !appCancel here because the notification may have
|
||||
// previously been dismissed & entry.isRowDismissed would still be true
|
||||
boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
|
||||
|| isClearAll || isUserDimiss || isSummaryCancel;
|
||||
|
||||
if (isSummaryOfBubbles) {
|
||||
return handleSummaryRemovalInterception(entry, userRemovedNotif);
|
||||
}
|
||||
|
||||
// The bubble notification sticks around in the data as long as the bubble is
|
||||
// not dismissed and the app hasn't cancelled the notification.
|
||||
Bubble bubble = mBubbleData.getBubbleWithKey(key);
|
||||
boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
|
||||
if (bubbleExtended) {
|
||||
bubble.setSuppressNotification(true);
|
||||
bubble.setShowDot(false /* show */, true /* animate */);
|
||||
mNotificationEntryManager.updateNotifications(
|
||||
"BubbleController.onNotificationRemoveRequested");
|
||||
return true;
|
||||
} else if (!userRemovedNotif && entry != null
|
||||
&& !isUserCreatedBubble(bubble.getKey())) {
|
||||
// This wasn't a user removal so we should remove the bubble as well
|
||||
mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
|
||||
&& (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
|
||||
if (wasAdjusted && !previouslyUserCreated) {
|
||||
// Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
|
||||
mUserCreatedBubbles.add(entry.getKey());
|
||||
}
|
||||
};
|
||||
|
||||
private boolean handleSummaryRemovalInterception(NotificationEntry summary,
|
||||
boolean userRemovedNotif) {
|
||||
String groupKey = summary.getSbn().getGroupKey();
|
||||
ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
|
||||
|
||||
if (userRemovedNotif) {
|
||||
// If it's a user dismiss we mark the children to be hidden from the shade.
|
||||
for (int i = 0; i < bubbleChildren.size(); i++) {
|
||||
Bubble bubbleChild = bubbleChildren.get(i);
|
||||
// As far as group manager is concerned, once a child is no longer shown
|
||||
// in the shade, it is essentially removed.
|
||||
mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
|
||||
bubbleChild.setSuppressNotification(true);
|
||||
bubbleChild.setShowDot(false /* show */, true /* animate */);
|
||||
}
|
||||
// And since all children are removed, remove the summary.
|
||||
mNotificationGroupManager.onEntryRemoved(summary);
|
||||
|
||||
// If the summary was auto-generated we don't need to keep that notification around
|
||||
// because apps can't cancel it; so we only intercept & suppress real summaries.
|
||||
boolean isAutogroupSummary = (summary.getSbn().getNotification().flags
|
||||
& FLAG_AUTOGROUP_SUMMARY) != 0;
|
||||
if (!isAutogroupSummary) {
|
||||
mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
|
||||
summary.getKey());
|
||||
// Tell shade to update for the suppression
|
||||
mNotificationEntryManager.updateNotifications(
|
||||
"BubbleController.handleSummaryRemovalInterception");
|
||||
}
|
||||
return !isAutogroupSummary;
|
||||
} else {
|
||||
// If it's not a user dismiss it's a cancel.
|
||||
for (int i = 0; i < bubbleChildren.size(); i++) {
|
||||
// First check if any of these are user-created (i.e. experimental bubbles)
|
||||
if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) {
|
||||
// Experimental bubble! Intercept the removal.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Not an experimental bubble, safe to remove.
|
||||
mBubbleData.removeSuppressedSummary(groupKey);
|
||||
// Remove any associated bubble children with the summary.
|
||||
for (int i = 0; i < bubbleChildren.size(); i++) {
|
||||
Bubble bubbleChild = bubbleChildren.get(i);
|
||||
mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
|
||||
DISMISS_GROUP_CANCELLED);
|
||||
}
|
||||
return false;
|
||||
updateBubble(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
|
||||
@Override
|
||||
public void onNotificationAdded(NotificationEntry entry) {
|
||||
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
|
||||
boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
|
||||
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
|
||||
mContext, entry, previouslyUserCreated, userBlocked);
|
||||
private void onEntryUpdated(NotificationEntry entry) {
|
||||
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
|
||||
boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
|
||||
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
|
||||
mContext, entry, previouslyUserCreated, userBlocked);
|
||||
|
||||
if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
|
||||
&& (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
|
||||
if (wasAdjusted && !previouslyUserCreated) {
|
||||
// Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
|
||||
mUserCreatedBubbles.add(entry.getKey());
|
||||
}
|
||||
updateBubble(entry);
|
||||
boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
|
||||
&& (canLaunchInActivityView(mContext, entry) || wasAdjusted);
|
||||
if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
|
||||
// It was previously a bubble but no longer a bubble -- lets remove it
|
||||
removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
|
||||
} else if (shouldBubble) {
|
||||
if (wasAdjusted && !previouslyUserCreated) {
|
||||
// Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
|
||||
mUserCreatedBubbles.add(entry.getKey());
|
||||
}
|
||||
updateBubble(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreEntryUpdated(NotificationEntry entry) {
|
||||
boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
|
||||
boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
|
||||
boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
|
||||
mContext, entry, previouslyUserCreated, userBlocked);
|
||||
|
||||
boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
|
||||
&& (canLaunchInActivityView(mContext, entry) || wasAdjusted);
|
||||
if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
|
||||
// It was previously a bubble but no longer a bubble -- lets remove it
|
||||
removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
|
||||
} else if (shouldBubble) {
|
||||
if (wasAdjusted && !previouslyUserCreated) {
|
||||
// Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
|
||||
mUserCreatedBubbles.add(entry.getKey());
|
||||
}
|
||||
updateBubble(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRankingUpdated(RankingMap rankingMap) {
|
||||
// Forward to BubbleData to block any bubbles which should no longer be shown
|
||||
mBubbleData.notificationRankingUpdated(rankingMap);
|
||||
}
|
||||
};
|
||||
private void onRankingUpdated(RankingMap rankingMap) {
|
||||
// Forward to BubbleData to block any bubbles which should no longer be shown
|
||||
mBubbleData.notificationRankingUpdated(rankingMap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
|
||||
@@ -888,9 +887,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
if (reason != DISMISS_USER_CHANGED) {
|
||||
if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
|
||||
&& !bubble.showInShade()) {
|
||||
// The bubble is gone & the notification is gone, time to actually remove it
|
||||
mNotificationEntryManager.performRemoveNotification(
|
||||
bubble.getEntry().getSbn(), UNDEFINED_DISMISS_REASON);
|
||||
// The bubble is now gone & the notification is hidden from the shade, so
|
||||
// time to actually remove it
|
||||
for (NotifCallback cb : mCallbacks) {
|
||||
cb.removeNotification(bubble.getEntry());
|
||||
}
|
||||
} else {
|
||||
// Update the flag for SysUI
|
||||
bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
|
||||
@@ -905,32 +906,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
}
|
||||
}
|
||||
|
||||
// Check if removed bubble has an associated suppressed group summary that needs
|
||||
// to be removed now.
|
||||
final String groupKey = bubble.getEntry().getSbn().getGroupKey();
|
||||
if (mBubbleData.isSummarySuppressed(groupKey)
|
||||
&& mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
|
||||
// Time to actually remove the summary.
|
||||
String notifKey = mBubbleData.getSummaryKey(groupKey);
|
||||
mBubbleData.removeSuppressedSummary(groupKey);
|
||||
NotificationEntry entry =
|
||||
mNotificationEntryManager.getActiveNotificationUnfiltered(notifKey);
|
||||
mNotificationEntryManager.performRemoveNotification(
|
||||
entry.getSbn(), UNDEFINED_DISMISS_REASON);
|
||||
}
|
||||
|
||||
// Check if summary should be removed from NoManGroup
|
||||
NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(
|
||||
bubble.getEntry().getSbn());
|
||||
if (summary != null) {
|
||||
ArrayList<NotificationEntry> summaryChildren =
|
||||
mNotificationGroupManager.getLogicalChildren(summary.getSbn());
|
||||
boolean isSummaryThisNotif = summary.getKey().equals(
|
||||
bubble.getEntry().getKey());
|
||||
if (!isSummaryThisNotif
|
||||
&& (summaryChildren == null || summaryChildren.isEmpty())) {
|
||||
mNotificationEntryManager.performRemoveNotification(
|
||||
summary.getSbn(), UNDEFINED_DISMISS_REASON);
|
||||
if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
|
||||
// Time to potentially remove the summary
|
||||
for (NotifCallback cb : mCallbacks) {
|
||||
cb.maybeCancelSummary(bubble.getEntry());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -959,8 +939,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
mStackView.setExpanded(true);
|
||||
}
|
||||
|
||||
mNotificationEntryManager.updateNotifications(
|
||||
"BubbleData.Listener.applyUpdate");
|
||||
for (NotifCallback cb : mCallbacks) {
|
||||
cb.invalidateNotificationFilter("BubbleData.Listener.applyUpdate");
|
||||
}
|
||||
updateStack();
|
||||
|
||||
if (DEBUG_BUBBLE_CONTROLLER) {
|
||||
@@ -980,6 +961,127 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* We intercept notification entries cancelled by the user (i.e. dismissed) when there is an
|
||||
* active bubble associated with it. We do this so that developers can still cancel it
|
||||
* (and hence the bubbles associated with it). However, these intercepted notifications
|
||||
* should then be hidden from the shade since the user has cancelled them, so we update
|
||||
* {@link Bubble#showInShade}.
|
||||
*
|
||||
* The cancellation of summaries with children associated with bubbles are also handled in this
|
||||
* method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}.
|
||||
*
|
||||
* @return true if we want to intercept the dismissal of the entry, else false
|
||||
*/
|
||||
public boolean shouldInterceptDismissal(NotificationEntry entry, int dismissReason) {
|
||||
if (entry == null) {
|
||||
return false;
|
||||
}
|
||||
String key = entry.getKey();
|
||||
String groupKey = entry != null ? entry.getSbn().getGroupKey() : null;
|
||||
ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
|
||||
|
||||
boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
|
||||
boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
|
||||
&& mBubbleData.getSummaryKey(groupKey).equals(key));
|
||||
boolean isSummary = entry != null
|
||||
&& entry.getSbn().getNotification().isGroupSummary();
|
||||
boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
|
||||
&& bubbleChildren != null && !bubbleChildren.isEmpty();
|
||||
|
||||
if (!inBubbleData && !isSummaryOfBubbles) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
|
||||
final boolean isUserDimiss = dismissReason == REASON_CANCEL
|
||||
|| dismissReason == REASON_CLICK;
|
||||
final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
|
||||
|| dismissReason == REASON_APP_CANCEL_ALL;
|
||||
final boolean isSummaryCancel = dismissReason == REASON_GROUP_SUMMARY_CANCELED;
|
||||
|
||||
// Need to check for !appCancel here because the notification may have
|
||||
// previously been dismissed & entry.isRowDismissed would still be true
|
||||
boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
|
||||
|| isClearAll || isUserDimiss || isSummaryCancel;
|
||||
if (isSummaryOfBubbles) {
|
||||
return handleSummaryRemovalInterception(entry, userRemovedNotif);
|
||||
}
|
||||
|
||||
// The bubble notification sticks around in the data as long as the bubble is
|
||||
// not dismissed and the app hasn't cancelled the notification.
|
||||
Bubble bubble = mBubbleData.getBubbleWithKey(key);
|
||||
boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
|
||||
if (bubbleExtended) {
|
||||
bubble.setSuppressNotification(true);
|
||||
bubble.setShowDot(false /* show */, true /* animate */);
|
||||
for (NotifCallback cb : mCallbacks) {
|
||||
cb.invalidateNotificationFilter("BubbleController"
|
||||
+ ".shouldInterceptDismissal");
|
||||
}
|
||||
return true;
|
||||
} else if (!userRemovedNotif && entry != null
|
||||
&& !isUserCreatedBubble(bubble.getKey())) {
|
||||
// This wasn't a user removal so we should remove the bubble as well
|
||||
mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleSummaryRemovalInterception(NotificationEntry summary,
|
||||
boolean userRemovedNotif) {
|
||||
String groupKey = summary.getSbn().getGroupKey();
|
||||
ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
|
||||
|
||||
if (userRemovedNotif) {
|
||||
// If it's a user dismiss we mark the children to be hidden from the shade.
|
||||
for (int i = 0; i < bubbleChildren.size(); i++) {
|
||||
Bubble bubbleChild = bubbleChildren.get(i);
|
||||
// As far as group manager is concerned, once a child is no longer shown
|
||||
// in the shade, it is essentially removed.
|
||||
mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
|
||||
bubbleChild.setSuppressNotification(true);
|
||||
bubbleChild.setShowDot(false /* show */, true /* animate */);
|
||||
}
|
||||
// And since all children are removed, remove the summary.
|
||||
mNotificationGroupManager.onEntryRemoved(summary);
|
||||
|
||||
// If the summary was auto-generated we don't need to keep that notification around
|
||||
// because apps can't cancel it; so we only intercept & suppress real summaries.
|
||||
boolean isAutogroupSummary = (summary.getSbn().getNotification().flags
|
||||
& FLAG_AUTOGROUP_SUMMARY) != 0;
|
||||
if (!isAutogroupSummary) {
|
||||
// TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
|
||||
mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
|
||||
summary.getKey());
|
||||
// Tell shade to update for the suppression
|
||||
mNotificationEntryManager.updateNotifications("BubbleController"
|
||||
+ ".handleSummaryRemovalInterception");
|
||||
}
|
||||
return !isAutogroupSummary;
|
||||
} else {
|
||||
// If it's not a user dismiss it's a cancel.
|
||||
for (int i = 0; i < bubbleChildren.size(); i++) {
|
||||
// First check if any of these are user-created (i.e. experimental bubbles)
|
||||
if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) {
|
||||
// Experimental bubble! Intercept the removal.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Not an experimental bubble, safe to remove.
|
||||
mBubbleData.removeSuppressedSummary(groupKey);
|
||||
// Remove any associated bubble children with the summary.
|
||||
for (int i = 0; i < bubbleChildren.size(); i++) {
|
||||
Bubble bubbleChild = bubbleChildren.get(i);
|
||||
mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
|
||||
DISMISS_GROUP_CANCELLED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets any listeners know if bubble state has changed.
|
||||
* Updates the visibility of the bubbles based on current state.
|
||||
|
||||
@@ -140,7 +140,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
|
||||
boolean hideMedia = Utils.useQsMediaPlayer(mContext);
|
||||
if (ent.isRowDismissed() || ent.isRowRemoved()
|
||||
|| (ent.isMediaNotification() && hideMedia)
|
||||
|| mBubbleController.isBubbleNotificationSuppressedFromShade(ent.getKey())) {
|
||||
|| mBubbleController.isBubbleNotificationSuppressedFromShade(ent)) {
|
||||
// we don't want to update removed notifications because they could
|
||||
// temporarily become children if they were isolated before.
|
||||
continue;
|
||||
|
||||
@@ -214,6 +214,7 @@ public class NotifCollection implements Dumpable {
|
||||
private void onNotificationRankingUpdate(RankingMap rankingMap) {
|
||||
Assert.isMainThread();
|
||||
applyRanking(rankingMap);
|
||||
dispatchNotificationRankingUpdate(rankingMap);
|
||||
rebuildList();
|
||||
}
|
||||
|
||||
@@ -393,6 +394,14 @@ public class NotifCollection implements Dumpable {
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
private void dispatchNotificationRankingUpdate(RankingMap map) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onRankingUpdate(map);
|
||||
}
|
||||
mAmDispatchingToOtherCode = false;
|
||||
}
|
||||
|
||||
private void dispatchOnEntryRemoved(
|
||||
NotificationEntry entry,
|
||||
@CancellationReason int reason,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.statusbar.notification.collection.notifcollection;
|
||||
|
||||
import android.service.notification.NotificationListenerService;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.NotifCollection;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
@@ -47,4 +49,11 @@ public interface NotifCollectionListener {
|
||||
@CancellationReason int reason,
|
||||
boolean removedByUser) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the RankingMap is updated by system server. By the time this listener is
|
||||
* called, the Rankings of all entries will have been updated.
|
||||
*/
|
||||
default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,8 +204,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State
|
||||
}
|
||||
int childCount = 0;
|
||||
boolean hasBubbles = false;
|
||||
for (String key : group.children.keySet()) {
|
||||
if (!getBubbleController().isBubbleNotificationSuppressedFromShade(key)) {
|
||||
for (NotificationEntry entry : group.children.values()) {
|
||||
if (!getBubbleController().isBubbleNotificationSuppressedFromShade(entry)) {
|
||||
childCount++;
|
||||
} else {
|
||||
hasBubbles = true;
|
||||
|
||||
@@ -279,7 +279,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
// Make it look like dismissed notif
|
||||
mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
|
||||
@@ -323,7 +323,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
// We should have bubbles & their notifs should not be suppressed
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
assertFalse(mNotificationShadeWindowController.getBubbleExpanded());
|
||||
|
||||
// Expand the stack
|
||||
@@ -335,7 +335,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
// Make sure the notif is suppressed
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
// Collapse
|
||||
mBubbleController.collapseStack();
|
||||
@@ -355,9 +355,9 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
// We should have bubbles & their notifs should not be suppressed
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow2.getEntry().getKey()));
|
||||
mRow2.getEntry()));
|
||||
|
||||
// Expand
|
||||
BubbleStackView stackView = mBubbleController.getStackView();
|
||||
@@ -368,14 +368,14 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
// Last added is the one that is expanded
|
||||
assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow2.getEntry().getKey()));
|
||||
mRow2.getEntry()));
|
||||
|
||||
// Switch which bubble is expanded
|
||||
mBubbleController.selectBubble(mRow.getEntry().getKey());
|
||||
mBubbleData.setExpanded(true);
|
||||
assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry());
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
// collapse for previous bubble
|
||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
|
||||
@@ -396,7 +396,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
// We should have bubbles & their notifs should not be suppressed
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
mTestableLooper.processAllMessages();
|
||||
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
@@ -408,7 +408,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
// Notif is suppressed after expansion
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
// Notif shouldn't show dot after expansion
|
||||
assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
}
|
||||
@@ -422,7 +422,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
// We should have bubbles & their notifs should not be suppressed
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
mTestableLooper.processAllMessages();
|
||||
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
@@ -434,7 +434,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
// Notif is suppressed after expansion
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
// Notif shouldn't show dot after expansion
|
||||
assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
|
||||
@@ -444,7 +444,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
// Nothing should have changed
|
||||
// Notif is suppressed after expansion
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
// Notif shouldn't show dot after expansion
|
||||
assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
}
|
||||
@@ -468,7 +468,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
// Last added is the one that is expanded
|
||||
assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry());
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow2.getEntry().getKey()));
|
||||
mRow2.getEntry()));
|
||||
|
||||
// Dismiss currently expanded
|
||||
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
|
||||
@@ -537,7 +537,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
// Notif should be suppressed because we were foreground
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
// Dot + flyout is hidden because notif is suppressed
|
||||
assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
|
||||
@@ -552,7 +552,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
// Should not be suppressed
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
// Should show dot
|
||||
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
|
||||
@@ -563,7 +563,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
// Notif should be suppressed
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
// Dot + flyout is hidden because notif is suppressed
|
||||
assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout());
|
||||
@@ -590,7 +590,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
public void testMarkNewNotificationAsShowInShade() {
|
||||
mEntryListener.onNotificationAdded(mRow.getEntry());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
mTestableLooper.processAllMessages();
|
||||
assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
|
||||
@@ -663,7 +663,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
|
||||
mRow.getEntry().getKey(), REASON_CANCEL_ALL);
|
||||
@@ -672,7 +672,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
assertTrue(intercepted);
|
||||
// Should update show in shade state
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
verify(mNotificationEntryManager, never()).performRemoveNotification(
|
||||
any(), anyInt());
|
||||
@@ -686,7 +686,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
|
||||
mRow.getEntry().getKey(), REASON_CANCEL);
|
||||
@@ -695,7 +695,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
assertTrue(intercepted);
|
||||
// Should update show in shade state
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
verify(mNotificationEntryManager, never()).performRemoveNotification(
|
||||
any(), anyInt());
|
||||
@@ -709,7 +709,7 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
// Dismiss the bubble
|
||||
mBubbleController.removeBubble(
|
||||
@@ -734,13 +734,13 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
mRemoveInterceptor.onNotificationRemoveRequested(mRow.getEntry().getKey(), REASON_CANCEL);
|
||||
|
||||
// Should update show in shade state
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
// Should notify delegate that shade state changed
|
||||
verify(listener).onBubbleNotificationSuppressionChange(
|
||||
@@ -757,13 +757,13 @@ public class BubbleControllerTest extends SysuiTestCase {
|
||||
|
||||
assertTrue(mBubbleController.hasBubbles());
|
||||
assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
mBubbleData.setExpanded(true);
|
||||
|
||||
// Once a bubble is expanded the notif is suppressed
|
||||
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
|
||||
mRow.getEntry().getKey()));
|
||||
mRow.getEntry()));
|
||||
|
||||
// Should notify delegate that shade state changed
|
||||
verify(listener).onBubbleNotificationSuppressionChange(
|
||||
|
||||
Reference in New Issue
Block a user