"Hide silent notifications in status bar" should apply to silent conversations,too

Notification is designed to be in a single section. So convo has two options when it is silent, convo or silent one.
This is our current section prio :  HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
As convo(people) has higher priority than silent, convo notifs always falls convo.

We decided to divide People notification into two groups, silent and alerting. Given that, new prio is going to be:

HeadsUp > FGS > People(alerting) > People(silent) >  Alerting > Silent > Minimized > Unknown/Default

Fixes: b/275711619
Test: atest NotificationIconAreaControllerTest
Change-Id: Ib9efcc199d8675fd16dc394075a53ff25a060786
This commit is contained in:
Ibrahim Yilmaz
2023-03-30 15:23:47 +00:00
parent 5a582c83cc
commit 087952346e
7 changed files with 220 additions and 60 deletions

View File

@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.PeopleHeader
import com.android.systemui.statusbar.notification.icon.ConversationIconManager
@@ -40,27 +41,28 @@ import javax.inject.Inject
*/
@CoordinatorScope
class ConversationCoordinator @Inject constructor(
private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
private val conversationIconManager: ConversationIconManager,
@PeopleHeader peopleHeaderController: NodeController
private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
private val conversationIconManager: ConversationIconManager,
private val highPriorityProvider: HighPriorityProvider,
@PeopleHeader private val peopleHeaderController: NodeController,
) : Coordinator {
private val promotedEntriesToSummaryOfSameChannel =
mutableMapOf<NotificationEntry, NotificationEntry>()
mutableMapOf<NotificationEntry, NotificationEntry>()
private val onBeforeRenderListListener = OnBeforeRenderListListener { _ ->
val unimportantSummaries = promotedEntriesToSummaryOfSameChannel
.mapNotNull { (promoted, summary) ->
val originalGroup = summary.parent
when {
originalGroup == null -> null
originalGroup == promoted.parent -> null
originalGroup.parent == null -> null
originalGroup.summary != summary -> null
originalGroup.children.any { it.channel == summary.channel } -> null
else -> summary.key
.mapNotNull { (promoted, summary) ->
val originalGroup = summary.parent
when {
originalGroup == null -> null
originalGroup == promoted.parent -> null
originalGroup.parent == null -> null
originalGroup.summary != summary -> null
originalGroup.children.any { it.channel == summary.channel } -> null
else -> summary.key
}
}
}
conversationIconManager.setUnimportantConversations(unimportantSummaries)
promotedEntriesToSummaryOfSameChannel.clear()
}
@@ -78,21 +80,23 @@ class ConversationCoordinator @Inject constructor(
}
}
val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) {
val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
override fun isInSection(entry: ListEntry): Boolean =
isConversation(entry)
highPriorityProvider.isHighPriorityConversation(entry)
override fun getComparator() = object : NotifComparator("People") {
override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
val type1 = getPeopleType(entry1)
val type2 = getPeopleType(entry2)
return type2.compareTo(type1)
}
}
override fun getComparator(): NotifComparator = notifComparator
override fun getHeaderNodeController() =
// TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
}
val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
// Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
// All remaining conversations must be silent.
override fun isInSection(entry: ListEntry): Boolean = isConversation(entry)
override fun getComparator(): NotifComparator = notifComparator
override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
}
override fun attach(pipeline: NotifPipeline) {
@@ -101,15 +105,27 @@ class ConversationCoordinator @Inject constructor(
}
private fun isConversation(entry: ListEntry): Boolean =
getPeopleType(entry) != TYPE_NON_PERSON
getPeopleType(entry) != TYPE_NON_PERSON
@PeopleNotificationType
private fun getPeopleType(entry: ListEntry): Int =
entry.representativeEntry?.let {
peopleNotificationIdentifier.getPeopleNotificationType(it)
} ?: TYPE_NON_PERSON
entry.representativeEntry?.let {
peopleNotificationIdentifier.getPeopleNotificationType(it)
} ?: TYPE_NON_PERSON
companion object {
private val notifComparator: NotifComparator = object : NotifComparator("People") {
override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
val type1 = getPeopleType(entry1)
val type2 = getPeopleType(entry2)
return type2.compareTo(type1)
}
}
// TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
private val conversationHeaderNodeController: NodeController? =
if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
private companion object {
private const val TAG = "ConversationCoordinator"
}
}

View File

