Merge "Clear conversation count after expanding"

This commit is contained in:
Jay Aliomer
2022-01-14 15:37:36 +00:00
committed by Android (Google) Code Review
3 changed files with 111 additions and 70 deletions

View File

@@ -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) =

View File

@@ -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();
}

View File

@@ -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