Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6383eb9 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge changes I62e71c42,I0c0a1514,Ib170238f into main

* changes:
  [SB][Notif] Make sure re-tapping notif chip hides HUN immediately.
  [SB][Notif] When HUN is PinnedByUser, always immediately show it.
  [SB][Notif] Stop HUN animation at status bar if HUN has notif chip.
parents 62a6069f 02e6e7e3
Loading
Loading
Loading
Loading
+79 −0
Original line number Diff line number Diff line
@@ -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
@@ -171,6 +172,18 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
            assertThat(unused).isEqualTo(MultipleOngoingActivityChipsModelLegacy())
        }

    @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 {
@@ -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() =
+4 −3
Original line number Diff line number Diff line
@@ -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
+78 −26
Original line number Diff line number Diff line
@@ -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)
        }
    }
}
+48 −9
Original line number Diff line number Diff line
@@ -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)
    }

+53 −6
Original line number Diff line number Diff line
@@ -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,9 +137,10 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
        hostView.addView(notificationRow)

        if (NotificationsHunSharedAnimationValues.isEnabled) {
             headsUpAnimator = HeadsUpAnimator(context)
            headsUpAnimator = HeadsUpAnimator(context, kosmos.fakeSystemBarUtilsProxy)
        }
        stackScrollAlgorithm = StackScrollAlgorithm(
        stackScrollAlgorithm =
            StackScrollAlgorithm(
                context,
                hostView,
                if (::headsUpAnimator.isInitialized) headsUpAnimator else null,
@@ -449,6 +456,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)
Loading