@@ -21,6 +21,7 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import javax.inject.Inject
/**
@@ -32,6 +33,7 @@ interface NotifCoordinators : Coordinator, PipelineDumpable
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
notifPipelineFlags: NotifPipelineFlags,
sectionStyleProvider: SectionStyleProvider,
dataStoreCoordinator: DataStoreCoordinator,
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
@@ -56,7 +58,7 @@ class NotifCoordinatorsImpl @Inject constructor(
viewConfigCoordinator: ViewConfigCoordinator,
visualStabilityCoordinator: VisualStabilityCoordinator,
sensitiveContentCoordinator: SensitiveContentCoordinator,
dismissibilityCoordinator: DismissibilityCoordinator
dismissibilityCoordinator: DismissibilityCoordinator,
) : NotifCoordinators {
private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -99,13 +101,20 @@ class NotifCoordinatorsImpl @Inject constructor(
mCoordinators.add(dismissibilityCoordinator)
// Manually add Ordered Sections
// HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
mOrderedSections.add(headsUpCoordinator.sectioner)
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
mOrderedSections.add(conversationCoordinator.sectioner) // People
mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
sectionStyleProvider.setSilentSections(listOf(
conversationCoordinator.peopleSilentSectioner,
rankingCoordinator.silentSectioner,
rankingCoordinator.minimizedSectioner,
))
}
/**

View File

@@ -27,15 +27,12 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
@@ -52,7 +49,6 @@ public class RankingCoordinator implements Coordinator {
public static final boolean SHOW_ALL_SECTIONS = false;
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
private final SectionStyleProvider mSectionStyleProvider;
private final NodeController mSilentNodeController;
private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
@@ -63,13 +59,11 @@ public class RankingCoordinator implements Coordinator {
public RankingCoordinator(
StatusBarStateController statusBarStateController,
HighPriorityProvider highPriorityProvider,
SectionStyleProvider sectionStyleProvider,
@AlertingHeader NodeController alertingHeaderController,
@SilentHeader SectionHeaderController silentHeaderController,
@SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
mSectionStyleProvider = sectionStyleProvider;
mAlertingHeaderController = alertingHeaderController;
mSilentNodeController = silentNodeController;
mSilentHeaderController = silentHeaderController;
@@ -78,9 +72,6 @@ public class RankingCoordinator implements Coordinator {
@Override
public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
mSectionStyleProvider.setSilentSections(
Arrays.asList(mSilentNotifSectioner, mMinimizedNotifSectioner));
pipeline.addPreGroupFilter(mSuspendedFilter);
pipeline.addPreGroupFilter(mDndVisualEffectsFilter);

View File

@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.notification.collection.provider;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -63,7 +66,7 @@ public class HighPriorityProvider {
* A GroupEntry is considered high priority if its representativeEntry (summary) or children are
* high priority
*/
public boolean isHighPriority(ListEntry entry) {
public boolean isHighPriority(@Nullable ListEntry entry) {
if (entry == null) {
return false;
}
@@ -78,6 +81,36 @@ public class HighPriorityProvider {
|| hasHighPriorityChild(entry);
}
/**
* @return true if the ListEntry is high priority conversation, else false
*/
public boolean isHighPriorityConversation(@NonNull ListEntry entry) {
final NotificationEntry notifEntry = entry.getRepresentativeEntry();
if (notifEntry == null) {
return false;
}
if (!isPeopleNotification(notifEntry)) {
return false;
}
if (notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT) {
return true;
}
return isNotificationEntryWithAtLeastOneImportantChild(entry);
}
private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull ListEntry entry) {
if (!(entry instanceof GroupEntry)) {
return false;
}
final GroupEntry groupEntry = (GroupEntry) entry;
return groupEntry.getChildren().stream().anyMatch(
childEntry ->
childEntry.getRanking().getImportance()
>= NotificationManager.IMPORTANCE_DEFAULT);
}
private boolean hasHighPriorityChild(ListEntry entry) {
if (entry instanceof NotificationEntry
@@ -93,7 +126,6 @@ public class HighPriorityProvider {
}
}
}
return false;
}

View File

