summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt79
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt104
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java68
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt8
26 files changed, 838 insertions, 153 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 2887de38fe23..e3f93f237742 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -48,6 +48,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
+import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModelLegacy
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -172,6 +173,18 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
+ fun visibleChipKeys_allInactive() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ screenRecordState.value = ScreenRecordModel.DoingNothing
+ mediaProjectionState.value = MediaProjectionState.NotProjecting
+ setNotifs(emptyList())
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
fun primaryChip_screenRecordShow_restHidden_screenRecordShown() =
kosmos.runTest {
screenRecordState.value = ScreenRecordModel.Recording
@@ -245,6 +258,20 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
+ @Test
+ fun visibleChipKeys_screenRecordShowAndCallShow_hasBothKeys() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ screenRecordState.value = ScreenRecordModel.Recording
+ addOngoingCallState(callNotificationKey)
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey)
+ .inOrder()
+ }
+
@EnableChipsModernization
@Test
fun chips_screenRecordAndCallActive_inThatOrder() =
@@ -864,6 +891,37 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
}
+ @Test
+ fun visibleChipKeys_threePromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("firstNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("secondNotif").build(),
+ ),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent =
+ PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ ),
+ )
+ )
+
+ assertThat(latest).containsExactly("firstNotif", "secondNotif").inOrder()
+ }
+
@DisableChipsModernization
@Test
fun chipsLegacy_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() =
@@ -957,6 +1015,27 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModel())
}
+ @Test
+ fun visibleChipKeys_screenRecordAndCallAndPromotedNotifs_topTwoInList() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.visibleChipKeys)
+
+ val callNotificationKey = "call"
+ addOngoingCallState(callNotificationKey)
+ screenRecordState.value = ScreenRecordModel.Recording
+ activeNotificationListRepository.addNotif(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ )
+ )
+
+ assertThat(latest)
+ .containsExactly(ScreenRecordChipViewModel.KEY, callNotificationKey)
+ .inOrder()
+ }
+
@EnableChipsModernization
@Test
fun chips_screenRecordAndCallAndPromotedNotif_notifInOverflow() =
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 609885d0214b..30983550f0f9 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
@@ -549,7 +549,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTap() =
+ fun onPromotedNotificationChipTapped_chipTappedTwice_hunHiddenOnSecondTapImmediately() =
testScope.runTest {
whenever(notifCollection.getEntry(entry.key)).thenReturn(entry)
@@ -570,8 +570,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
executor.runAllReady()
beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
- // THEN HUN is hidden
- verify(headsUpManager).removeNotification(eq(entry.key), eq(false), any())
+ // THEN HUN is hidden and it's hidden immediately
+ verify(headsUpManager)
+ .removeNotification(eq(entry.key), /* releaseImmediately= */ eq(true), any())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
index dc27859df421..a2e4a328697e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
@@ -17,9 +17,10 @@ package com.android.systemui.statusbar.notification.headsup
import android.app.Notification
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper.RunWithLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
@@ -53,12 +55,18 @@ import org.mockito.Mockito
import org.mockito.invocation.InvocationOnMock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWithLooper
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
-class AvalancheControllerTest : SysuiTestCase() {
+class AvalancheControllerTest(val flags: FlagsParameterization) : SysuiTestCase() {
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
private val kosmos = testKosmos()
// For creating mocks
@@ -72,10 +80,10 @@ class AvalancheControllerTest : SysuiTestCase() {
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
- @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
+ private val headsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
@Mock private lateinit var mBgHandler: Handler
- private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mLogger = Mockito.spy(headsUpManagerLogger)
private val mGlobalSettings = FakeGlobalSettings()
private val mSystemClock = FakeSystemClock()
private val mExecutor = FakeExecutor(mSystemClock)
@@ -95,7 +103,7 @@ class AvalancheControllerTest : SysuiTestCase() {
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
mAvalancheController =
- AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
+ AvalancheController(dumpManager, mUiEventLoggerFake, headsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
HeadsUpManagerImpl(
@@ -278,7 +286,7 @@ class AvalancheControllerTest : SysuiTestCase() {
// Delete
mAvalancheController.delete(firstEntry, runnableMock, "testLabel")
- // Next entry is shown
+ // Showing entry becomes previous
assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key)
}
@@ -296,12 +304,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Delete
mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
- // Next entry is shown
+ // Previous key not filled in
assertThat(mAvalancheController.previousHunKey).isEqualTo("")
}
@Test
- fun testGetDurationMs_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
+ fun testGetDuration_untrackedEntryEmptyAvalanche_useAutoDismissTime() {
val givenEntry = createHeadsUpEntry(id = 0)
// Nothing is showing
@@ -310,12 +318,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Nothing is next
mAvalancheController.clearNext()
- val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(givenEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
+ fun testGetDuration_untrackedEntryNonEmptyAvalanche_useAutoDismissTime() {
val givenEntry = createHeadsUpEntry(id = 0)
// Given entry not tracked
@@ -325,12 +333,12 @@ class AvalancheControllerTest : SysuiTestCase() {
val nextEntry = createHeadsUpEntry(id = 2)
mAvalancheController.addToNext(nextEntry, runnableMock!!)
- val durationMs = mAvalancheController.getDurationMs(givenEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(givenEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_lastEntry_useAutoDismissTime() {
+ fun testGetDuration_lastEntry_useAutoDismissTime() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -338,12 +346,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Nothing is next
mAvalancheController.clearNext()
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_nextEntryLowerPriority_5000() {
+ fun testGetDuration_nextEntryLowerPriority_5000() {
// Entry is showing
val showingEntry = createFsiHeadsUpEntry(id = 1)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -355,12 +363,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Next entry has lower priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(1)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(5000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(5000)
}
@Test
- fun testGetDurationMs_nextEntrySamePriority_1000() {
+ fun testGetDuration_nextEntrySamePriority_1000() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -372,12 +380,12 @@ class AvalancheControllerTest : SysuiTestCase() {
// Same priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(0)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(1000)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(1000)
}
@Test
- fun testGetDurationMs_nextEntryHigherPriority_500() {
+ fun testGetDuration_nextEntryHigherPriority_500() {
// Entry is showing
val showingEntry = createHeadsUpEntry(id = 0)
mAvalancheController.headsUpEntryShowing = showingEntry
@@ -389,7 +397,51 @@ class AvalancheControllerTest : SysuiTestCase() {
// Next entry has higher priority
assertThat(nextEntry.compareNonTimeFields(showingEntry)).isEqualTo(-1)
- val durationMs = mAvalancheController.getDurationMs(showingEntry, autoDismissMs = 5000)
- assertThat(durationMs).isEqualTo(500)
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(500)
+ }
+
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testGetDuration_nextEntryIsPinnedByUser_flagOff_1000() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next and it's PinnedByUser
+ val nextEntry = createHeadsUpEntry(id = 1)
+ nextEntry.requestedPinnedStatus = PinnedStatus.PinnedByUser
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ val durationMs = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+
+ // BUT PinnedByUser is ignored because flag is off, so the duration for a SAME priority next
+ // is used
+ assertThat((durationMs as RemainingDuration.UpdatedDuration).duration).isEqualTo(1000)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testGetDuration_nextEntryIsPinnedByUser_flagOn_hideImmediately() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next and it's PinnedByUser
+ val nextEntry = createHeadsUpEntry(id = 1)
+ nextEntry.requestedPinnedStatus = PinnedStatus.PinnedByUser
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ val duration = mAvalancheController.getDuration(showingEntry, autoDismissMsValue = 5000)
+
+ assertThat(duration).isEqualTo(RemainingDuration.HideImmediately)
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(StatusBarNotifChips.FLAG_NAME)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
index 206eb89db94f..706885bf5dee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimatorTest.kt
@@ -21,6 +21,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
@@ -30,6 +32,8 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
class HeadsUpAnimatorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
context.getOrCreateTestableResources().apply {
@@ -38,34 +42,64 @@ class HeadsUpAnimatorTest : SysuiTestCase() {
}
@Test
- fun getHeadsUpYTranslation_fromBottomTrue_usesBottomAndYAbove() {
- val underTest = HeadsUpAnimator(context)
+ fun getHeadsUpYTranslation_fromBottomTrue_hasStatusBarChipFalse_usesBottomAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = false)
+
+ assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
+ }
+
+ @Test
+ fun getHeadsUpYTranslation_fromBottomTrue_hasStatusBarChipTrue_usesBottomAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = true)
+ // fromBottom takes priority
assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
}
@Test
- fun getHeadsUpYTranslation_fromBottomFalse_usesTopMarginAndYAbove() {
- val underTest = HeadsUpAnimator(context)
+ fun getHeadsUpYTranslation_fromBottomFalse_hasStatusBarChipFalse_usesTopMarginAndYAbove() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false, hasStatusBarChip = false)
assertThat(yTranslation).isEqualTo(-30 - TEST_Y_ABOVE_SCREEN)
}
@Test
+ fun getHeadsUpYTranslation_fromBottomFalse_hasStatusBarChipTrue_usesTopMarginAndStatusBarHeight() {
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
+ underTest.stackTopMargin = 30
+ underTest.headsUpAppearHeightBottom = 300
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = 75
+ underTest.updateResources(context)
+
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = false, hasStatusBarChip = true)
+
+ assertThat(yTranslation).isEqualTo(75 - 30)
+ }
+
+ @Test
fun getHeadsUpYTranslation_resourcesUpdated() {
- val underTest = HeadsUpAnimator(context)
+ val underTest = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
underTest.stackTopMargin = 30
underTest.headsUpAppearHeightBottom = 300
- val yTranslation = underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true)
+ val yTranslation =
+ underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true, hasStatusBarChip = false)
assertThat(yTranslation).isEqualTo(TEST_Y_ABOVE_SCREEN + 300)
@@ -77,7 +111,12 @@ class HeadsUpAnimatorTest : SysuiTestCase() {
underTest.updateResources(context)
// THEN HeadsUpAnimator knows about it
- assertThat(underTest.getHeadsUpYTranslation(isHeadsUpFromBottom = true))
+ assertThat(
+ underTest.getHeadsUpYTranslation(
+ isHeadsUpFromBottom = true,
+ hasStatusBarChip = false,
+ )
+ )
.isEqualTo(newYAbove + 300)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 954515015fd9..08ecbac1582c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification.stack
import android.annotation.DimenRes
import android.content.pm.PackageManager
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -19,6 +20,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.RoundableState
import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -32,6 +34,8 @@ import com.android.systemui.statusbar.notification.headsup.NotificationsHunShare
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import org.junit.Assume
@@ -53,6 +57,8 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
@JvmField @Rule var expect: Expect = Expect.create()
+ private val kosmos = testKosmos()
+
private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
private val avalancheController = mock<AvalancheController>()
@@ -131,13 +137,14 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
hostView.addView(notificationRow)
if (NotificationsHunSharedAnimationValues.isEnabled) {
- headsUpAnimator = HeadsUpAnimator(context)
+ headsUpAnimator = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
}
- stackScrollAlgorithm = StackScrollAlgorithm(
- context,
- hostView,
- if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
- )
+ stackScrollAlgorithm =
+ StackScrollAlgorithm(
+ context,
+ hostView,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
private fun isTv(): Boolean {
@@ -450,6 +457,46 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_noStatusBarChip_hunTranslatedToTopOfScreen() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ headsUpAnimator?.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+
+ val statusBarHeight = 432
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ whenever(notificationRow.hasStatusBarChipDuringHeadsUpAnimation()).thenReturn(false)
+
+ resetViewStates_hunYTranslationIs(
+ expected = -topMargin - stackScrollAlgorithm.mHeadsUpAppearStartAboveScreen
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun resetViewStates_hunAnimatingAway_withStatusBarChip_hunTranslatedToBottomOfStatusBar() {
+ val topMargin = 100f
+ ambientState.maxHeadsUpTranslation = 2000f
+ ambientState.stackTopMargin = topMargin.toInt()
+ headsUpAnimator?.stackTopMargin = topMargin.toInt()
+ whenever(notificationRow.intrinsicHeight).thenReturn(100)
+
+ val statusBarHeight = 432
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
+ whenever(notificationRow.hasStatusBarChipDuringHeadsUpAnimation()).thenReturn(true)
+
+ resetViewStates_hunYTranslationIs(expected = statusBarHeight - topMargin)
+ }
+
+ @Test
fun resetViewStates_hunAnimatingAway_bottomNotClipped() {
whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index cb4642cc21be..f6c031f54818 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -26,12 +26,15 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator
import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR
+import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -46,7 +49,6 @@ import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.description
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
-import org.mockito.kotlin.doNothing
private const val VIEW_HEIGHT = 100
private const val FULL_SHADE_APPEAR_TRANSLATION = 300
@@ -60,6 +62,8 @@ class StackStateAnimatorTest : SysuiTestCase() {
@get:Rule val setFlagsRule = SetFlagsRule()
@get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private val kosmos = testKosmos()
+
private lateinit var stackStateAnimator: StackStateAnimator
private lateinit var headsUpAnimator: HeadsUpAnimator
private val stackScroller: NotificationStackScrollLayout = mock()
@@ -80,13 +84,14 @@ class StackStateAnimatorTest : SysuiTestCase() {
whenever(view.viewState).thenReturn(viewState)
if (NotificationsHunSharedAnimationValues.isEnabled) {
- headsUpAnimator = HeadsUpAnimator(context)
+ headsUpAnimator = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
}
- stackStateAnimator = StackStateAnimator(
- mContext,
- stackScroller,
- if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
- )
+ stackStateAnimator =
+ StackStateAnimator(
+ mContext,
+ stackScroller,
+ if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
+ )
}
@Test
@@ -134,6 +139,62 @@ class StackStateAnimatorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_andHasStatusBarChipFalse() {
+ val statusBarHeight = 156
+ val topMargin = 50f
+ val expectedStartY = -topMargin - HEADS_UP_ABOVE_SCREEN
+
+ headsUpAnimator.stackTopMargin = topMargin.toInt()
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator.updateResources(context)
+
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ event.headsUpHasStatusBarChip = false
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setFinalActualHeight(VIEW_HEIGHT)
+ verify(view, description("should animate from the top")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* isHeadsUpCycling= */ false,
+ /* onEndRunnable= */ null,
+ )
+ }
+
+ @Test
+ @EnableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun startAnimationForEvents_headsUpFromTop_andHasStatusBarChipTrue() {
+ val statusBarHeight = 156
+ val topMargin = 50f
+ val expectedStartY = statusBarHeight - topMargin
+
+ headsUpAnimator!!.stackTopMargin = topMargin.toInt()
+ kosmos.fakeSystemBarUtilsProxy.fakeStatusBarHeight = statusBarHeight
+ headsUpAnimator!!.updateResources(context)
+
+ val event = AnimationEvent(view, AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR)
+ event.headsUpHasStatusBarChip = true
+
+ stackStateAnimator.startAnimationForEvents(arrayListOf(event), 0)
+
+ verify(view).setFinalActualHeight(VIEW_HEIGHT)
+ verify(view, description("should animate below status bar")).translationY = expectedStartY
+ verify(view)
+ .performAddAnimation(
+ /* delay= */ 0L,
+ /* duration= */ ANIMATION_DURATION_HEADS_UP_APPEAR.toLong(),
+ /* isHeadsUpAppear= */ true,
+ /* isHeadsUpCycling= */ false,
+ /* onEndRunnable= */ null,
+ )
+ }
+
+ @Test
@DisableFlags(NotificationsHunSharedAnimationValues.FLAG_NAME)
fun startAnimationForEvents_headsUpFromBottom_startsHeadsUpAppearAnim_flagOff() {
val screenHeight = 2000f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index 1a30caf0150b..eae2c25d77d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -350,6 +350,26 @@ constructor(
.stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModelLegacy())
}
+ private val activeChips =
+ if (StatusBarChipsModernization.isEnabled) {
+ chips.map { it.active }
+ } else {
+ chipsLegacy.map {
+ val list = mutableListOf<OngoingActivityChipModel.Active>()
+ if (it.primary is OngoingActivityChipModel.Active) {
+ list.add(it.primary)
+ }
+ if (it.secondary is OngoingActivityChipModel.Active) {
+ list.add(it.secondary)
+ }
+ list
+ }
+ }
+
+ /** A flow modeling just the keys for the currently visible chips. */
+ val visibleChipKeys: Flow<List<String>> =
+ activeChips.map { chips -> chips.filter { !it.isHidden }.map { it.key } }
+
/**
* Sort the given chip [bundle] in order of priority, and divide the chips between active,
* overflow, and inactive (see [MultipleOngoingActivityChipsModel] for a description of each).
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 f6e66237d438..fdb8cd871dd9 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
@@ -27,10 +27,10 @@ import com.android.systemui.statusbar.chips.notification.domain.interactor.Statu
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
-import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.PipelineEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
@@ -459,7 +459,12 @@ constructor(
} else {
if (posted.isHeadsUpEntry) {
// We don't want this to be interrupting anymore, let's remove it
- hunMutator.removeNotification(posted.key, false /*removeImmediately*/)
+ // If the notification is pinned by the user, the only way a user can un-pin
+ // it is by tapping the status bar notification chip. Since that's a clear
+ // user action, we should remove the HUN immediately instead of waiting for
+ // any sort of minimum timeout.
+ val shouldRemoveImmediately = posted.isPinnedByUser
+ hunMutator.removeNotification(posted.key, shouldRemoveImmediately)
} else {
// Don't let the bind finish
cancelHeadsUpBind(posted.entry)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index 8eca16622084..c401d8212c29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.headsup
import android.os.Handler
-import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
@@ -24,9 +23,9 @@ import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.util.Compile
import java.io.PrintWriter
import javax.inject.Inject
@@ -155,6 +154,7 @@ constructor(
} else if (entry in nextMap) {
outcome = "update next"
nextMap[entry]?.add(runnable)
+ checkNextPinnedByUser(entry)?.let { outcome = "$outcome & $it" }
} else if (headsUpEntryShowing == null) {
outcome = "show now"
showNow(entry, arrayListOf(runnable))
@@ -166,17 +166,22 @@ constructor(
outcome = "add next"
addToNext(entry, runnable)
- // Shorten headsUpEntryShowing display time
- val nextIndex = nextList.indexOf(entry)
- val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
- if (isOnlyNextEntry) {
- // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
- // and goes to the isShowing case above
- headsUpEntryShowing!!.updateEntry(
- /* updatePostTime= */ false,
- /* updateEarliestRemovalTime= */ false,
- /* reason= */ "shorten duration of previously-last HUN",
- )
+ val nextIsPinnedByUserResult = checkNextPinnedByUser(entry)
+ if (nextIsPinnedByUserResult != null) {
+ outcome = "$outcome & $nextIsPinnedByUserResult"
+ } else {
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ "shorten duration of previously-last HUN",
+ )
+ }
}
}
outcome += getStateStr()
@@ -190,6 +195,28 @@ constructor(
}
/**
+ * Checks if the given entry is requesting [PinnedStatus.PinnedByUser] status and makes the
+ * correct updates if needed.
+ *
+ * @return a string representing the outcome, or null if nothing changed.
+ */
+ private fun checkNextPinnedByUser(entry: HeadsUpEntry): String? {
+ if (
+ StatusBarNotifChips.isEnabled &&
+ entry.requestedPinnedStatus == PinnedStatus.PinnedByUser
+ ) {
+ val string = "next is PinnedByUser"
+ headsUpEntryShowing?.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ string,
+ )
+ return string
+ }
+ return null
+ }
+
+ /**
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
@@ -243,19 +270,22 @@ constructor(
outcome = "remove showing. ${getStateStr()}"
} else {
runnable.run()
- outcome = "run runnable for untracked HUN " +
+ outcome =
+ "run runnable for untracked HUN " +
"(was dropped or shown when AC was disabled). ${getStateStr()}"
}
headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
- * Returns duration based on
+ * Returns how much longer the given entry should show based on:
* 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
- * 2) The priority of the top HUN in the next batch Used by
- * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
+ * 2) The priority of the top HUN in the next batch
+ *
+ * Used by [HeadsUpManagerImpl.HeadsUpEntry]'s finishTimeCalculator to shorten display duration.
*/
- fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
+ fun getDuration(entry: HeadsUpEntry?, autoDismissMsValue: Int): RemainingDuration {
+ val autoDismissMs = RemainingDuration.UpdatedDuration(autoDismissMsValue)
if (!isEnabled()) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
@@ -273,7 +303,11 @@ constructor(
val thisKey = getKey(entry)
if (entryList.isEmpty()) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "No avalanche HUNs, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "No avalanche HUNs, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
// entryList.indexOf(entry) returns -1 even when the entry is in entryList
@@ -285,28 +319,64 @@ constructor(
}
if (thisEntryIndex == -1) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "Untracked entry, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "Untracked entry, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
val nextEntryIndex = thisEntryIndex + 1
if (nextEntryIndex >= entryList.size) {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "Last entry, use default", nextKey = "")
+ thisKey,
+ autoDismissMs,
+ "Last entry, use default",
+ nextKey = "",
+ )
return autoDismissMs
}
val nextEntry = entryList[nextEntryIndex]
val nextKey = getKey(nextEntry)
+
+ if (
+ StatusBarNotifChips.isEnabled &&
+ nextEntry.requestedPinnedStatus == PinnedStatus.PinnedByUser
+ ) {
+ return RemainingDuration.HideImmediately.also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "next is PinnedByUser",
+ nextKey,
+ )
+ }
+ }
if (nextEntry.compareNonTimeFields(entry) == -1) {
- headsUpManagerLogger.logAvalancheDuration(
- thisKey, 500, "LOWER priority than next: ", nextKey)
- return 500
+ return RemainingDuration.UpdatedDuration(500).also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "LOWER priority than next: ",
+ nextKey,
+ )
+ }
} else if (nextEntry.compareNonTimeFields(entry) == 0) {
- headsUpManagerLogger.logAvalancheDuration(
- thisKey, 1000, "SAME priority as next: ", nextKey)
- return 1000
+ return RemainingDuration.UpdatedDuration(1000).also {
+ headsUpManagerLogger.logAvalancheDuration(
+ thisKey,
+ duration = it,
+ "SAME priority as next: ",
+ nextKey,
+ )
+ }
} else {
headsUpManagerLogger.logAvalancheDuration(
- thisKey, autoDismissMs, "HIGHER priority than next: ", nextKey)
+ thisKey,
+ autoDismissMs,
+ "HIGHER priority than next: ",
+ nextKey,
+ )
return autoDismissMs
}
}
@@ -377,11 +447,11 @@ constructor(
}
private fun showNext() {
- headsUpManagerLogger.logAvalancheStage("show next", key = "")
+ headsUpManagerLogger.logAvalancheStage("show next", key = "")
headsUpEntryShowing = null
if (nextList.isEmpty()) {
- headsUpManagerLogger.logAvalancheStage("no more", key = "")
+ headsUpManagerLogger.logAvalancheStage("no more", key = "")
previousHunKey = ""
return
}
@@ -432,10 +502,12 @@ constructor(
private fun getStateStr(): String {
return "\n[AC state]" +
- "\nshow: ${getKey(headsUpEntryShowing)}" +
- "\nprevious: $previousHunKey" +
- "\n$nextStr" +
- "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " + baseEntryMapStr() + "\n"
+ "\nshow: ${getKey(headsUpEntryShowing)}" +
+ "\nprevious: $previousHunKey" +
+ "\n$nextStr" +
+ "\n[HeadsUpManagerImpl.mHeadsUpEntryMap] " +
+ baseEntryMapStr() +
+ "\n"
}
private val nextStr: String
@@ -447,7 +519,7 @@ constructor(
// This should never happen
val nextMapStr = nextMap.keys.joinToString("\n ") { getKey(it) }
return "next list (${nextList.size}):\n $nextListStr" +
- "\nnext map (${nextMap.size}):\n $nextMapStr"
+ "\nnext map (${nextMap.size}):\n $nextMapStr"
}
fun getKey(entry: HeadsUpEntry?): String {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt
new file mode 100644
index 000000000000..ab8489653f50
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimationEvent.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.headsup
+
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+
+/** Models the data needed for a heads-up notification animation. */
+data class HeadsUpAnimationEvent(
+ /** The row corresponding to the heads-up notification. */
+ val row: ExpandableNotificationRow,
+ /**
+ * True if this notification should do a appearance animation, false if this notification should
+ * do a disappear animation.
+ */
+ val isHeadsUpAppearance: Boolean,
+ /** True if the status bar is showing a chip corresponding to this notification. */
+ val hasStatusBarChip: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
index 177574f57c1c..b5d732117a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpAnimator.kt
@@ -17,13 +17,18 @@
package com.android.systemui.statusbar.notification.headsup
import android.content.Context
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.res.R
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
/**
* A class shared between [StackScrollAlgorithm] and [StackStateAnimator] to ensure all heads up
* animations use the same animation values.
+ *
+ * @param systemBarUtilsProxy optional utility class to provide the status bar height. Typically
+ * null in production code and non-null in tests.
*/
-class HeadsUpAnimator(context: Context) {
+class HeadsUpAnimator(context: Context, private val systemBarUtilsProxy: SystemBarUtilsProxy?) {
init {
NotificationsHunSharedAnimationValues.unsafeAssertInNewMode()
}
@@ -32,6 +37,7 @@ class HeadsUpAnimator(context: Context) {
var stackTopMargin: Int = 0
private var headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+ private var statusBarHeight = fetchStatusBarHeight(context)
/**
* Returns the Y translation for a heads-up notification animation.
@@ -40,7 +46,7 @@ class HeadsUpAnimator(context: Context) {
* animation. For a disappear animation, the returned Y translation should be the ending value
* of the animation.
*/
- fun getHeadsUpYTranslation(isHeadsUpFromBottom: Boolean): Int {
+ fun getHeadsUpYTranslation(isHeadsUpFromBottom: Boolean, hasStatusBarChip: Boolean): Int {
NotificationsHunSharedAnimationValues.unsafeAssertInNewMode()
if (isHeadsUpFromBottom) {
@@ -48,6 +54,12 @@ class HeadsUpAnimator(context: Context) {
return headsUpAppearHeightBottom + headsUpAppearStartAboveScreen
}
+ if (hasStatusBarChip) {
+ // If this notification is also represented by a chip in the status bar, we don't want
+ // any HUN transitions to obscure that chip.
+ return statusBarHeight - stackTopMargin
+ }
+
// start from or end at the top of the screen
return -stackTopMargin - headsUpAppearStartAboveScreen
}
@@ -55,9 +67,15 @@ class HeadsUpAnimator(context: Context) {
/** Should be invoked when resource values may have changed. */
fun updateResources(context: Context) {
headsUpAppearStartAboveScreen = context.fetchHeadsUpAppearStartAboveScreen()
+ statusBarHeight = fetchStatusBarHeight(context)
}
private fun Context.fetchHeadsUpAppearStartAboveScreen(): Int {
return this.resources.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen)
}
+
+ private fun fetchStatusBarHeight(context: Context): Int {
+ return systemBarUtilsProxy?.getStatusBarHeight()
+ ?: SystemBarUtils.getStatusBarHeight(context)
+ }
}
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 d16ad80ca8b9..a07223fdb000 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
@@ -46,7 +46,6 @@ 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.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
@@ -320,15 +319,17 @@ public class HeadsUpManagerImpl
mLogger.logShowNotificationRequest(entry, isPinnedByUser);
+ PinnedStatus requestedPinnedStatus =
+ isPinnedByUser
+ ? PinnedStatus.PinnedByUser
+ : PinnedStatus.PinnedBySystem;
+ headsUpEntry.setRequestedPinnedStatus(requestedPinnedStatus);
+
Runnable runnable = () -> {
mLogger.logShowNotification(entry, isPinnedByUser);
// Add new entry and begin managing it
mHeadsUpEntryMap.put(entry.getKey(), 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);
@@ -1289,10 +1290,17 @@ public class HeadsUpManagerImpl
@Nullable private Runnable mCancelRemoveRunnable;
private boolean mGutsShownPinned;
+ /** The *current* pinned status of this HUN. */
private final MutableStateFlow<PinnedStatus> mPinnedStatus =
StateFlowKt.MutableStateFlow(PinnedStatus.NotPinned);
/**
+ * The *requested* pinned status of this HUN. {@link AvalancheController} uses this value to
+ * know if the current HUN needs to be removed so that a pinned-by-user HUN can show.
+ */
+ private PinnedStatus mRequestedPinnedStatus = PinnedStatus.NotPinned;
+
+ /**
* If the time this entry has been on was extended
*/
private boolean extended;
@@ -1352,6 +1360,20 @@ public class HeadsUpManagerImpl
}
}
+ /** Sets what pinned status this HUN is requesting. */
+ void setRequestedPinnedStatus(PinnedStatus pinnedStatus) {
+ if (!StatusBarNotifChips.isEnabled() && pinnedStatus == PinnedStatus.PinnedByUser) {
+ Log.w(TAG, "PinnedByUser status not allowed if StatusBarNotifChips is disabled");
+ mRequestedPinnedStatus = PinnedStatus.NotPinned;
+ } else {
+ mRequestedPinnedStatus = pinnedStatus;
+ }
+ }
+
+ PinnedStatus getRequestedPinnedStatus() {
+ return mRequestedPinnedStatus;
+ }
+
@VisibleForTesting
void setRowPinnedStatus(PinnedStatus pinnedStatus) {
if (mEntry != null) mEntry.setRowPinnedStatus(pinnedStatus);
@@ -1410,11 +1432,29 @@ public class HeadsUpManagerImpl
}
FinishTimeUpdater finishTimeCalculator = () -> {
- final long finishTime = calculateFinishTime();
+ RemainingDuration remainingDuration =
+ mAvalancheController.getDuration(this, mAutoDismissTime);
+
+ if (remainingDuration instanceof RemainingDuration.HideImmediately) {
+ StatusBarNotifChips.assertInNewMode();
+ return 0;
+ }
+
+ int remainingTimeoutMs;
+ if (isStickyForSomeTime()) {
+ remainingTimeoutMs = mStickyForSomeTimeAutoDismissTime;
+ } else {
+ remainingTimeoutMs =
+ ((RemainingDuration.UpdatedDuration) remainingDuration).getDuration();
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(remainingTimeoutMs);
+ final long timeoutTimestamp =
+ mPostTime + duration + (extended ? mExtensionTime : 0);
+
final long now = mSystemClock.elapsedRealtime();
return NotificationThrottleHun.isEnabled()
- ? Math.max(finishTime, mEarliestRemovalTime) - now
- : Math.max(finishTime - now, mMinimumDisplayTimeDefault);
+ ? Math.max(timeoutTimestamp, mEarliestRemovalTime) - now
+ : Math.max(timeoutTimestamp - now, mMinimumDisplayTimeDefault);
};
scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
@@ -1696,21 +1736,6 @@ public class HeadsUpManagerImpl
}
/**
- * @return When the notification should auto-dismiss itself, based on
- * {@link SystemClock#elapsedRealtime()}
- */
- private long calculateFinishTime() {
- int requestedTimeOutMs;
- if (isStickyForSomeTime()) {
- requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
- } else {
- requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
- }
- final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
- return mPostTime + duration + (extended ? mExtensionTime : 0);
- }
-
- /**
* Get user-preferred or default timeout duration. The larger one will be returned.
* @return milliseconds before auto-dismiss
*/
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 388d357b3b15..00b05cbd7bec 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
@@ -106,13 +106,23 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
)
}
- fun logAvalancheDuration(thisKey: String, duration: Int, reason: String, nextKey: String) {
+ fun logAvalancheDuration(
+ thisKey: String,
+ duration: RemainingDuration,
+ reason: String,
+ nextKey: String,
+ ) {
+ val durationMs =
+ when (duration) {
+ is RemainingDuration.UpdatedDuration -> duration.duration
+ is RemainingDuration.HideImmediately -> 0
+ }
buffer.log(
TAG,
INFO,
{
str1 = thisKey
- int1 = duration
+ int1 = durationMs
str2 = reason
str3 = nextKey
},
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt
new file mode 100644
index 000000000000..fd7f4e87e8e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/RemainingDuration.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.headsup
+
+/** Models how much longer a HUN should be displayed. */
+sealed interface RemainingDuration {
+ /** This HUN should be hidden immediately, regardless of any minimum time enforcements. */
+ data object HideImmediately : RemainingDuration
+
+ /** This HUN should hide after [duration] milliseconds have occurred. */
+ data class UpdatedDuration(val duration: Int) : RemainingDuration
+}
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 66929a579eca..987068df3ee9 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
@@ -99,6 +99,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -179,6 +180,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mIsFaded;
private boolean mIsPromotedOngoing = false;
+ private boolean mHasStatusBarChipDuringHeadsUpAnimation = false;
@Nullable
public ImageModelIndex mImageModelIndex = null;
@@ -2943,6 +2945,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setExpandable(!mIsPromotedOngoing);
}
+ /**
+ * Sets whether the status bar is showing a chip corresponding to this notification.
+ *
+ * Only set when this notification's heads-up status changes since that's the only time it's
+ * relevant.
+ */
+ public void setHasStatusBarChipDuringHeadsUpAnimation(boolean hasStatusBarChip) {
+ if (StatusBarNotifChips.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+ mHasStatusBarChipDuringHeadsUpAnimation = hasStatusBarChip;
+ }
+
+ /**
+ * Returns true if the status bar is showing a chip corresponding to this notification during a
+ * heads-up appear or disappear animation.
+ *
+ * Note that this value is only set when this notification's heads-up status changes since
+ * that's the only time it's relevant.
+ */
+ public boolean hasStatusBarChipDuringHeadsUpAnimation() {
+ return StatusBarNotifChips.isEnabled() && mHasStatusBarChipDuringHeadsUpAnimation;
+ }
+
@Override
public void setClipToActualHeight(boolean clipToActualHeight) {
super.setClipToActualHeight(clipToActualHeight || isUserLocked());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f70d57653ac0..531baa8dc302 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -104,6 +104,7 @@ import com.android.systemui.shade.QSHeaderBoundsProvider;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.headsup.shared.StatusBarNoHunBehavior;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
@@ -117,6 +118,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimationEvent;
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
@@ -139,6 +141,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScr
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
+import com.android.systemui.statusbar.ui.SystemBarUtilsProxy;
import com.android.systemui.util.Assert;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.DumpUtilsKt;
@@ -351,10 +354,11 @@ public class NotificationStackScrollLayout
private final int[] mTempInt2 = new int[2];
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
- private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
- = new HashSet<>();
+ private final Map<ExpandableNotificationRow, HeadsUpAnimationEvent> mHeadsUpChangeAnimations
+ = new HashMap<>();
private boolean mForceNoOverlappingRendering;
- private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
+ private final ArrayList<ExpandableNotificationRow> mTmpHeadsUpChangeAnimations =
+ new ArrayList<>();
private boolean mAnimationRunning;
private final ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@@ -673,7 +677,7 @@ public class NotificationStackScrollLayout
mExpandHelper.setScrollAdapter(mScrollAdapter);
if (NotificationsHunSharedAnimationValues.isEnabled()) {
- mHeadsUpAnimator = new HeadsUpAnimator(context);
+ mHeadsUpAnimator = new HeadsUpAnimator(context, /* systemBarUtilsProxy= */ null);
} else {
mHeadsUpAnimator = null;
}
@@ -3074,20 +3078,20 @@ public class NotificationStackScrollLayout
*/
private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
boolean hasAddEvent = false;
- for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
- ExpandableNotificationRow row = eventPair.first;
- boolean isHeadsUp = eventPair.second;
+ for (HeadsUpAnimationEvent event : mHeadsUpChangeAnimations.values()) {
+ ExpandableNotificationRow row = event.getRow();
+ boolean isHeadsUp = event.isHeadsUpAppearance();
if (child == row) {
- mTmpList.add(eventPair);
+ mTmpHeadsUpChangeAnimations.add(event.getRow());
hasAddEvent |= isHeadsUp;
}
}
if (hasAddEvent) {
// This child was just added lets remove all events.
- mHeadsUpChangeAnimations.removeAll(mTmpList);
+ mTmpHeadsUpChangeAnimations.forEach((row) -> mHeadsUpChangeAnimations.remove(row));
((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
}
- mTmpList.clear();
+ mTmpHeadsUpChangeAnimations.clear();
return hasAddEvent && mAddedHeadsUpChildren.contains(child);
}
@@ -3373,9 +3377,9 @@ public class NotificationStackScrollLayout
}
private void generateHeadsUpAnimationEvents() {
- for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
- ExpandableNotificationRow row = eventPair.first;
- boolean isHeadsUp = eventPair.second;
+ for (HeadsUpAnimationEvent headsUpEvent : mHeadsUpChangeAnimations.values()) {
+ ExpandableNotificationRow row = headsUpEvent.getRow();
+ boolean isHeadsUp = headsUpEvent.isHeadsUpAppearance();
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
// do the animations at all.
@@ -3433,6 +3437,10 @@ public class NotificationStackScrollLayout
}
AnimationEvent event = new AnimationEvent(row, type);
event.headsUpFromBottom = onBottom;
+
+ boolean hasStatusBarChip =
+ StatusBarNotifChips.isEnabled() && headsUpEvent.getHasStatusBarChip();
+ event.headsUpHasStatusBarChip = hasStatusBarChip;
// TODO(b/283084712) remove this and update the HUN filters at creation
event.filter.animateHeight = false;
mAnimationEvents.add(event);
@@ -5068,10 +5076,11 @@ public class NotificationStackScrollLayout
mAnimationFinishedRunnables.add(runnable);
}
- public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(
+ NotificationEntry entry, boolean isHeadsUp, boolean hasStatusBarChip) {
SceneContainerFlag.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
- generateHeadsUpAnimation(row, isHeadsUp);
+ generateHeadsUpAnimation(row, isHeadsUp, hasStatusBarChip);
}
/**
@@ -5080,8 +5089,11 @@ public class NotificationStackScrollLayout
*
* @param row to animate
* @param isHeadsUp true for appear, false for disappear animations
+ * @param hasStatusBarChip true if the status bar is currently displaying a chip for the given
+ * notification
*/
- public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(
+ ExpandableNotificationRow row, boolean isHeadsUp, boolean hasStatusBarChip) {
boolean addAnimation =
mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
if (NotificationThrottleHun.isEnabled()) {
@@ -5096,19 +5108,26 @@ public class NotificationStackScrollLayout
: " isSeenInShade=" + row.getEntry().isSeenInShade()
+ " row=" + row.getKey())
+ " mIsExpanded=" + mIsExpanded
- + " isHeadsUp=" + isHeadsUp);
+ + " isHeadsUp=" + isHeadsUp
+ + " hasStatusBarChip=" + hasStatusBarChip);
}
+
if (addAnimation) {
// If we're hiding a HUN we just started showing THIS FRAME, then remove that event,
// and do not add the disappear event either.
- if (!isHeadsUp && mHeadsUpChangeAnimations.remove(new Pair<>(row, true))) {
+ boolean showingHunThisFrame =
+ mHeadsUpChangeAnimations.containsKey(row)
+ && mHeadsUpChangeAnimations.get(row).isHeadsUpAppearance();
+ if (!isHeadsUp && showingHunThisFrame) {
+ mHeadsUpChangeAnimations.remove(row);
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
}
logHunAnimationSkipped(row, "previous hun appear animation cancelled");
return;
}
- mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
+ mHeadsUpChangeAnimations.put(
+ row, new HeadsUpAnimationEvent(row, isHeadsUp, hasStatusBarChip));
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
@@ -5116,6 +5135,9 @@ public class NotificationStackScrollLayout
setHeadsUpAnimatingAway(true);
}
}
+ if (StatusBarNotifChips.isEnabled()) {
+ row.setHasStatusBarChipDuringHeadsUpAnimation(hasStatusBarChip);
+ }
requestChildrenUpdate();
}
}
@@ -6702,6 +6724,7 @@ public class NotificationStackScrollLayout
final long length;
View viewAfterChangingView;
boolean headsUpFromBottom;
+ boolean headsUpHasStatusBarChip;
AnimationEvent(ExpandableView view, int type) {
this(view, type, LENGTHS[type]);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 124e6f590bfe..bb3abc1fba38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -92,6 +92,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
@@ -325,6 +326,14 @@ public class NotificationStackScrollLayoutController implements Dumpable {
*/
private float mMaxAlphaForGlanceableHub = 1.0f;
+ /**
+ * A list of keys for the visible status bar chips.
+ *
+ * Note that this list can contain both notification keys, as well as keys for other types of
+ * chips like screen recording.
+ */
+ private List<String> mVisibleStatusBarChipKeys = new ArrayList<>();
+
private final NotificationListViewBinder mViewBinder;
private void updateResources() {
@@ -1580,8 +1589,16 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mView.getFirstChildNotGone();
}
+ /** Sets the list of keys that have currently visible status bar chips. */
+ public void updateStatusBarChipKeys(List<String> visibleStatusBarChipKeys) {
+ mVisibleStatusBarChipKeys = visibleStatusBarChipKeys;
+ }
+
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
- mView.generateHeadsUpAnimation(entry, isHeadsUp);
+ boolean hasStatusBarChip =
+ StatusBarNotifChips.isEnabled()
+ && mVisibleStatusBarChipKeys.contains(entry.getKey());
+ mView.generateHeadsUpAnimation(entry, isHeadsUp, hasStatusBarChip);
}
public void setMaxTopPadding(int padding) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 4effb76c6570..d23a4c6307fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -1059,7 +1059,9 @@ public class StackScrollAlgorithm {
shouldHunAppearFromBottom(ambientState, childState);
if (NotificationsHunSharedAnimationValues.isEnabled()) {
int yTranslation =
- mHeadsUpAnimator.getHeadsUpYTranslation(shouldHunAppearFromBottom);
+ mHeadsUpAnimator.getHeadsUpYTranslation(
+ shouldHunAppearFromBottom,
+ row.hasStatusBarChipDuringHeadsUpAnimation());
childState.setYTranslation(yTranslation);
} else {
if (shouldHunAppearFromBottom) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index efbcaed73b62..5414318b29bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -38,6 +38,7 @@ import com.android.internal.dynamicanimation.animation.DynamicAnimation;
import com.android.systemui.res.R;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.PhysicsPropertyAnimator;
import com.android.systemui.statusbar.notification.headsup.HeadsUpAnimator;
import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
@@ -560,7 +561,9 @@ public class StackStateAnimator {
mHeadsUpAppearChildren.add(changingView);
mTmpState.copyFrom(changingView.getViewState());
- mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
+ mTmpState.setYTranslation(
+ getHeadsUpYTranslationStart(
+ event.headsUpFromBottom, event.headsUpHasStatusBarChip));
// set the height and the initial position
mTmpState.applyToView(changingView);
mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
@@ -672,7 +675,9 @@ public class StackStateAnimator {
// StackScrollAlgorithm cannot find this view because it has been removed
// from the NSSL. To correctly translate the view to the top or bottom of
// the screen (where it animated from), we need to update its translation.
- mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
+ mTmpState.setYTranslation(
+ getHeadsUpYTranslationStart(
+ event.headsUpFromBottom, event.headsUpHasStatusBarChip));
endRunnable = changingView::removeFromTransientContainer;
}
@@ -743,9 +748,9 @@ public class StackStateAnimator {
return needsCustomAnimation;
}
- private float getHeadsUpYTranslationStart(boolean headsUpFromBottom) {
+ private float getHeadsUpYTranslationStart(boolean headsUpFromBottom, boolean hasStatusBarChip) {
if (NotificationsHunSharedAnimationValues.isEnabled()) {
- return mHeadsUpAnimator.getHeadsUpYTranslation(headsUpFromBottom);
+ return mHeadsUpAnimator.getHeadsUpYTranslation(headsUpFromBottom, hasStatusBarChip);
}
if (headsUpFromBottom) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 1c079c198cd4..facb8941f1fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -33,6 +33,7 @@ import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.dagger.SilentHeader
@@ -133,6 +134,14 @@ constructor(
}
}
+ if (StatusBarNotifChips.isEnabled) {
+ launch {
+ viewModel.visibleStatusBarChipKeys.collect { keys ->
+ viewController.updateStatusBarChipKeys(keys)
+ }
+ }
+ }
+
launch { bindLogger(view) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5ed1889de01e..c1eb70ed7d25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -20,6 +20,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
@@ -56,6 +57,7 @@ class NotificationListViewModel
constructor(
val shelf: NotificationShelfViewModel,
val hideListViewModel: HideListViewModel,
+ val ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
val footerViewModelFactory: FooterViewModel.Factory,
val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory,
val logger: Optional<NotificationLoggerViewModel>,
@@ -364,6 +366,14 @@ constructor(
}
}
+ /**
+ * A list of keys for the visible status bar chips.
+ *
+ * Note that this list can contain both notification keys, as well as keys for other types of
+ * chips like screen recording.
+ */
+ val visibleStatusBarChipKeys = ongoingActivityChipsViewModel.visibleChipKeys
+
// TODO(b/325936094) use it for the text displayed in the StatusBar
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowViewModel =
HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index feb74098f071..bc533148f514 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
@@ -27,20 +29,29 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import com.android.app.tracing.coroutines.launchTraced as launch
class HeadsUpNotificationViewBinder
@Inject
-constructor(private val viewModel: NotificationListViewModel) {
+constructor(
+ private val viewModel: NotificationListViewModel,
+ private val ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
+) {
suspend fun bindHeadsUpNotifications(parentView: NotificationStackScrollLayout): Unit =
coroutineScope {
launch {
var previousKeys = emptySet<HeadsUpRowKey>()
- combine(viewModel.pinnedHeadsUpRowKeys, viewModel.activeHeadsUpRowKeys, ::Pair)
+ combine(
+ viewModel.pinnedHeadsUpRowKeys,
+ viewModel.activeHeadsUpRowKeys,
+ ongoingActivityChipsViewModel.visibleChipKeys,
+ ::Triple,
+ )
.sample(viewModel.headsUpAnimationsEnabled, ::Pair)
.collect { (newKeys, animationsEnabled) ->
val pinned = newKeys.first
val all = newKeys.second
+ val statusBarChips: List<String> = newKeys.third
+
val added = all.union(pinned) - previousKeys
val removed = previousKeys - pinned
previousKeys = pinned
@@ -48,15 +59,23 @@ constructor(private val viewModel: NotificationListViewModel) {
if (animationsEnabled) {
added.forEach { key ->
+ val row = obtainView(key)
+ val hasStatusBarChip = statusBarChips.contains(row.entry.key)
parentView.generateHeadsUpAnimation(
- obtainView(key),
+ row,
/* isHeadsUp = */ true,
+ hasStatusBarChip,
)
}
removed.forEach { key ->
val row = obtainView(key)
+ val hasStatusBarChip = statusBarChips.contains(row.entry.key)
if (!parentView.isBeingDragged()) {
- parentView.generateHeadsUpAnimation(row, /* isHeadsUp= */ false)
+ parentView.generateHeadsUpAnimation(
+ row,
+ /* isHeadsUp= */ false,
+ hasStatusBarChip,
+ )
}
row.markHeadsUpSeen()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index acfa94a0218b..cd2ea7d25699 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -67,6 +67,7 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.SourceType;
@@ -1128,6 +1129,30 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
assertThat(row.mustStayOnScreen()).isFalse();
}
+ @Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void hasStatusBarChipDuringHeadsUpAnimation_flagOff_false() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(true);
+
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void hasStatusBarChipDuringHeadsUpAnimation_flagOn_returnsValue() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(true);
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isTrue();
+
+ row.setHasStatusBarChipDuringHeadsUpAnimation(false);
+ assertThat(row.hasStatusBarChipDuringHeadsUpAnimation()).isFalse();
+ }
+
private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
Drawable rightIconDrawable) {
ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 2a58890f8767..8fb2a245921a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -82,6 +82,7 @@ import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -92,6 +93,7 @@ import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShade
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
@@ -1260,7 +1262,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN headsUpAnimatingAway is true
verify(headsUpAnimatingAwayListener).accept(true);
@@ -1269,6 +1272,51 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
@EnableSceneContainer
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOff_statusBarChipNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true);
+
+ verify(row, never()).setHasStatusBarChipDuringHeadsUpAnimation(anyBoolean());
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToFalse() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
+
+ verify(row).setHasStatusBarChipDuringHeadsUpAnimation(false);
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToTrue() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true);
+
+ verify(row).setHasStatusBarChipDuringHeadsUpAnimation(true);
+ }
+
+ @Test
+ @EnableSceneContainer
public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
// GIVEN NSSL would be ready for HUN animations, BUT it is expanded
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
@@ -1279,7 +1327,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN nothing happens
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1294,10 +1343,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// BUT there is a pending appear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
// THEN nothing happens
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1313,7 +1364,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// WHEN we generate a disappear event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// THEN headsUpAnimatingWay is not set
verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
@@ -1335,7 +1387,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
when(row.getEntry()).thenReturn(entry);
// WHEN we generate an add event
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false);
// THEN nothing happens
assertThat(mStackScroller.isAddOrRemoveAnimationPending()).isFalse();
@@ -1350,7 +1403,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
// AND there is a HUN animating away
- mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ mStackScroller.generateHeadsUpAnimation(
+ row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false);
assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway);
// WHEN the child animations are finished
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index fbc2a21b0888..219ecbfe963b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory
@@ -35,6 +36,7 @@ val Kosmos.notificationListViewModel by Fixture {
NotificationListViewModel(
notificationShelfViewModel,
hideListViewModel,
+ ongoingActivityChipsViewModel,
footerViewModelFactory,
emptyShadeViewModelFactory,
Optional.of(notificationListLoggerViewModel),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
index 6a995c08ecae..2c5aed40b222 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinderKosmos.kt
@@ -17,7 +17,13 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
val Kosmos.headsUpNotificationViewBinder by
- Kosmos.Fixture { HeadsUpNotificationViewBinder(viewModel = notificationListViewModel) }
+ Kosmos.Fixture {
+ HeadsUpNotificationViewBinder(
+ viewModel = notificationListViewModel,
+ ongoingActivityChipsViewModel = ongoingActivityChipsViewModel,
+ )
+ }