Merge ""Hide silent notifications in status bar" should apply to silent conversations,too" into udc-dev

This commit is contained in:
Ibrahim Yilmaz
2023-04-07 15:13:02 +00:00
committed by Android (Google) Code Review
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);