@@ -96,6 +96,59 @@ public class HighPriorityProviderTest extends SysuiTestCase {
assertTrue(mHighPriorityProvider.isHighPriority(entry));
}
@Test
public void highImportanceConversation() {
// GIVEN notification is high importance and is a people notification
final Notification notification = new Notification.Builder(mContext, "test")
.build();
final NotificationEntry entry = new NotificationEntryBuilder()
.setNotification(notification)
.setImportance(IMPORTANCE_HIGH)
.build();
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry))
.thenReturn(TYPE_PERSON);
// THEN it is high priority conversation
assertTrue(mHighPriorityProvider.isHighPriorityConversation(entry));
}
@Test
public void lowImportanceConversation() {
// GIVEN notification is high importance and is a people notification
final Notification notification = new Notification.Builder(mContext, "test")
.build();
final NotificationEntry entry = new NotificationEntryBuilder()
.setNotification(notification)
.setImportance(IMPORTANCE_LOW)
.build();
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry))
.thenReturn(TYPE_PERSON);
// THEN it is low priority conversation
assertFalse(mHighPriorityProvider.isHighPriorityConversation(entry));
}
@Test
public void highImportanceConversationWhenAnyOfChildIsHighPriority() {
// GIVEN notification is high importance and is a people notification
final NotificationEntry summary = createNotifEntry(false);
final NotificationEntry lowPriorityChild = createNotifEntry(false);
final NotificationEntry highPriorityChild = createNotifEntry(true);
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(summary))
.thenReturn(TYPE_PERSON);
final GroupEntry groupEntry = new GroupEntryBuilder()
.setParent(GroupEntry.ROOT_ENTRY)
.setSummary(summary)
.setChildren(List.of(lowPriorityChild, highPriorityChild))
.build();
// THEN the groupEntry is high priority conversation since it has a high priority child
assertTrue(mHighPriorityProvider.isHighPriorityConversation(groupEntry));
}
@Test
public void messagingStyle() {
// GIVEN notification is low importance but has messaging style

View File

@@ -17,6 +17,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -31,10 +34,13 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
@@ -55,7 +61,8 @@ import org.mockito.Mockito.`when` as whenever
class ConversationCoordinatorTest : SysuiTestCase() {
// captured listeners and pluggables:
private lateinit var promoter: NotifPromoter
private lateinit var peopleSectioner: NotifSectioner
private lateinit var peopleAlertingSectioner: NotifSectioner
private lateinit var peopleSilentSectioner: NotifSectioner
private lateinit var peopleComparator: NotifComparator
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@@ -76,6 +83,7 @@ class ConversationCoordinatorTest : SysuiTestCase() {
coordinator = ConversationCoordinator(
peopleNotificationIdentifier,
conversationIconManager,
HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
headerController
)
whenever(channel.isImportantConversation).thenReturn(true)
@@ -90,12 +98,13 @@ class ConversationCoordinatorTest : SysuiTestCase() {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
peopleSectioner = coordinator.sectioner
peopleComparator = peopleSectioner.comparator!!
peopleAlertingSectioner = coordinator.peopleAlertingSectioner
peopleSilentSectioner = coordinator.peopleSilentSectioner
peopleComparator = peopleAlertingSectioner.comparator!!
entry = NotificationEntryBuilder().setChannel(channel).build()
val section = NotifSection(peopleSectioner, 0)
val section = NotifSection(peopleAlertingSectioner, 0)
entryA = NotificationEntryBuilder().setChannel(channel)
.setSection(section).setTag("A").build()
entryB = NotificationEntryBuilder().setChannel(channel)
@@ -129,13 +138,67 @@ class ConversationCoordinatorTest : SysuiTestCase() {
}
@Test
fun testInPeopleSection() {
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
.thenReturn(TYPE_PERSON)
fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
// GIVEN
val alertingEntry = NotificationEntryBuilder().setChannel(channel)
.setImportance(IMPORTANCE_DEFAULT).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
.thenReturn(TYPE_PERSON)
// only put people notifications in this section
assertTrue(peopleSectioner.isInSection(entry))
assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build()))
// put alerting people notifications in this section
assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
}
@Test
fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
// GIVEN
val silentEntry = NotificationEntryBuilder().setChannel(channel)
.setImportance(IMPORTANCE_LOW).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
.thenReturn(TYPE_PERSON)
// THEN put silent people notifications in this section
assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
// People Alerting sectioning happens before the silent one.
// It claims high important conversations and rest of conversations will be considered as silent.
assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
}
@Test
fun testNotInPeopleSection() {
// GIVEN
val entry = NotificationEntryBuilder().setChannel(channel)
.setImportance(IMPORTANCE_LOW).build()
val importantEntry = NotificationEntryBuilder().setChannel(channel)
.setImportance(IMPORTANCE_HIGH).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
.thenReturn(TYPE_NON_PERSON)
whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
.thenReturn(TYPE_NON_PERSON)
// THEN - only put people notification either silent or alerting
assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse()
}
@Test
fun testInAlertingPeopleSectionWhenThereIsAnImportantChild(){
// GIVEN
val altChildA = NotificationEntryBuilder().setTag("A")
.setImportance(IMPORTANCE_DEFAULT).build()
val altChildB = NotificationEntryBuilder().setTag("B")
.setImportance(IMPORTANCE_LOW).build()
val summary = NotificationEntryBuilder().setId(2)
.setImportance(IMPORTANCE_LOW).setChannel(channel).build()
val groupEntry = GroupEntryBuilder()
.setParent(GroupEntry.ROOT_ENTRY)
.setSummary(summary)
.setChildren(listOf(altChildA, altChildB))
.build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
.thenReturn(TYPE_PERSON)
// THEN
assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
}
@Test

View File

@@ -52,7 +52,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -73,7 +72,6 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private SectionStyleProvider mSectionStyleProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentNodeController;
@@ -100,7 +98,6 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mRankingCoordinator = new RankingCoordinator(
mStatusBarStateController,
mHighPriorityProvider,
mSectionStyleProvider,
mAlertingHeaderController,
mSilentHeaderController,
mSilentNodeController);
@@ -108,7 +105,6 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mEntry.setRanking(getRankingForUnfilteredNotif().build());
mRankingCoordinator.attach(mNotifPipeline);
verify(mSectionStyleProvider).setMinimizedSections(any());
verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);