diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index b0d41f1557134..3449bd8e26863 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -32,6 +32,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationContentView import com.android.systemui.statusbar.notification.stack.StackStateAnimator @@ -132,12 +134,15 @@ class AnimatedImageNotificationManager @Inject constructor( /** * Tracks state related to conversation notifications, and updates the UI of existing notifications * when necessary. + * TODO(b/214083332) Refactor this class to use the right coordinators and controllers */ @SysUISingleton class ConversationNotificationManager @Inject constructor( private val notificationEntryManager: NotificationEntryManager, private val notificationGroupManager: NotificationGroupManagerLegacy, private val context: Context, + private val notifCollection: CommonNotifCollection, + private val featureFlags: NotifPipelineFlags, @Main private val mainHandler: Handler ) { // Need this state to be thread safe, since it's accessed from the ui thread @@ -146,76 +151,93 @@ class ConversationNotificationManager @Inject constructor( private var notifPanelCollapsed = true + private val entryManagerListener = object : NotificationEntryListener { + override fun onNotificationRankingUpdated(rankingMap: RankingMap) = + updateNotificationRanking(rankingMap) + override fun onEntryInflated(entry: NotificationEntry) = + onEntryViewBound(entry) + override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) + override fun onEntryRemoved( + entry: NotificationEntry, + visibility: NotificationVisibility?, + removedByUser: Boolean, + reason: Int + ) = removeTrackedEntry(entry) + } + + private val notifCollectionListener = object : NotifCollectionListener { + override fun onRankingUpdate(ranking: RankingMap) = + updateNotificationRanking(ranking) + + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + removeTrackedEntry(entry) + } + } + + private fun updateNotificationRanking(rankingMap: RankingMap) { + fun getLayouts(view: NotificationContentView) = + sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) + val ranking = Ranking() + val activeConversationEntries = states.keys.asSequence() + .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } + for (entry in activeConversationEntries) { + if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { + val important = ranking.channel.isImportantConversation + var changed = false + entry.row?.layouts?.asSequence() + ?.flatMap(::getLayouts) + ?.mapNotNull { it as? ConversationLayout } + ?.filterNot { it.isImportantConversation == important } + ?.forEach { layout -> + changed = true + if (important && entry.isMarkedForUserTriggeredMovement) { + // delay this so that it doesn't animate in until after + // the notif has been moved in the shade + mainHandler.postDelayed( + { + layout.setIsImportantConversation( + important, + true) + }, + IMPORTANCE_ANIMATION_DELAY.toLong()) + } else { + layout.setIsImportantConversation(important, false) + } + } + if (changed) { + notificationGroupManager.updateIsolation(entry) + } + } + } + } + fun onEntryViewBound(entry: NotificationEntry) { + if (!entry.ranking.isConversation) { + return + } + fun updateCount(isExpanded: Boolean) { + if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) { + resetCount(entry.key) + entry.row?.let(::resetBadgeUi) + } + } + entry.row?.setOnExpansionChangedListener { isExpanded -> + if (entry.row?.isShown == true && isExpanded) { + entry.row.performOnIntrinsicHeightReached { + updateCount(isExpanded) + } + } else { + updateCount(isExpanded) + } + } + updateCount(entry.row?.isExpanded == true) + } + init { - notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { - override fun onNotificationRankingUpdated(rankingMap: RankingMap) { - fun getLayouts(view: NotificationContentView) = - sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) - val ranking = Ranking() - val activeConversationEntries = states.keys.asSequence() - .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } - for (entry in activeConversationEntries) { - if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { - val important = ranking.channel.isImportantConversation - var changed = false - entry.row?.layouts?.asSequence() - ?.flatMap(::getLayouts) - ?.mapNotNull { it as? ConversationLayout } - ?.filterNot { it.isImportantConversation == important } - ?.forEach { layout -> - changed = true - if (important && entry.isMarkedForUserTriggeredMovement) { - // delay this so that it doesn't animate in until after - // the notif has been moved in the shade - mainHandler.postDelayed( - { - layout.setIsImportantConversation( - important, - true) - }, - IMPORTANCE_ANIMATION_DELAY.toLong()) - } else { - layout.setIsImportantConversation(important, false) - } - } - if (changed) { - notificationGroupManager.updateIsolation(entry) - } - } - } - } - - override fun onEntryInflated(entry: NotificationEntry) { - if (!entry.ranking.isConversation) { - return - } - fun updateCount(isExpanded: Boolean) { - if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) { - resetCount(entry.key) - entry.row?.let(::resetBadgeUi) - } - } - entry.row?.setOnExpansionChangedListener { isExpanded -> - if (entry.row?.isShown == true && isExpanded) { - entry.row.performOnIntrinsicHeightReached { - updateCount(isExpanded) - } - } else { - updateCount(isExpanded) - } - } - updateCount(entry.row?.isExpanded == true) - } - - override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) - - override fun onEntryRemoved( - entry: NotificationEntry, - visibility: NotificationVisibility?, - removedByUser: Boolean, - reason: Int - ) = removeTrackedEntry(entry) - }) + if (featureFlags.isNewPipelineEnabled()) { + notifCollection.addCollectionListener(notifCollectionListener) + } else { + notificationEntryManager.addNotificationEntryListener(entryManagerListener) + } } private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index ec4e0391c171b..195f3672dc562 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -31,6 +31,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -98,6 +99,7 @@ public class PreparationCoordinator implements Coordinator { /** How long we can delay a group while waiting for all children to inflate */ private final long mMaxGroupInflationDelay; + private final ConversationNotificationManager mConversationManager; @Inject public PreparationCoordinator( @@ -106,7 +108,8 @@ public class PreparationCoordinator implements Coordinator { NotifInflationErrorManager errorManager, NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, - IStatusBarService service) { + IStatusBarService service, + ConversationNotificationManager conversationManager) { this( logger, notifInflater, @@ -114,6 +117,7 @@ public class PreparationCoordinator implements Coordinator { viewBarn, adjustmentProvider, service, + conversationManager, CHILD_BIND_CUTOFF, MAX_GROUP_INFLATION_DELAY); } @@ -126,6 +130,7 @@ public class PreparationCoordinator implements Coordinator { NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service, + ConversationNotificationManager conversationManager, int childBindCutoff, long maxGroupInflationDelay) { mLogger = logger; @@ -136,6 +141,7 @@ public class PreparationCoordinator implements Coordinator { mStatusBarService = service; mChildBindCutoff = childBindCutoff; mMaxGroupInflationDelay = maxGroupInflationDelay; + mConversationManager = conversationManager; } @Override @@ -363,6 +369,9 @@ public class PreparationCoordinator implements Coordinator { mInflatingNotifs.remove(entry); mViewBarn.registerViewForEntry(entry, controller); mInflationStates.put(entry, STATE_INFLATED); + // NOTE: under the new pipeline there's no way to register for an inflation callback, + // so this one method is called by the PreparationCoordinator directly. + mConversationManager.onEntryViewBound(entry); mNotifInflatingFilter.invalidateList(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index f70330dbe506e..bde6734bbf926 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.SectionClassifier; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; @@ -92,6 +93,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Mock private NotifSection mNotifSection; @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; + @Mock private ConversationNotificationManager mConvoManager; @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater(); private final SectionClassifier mSectionClassifier = new SectionClassifier(); private final NotifUiAdjustmentProvider mAdjustmentProvider = @@ -119,6 +121,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mock(NotifViewBarn.class), mAdjustmentProvider, mService, + mConvoManager, TEST_CHILD_BIND_CUTOFF, TEST_MAX_GROUP_DELAY); @@ -404,6 +407,13 @@ public class PreparationCoordinatorTest extends SysuiTestCase { assertFalse(mUninflatedFilter.shouldFilterOut(child1, 401)); } + @Test + public void testCallConversationManagerBindWhenInflated() { + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + mNotifInflater.getInflateCallback(mEntry).onInflationFinished(mEntry, null); + verify(mConvoManager, times(1)).onEntryViewBound(eq(mEntry)); + } + @Test public void testPartiallyInflatedGroupsAreReleasedAfterTimeout() { // GIVEN a newly-posted group with a summary and two children