diff options
26 files changed, 914 insertions, 320 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 8a4ddceb0d3a..61e3b380c1ab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -22,8 +22,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor @@ -32,12 +35,16 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith @@ -47,7 +54,12 @@ import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @EnableFlags(StatusBarNotifChips.FLAG_NAME) class NotifChipsViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos().useUnconfinedTestDispatcher() + private val kosmos = + testKosmos().useUnconfinedTestDispatcher().apply { + // Don't be in lockscreen so that HUNs are allowed + fakeKeyguardTransitionRepository = + FakeKeyguardTransitionRepository(initInLockscreen = false, testScope = testScope) + } private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val underTest by lazy { kosmos.notifChipsViewModel } @@ -199,6 +211,54 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + fun chips_noHeadsUp_showsTime() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + ) + ) + ) + + // WHEN there's no HUN + kosmos.headsUpNotificationRepository.setNotifications(emptyList()) + + // THEN the chip shows the time + assertThat(latest!![0]) + .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + } + + @Test + fun chips_hasHeadsUpByUser_onlyShowsIcon() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + ) + ) + ) + + // WHEN there's a HUN pinned by a user + kosmos.headsUpNotificationRepository.setNotifications( + UnconfinedFakeHeadsUpRowRepository( + key = "notif", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser), + ) + ) + + assertThat(latest!![0]) + .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) + } + + @Test fun chips_clickingChipNotifiesInteractor() = kosmos.runTest { val latest by collectLastValue(underTest.chips) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 1d7f25784327..a3ffd91b1728 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -50,7 +50,6 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider -import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback import com.android.systemui.statusbar.phone.NotificationGroupTestHelper import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor @@ -76,6 +75,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.kotlin.argumentCaptor @SmallTest @RunWith(AndroidJUnit4::class) @@ -315,7 +315,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // WHEN the notification is added but not yet binding collectionListener.onEntryAdded(entry) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any(), any()) // THEN only promote mEntry assertTrue(notifPromoter.shouldPromoteToTopLevel(entry)) @@ -330,7 +330,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(entry) beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) - verify(headsUpViewBinder).bindHeadsUpView(eq(entry), any()) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), eq(false), any()) // THEN only promote mEntry assertTrue(notifPromoter.shouldPromoteToTopLevel(entry)) @@ -382,11 +382,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(entry) beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) - verify(headsUpManager, never()).showNotification(entry) - withArgCaptor<BindCallback> { - verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) - } - .onBindFinished(entry) + + finishBind(entry) // THEN we tell the HeadsUpManager to show the notification verify(headsUpManager).showNotification(entry) @@ -401,8 +398,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN we never bind the heads up view or tell HeadsUpManager to show the notification - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any()) - verify(headsUpManager, never()).showNotification(entry) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any(), any()) + verify(headsUpManager, never()).showNotification(eq(entry), any()) } @Test @@ -435,8 +432,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is never bound or shown - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(headsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) + verify(headsUpManager, never()).showNotification(any(), any()) } @Test @@ -471,7 +468,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) finishBind(entry) - verify(headsUpManager).showNotification(entry) + verify(headsUpManager).showNotification(entry, isPinnedByUser = true) } @Test @@ -485,8 +482,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { executor.runAllReady() beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any()) - verify(headsUpManager, never()).showNotification(entry) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any(), any()) + verify(headsUpManager, never()).showNotification(eq(entry), any()) } @Test @@ -509,7 +506,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN it's still shown as heads up finishBind(entry) - verify(headsUpManager).showNotification(entry) + verify(headsUpManager).showNotification(entry, isPinnedByUser = true) } @Test @@ -525,8 +522,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeTransformGroupsListener.onBeforeTransformGroups(listOf(promotedEntry)) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(promotedEntry)) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(promotedEntry), any()) - verify(headsUpManager, never()).showNotification(promotedEntry) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(promotedEntry), any(), any()) + verify(headsUpManager, never()).showNotification(eq(promotedEntry), any()) // Then a new notification comes in that should be heads up setShouldHeadsUp(entry, false) @@ -544,10 +541,10 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN the promoted entry is shown as a HUN, *not* the new entry finishBind(promotedEntry) - verify(headsUpManager).showNotification(promotedEntry) + verify(headsUpManager).showNotification(promotedEntry, isPinnedByUser = true) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any()) - verify(headsUpManager, never()).showNotification(entry) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any(), any()) + verify(headsUpManager, never()).showNotification(eq(entry), any()) } @Test @@ -558,10 +555,10 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupSibling1) beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupSibling1)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupSibling1)) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupSibling1) verify(headsUpManager, never()).showNotification(groupSummary) @@ -580,10 +577,10 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSummary) collectionListener.onEntryAdded(groupChild1) beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupChild1)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupChild1)) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupChild1) verify(headsUpManager, never()).showNotification(groupSummary) @@ -603,12 +600,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupSibling2) val entryList = listOf(groupSibling1, groupSibling2) beforeTransformGroupsListener.onBeforeTransformGroups(entryList) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupSibling1) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any(), any()) // THEN we tell the HeadsUpManager to show the notification verify(headsUpManager, never()).showNotification(groupSummary) @@ -629,12 +626,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { collectionListener.onEntryAdded(groupChild2) val entryList = listOf(groupChild1, groupChild2) beforeTransformGroupsListener.onBeforeTransformGroups(entryList) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupChild1) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any(), any()) // THEN we tell the HeadsUpManager to show the notification verify(headsUpManager, never()).showNotification(groupSummary) @@ -661,7 +658,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) val afterTransformGroup = GroupEntryBuilder() @@ -672,10 +669,10 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { listOf(groupPriority, afterTransformGroup) ) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupPriority) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any(), any()) // THEN we tell the HeadsUpManager to show the notification verify(headsUpManager, never()).showNotification(groupSummary) @@ -702,7 +699,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) val afterTransformGroup = GroupEntryBuilder() @@ -713,10 +710,10 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { listOf(groupPriority, afterTransformGroup) ) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupPriority) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any(), any()) verify(headsUpManager, never()).showNotification(groupSummary) verify(headsUpManager).showNotification(groupPriority) @@ -740,7 +737,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) val afterTransformGroup = GroupEntryBuilder() @@ -751,10 +748,10 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { listOf(groupPriority, afterTransformGroup) ) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupPriority) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any(), any()) verify(headsUpManager, never()).showNotification(groupSummary) verify(headsUpManager).showNotification(groupPriority) @@ -779,7 +776,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) val afterTransformGroup = GroupEntryBuilder() @@ -791,9 +788,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { ) finishBind(groupSummary) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupPriority), any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupPriority), any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any(), any()) verify(headsUpManager).showNotification(groupSummary) verify(headsUpManager, never()).showNotification(groupPriority) @@ -816,12 +813,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .setChildren(listOf(groupSibling1, groupSibling2)) .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) finishBind(groupSummary) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any(), any()) verify(headsUpManager).showNotification(groupSummary) verify(headsUpManager, never()).showNotification(groupSibling1) @@ -842,12 +839,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .setChildren(listOf(groupChild1, groupChild2)) .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) finishBind(groupSummary) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild1), any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild1), any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any(), any()) verify(headsUpManager).showNotification(groupSummary) verify(headsUpManager, never()).showNotification(groupChild1) @@ -871,12 +868,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { .setChildren(listOf(groupChild1, groupChild2)) .build() beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any(), any()) finishBind(groupChild1) - verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any(), any()) verify(headsUpManager, never()).showNotification(groupSummary) verify(headsUpManager).showNotification(groupChild1) @@ -900,7 +897,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN the notification is shown finishBind(entry) - verify(headsUpManager).showNotification(entry) + verify(headsUpManager).showNotification(entry, isPinnedByUser = false) } @Test @@ -917,8 +914,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is never bound or shown - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(headsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) + verify(headsUpManager, never()).showNotification(any(), any()) } @Test @@ -938,7 +935,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN the notification is shown finishBind(entry) - verify(headsUpManager).showNotification(entry) + verify(headsUpManager).showNotification(entry, isPinnedByUser = false) } @Test @@ -958,8 +955,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is never bound or shown - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(headsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) + verify(headsUpManager, never()).showNotification(any(), any()) } @Test @@ -1022,8 +1019,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN it should full screen and log but it should NOT HUN verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(headsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) + verify(headsUpManager, never()).showNotification(any(), any()) verifyLoggedFullScreenIntentDecision( entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE, @@ -1063,8 +1060,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN it should still not yet full screen or HUN verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(headsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) + verify(headsUpManager, never()).showNotification(any(), any()) // Same decision as before; is not logged verifyNoFullScreenIntentDecisionLogged() @@ -1080,8 +1077,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN it should full screen and log but it should NOT HUN verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(headsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) + verify(headsUpManager, never()).showNotification(any(), any()) verifyLoggedFullScreenIntentDecision( entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE, @@ -1107,8 +1104,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // THEN it should NOT full screen or HUN verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(headsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any(), any()) + verify(headsUpManager, never()).showNotification(any(), any()) // NOW the DND logic changes and FSI and HUN are available clearInvocations(launchFullScreenIntentProvider) @@ -1121,7 +1118,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // VERIFY that the FSI didn't happen, but that we do HUN verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) finishBind(entry) - verify(headsUpManager).showNotification(entry) + verify(headsUpManager).showNotification(entry, isPinnedByUser = false) } @Test @@ -1206,10 +1203,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { } private fun finishBind(entry: NotificationEntry) { - verify(headsUpManager, never()).showNotification(entry) - withArgCaptor<BindCallback> { - verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + verify(headsUpManager, never()).showNotification(eq(entry), any()) + val isPinnedByUserCaptor = argumentCaptor<Boolean>() + withArgCaptor<HeadsUpViewBinder.HeadsUpBindCallback> { + verify(headsUpViewBinder) + .bindHeadsUpView(eq(entry), isPinnedByUserCaptor.capture(), capture()) } - .onBindFinished(entry) + .onHeadsUpBindFinished(entry, isPinnedByUserCaptor.firstValue) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt index dc0231f40609..22ef408e266c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepo import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository @@ -409,76 +410,101 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { } @Test - fun showHeadsUpStatusBar_true() = + fun statusBarHeadsUpState_pinnedBySystem() = testScope.runTest { - val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) - // WHEN a row is pinned - headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + headsUpRepository.setNotifications( + FakeHeadsUpRowRepository(key = "key 0", pinnedStatus = PinnedStatus.PinnedBySystem) + ) + runCurrent() - assertThat(showHeadsUpStatusBar).isTrue() + assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.PinnedBySystem) } @Test - fun showHeadsUpStatusBar_withoutPinnedNotifications_false() = + fun statusBarHeadsUpState_pinnedByUser() = testScope.runTest { - val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) - // WHEN no row is pinned - headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = false)) + headsUpRepository.setNotifications( + FakeHeadsUpRowRepository(key = "key 0", pinnedStatus = PinnedStatus.PinnedByUser) + ) + runCurrent() - assertThat(showHeadsUpStatusBar).isFalse() + assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.PinnedByUser) } @Test - fun showHeadsUpStatusBar_whenShadeExpanded_false() = + fun statusBarHeadsUpState_withoutPinnedNotifications_notPinned() = testScope.runTest { - val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) + + headsUpRepository.setNotifications( + FakeHeadsUpRowRepository(key = "key 0", PinnedStatus.NotPinned) + ) + runCurrent() + + assertThat(statusBarHeadsUpState).isEqualTo(PinnedStatus.NotPinned) + } + + @Test + fun statusBarHeadsUpState_whenShadeExpanded_false() = + testScope.runTest { + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + runCurrent() // AND the shade is expanded shadeTestUtil.setShadeExpansion(1.0f) + // Needed if SceneContainer flag is off: `ShadeTestUtil.setShadeExpansion(1f)` + // incorrectly causes `ShadeInteractor.isShadeFullyCollapsed` to emit `true`, when it + // should emit `false`. + kosmos.fakeShadeRepository.setLegacyShadeExpansion(1.0f) - assertThat(showHeadsUpStatusBar).isFalse() + assertThat(statusBarHeadsUpState!!.isPinned).isFalse() } @Test - fun showHeadsUpStatusBar_notificationsAreHidden_false() = + fun statusBarHeadsUpState_notificationsAreHidden_false() = testScope.runTest { - val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + runCurrent() // AND the notifications are hidden keyguardViewStateRepository.areNotificationsFullyHidden.value = true - assertThat(showHeadsUpStatusBar).isFalse() + assertThat(statusBarHeadsUpState!!.isPinned).isFalse() } @Test - fun showHeadsUpStatusBar_onLockScreen_false() = + fun statusBarHeadsUpState_onLockScreen_false() = testScope.runTest { - val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + runCurrent() // AND the lock screen is shown keyguardTransitionRepository.emitInitialStepsFromOff( to = KeyguardState.LOCKSCREEN, testSetup = true, ) - assertThat(showHeadsUpStatusBar).isFalse() + assertThat(statusBarHeadsUpState!!.isPinned).isFalse() } @Test - fun showHeadsUpStatusBar_onByPassLockScreen_true() = + fun statusBarHeadsUpState_onByPassLockScreen_true() = testScope.runTest { - val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) // WHEN a row is pinned headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + runCurrent() // AND the lock screen is shown keyguardTransitionRepository.emitInitialStepsFromOff( to = KeyguardState.LOCKSCREEN, @@ -487,13 +513,13 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { // AND bypass is enabled faceAuthRepository.isBypassEnabled.value = true - assertThat(showHeadsUpStatusBar).isTrue() + assertThat(statusBarHeadsUpState!!.isPinned).isTrue() } @Test - fun showHeadsUpStatusBar_onByPassLockScreen_withoutNotifications_false() = + fun statusBarHeadsUpState_onByPassLockScreen_withoutNotifications_false() = testScope.runTest { - val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + val statusBarHeadsUpState by collectLastValue(underTest.statusBarHeadsUpState) // WHEN no pinned rows // AND the lock screen is shown @@ -504,7 +530,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { // AND bypass is enabled faceAuthRepository.isBypassEnabled.value = true - assertThat(showHeadsUpStatusBar).isFalse() + assertThat(statusBarHeadsUpState!!.isPinned).isFalse() } private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt index 8420c49755b1..98bf0e6170d4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider @@ -179,6 +180,44 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + fun pinnedHeadsUpStatuses_noHeadsUp() { + assertThat(underTest.hasPinnedHeadsUp()).isFalse() + assertThat(underTest.pinnedHeadsUpStatus()).isEqualTo(PinnedStatus.NotPinned) + } + + @Test + fun pinnedHeadsUpStatuses_pinnedBySystem() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + entry.row = testHelper.createRow() + underTest.showNotification(entry, isPinnedByUser = false) + + assertThat(underTest.hasPinnedHeadsUp()).isTrue() + assertThat(underTest.pinnedHeadsUpStatus()).isEqualTo(PinnedStatus.PinnedBySystem) + } + + @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun pinnedHeadsUpStatuses_pinnedByUser_butFlagOff_returnsNotPinned() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + entry.row = testHelper.createRow() + underTest.showNotification(entry, isPinnedByUser = true) + + assertThat(underTest.hasPinnedHeadsUp()).isFalse() + assertThat(underTest.pinnedHeadsUpStatus()).isEqualTo(PinnedStatus.NotPinned) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun pinnedHeadsUpStatuses_pinnedByUser_flagOn() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + entry.row = testHelper.createRow() + underTest.showNotification(entry, isPinnedByUser = true) + + assertThat(underTest.hasPinnedHeadsUp()).isTrue() + assertThat(underTest.pinnedHeadsUpStatus()).isEqualTo(PinnedStatus.PinnedByUser) + } + + @Test @EnableFlags(NotificationThrottleHun.FLAG_NAME) fun testGetHeadsUpEntryList_includesAvalancheEntryList() { val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) @@ -199,10 +238,10 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testShowNotification_addsEntry() { + fun testShowNotification_notPinnedByUser_addsEntry() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = false) assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() assertThat(underTest.hasNotifications()).isTrue() @@ -210,20 +249,43 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testShowNotification_autoDismisses() { + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testShowNotification_isPinnedByUser_addsEntry() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = true) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + assertThat(underTest.hasNotifications()).isTrue() + assertThat(underTest.getEntry(entry.key)).isEqualTo(entry) + } + + @Test + fun testShowNotification_notPinnedByUser_autoDismisses() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry, isPinnedByUser = false) systemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong()) assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() } @Test - fun testRemoveNotification_removeDeferred() { + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testShowNotification_isPinnedByUser_autoDismisses() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = true) + systemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong()) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + fun testRemoveNotification_notPinnedByUser_removeDeferred() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry, isPinnedByUser = false) val removedImmediately = underTest.removeNotification( @@ -236,10 +298,27 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testRemoveNotification_forceRemove() { + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testRemoveNotification_isPinnedByUser_removeDeferred() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = true) + + val removedImmediately = + underTest.removeNotification( + entry.key, + /* releaseImmediately= */ false, + "removeDeferred", + ) + assertThat(removedImmediately).isFalse() + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + fun testRemoveNotification_notPinnedByUser_forceRemove() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry, isPinnedByUser = false) val removedImmediately = underTest.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove") @@ -248,11 +327,26 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testRemoveNotification_isPinnedByUser_forceRemove() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry, isPinnedByUser = true) + + val removedImmediately = + underTest.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove") + assertThat(removedImmediately).isTrue() + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun testReleaseAllImmediately() { for (i in 0 until 4) { val entry = HeadsUpManagerTestUtil.createEntry(i, mContext) - entry.row = mock<ExpandableNotificationRow>() - underTest.showNotification(entry) + entry.row = testHelper.createRow() + val isPinnedByUser = i % 2 == 0 + underTest.showNotification(entry, isPinnedByUser) } underTest.releaseAllImmediately() @@ -261,10 +355,21 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testCanRemoveImmediately_notShownLongEnough() { + fun testCanRemoveImmediately_notShownLongEnough_notPinnedByUser() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = false) + + // The entry has just been added so we should not remove immediately. + assertThat(underTest.canRemoveImmediately(entry.key)).isFalse() + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testCanRemoveImmediately_notShownLongEnough_isPinnedByUser() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry, isPinnedByUser = true) // The entry has just been added so we should not remove immediately. assertThat(underTest.canRemoveImmediately(entry.key)).isFalse() @@ -365,17 +470,48 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testSnooze() { + fun testSnooze_notPinnedByUser() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = false) + underTest.snooze() + assertThat(underTest.isSnoozed(entry.sbn.packageName)).isTrue() } @Test - fun testSwipedOutNotification() { + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testSnooze_isPinnedByUser() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = true) + + underTest.snooze() + + assertThat(underTest.isSnoozed(entry.sbn.packageName)).isTrue() + } + + @Test + fun testSwipedOutNotification_notPinnedByUser() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + underTest.showNotification(entry, isPinnedByUser = false) + underTest.addSwipedOutNotification(entry.key) + + // Remove should succeed because the notification is swiped out + val removedImmediately = + underTest.removeNotification( + entry.key, + /* releaseImmediately= */ false, + /* reason= */ "swipe out", + ) + assertThat(removedImmediately).isTrue() + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testSwipedOutNotification_isPinnedByUser() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + underTest.showNotification(entry, isPinnedByUser = true) underTest.addSwipedOutNotification(entry.key) // Remove should succeed because the notification is swiped out @@ -413,10 +549,24 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { } @Test - fun testExtendHeadsUp() { + fun testExtendHeadsUp_notPinnedByUser() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(entry) + underTest.showNotification(entry, isPinnedByUser = false) + + underTest.extendHeadsUp() + + systemClock.advanceTime(((TEST_AUTO_DISMISS_TIME + TEST_EXTENSION_TIME) / 2).toLong()) + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun testExtendHeadsUp_isPinnedByUser() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + underTest.showNotification(entry, isPinnedByUser = true) + underTest.extendHeadsUp() + systemClock.advanceTime(((TEST_AUTO_DISMISS_TIME + TEST_EXTENSION_TIME) / 2).toLong()) assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() } @@ -465,7 +615,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.visualStabilityProvider.isReorderingAllowed = true val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - underTest.showNotification(notifEntry) + underTest.showNotification(notifEntry, isPinnedByUser = false) assertThat(notifEntry.isSeenInShade).isFalse() } @@ -604,7 +754,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned - underTest.showNotification(notifEntry) + underTest.showNotification(notifEntry, isPinnedByUser = false) val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key) headsUpEntry!!.mWasUnpinned = false @@ -621,7 +771,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) // Add notifEntry to ANM mAlertEntries map and make it unpinned - underTest.showNotification(notifEntry) + underTest.showNotification(notifEntry, isPinnedByUser = false) val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key) headsUpEntry!!.mWasUnpinned = true @@ -793,11 +943,11 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { ) // Note: the standard way to show a notification would be calling showNotification rather - // than onAlertEntryAdded. However, in practice showNotification in effect adds + // than onEntryAdded. However, in practice showNotification in effect adds // the notification and then updates it; in order to not log twice, the entry needs // to have a functional ExpandableNotificationRow that can keep track of whether it's // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. - underTest.onEntryAdded(entryToPin) + underTest.onEntryAdded(entryToPin, /* requestedPinnedStatus= */ PinnedStatus.PinnedBySystem) assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2) assertThat(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId()) @@ -816,11 +966,11 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { ) // Note: the standard way to show a notification would be calling showNotification rather - // than onAlertEntryAdded. However, in practice showNotification in effect adds + // than onEntryAdded. However, in practice showNotification in effect adds // the notification and then updates it; in order to not log twice, the entry needs // to have a functional ExpandableNotificationRow that can keep track of whether it's // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. - underTest.onEntryAdded(entryToPin) + underTest.onEntryAdded(entryToPin, /* requestedPinnedStatus= */ PinnedStatus.PinnedBySystem) assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id) @@ -889,7 +1039,10 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { val flags: List<FlagsParameterization> get() = buildList { addAll( - FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME) + FlagsParameterization.allCombinationsOf( + NotificationThrottleHun.FLAG_NAME, + StatusBarNotifChips.FLAG_NAME, + ) .andSceneContainer() ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java index af2789b8401a..f7673da6dfb0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java @@ -75,8 +75,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { return new CancellationSignal(); }); - mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq(mEntry)); + mViewBinder.bindHeadsUpView(mEntry, /* isPinnedByUser= */ false, null); + verify(mLogger).startBindingHun(mEntry, /* isPinnedByUser= */ false); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); @@ -85,8 +85,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); - mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq(mEntry)); + mViewBinder.bindHeadsUpView(mEntry, /* isPinnedByUser= */ true, null); + verify(mLogger).startBindingHun(mEntry, /* isPinnedByUser= */ true); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); @@ -116,8 +116,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { return new CancellationSignal(); }); - mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq(mEntry)); + mViewBinder.bindHeadsUpView(mEntry, /* isPinnedByUser= */ false, null); + verify(mLogger).startBindingHun(mEntry, /* isPinnedByUser= */ false); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); @@ -140,8 +140,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { return new CancellationSignal(); }); - mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq(mEntry)); + mViewBinder.bindHeadsUpView(mEntry, /* isPinnedByUser= */ true, null); + verify(mLogger).startBindingHun(mEntry, /* isPinnedByUser= */ true); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); @@ -167,8 +167,8 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { return new CancellationSignal(); }); - mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq(mEntry)); + mViewBinder.bindHeadsUpView(mEntry, /* isPinnedByUser= */ false, null); + verify(mLogger).startBindingHun(mEntry, /* isPinnedByUser= */ false); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt index a9afb0658c50..f4c254562420 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.view.View @@ -23,12 +24,15 @@ import android.widget.TextView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.plugins.fakeDarkIconDispatcher import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.shade.ShadeHeadsUpTracker import com.android.systemui.shade.shadeViewController import com.android.systemui.statusbar.HeadsUpStatusBarView +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor @@ -114,65 +118,172 @@ class HeadsUpAppearanceControllerTest : SysuiTestCase() { } @Test - fun testShowinEntryUpdated() { + fun showingEntryUpdated_whenPinnedBySystem() { row.setPinnedStatus(PinnedStatus.PinnedBySystem) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) - whenever(headsUpManager.getTopEntry()).thenReturn(entry) + setHeadsUpNotifOnManager(entry) underTest.onHeadsUpPinned(entry) + assertThat(headsUpStatusBarView.showingEntry).isEqualTo(row.entry) row.setPinnedStatus(PinnedStatus.NotPinned) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + setHeadsUpNotifOnManager(null) underTest.onHeadsUpUnPinned(entry) + + assertThat(headsUpStatusBarView.showingEntry).isNull() + } + + @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun showingEntryUpdated_whenPinnedByUser_andFlagOff() { + row.setPinnedStatus(PinnedStatus.PinnedByUser) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + assertThat(headsUpStatusBarView.showingEntry).isEqualTo(row.entry) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun showingEntryNotUpdated_whenPinnedByUser_andFlagOn() { + // WHEN the HUN was pinned by the user + row.setPinnedStatus(PinnedStatus.PinnedByUser) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + // THEN we don't show the HUN status bar view assertThat(headsUpStatusBarView.showingEntry).isNull() } @Test - fun testPinnedStatusUpdated() { + fun pinnedStatusUpdatedToSystem_whenPinnedBySystem() { row.setPinnedStatus(PinnedStatus.PinnedBySystem) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) - whenever(headsUpManager.getTopEntry()).thenReturn(entry) + setHeadsUpNotifOnManager(entry) underTest.onHeadsUpPinned(entry) assertThat(underTest.pinnedStatus).isEqualTo(PinnedStatus.PinnedBySystem) row.setPinnedStatus(PinnedStatus.NotPinned) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + setHeadsUpNotifOnManager(null) underTest.onHeadsUpUnPinned(entry) assertThat(underTest.pinnedStatus).isEqualTo(PinnedStatus.NotPinned) } @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun pinnedStatusUpdatedToNotPinned_whenPinnedByUser_andFlagOn() { + row.setPinnedStatus(PinnedStatus.PinnedByUser) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + // It's unintuitive that the pinnedStatus wouldn't match the status on the notification. + // Explanation: HeadsUpAppearanceController#updateTopEntry doesn't do anything if + // HeadsUpManager.pinnedHeadsUpStatus != PinnedBySystem. So when we're PinnedByUser, + // HeadsUpAppearanceController early-returns before even updating the pinned status. + assertThat(underTest.pinnedStatus).isEqualTo(PinnedStatus.NotPinned) + } + + @Test + fun isolatedIconSet_whenPinnedBySystem() = + kosmos.runTest { + val latestIsolatedIcon by + collectLastValue(kosmos.headsUpNotificationIconInteractor.isolatedNotification) + + row.setPinnedStatus(PinnedStatus.PinnedBySystem) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + assertThat(latestIsolatedIcon).isEqualTo(entry.key) + + row.setPinnedStatus(PinnedStatus.NotPinned) + setHeadsUpNotifOnManager(null) + underTest.onHeadsUpUnPinned(entry) + + assertThat(latestIsolatedIcon).isNull() + } + + @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun isolatedIconSet_whenPinnedByUser_andFlagOff() = + kosmos.runTest { + val latestIsolatedIcon by + collectLastValue(kosmos.headsUpNotificationIconInteractor.isolatedNotification) + + row.setPinnedStatus(PinnedStatus.PinnedByUser) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + assertThat(latestIsolatedIcon).isEqualTo(entry.key) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun isolatedIconNotSet_whenPinnedByUser_andFlagOn() = + kosmos.runTest { + val latestIsolatedIcon by + collectLastValue(kosmos.headsUpNotificationIconInteractor.isolatedNotification) + + row.setPinnedStatus(PinnedStatus.PinnedByUser) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + assertThat(latestIsolatedIcon).isNull() + } + + @Test @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) fun testHeaderUpdated() { row.setPinnedStatus(PinnedStatus.PinnedBySystem) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) - whenever(headsUpManager.getTopEntry()).thenReturn(entry) + setHeadsUpNotifOnManager(entry) underTest.onHeadsUpPinned(entry) assertThat(row.headerVisibleAmount).isEqualTo(0.0f) row.setPinnedStatus(PinnedStatus.NotPinned) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + setHeadsUpNotifOnManager(null) underTest.onHeadsUpUnPinned(entry) assertThat(row.headerVisibleAmount).isEqualTo(1.0f) } @Test - fun testOperatorNameViewUpdated() { + fun operatorNameViewUpdated_whenPinnedBySystem() { underTest.setAnimationsEnabled(false) row.setPinnedStatus(PinnedStatus.PinnedBySystem) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) - whenever(headsUpManager.getTopEntry()).thenReturn(entry) + setHeadsUpNotifOnManager(entry) underTest.onHeadsUpPinned(entry) assertThat(operatorNameView.visibility).isEqualTo(View.INVISIBLE) row.setPinnedStatus(PinnedStatus.NotPinned) - whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + setHeadsUpNotifOnManager(null) underTest.onHeadsUpUnPinned(entry) assertThat(operatorNameView.visibility).isEqualTo(View.VISIBLE) } @Test + @DisableFlags(StatusBarNotifChips.FLAG_NAME) + fun operatorNameViewUpdated_whenPinnedByUser_andFlagOff() { + underTest.setAnimationsEnabled(false) + + row.setPinnedStatus(PinnedStatus.PinnedByUser) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + assertThat(operatorNameView.visibility).isEqualTo(View.INVISIBLE) + } + + @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun operatorNameViewNotUpdated_whenPinnedByUser_andFlagOn() { + underTest.setAnimationsEnabled(false) + + // WHEN the row was pinned by the user + row.setPinnedStatus(PinnedStatus.PinnedByUser) + setHeadsUpNotifOnManager(entry) + underTest.onHeadsUpPinned(entry) + + // THEN we don't need to hide the operator name view + assertThat(operatorNameView.visibility).isEqualTo(View.VISIBLE) + } + + @Test fun constructor_animationValuesUpdated() { val appearFraction = .75f val expandedHeight = 400f @@ -276,4 +387,16 @@ class HeadsUpAppearanceControllerTest : SysuiTestCase() { verify(phoneStatusBarTransitions).onHeadsUpStateChanged(false) } + + private fun setHeadsUpNotifOnManager(entry: NotificationEntry?) { + if (entry != null) { + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + whenever(headsUpManager.pinnedHeadsUpStatus()).thenReturn(entry.pinnedStatus) + whenever(headsUpManager.getTopEntry()).thenReturn(entry) + } else { + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + whenever(headsUpManager.pinnedHeadsUpStatus()).thenReturn(PinnedStatus.NotPinned) + whenever(headsUpManager.getTopEntry()).thenReturn(null) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt index 7b04fc827d83..5c1141b94bc8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt @@ -67,8 +67,9 @@ import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationSt import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore -import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository +import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository @@ -76,6 +77,7 @@ import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBar import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher import org.junit.Before @@ -518,7 +520,27 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { } @Test - fun isClockVisible_allowedByFlags_hunActive_notVisible() = + @EnableFlags(StatusBarNotifChips.FLAG_NAME) + fun isClockVisible_allowedByFlags_hunPinnedByUser_visible() = + kosmos.runTest { + val latest by collectLastValue(underTest.isClockVisible) + transitionKeyguardToGone() + + fakeDisableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) + // there is an active HUN + headsUpNotificationRepository.setNotifications( + UnconfinedFakeHeadsUpRowRepository( + key = "key", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser), + ) + ) + + assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun isClockVisible_allowedByFlags_hunPinnedBySystem_notVisible() = kosmos.runTest { val latest by collectLastValue(underTest.isClockVisible) transitionKeyguardToGone() @@ -527,7 +549,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) // there is an active HUN headsUpNotificationRepository.setNotifications( - fakeHeadsUpRowRepository(isPinned = true) + UnconfinedFakeHeadsUpRowRepository( + key = "key", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem), + ) ) assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE) @@ -541,10 +566,14 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { fakeDisableFlagsRepository.disableFlags.value = DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) - // there is an active HUN + // there is an active HUN pinned by the system headsUpNotificationRepository.setNotifications( - fakeHeadsUpRowRepository(isPinned = true) + UnconfinedFakeHeadsUpRowRepository( + key = "key", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem), + ) ) + assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE) // hun goes away headsUpNotificationRepository.setNotifications(listOf()) @@ -562,7 +591,10 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE) // there is an active HUN headsUpNotificationRepository.setNotifications( - fakeHeadsUpRowRepository(isPinned = true) + UnconfinedFakeHeadsUpRowRepository( + key = "key", + pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem), + ) ) assertThat(latest!!.visibility).isEqualTo(View.INVISIBLE) @@ -898,10 +930,6 @@ class HomeStatusBarViewModelImplTest : SysuiTestCase() { assertThat(systemInfoVisible!!.baseVisibility.visibility).isEqualTo(View.GONE) } - // Cribbed from [HeadsUpNotificationInteractorTest.kt] - private fun fakeHeadsUpRowRepository(key: String = "test key", isPinned: Boolean = false) = - FakeHeadsUpRowRepository(key = key, isPinned = isPinned) - private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) = ActiveNotificationsStore.Builder() .apply { notifications.forEach(::addIndividualNotif) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 3a6c250e55f1..d347084f435d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -3185,7 +3185,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } if (mHeadsUpAppearanceController != null - && mHeadsUpAppearanceController.shouldBeVisible()) { + && mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()) { return false; } return !mShowIconsWhenExpanded; @@ -4634,7 +4634,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public boolean shouldHeadsUpBeVisible() { return mHeadsUpAppearanceController != null && - mHeadsUpAppearanceController.shouldBeVisible(); + mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index 2cd5bb339072..040d42b1cc61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -25,10 +25,12 @@ import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifCh import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor +import com.android.systemui.statusbar.notification.headsup.PinnedStatus import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch /** A view model for status bar chips for promoted ongoing notifications. */ @@ -38,18 +40,24 @@ class NotifChipsViewModel constructor( @Application private val applicationScope: CoroutineScope, private val notifChipsInteractor: StatusBarNotificationChipsInteractor, + headsUpNotificationInteractor: HeadsUpNotificationInteractor, ) { /** * A flow modeling the notification chips that should be shown. Emits an empty list if there are * no notifications that should show a status bar chip. */ val chips: Flow<List<OngoingActivityChipModel.Shown>> = - notifChipsInteractor.notificationChips.map { notifications -> - notifications.map { it.toActivityChipModel() } + combine( + notifChipsInteractor.notificationChips, + headsUpNotificationInteractor.statusBarHeadsUpState, + ) { notifications, headsUpState -> + notifications.map { it.toActivityChipModel(headsUpState) } } /** Converts the notification to the [OngoingActivityChipModel] object. */ - private fun NotificationChipModel.toActivityChipModel(): OngoingActivityChipModel.Shown { + private fun NotificationChipModel.toActivityChipModel( + headsUpState: PinnedStatus + ): OngoingActivityChipModel.Shown { StatusBarNotifChips.assertInNewMode() val icon = if (this.statusBarChipIconView != null) { @@ -71,12 +79,18 @@ constructor( ) } } - return OngoingActivityChipModel.Shown.ShortTimeDelta( - icon, - colors, - time = this.whenTime, - onClickListener, - ) + return if (headsUpState == PinnedStatus.PinnedByUser) { + // If the user tapped the chip to show the HUN, we want to just show the icon because + // the HUN will show the rest of the information. + OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + } else { + OngoingActivityChipModel.Shown.ShortTimeDelta( + icon, + colors, + time = this.whenTime, + onClickListener, + ) + } // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time. // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`. // TODO(b/364653005): If the app that posted the notification is in the foreground, don't diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 6b84b6d07702..c38b84b710bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -666,7 +666,16 @@ public final class NotificationEntry extends ListEntry { } public boolean isRowPinned() { - return row != null && row.isPinned(); + return getPinnedStatus().isPinned(); + } + + /** Returns this notification's current pinned status. */ + public PinnedStatus getPinnedStatus() { + if (row != null) { + return row.getPinnedStatus(); + } else { + return PinnedStatus.NotPinned; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 0269b16d4490..eb6ec9f59a3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl import com.android.systemui.statusbar.notification.dagger.IncomingHeader import com.android.systemui.statusbar.notification.headsup.HeadsUpManager import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener +import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider import com.android.systemui.statusbar.notification.logKey @@ -145,6 +146,7 @@ constructor( // heads-up is considered to be the top notification. shouldHeadsUpEver = true, shouldHeadsUpAgain = true, + isPinnedByUser = true, isHeadsUpEntry = mHeadsUpManager.isHeadsUpEntry(entry.key), isBinding = isEntryBinding(entry), ) @@ -155,8 +157,8 @@ constructor( } } - private fun onHeadsUpViewBound(entry: NotificationEntry) { - mHeadsUpManager.showNotification(entry) + private fun onHeadsUpViewBound(entry: NotificationEntry, isPinnedByUser: Boolean) { + mHeadsUpManager.showNotification(entry, isPinnedByUser) mEntriesBindingUntil.remove(entry.key) } @@ -424,6 +426,7 @@ constructor( private fun handlePostedEntry(posted: PostedEntry, hunMutator: HunMutator, scenario: String) { mLogger.logPostedEntryWillEvaluate(posted, scenario) + if (posted.wasAdded) { if (posted.shouldHeadsUpEver) { bindForAsyncHeadsUp(posted) @@ -437,7 +440,17 @@ constructor( // If showing heads up, we need to post an update. Otherwise we're still // binding, and we can just let that finish. if (posted.isHeadsUpEntry) { - hunMutator.updateNotification(posted.key, posted.shouldHeadsUpAgain) + val pinnedStatus = + if (posted.shouldHeadsUpAgain) { + if (StatusBarNotifChips.isEnabled && posted.isPinnedByUser) { + PinnedStatus.PinnedByUser + } else { + PinnedStatus.PinnedBySystem + } + } else { + PinnedStatus.NotPinned + } + hunMutator.updateNotification(posted.key, pinnedStatus) } } else { if (posted.isHeadsUpEntry) { @@ -461,10 +474,11 @@ constructor( } private fun bindForAsyncHeadsUp(posted: PostedEntry) { + val isPinnedByUser = StatusBarNotifChips.isEnabled && posted.isPinnedByUser // TODO: Add a guarantee to bindHeadsUpView of some kind of callback if the bind is // cancelled so that we don't need to have this sad timeout hack. mEntriesBindingUntil[posted.key] = mNow + BIND_TIMEOUT - mHeadsUpViewBinder.bindHeadsUpView(posted.entry, this::onHeadsUpViewBound) + mHeadsUpViewBinder.bindHeadsUpView(posted.entry, isPinnedByUser, this::onHeadsUpViewBound) } private val mNotifCollectionListener = @@ -906,6 +920,7 @@ constructor( var wasUpdated: Boolean, var shouldHeadsUpEver: Boolean, var shouldHeadsUpAgain: Boolean, + var isPinnedByUser: Boolean = false, var isHeadsUpEntry: Boolean, var isBinding: Boolean, ) { @@ -943,7 +958,7 @@ private fun <R> HeadsUpManager.modifyHuns(block: (HunMutator) -> R): R { /** Mutates the HeadsUp state of notifications. */ private interface HunMutator { - fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) + fun updateNotification(key: String, requestedPinnedStatus: PinnedStatus) fun removeNotification(key: String, releaseImmediately: Boolean) } @@ -955,8 +970,8 @@ private interface HunMutator { private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator { private val deferred = mutableListOf<Pair<String, Boolean>>() - override fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) { - headsUpManager.updateNotification(key, shouldHeadsUpAgain) + override fun updateNotification(key: String, requestedPinnedStatus: PinnedStatus) { + headsUpManager.updateNotification(key, requestedPinnedStatus) } override fun removeNotification(key: String, releaseImmediately: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt index 64e78e4fbe48..75c7d2d5be98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository +import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -97,19 +98,22 @@ constructor( } } - /** Are there any pinned heads up rows to display? */ - val hasPinnedRows: Flow<Boolean> = + /** What [PinnedStatus] does the top row have? */ + private val topPinnedStatus: Flow<PinnedStatus> = headsUpRepository.activeHeadsUpRows.flatMapLatest { rows -> if (rows.isNotEmpty()) { combine(rows.map { it.pinnedStatus }) { pinnedStatus -> - pinnedStatus.any { it.isPinned } + pinnedStatus.firstOrNull { it.isPinned } ?: PinnedStatus.NotPinned } } else { // if the set is empty, there are no flows to combine - flowOf(false) + flowOf(PinnedStatus.NotPinned) } } + /** Are there any pinned heads up rows to display? */ + val hasPinnedRows: Flow<Boolean> = topPinnedStatus.map { it.isPinned } + val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { flowOf(false) @@ -138,9 +142,14 @@ constructor( } } - val showHeadsUpStatusBar = - combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp -> - hasPinnedRows && canShowHeadsUp + /** Emits the pinned notification state as it relates to the status bar. */ + val statusBarHeadsUpState: Flow<PinnedStatus> = + combine(topPinnedStatus, canShowHeadsUp) { topPinnedStatus, canShowHeadsUp -> + if (canShowHeadsUp) { + topPinnedStatus + } else { + PinnedStatus.NotPinned + } } fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt index 424a3c5e6af9..95234dacc899 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt @@ -88,6 +88,12 @@ interface HeadsUpManager : Dumpable { /** Returns whether there are any pinned Heads Up Notifications or not. */ fun hasPinnedHeadsUp(): Boolean + /** + * Returns the status of the top Heads Up Notification, or returns [PinnedStatus.NotPinned] if + * there is no pinned HUN. + */ + fun pinnedHeadsUpStatus(): PinnedStatus + /** Returns whether or not the given notification is managed by this manager. */ fun isHeadsUpEntry(key: String): Boolean @@ -204,8 +210,10 @@ interface HeadsUpManager : Dumpable { * the notification to be managed. * * @param entry entry to show + * @param isPinnedByUser true if the notification was pinned by the user and false if the + * notification was pinned by the system. */ - fun showNotification(entry: NotificationEntry) + fun showNotification(entry: NotificationEntry, isPinnedByUser: Boolean = false) fun snooze() @@ -216,7 +224,15 @@ interface HeadsUpManager : Dumpable { */ fun unpinAll(userUnPinned: Boolean) - fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) + /** + * Called when the notification state has been updated. + * + * @param key the key of the entry that was updated + * @param requestedPinnedStatus whether and how the notification should be pinned. If equal to + * [PinnedStatus.NotPinned], the notification won't show again. Otherwise, the notification + * should show again and will force reevaluation of removal time. + */ + fun updateNotification(key: String, requestedPinnedStatus: PinnedStatus) fun onEntryAnimatingAwayEnded(entry: NotificationEntry) } @@ -262,6 +278,8 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager { override fun hasPinnedHeadsUp() = false + override fun pinnedHeadsUpStatus() = PinnedStatus.NotPinned + override fun isHeadsUpEntry(key: String) = false override fun isHeadsUpAnimatingAwayValue() = false @@ -306,13 +324,13 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager { override fun shouldSwallowClick(key: String): Boolean = false - override fun showNotification(entry: NotificationEntry) {} + override fun showNotification(entry: NotificationEntry, isPinnedByUser: Boolean) {} override fun snooze() {} override fun unpinAll(userUnPinned: Boolean) {} - override fun updateNotification(key: String, alert: Boolean) {} + override fun updateNotification(key: String, requestedPinnedStatus: PinnedStatus) {} override fun onEntryAnimatingAwayEnded(entry: NotificationEntry) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java index 0b188afa1c35..66723f77dab3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java @@ -45,6 +45,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeDisplayAware; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener; import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener; @@ -99,6 +100,7 @@ public class HeadsUpManagerImpl protected int mTouchAcceptanceDelay; protected int mSnoozeLengthMs; protected boolean mHasPinnedNotification; + private PinnedStatus mPinnedNotificationStatus = PinnedStatus.NotPinned; protected int mUser; private final ArrayMap<String, Long> mSnoozedPackages; @@ -303,28 +305,28 @@ public class HeadsUpManagerImpl + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding); } - /** - * Called when posting a new notification that should appear on screen. - * Adds the notification to be managed. - * @param entry entry to show - */ @Override - public void showNotification(@NonNull NotificationEntry entry) { + public void showNotification( + @NonNull NotificationEntry entry, boolean isPinnedByUser) { HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry); - mLogger.logShowNotificationRequest(entry); + mLogger.logShowNotificationRequest(entry, isPinnedByUser); Runnable runnable = () -> { - mLogger.logShowNotification(entry); + mLogger.logShowNotification(entry, isPinnedByUser); // Add new entry and begin managing it mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry); - onEntryAdded(headsUpEntry); + PinnedStatus requestedPinnedStatus = + isPinnedByUser + ? PinnedStatus.PinnedByUser + : PinnedStatus.PinnedBySystem; + onEntryAdded(headsUpEntry, requestedPinnedStatus); // TODO(b/328390331) move accessibility events to the view layer entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); entry.setIsHeadsUpEntry(true); - updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */); + updateNotificationInternal(entry.getKey(), requestedPinnedStatus); entry.setInterruption(); }; mAvalancheController.update(headsUpEntry, runnable, "showNotification"); @@ -375,25 +377,22 @@ public class HeadsUpManagerImpl return false; } - /** - * Called when the notification state has been updated. - * @param key the key of the entry that was updated - * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation - * of removal time - */ - public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) { + @Override + public void updateNotification( + @NonNull String key, @NonNull PinnedStatus requestedPinnedStatus) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); - mLogger.logUpdateNotificationRequest(key, shouldHeadsUpAgain, headsUpEntry != null); + mLogger.logUpdateNotificationRequest(key, requestedPinnedStatus, headsUpEntry != null); Runnable runnable = () -> { - updateNotificationInternal(key, shouldHeadsUpAgain); + updateNotificationInternal(key, requestedPinnedStatus); }; mAvalancheController.update(headsUpEntry, runnable, "updateNotification"); } - private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) { + private void updateNotificationInternal( + @NonNull String key, PinnedStatus requestedPinnedStatus) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); - mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null); + mLogger.logUpdateNotification(key, requestedPinnedStatus, headsUpEntry != null); if (headsUpEntry == null) { // the entry was released before this update (i.e by a listener) This can happen // with the groupmanager @@ -404,11 +403,10 @@ public class HeadsUpManagerImpl headsUpEntry.mEntry.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } - if (shouldHeadsUpAgain) { + if (requestedPinnedStatus.isPinned()) { headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification"); - PinnedStatus pinnedStatus = shouldHeadsUpBecomePinned(headsUpEntry.mEntry) - ? PinnedStatus.PinnedBySystem - : PinnedStatus.NotPinned; + PinnedStatus pinnedStatus = + getNewPinnedStatusForEntry(headsUpEntry, requestedPinnedStatus); if (headsUpEntry != null) { setEntryPinned(headsUpEntry, pinnedStatus, "updateNotificationInternal"); } @@ -596,22 +594,43 @@ public class HeadsUpManagerImpl * Manager-specific logic that should occur when an entry is added. * @param headsUpEntry entry added */ - protected void onEntryAdded(HeadsUpEntry headsUpEntry) { + @VisibleForTesting + void onEntryAdded(HeadsUpEntry headsUpEntry, PinnedStatus requestedPinnedStatus) { NotificationEntry entry = headsUpEntry.mEntry; entry.setHeadsUp(true); - final PinnedStatus pinnedStatus = shouldHeadsUpBecomePinned(entry) - ? PinnedStatus.PinnedBySystem - : PinnedStatus.NotPinned; + PinnedStatus pinnedStatus = getNewPinnedStatusForEntry(headsUpEntry, requestedPinnedStatus); setEntryPinned(headsUpEntry, pinnedStatus, "onEntryAdded"); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */); for (OnHeadsUpChangedListener listener : mListeners) { + // TODO(b/382509804): It's odd that if pinnedStatus == PinnedStatus.NotPinned, then we + // still send isHeadsUp=true to listeners. Is this causing bugs? listener.onHeadsUpStateChanged(entry, true); } updateTopHeadsUpFlow(); updateHeadsUpFlow(); } + private PinnedStatus getNewPinnedStatusForEntry( + HeadsUpEntry headsUpEntry, PinnedStatus requestedPinnedStatus) { + NotificationEntry entry = headsUpEntry.mEntry; + if (entry == null) { + return PinnedStatus.NotPinned; + } + boolean shouldBecomePinned = shouldHeadsUpBecomePinned(entry); + if (!shouldBecomePinned) { + return PinnedStatus.NotPinned; + } + + if (!StatusBarNotifChips.isEnabled() + && requestedPinnedStatus == PinnedStatus.PinnedByUser) { + Log.wtf(TAG, "PinnedStatus.PinnedByUser not allowed if StatusBarNotifChips flag off"); + return PinnedStatus.NotPinned; + } + + return requestedPinnedStatus; + } + /** * Remove a notification from the alerting entries. * @param key key of notification to remove @@ -747,10 +766,11 @@ public class HeadsUpManagerImpl protected void updatePinnedMode() { boolean hasPinnedNotification = hasPinnedNotificationInternal(); + mPinnedNotificationStatus = pinnedNotificationStatusInternal(); if (hasPinnedNotification == mHasPinnedNotification) { return; } - mLogger.logUpdatePinnedMode(hasPinnedNotification); + mLogger.logUpdatePinnedMode(hasPinnedNotification, mPinnedNotificationStatus); mHasPinnedNotification = hasPinnedNotification; if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); @@ -941,13 +961,20 @@ public class HeadsUpManagerImpl pw.println(mTouchableRegion); } - /** - * Returns if there are any pinned Heads Up Notifications or not. - */ + @Override public boolean hasPinnedHeadsUp() { return mHasPinnedNotification; } + @Override + @NonNull + public PinnedStatus pinnedHeadsUpStatus() { + if (!StatusBarNotifChips.isEnabled()) { + return mHasPinnedNotification ? PinnedStatus.PinnedBySystem : PinnedStatus.NotPinned; + } + return mPinnedNotificationStatus; + } + private boolean hasPinnedNotificationInternal() { for (String key : mHeadsUpEntryMap.keySet()) { HeadsUpEntry entry = getHeadsUpEntry(key); @@ -958,6 +985,16 @@ public class HeadsUpManagerImpl return false; } + private PinnedStatus pinnedNotificationStatusInternal() { + for (String key : mHeadsUpEntryMap.keySet()) { + HeadsUpEntry entry = getHeadsUpEntry(key); + if (entry.mEntry != null && entry.mEntry.isRowPinned()) { + return entry.mEntry.getPinnedStatus(); + } + } + return PinnedStatus.NotPinned; + } + /** * Unpins all pinned Heads Up Notifications. * @param userUnPinned The unpinned action is trigger by user real operation. @@ -1305,10 +1342,6 @@ public class HeadsUpManagerImpl } } - protected boolean isRowPinned() { - return mEntry != null && mEntry.isRowPinned(); - } - protected void setRowPinnedStatus(PinnedStatus pinnedStatus) { if (mEntry != null) mEntry.setRowPinnedStatus(pinnedStatus); mPinnedStatus.setValue(pinnedStatus); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt index 80225c47e9ea..1ccc45b9c385 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt @@ -44,8 +44,16 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { buffer.log(TAG, INFO, {}, { "release all immediately" }) } - fun logShowNotificationRequest(entry: NotificationEntry) { - buffer.log(TAG, INFO, { str1 = entry.logKey }, { "request: show notification $str1" }) + fun logShowNotificationRequest(entry: NotificationEntry, isPinnedByUser: Boolean) { + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + bool1 = isPinnedByUser + }, + { "request: show notification $str1. isPinnedByUser=$bool1" }, + ) } fun logAvalancheUpdate( @@ -86,8 +94,16 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { ) } - fun logShowNotification(entry: NotificationEntry) { - buffer.log(TAG, INFO, { str1 = entry.logKey }, { "show notification $str1" }) + fun logShowNotification(entry: NotificationEntry, isPinnedByUser: Boolean) { + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + bool1 = isPinnedByUser + }, + { "show notification $str1. isPinnedByUser=$bool1" }, + ) } fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { @@ -224,29 +240,33 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " }) } - fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) { + fun logUpdateNotificationRequest( + key: String, + requestedPinnedStatus: PinnedStatus, + hasEntry: Boolean, + ) { buffer.log( TAG, INFO, { str1 = logKey(key) - bool1 = alert - bool2 = hasEntry + bool1 = hasEntry + str2 = requestedPinnedStatus.name }, - { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" }, + { "request: update notification $str1. hasEntry: $bool1. requestedPinnedStatus: $str2" }, ) } - fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) { + fun logUpdateNotification(key: String, requestedPinnedStatus: PinnedStatus, hasEntry: Boolean) { buffer.log( TAG, INFO, { str1 = logKey(key) - bool1 = alert - bool2 = hasEntry + bool1 = hasEntry + str2 = requestedPinnedStatus.name }, - { "update notification $str1 alert: $bool1 hasEntry: $bool2" }, + { "update notification $str1. hasEntry: $bool2. requestedPinnedStatus: $str2" }, ) } @@ -285,12 +305,18 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) { ) } - fun logUpdatePinnedMode(hasPinnedNotification: Boolean) { + fun logUpdatePinnedMode( + hasPinnedNotification: Boolean, + pinnedNotificationStatus: PinnedStatus, + ) { buffer.log( TAG, INFO, - { bool1 = hasPinnedNotification }, - { "has pinned notification changed to $bool1" }, + { + bool1 = hasPinnedNotification + str1 = pinnedNotificationStatus.name + }, + { "has pinned notification changed to $bool1, status=$str1" }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java index 9a7610ddd354..32ec02319241 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java @@ -28,7 +28,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator; -import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback; import com.android.systemui.statusbar.notification.row.RowContentBindParams; import com.android.systemui.statusbar.notification.row.RowContentBindStage; @@ -73,7 +72,10 @@ public class HeadsUpViewBinder { * Bind heads up view to the notification row. * @param callback callback after heads up view is bound */ - public void bindHeadsUpView(NotificationEntry entry, @Nullable BindCallback callback) { + public void bindHeadsUpView( + NotificationEntry entry, + boolean isPinnedByUser, + @Nullable HeadsUpBindCallback callback) { RowContentBindParams params = mStage.getStageParams(entry); final boolean isImportantMessage = mNotificationMessagingUtil.isImportantMessaging( entry.getSbn(), entry.getImportance()); @@ -84,16 +86,16 @@ public class HeadsUpViewBinder { CancellationSignal signal = mStage.requestRebind(entry, en -> { mLogger.entryBoundSuccessfully(entry); en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight()); - // requestRebing promises that if we called cancel before this callback would be + // requestRebind promises that if we called cancel before this callback would be // invoked, then we will not enter this callback, and because we always cancel before // adding to this map, we know this will remove the correct signal. mOngoingBindCallbacks.remove(entry); if (callback != null) { - callback.onBindFinished(en); + callback.onHeadsUpBindFinished(en, isPinnedByUser); } }); abortBindCallback(entry); - mLogger.startBindingHun(entry); + mLogger.startBindingHun(entry, isPinnedByUser); mOngoingBindCallbacks.put(entry, signal); } @@ -129,4 +131,14 @@ public class HeadsUpViewBinder { mLogger.entryContentViewMarkedFreeable(entry); mStage.requestRebind(entry, e -> mLogger.entryUnbound(e)); } + + /** + * Interface for bind callback. + */ + public interface HeadsUpBindCallback { + /** + * Called when all views are fully bound on the notification. + */ + void onHeadsUpBindFinished(NotificationEntry entry, boolean isPinnedByUser); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt index c6d2861a8c68..e690fa5a36e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt @@ -1,59 +1,63 @@ package com.android.systemui.statusbar.notification.interruption -import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val buffer: LogBuffer) { - fun startBindingHun(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "start binding heads up entry $str1 " - }) + fun startBindingHun(entry: NotificationEntry, isPinnedByUser: Boolean) { + buffer.log( + TAG, + INFO, + { + str1 = entry.logKey + bool1 = isPinnedByUser + }, + { "start binding heads up entry $str1. isPinnedByUser=$bool1 " }, + ) } fun currentOngoingBindingAborted(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "aborted potential ongoing heads up entry binding $str1 " - }) + buffer.log( + TAG, + INFO, + { str1 = entry.logKey }, + { "aborted potential ongoing heads up entry binding $str1 " }, + ) } fun entryBoundSuccessfully(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "heads up entry bound successfully $str1 " - }) + buffer.log( + TAG, + INFO, + { str1 = entry.logKey }, + { "heads up entry bound successfully $str1 " }, + ) } fun entryUnbound(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "heads up entry unbound successfully $str1 " - }) + buffer.log( + TAG, + INFO, + { str1 = entry.logKey }, + { "heads up entry unbound successfully $str1 " }, + ) } fun entryContentViewMarkedFreeable(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "start unbinding heads up entry $str1 " - }) + buffer.log(TAG, INFO, { str1 = entry.logKey }, { "start unbinding heads up entry $str1 " }) } fun entryBindStageParamsNullOnUnbind(entry: NotificationEntry) { - buffer.log(TAG, INFO, { - str1 = entry.logKey - }, { - "heads up entry bind stage params null on unbind $str1 " - }) + buffer.log( + TAG, + INFO, + { str1 = entry.logKey }, + { "heads up entry bind stage params null on unbind $str1 " }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index c8811fd3b76d..5a52c379d0d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1247,6 +1247,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + public PinnedStatus getPinnedStatus() { + return mPinnedStatus; + } + + @Override public int getPinnedHeadsUpHeight() { return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index ef6cad134534..f83a1d9b7833 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -40,6 +40,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.Roundable; import com.android.systemui.statusbar.notification.RoundableState; +import com.android.systemui.statusbar.notification.headsup.PinnedStatus; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.util.Compile; @@ -201,6 +202,11 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro return false; } + @NonNull + public PinnedStatus getPinnedStatus() { + return PinnedStatus.NotPinned; + } + public boolean isHeadsUpAnimatingAway() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 6cad68ffe8fb..53a29505510b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -23,6 +23,7 @@ import android.util.MathUtils; import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; @@ -36,6 +37,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.core.StatusBarRootModernization; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.SourceType; @@ -153,7 +155,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - if (shouldBeVisible()) { + if (shouldHeadsUpStatusBarBeVisible()) { updateTopEntry(); // trigger scroller to notify the latest panel translation @@ -217,35 +219,54 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar private void updateTopEntry() { NotificationEntry newEntry = null; - if (shouldBeVisible()) { + if (shouldHeadsUpStatusBarBeVisible()) { newEntry = mHeadsUpManager.getTopEntry(); } NotificationEntry previousEntry = mView.getShowingEntry(); mView.setEntry(newEntry); if (newEntry != previousEntry) { if (newEntry == null) { - // no heads up anymore, lets start the disappear animation + // No longer heads up setPinnedStatus(PinnedStatus.NotPinned); } else if (previousEntry == null) { - // We now have a headsUp and didn't have one before. Let's start the disappear - // animation - setPinnedStatus(PinnedStatus.PinnedBySystem); + // We now have a heads up when we didn't have one before + setPinnedStatus(newEntry.getPinnedStatus()); } - String isolatedIconKey; - if (newEntry != null) { - isolatedIconKey = newEntry.getRepresentativeEntry().getKey(); + mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey( + getIsolatedIconKey(newEntry)); + } + } + + private static @Nullable String getIsolatedIconKey(NotificationEntry newEntry) { + if (newEntry == null) { + return null; + } + if (StatusBarNotifChips.isEnabled()) { + // If the flag is on, only show the isolated icon if the HUN is pinned by the + // *system*. (If the HUN was pinned by the user, then the user tapped the + // notification status bar chip and we want to keep the chip showing.) + if (newEntry.getPinnedStatus() == PinnedStatus.PinnedBySystem) { + return newEntry.getRepresentativeEntry().getKey(); } else { - isolatedIconKey = null; + return null; } - mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(isolatedIconKey); + } else { + // If the flag is off, we know all HUNs are pinned by the system and should show + // the isolated icon + return newEntry.getRepresentativeEntry().getKey(); } } private void setPinnedStatus(PinnedStatus pinnedStatus) { if (mPinnedStatus != pinnedStatus) { mPinnedStatus = pinnedStatus; - if (pinnedStatus.isPinned()) { + + boolean shouldShowHunStatusBar = StatusBarNotifChips.isEnabled() + ? mPinnedStatus == PinnedStatus.PinnedBySystem + // If the flag isn't enabled, all HUNs get the normal treatment. + : mPinnedStatus.isPinned(); + if (shouldShowHunStatusBar) { updateParentClipping(false /* shouldClip */); mView.setVisibility(View.VISIBLE); show(mView); @@ -333,23 +354,36 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar return mPinnedStatus; } - /** - * Should the headsup status bar view be visible right now? This may be different from isShown, - * since the headsUp manager might not have notified us yet of the state change. - * - * @return if the heads up status bar view should be shown - * @deprecated use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead. - */ - public boolean shouldBeVisible() { + /** True if the device's current state allows us to show HUNs and false otherwise. */ + private boolean canShowHeadsUp() { boolean notificationsShown = !mWakeUpCoordinator.getNotificationsFullyHidden(); - boolean canShow = !isExpanded() && notificationsShown; if (mBypassController.getBypassEnabled() && (mStatusBarStateController.getState() == StatusBarState.KEYGUARD || mKeyguardStateController.isKeyguardGoingAway()) && notificationsShown) { - canShow = true; + return true; + } + return !isExpanded() && notificationsShown; + } + + /** + * True if the headsup status bar view (which has just the HUN icon and app name) should be + * visible right now and false otherwise. + * + * @deprecated use {@link com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor#getStatusBarHeadsUpState()} + * instead. + */ + @Deprecated + public boolean shouldHeadsUpStatusBarBeVisible() { + if (StatusBarNotifChips.isEnabled()) { + return canShowHeadsUp() + && mHeadsUpManager.pinnedHeadsUpStatus() == PinnedStatus.PinnedBySystem; + // Note: This means that if mHeadsUpManager.pinnedHeadsUpStatus() == PinnedByUser, + // #updateTopEntry won't do anything, so mPinnedStatus will remain as NotPinned and will + // *not* update to PinnedByUser. + } else { + return canShowHeadsUp() && mHeadsUpManager.hasPinnedHeadsUp(); } - return canShow && mHeadsUpManager.hasPinnedHeadsUp(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 724ba8cab352..d257288637df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -627,8 +627,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue StatusBarRootModernization.assertInLegacyMode(); // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead. - boolean headsUpVisible = - mHomeStatusBarComponent.getHeadsUpAppearanceController().shouldBeVisible(); + boolean headsUpVisible = mHomeStatusBarComponent + .getHeadsUpAppearanceController() + .shouldHeadsUpStatusBarBeVisible(); if (SceneContainerFlag.isEnabled()) { // With the scene container, only use the value calculated by the view model to diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt index c52275a9eaa4..6e9e1ec7253c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationSt import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor +import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor import com.android.systemui.statusbar.pipeline.shared.domain.interactor.CollapsedStatusBarInteractor @@ -235,14 +236,18 @@ constructor( override val isClockVisible: Flow<VisibilityModel> = combine( shouldHomeStatusBarBeVisible, - headsUpNotificationInteractor.showHeadsUpStatusBar, + headsUpNotificationInteractor.statusBarHeadsUpState, collapsedStatusBarInteractor.visibilityViaDisableFlags, - ) { shouldStatusBarBeVisible, showHeadsUp, visibilityViaDisableFlags -> + ) { shouldStatusBarBeVisible, headsUpState, visibilityViaDisableFlags -> + val hideClockForHeadsUp = headsUpState == PinnedStatus.PinnedBySystem val showClock = - shouldStatusBarBeVisible && visibilityViaDisableFlags.isClockAllowed && !showHeadsUp + shouldStatusBarBeVisible && + visibilityViaDisableFlags.isClockAllowed && + !hideClockForHeadsUp // Always use View.INVISIBLE here, so that animations work VisibilityModel(showClock.toVisibleOrInvisible(), visibilityViaDisableFlags.animate) } + override val isNotificationIconContainerVisible: Flow<VisibilityModel> = combine( shouldHomeStatusBarBeVisible, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt index fe1d64736991..6175ea190697 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** @@ -59,7 +60,7 @@ constructor( private val showingHeadsUpStatusBar: Flow<Boolean> = if (SceneContainerFlag.isEnabled) { - headsUpNotificationInteractor.showHeadsUpStatusBar + headsUpNotificationInteractor.statusBarHeadsUpState.map { it.isPinned } } else { flowOf(false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 3e24fbe6cd8d..b39e38bd71cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -533,7 +533,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); - when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -775,7 +775,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { /* hasPrimaryOngoingActivity= */ true, /* hasSecondaryOngoingActivity= */ false, /* shouldAnimate= */ false); - when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -792,7 +792,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { /* hasPrimaryOngoingActivity= */ true, /* hasSecondaryOngoingActivity= */ true, /* shouldAnimate= */ false); - when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -1091,9 +1091,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) - public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { + public void disable_shouldHeadsUpStatusBarBeVisibleTrue_clockDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -1102,9 +1102,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableFlags({StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME}) - public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { + public void disable_shouldHeadsUpStatusBarBeVisibleFalse_clockNotDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false); + when(mHeadsUpAppearanceController.shouldHeadsUpStatusBarBeVisible()).thenReturn(false); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt index 4bcce8601d64..d0c80c7332b3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt @@ -19,8 +19,13 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor +import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor val Kosmos.notifChipsViewModel: NotifChipsViewModel by Kosmos.Fixture { - NotifChipsViewModel(applicationCoroutineScope, statusBarNotificationChipsInteractor) + NotifChipsViewModel( + applicationCoroutineScope, + statusBarNotificationChipsInteractor, + headsUpNotificationInteractor, + ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt index 7de22d8c8b43..4a692d2ca4d9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt @@ -26,7 +26,8 @@ class FakeHeadsUpRowRepository(override val key: String, override val elementKey elementKey: Any = Any(), isPinned: Boolean, ) : this(key = key, elementKey = elementKey) { - this.pinnedStatus.value = if (isPinned) PinnedStatus.PinnedBySystem else PinnedStatus.NotPinned + this.pinnedStatus.value = + if (isPinned) PinnedStatus.PinnedBySystem else PinnedStatus.NotPinned } constructor( @@ -40,3 +41,10 @@ class FakeHeadsUpRowRepository(override val key: String, override val elementKey override val pinnedStatus: MutableStateFlow<PinnedStatus> = MutableStateFlow(PinnedStatus.NotPinned) } + +/** Use this fake if you're using [UnconfinedTestDispatcher]. See b/383528592 for reasoning. */ +class UnconfinedFakeHeadsUpRowRepository( + override val key: String, + override val elementKey: Any = Any(), + override val pinnedStatus: MutableStateFlow<PinnedStatus>, +) : HeadsUpRowRepository |