Clear conversation count after expanding
Test: manaual Fixes: 207960274 Change-Id: Iafe7aeaeadf5f35b0e1213c9a907b6ba373a7361
This commit is contained in:
@@ -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) =
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user