summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt149
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt205
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.kt153
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt10
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