diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 894ecf67bf346..ac06f95cadf4e 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -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 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 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 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 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 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 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 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. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 6b0b5dfeaf407..8d4a9efbcd7ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 92927cf104a0c..8ac4d30081792 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -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, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9cbc7d7efa661..6adcabd86fe6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -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) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index 8c947edd9a836..77337e95bb958 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -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; diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 28f01da56f062..5cfb4b39b16bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -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(