From 91e01cbc0d3b02c09f490bab9a7929b654730867 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Mon, 13 May 2024 20:33:01 +0000 Subject: Add a section just for priority people Bug: 340294566 Test: atest SystemUITests Flag: com.android.systemui.priority_people_section Change-Id: I1911d744e4018233d65d9002801e723ac0500720 --- packages/SystemUI/aconfig/systemui.aconfig | 7 + .../coordinator/ConversationCoordinatorTest.kt | 283 +++++++++++++++++++++ .../com/android/systemui/flags/FlagDependencies.kt | 3 + .../coordinator/ConversationCoordinator.kt | 9 + .../collection/coordinator/NotifCoordinators.kt | 4 + .../logging/NotificationPanelLogger.java | 4 +- .../notification/shared/PriorityPeopleSection.kt | 51 ++++ .../stack/NotificationPriorityBucket.kt | 20 +- .../stack/NotificationStackSizeCalculator.kt | 3 +- .../coordinator/ConversationCoordinatorTest.kt | 262 ------------------- .../collection/NotificationEntryBuilder.java | 17 ++ 11 files changed, 393 insertions(+), 270 deletions(-) create mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index de090f4b2a6d..bd8ea937827f 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -33,6 +33,13 @@ flag { bug: "316404716" } +flag { + name: "priority_people_section" + namespace: "systemui" + description: "Add a new section for priority people (aka important conversations)." + bug: "340294566" +} + flag { name: "notification_minimalism_prototype" namespace: "systemui" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt new file mode 100644 index 000000000000..285326421a14 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.app.Flags +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.NotificationManager.IMPORTANCE_LOW +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener +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.PeopleNotificationType +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON +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.statusbar.notification.people.PeopleNotificationIdentifierImpl +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +class ConversationCoordinatorTest : SysuiTestCase() { + // captured listeners and pluggables: + private lateinit var promoter: NotifPromoter + private lateinit var peopleAlertingSectioner: NotifSectioner + private lateinit var peopleSilentSectioner: NotifSectioner + private lateinit var peopleComparator: NotifComparator + private lateinit var beforeRenderListListener: OnBeforeRenderListListener + + private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier + private lateinit var peopleAlertingSection: NotifSection + + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var conversationIconManager: ConversationIconManager + @Mock private lateinit var headerController: NodeController + + private lateinit var coordinator: ConversationCoordinator + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + peopleNotificationIdentifier = + PeopleNotificationIdentifierImpl(mock(), GroupMembershipManagerImpl()) + coordinator = + ConversationCoordinator( + peopleNotificationIdentifier, + conversationIconManager, + HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), + headerController + ) + + coordinator.attach(pipeline) + + // capture arguments: + promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) } + beforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + peopleAlertingSectioner = coordinator.peopleAlertingSectioner + peopleSilentSectioner = coordinator.peopleSilentSectioner + if (!SortBySectionTimeFlag.isEnabled) + peopleComparator = peopleAlertingSectioner.comparator!! + + peopleAlertingSection = NotifSection(peopleAlertingSectioner, 0) + } + + @Test + fun priorityPeopleSectionerClaimsOnlyImportantConversations() { + val sectioner = coordinator.priorityPeopleSectioner + assertTrue(sectioner.isInSection(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON))) + assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_FULL_PERSON))) + assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_PERSON))) + assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_NON_PERSON))) + assertFalse(sectioner.isInSection(NotificationEntryBuilder().build())) + } + + @Test + fun testPromotesImportantConversations() { + assertTrue(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON))) + assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_FULL_PERSON))) + assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_PERSON))) + assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_NON_PERSON))) + assertFalse(promoter.shouldPromoteToTopLevel(NotificationEntryBuilder().build())) + } + + @Test + fun testPromotedImportantConversationsMakesSummaryUnimportant() { + val importantChannel = + mock().also { + whenever(it.isImportantConversation).thenReturn(true) + } + val otherChannel = + mock().also { + whenever(it.isImportantConversation).thenReturn(false) + } + val importantChild = + makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel) } + val altChildA = + makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("A") } + val altChildB = + makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("B") } + val summary = + makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel).setId(2) } + val groupEntry = + GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(importantChild, altChildA, altChildB)) + .build() + assertTrue(promoter.shouldPromoteToTopLevel(importantChild)) + assertFalse(promoter.shouldPromoteToTopLevel(altChildA)) + assertFalse(promoter.shouldPromoteToTopLevel(altChildB)) + NotificationEntryBuilder.setNewParent(importantChild, GroupEntry.ROOT_ENTRY) + GroupEntryBuilder.getRawChildren(groupEntry).remove(importantChild) + beforeRenderListListener.onBeforeRenderList(listOf(importantChild, groupEntry)) + verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key))) + } + + @Test + fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { + // GIVEN + val alertingEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_DEFAULT) } + + // put alerting people notifications in this section + assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) + fun testInAlertingPeopleSectionWhenTheImportanceIsLowerThanDefault() { + // GIVEN + val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) } + + // THEN put silent people notifications in alerting section + assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isTrue() + } + + @Test + @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) + fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() { + // GIVEN + val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) } + + // 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 = makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_LOW) } + val importantEntry = + makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_HIGH) } + + // THEN - only put people notification either silent or alerting + if (!SortBySectionTimeFlag.isEnabled) { + assertThat(peopleSilentSectioner.isInSection(entry)).isFalse() + } + assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse() + } + + @Test + fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() { + // GIVEN + val altChildA = + makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("A").setImportance(IMPORTANCE_DEFAULT) } + val altChildB = + makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("B").setImportance(IMPORTANCE_LOW) } + val summary = makeEntryOfPeopleType(TYPE_PERSON) { setId(2).setImportance(IMPORTANCE_LOW) } + val groupEntry = + GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(altChildA, altChildB)) + .build() + // THEN + assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue() + } + + @Test + @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) + fun testComparatorPutsImportantPeopleFirst() { + val entryA = + makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { + setSection(peopleAlertingSection).setTag("A") + } + val entryB = + makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") } + + // only put people notifications in this section + assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1) + } + + @Test + @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) + fun testComparatorEquatesPeopleWithSameType() { + val entryA = + makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("A") } + val entryB = + makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") } + + // only put people notifications in this section + assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0) + } + + @Test + @EnableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) + fun testNoSecondarySortForConversations() { + assertThat(peopleAlertingSectioner.comparator).isNull() + } + + private fun makeEntryOfPeopleType( + @PeopleNotificationType type: Int, + buildBlock: NotificationEntryBuilder.() -> Unit = {} + ): NotificationEntry { + val channel: NotificationChannel = mock() + whenever(channel.isImportantConversation).thenReturn(type == TYPE_IMPORTANT_PERSON) + val entry = + NotificationEntryBuilder() + .updateRanking { + it.setIsConversation(type != TYPE_NON_PERSON) + it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null) + it.setChannel(channel) + } + .also(buildBlock) + .build() + assertEquals(type, peopleNotificationIdentifier.getPeopleNotificationType(entry)) + return entry + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 67c556409615..140434040ca7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -29,11 +29,13 @@ import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import javax.inject.Inject /** A class in which engineers can define flag dependencies */ @@ -49,6 +51,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token + PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token // SceneContainer dependencies SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index 3d0fd89dce88..af2c1979ff77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -31,8 +31,10 @@ import com.android.systemui.statusbar.notification.dagger.PeopleHeader 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.PeopleNotificationType +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.stack.BUCKET_PEOPLE +import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE import javax.inject.Inject /** @@ -81,6 +83,13 @@ class ConversationCoordinator @Inject constructor( } } + val priorityPeopleSectioner = + object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) { + override fun isInSection(entry: ListEntry): Boolean { + return getPeopleType(entry) == TYPE_IMPORTANT_PERSON + } + } + // TODO(b/330193582): Rename to just "People" val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 387444bec1c0..4506385a2fb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import javax.inject.Inject /** @@ -120,6 +121,9 @@ constructor( if (NotificationMinimalismPrototype.V2.isEnabled) { mOrderedSections.add(keyguardCoordinator.unseenNotifSectioner) // Unseen (FGS) } + if (PriorityPeopleSection.isEnabled) { + mOrderedSections.add(conversationCoordinator.priorityPeopleSectioner) // Priority People + } mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting if (!SortBySectionTimeFlag.isEnabled) { mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java index 89aa3ab9c84d..9e0dd8fc4d92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java @@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationPrio import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PRIORITY_PEOPLE; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import android.annotation.Nullable; @@ -130,7 +131,8 @@ public interface NotificationPanelLogger { case BUCKET_HEADS_UP: return Notifications.Notification.SECTION_HEADS_UP; case BUCKET_FOREGROUND_SERVICE: return Notifications.Notification.SECTION_FOREGROUND_SERVICE; - case BUCKET_PEOPLE: return Notifications.Notification.SECTION_PEOPLE; + case BUCKET_PEOPLE, BUCKET_PRIORITY_PEOPLE: + return Notifications.Notification.SECTION_PEOPLE; case BUCKET_ALERTING: return Notifications.Notification.SECTION_ALERTING; case BUCKET_SILENT: return Notifications.Notification.SECTION_SILENT; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt new file mode 100644 index 000000000000..472fd9564963 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for com.android.systemui.Flags.FLAG_PRIORITY_PEOPLE_SECTION */ +@Suppress("NOTHING_TO_INLINE") +object PriorityPeopleSection { + const val FLAG_NAME = Flags.FLAG_PRIORITY_PEOPLE_SECTION + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Are sections sorted by time? */ + @JvmStatic + inline val isEnabled + get() = Flags.priorityPeopleSection() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt index 31f4857e4b04..fc28a99ef4ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt @@ -3,15 +3,22 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.IntDef /** - * For now, declare the available notification buckets (sections) here so that other - * presentation code can decide what to do based on an entry's buckets + * For now, declare the available notification buckets (sections) here so that other presentation + * code can decide what to do based on an entry's buckets */ @Retention(AnnotationRetention.SOURCE) @IntDef( - prefix = ["BUCKET_"], - value = [ - BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, - BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT + prefix = ["BUCKET_"], + value = + [ + BUCKET_UNKNOWN, + BUCKET_MEDIA_CONTROLS, + BUCKET_HEADS_UP, + BUCKET_FOREGROUND_SERVICE, + BUCKET_PRIORITY_PEOPLE, + BUCKET_PEOPLE, + BUCKET_ALERTING, + BUCKET_SILENT ] ) annotation class PriorityBucket @@ -20,6 +27,7 @@ const val BUCKET_UNKNOWN = 0 const val BUCKET_MEDIA_CONTROLS = 1 const val BUCKET_HEADS_UP = 2 const val BUCKET_FOREGROUND_SERVICE = 3 +const val BUCKET_PRIORITY_PEOPLE = 7 const val BUCKET_PEOPLE = 4 const val BUCKET_ALERTING = 5 const val BUCKET_SILENT = 6 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 749c241ce00c..4b0b1e0029f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -388,7 +388,8 @@ constructor( null, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, - BUCKET_FOREGROUND_SERVICE -> false + BUCKET_FOREGROUND_SERVICE, + BUCKET_PRIORITY_PEOPLE -> false else -> true } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt deleted file mode 100644 index c5d7e1f490ee..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator - -import android.app.Flags -import android.app.NotificationChannel -import android.app.NotificationManager.IMPORTANCE_DEFAULT -import android.app.NotificationManager.IMPORTANCE_HIGH -import android.app.NotificationManager.IMPORTANCE_LOW -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.SetFlagsRule -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.notification.collection.GroupEntry -import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder -import com.android.systemui.statusbar.notification.collection.NotifPipeline -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag -import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection -import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener -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 -import com.google.common.truth.Truth.assertThat -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.`when` as whenever - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class ConversationCoordinatorTest : SysuiTestCase() { - // captured listeners and pluggables: - private lateinit var promoter: NotifPromoter - private lateinit var peopleAlertingSectioner: NotifSectioner - private lateinit var peopleSilentSectioner: NotifSectioner - private lateinit var peopleComparator: NotifComparator - private lateinit var beforeRenderListListener: OnBeforeRenderListListener - - @Mock private lateinit var pipeline: NotifPipeline - @Mock private lateinit var conversationIconManager: ConversationIconManager - @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier - @Mock private lateinit var channel: NotificationChannel - @Mock private lateinit var headerController: NodeController - private lateinit var entry: NotificationEntry - private lateinit var entryA: NotificationEntry - private lateinit var entryB: NotificationEntry - - private lateinit var coordinator: ConversationCoordinator - - @Rule @JvmField public val setFlagsRule = SetFlagsRule() - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - coordinator = - ConversationCoordinator( - peopleNotificationIdentifier, - conversationIconManager, - HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), - headerController - ) - whenever(channel.isImportantConversation).thenReturn(true) - - coordinator.attach(pipeline) - - // capture arguments: - promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) } - beforeRenderListListener = withArgCaptor { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - peopleAlertingSectioner = coordinator.peopleAlertingSectioner - peopleSilentSectioner = coordinator.peopleSilentSectioner - if (!SortBySectionTimeFlag.isEnabled) - peopleComparator = peopleAlertingSectioner.comparator!! - - entry = NotificationEntryBuilder().setChannel(channel).build() - - val section = NotifSection(peopleAlertingSectioner, 0) - entryA = - NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build() - entryB = - NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build() - } - - @Test - fun testPromotesImportantConversations() { - // only promote important conversations - assertTrue(promoter.shouldPromoteToTopLevel(entry)) - assertFalse(promoter.shouldPromoteToTopLevel(NotificationEntryBuilder().build())) - } - - @Test - fun testPromotedImportantConversationsMakesSummaryUnimportant() { - val altChildA = NotificationEntryBuilder().setTag("A").build() - val altChildB = NotificationEntryBuilder().setTag("B").build() - val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build() - val groupEntry = - GroupEntryBuilder() - .setParent(GroupEntry.ROOT_ENTRY) - .setSummary(summary) - .setChildren(listOf(entry, altChildA, altChildB)) - .build() - assertTrue(promoter.shouldPromoteToTopLevel(entry)) - assertFalse(promoter.shouldPromoteToTopLevel(altChildA)) - assertFalse(promoter.shouldPromoteToTopLevel(altChildB)) - NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY) - GroupEntryBuilder.getRawChildren(groupEntry).remove(entry) - beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry)) - verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key))) - } - - @Test - fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { - // GIVEN - val alertingEntry = - NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build() - whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry)) - .thenReturn(TYPE_PERSON) - - // put alerting people notifications in this section - assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() - } - - @Test - @EnableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) - fun testInAlertingPeopleSectionWhenTheImportanceIsLowerThanDefault() { - // GIVEN - val silentEntry = - NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() - whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) - .thenReturn(TYPE_PERSON) - - // THEN put silent people notifications in alerting section - assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isTrue() - } - - @Test - @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) - 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 - if (!SortBySectionTimeFlag.isEnabled) - 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 - @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) - fun testComparatorPutsImportantPeopleFirst() { - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) - .thenReturn(TYPE_IMPORTANT_PERSON) - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) - .thenReturn(TYPE_PERSON) - - // only put people notifications in this section - assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1) - } - - @Test - @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) - fun testComparatorEquatesPeopleWithSameType() { - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) - .thenReturn(TYPE_PERSON) - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) - .thenReturn(TYPE_PERSON) - - // only put people notifications in this section - assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0) - } - - @Test - @EnableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) - fun testNoSecondarySortForConversations() { - assertThat(peopleAlertingSectioner.comparator).isNull() - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java index 2bd584eac5e3..562ac0c15a0b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java @@ -27,6 +27,8 @@ import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; +import androidx.annotation.NonNull; + import com.android.internal.logging.InstanceId; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; @@ -36,6 +38,7 @@ import com.android.systemui.util.time.FakeSystemClock; import kotlin.Unit; import java.util.ArrayList; +import java.util.function.Consumer; /** * Combined builder for constructing a NotificationEntry and its associated StatusBarNotification @@ -73,6 +76,20 @@ public class NotificationEntryBuilder { mCreationTime = source.getCreationTime(); } + /** Allows the caller to sub-build the ranking */ + @NonNull + public NotificationEntryBuilder updateRanking(@NonNull Consumer rankingUpdater) { + rankingUpdater.accept(mRankingBuilder); + return this; + } + + /** Allows the caller to sub-build the SBN */ + @NonNull + public NotificationEntryBuilder updateSbn(@NonNull Consumer sbnUpdater) { + sbnUpdater.accept(mSbnBuilder); + return this; + } + /** Update an the parent on an existing entry */ public static void setNewParent(NotificationEntry entry, GroupEntry parent) { entry.setParent(parent); -- cgit v1.2.3-59-g8ed1b