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

Commit 9e0349d2 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Restructure the NotificationWakeUpCoordinator's dozeAmount recalculation.

Bug: 269085199
Test: atest NotificationWakeUpCoordinatorTest NotificationWakeUpCoordinatorLoggerTest
Change-Id: Ib7598a3faf0fd8738b24eda7620e5460a75c8c13
parent 15cddc9d
Loading
Loading
Loading
Loading
+84 −50
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification

import android.animation.ObjectAnimator
import android.util.FloatProperty
import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.animation.Interpolators
@@ -66,10 +67,16 @@ class NotificationWakeUpCoordinator @Inject constructor(
    private lateinit var mStackScrollerController: NotificationStackScrollLayoutController
    private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE

    private var mLinearDozeAmount: Float = 0.0f
    private var mDozeAmount: Float = 0.0f
    private var mDozeAmountSource: String = "init"
    private var mNotifsHiddenByDozeAmountOverride: Boolean = false
    private var inputLinearDozeAmount: Float = 0.0f
    private var inputEasedDozeAmount: Float = 0.0f
    /** Valid values: {1f, 0f, null} null => use input */
    private var hardDozeAmountOverride: Float? = null
    private var hardDozeAmountOverrideSource: String = "n/a"
    private var outputLinearDozeAmount: Float = 0.0f
    private var outputEasedDozeAmount: Float = 0.0f
    @VisibleForTesting
    val dozeAmountInterpolator: Interpolator = Interpolators.FAST_OUT_SLOW_IN

    private var mNotificationVisibleAmount = 0.0f
    private var mNotificationsVisible = false
    private var mNotificationsVisibleForExpansion = false
@@ -104,7 +111,7 @@ class NotificationWakeUpCoordinator @Inject constructor(

    var willWakeUp = false
        set(value) {
            if (!value || mDozeAmount != 0.0f) {
            if (!value || outputLinearDozeAmount != 0.0f) {
                field = value
            }
        }
@@ -156,7 +163,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
        override fun onBypassStateChanged(isEnabled: Boolean) {
            // When the bypass state changes, we have to check whether we should re-show the
            // notifications by clearing the doze amount override which hides them.
            maybeClearDozeAmountOverrideHidingNotifs()
            maybeClearHardDozeAmountOverrideHidingNotifs()
        }
    }

@@ -229,7 +236,8 @@ class NotificationWakeUpCoordinator @Inject constructor(
        var visible = mNotificationsVisibleForExpansion || mHeadsUpManager.hasNotifications()
        visible = visible && canShowPulsingHuns

        if (!visible && mNotificationsVisible && (wakingUp || willWakeUp) && mDozeAmount != 0.0f) {
        if (!visible && mNotificationsVisible && (wakingUp || willWakeUp) &&
                outputLinearDozeAmount != 0.0f) {
            // let's not make notifications invisible while waking up, otherwise the animation
            // is strange
            return
@@ -257,7 +265,9 @@ class NotificationWakeUpCoordinator @Inject constructor(

    override fun onDozeAmountChanged(linear: Float, eased: Float) {
        logger.logOnDozeAmountChanged(linear = linear, eased = eased)
        if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
        inputLinearDozeAmount = linear
        inputEasedDozeAmount = eased
        if (overrideDozeAmountIfAnimatingScreenOff()) {
            return
        }

@@ -265,29 +275,52 @@ class NotificationWakeUpCoordinator @Inject constructor(
            return
        }

        if (linear != 1.0f && linear != 0.0f &&
            (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) {
            // Let's notify the scroller that an animation started
            notifyAnimationStart(mLinearDozeAmount == 1.0f)
        if (clearHardDozeAmountOverride()) {
            return
        }
        setDozeAmount(linear, eased, source = "StatusBar")

        updateDozeAmount()
    }

    fun setDozeAmount(
        linear: Float,
        eased: Float,
        source: String,
        hidesNotifsByOverride: Boolean = false
    ) {
        val changed = linear != mLinearDozeAmount
        logger.logSetDozeAmount(linear, eased, source, statusBarStateController.state, changed)
        mLinearDozeAmount = linear
        mDozeAmount = eased
        mDozeAmountSource = source
        mNotifsHiddenByDozeAmountOverride = hidesNotifsByOverride
        mStackScrollerController.setDozeAmount(mDozeAmount)
    private fun setHardDozeAmountOverride(dozing: Boolean, source: String) {
        logger.logSetDozeAmountOverride(dozing = dozing, source = source)
        hardDozeAmountOverride = if (dozing) 1f else 0f
        hardDozeAmountOverrideSource = source
        updateDozeAmount()
    }

    private fun clearHardDozeAmountOverride(): Boolean {
        if (hardDozeAmountOverride == null) return false
        hardDozeAmountOverride = null
        hardDozeAmountOverrideSource = "Cleared: $hardDozeAmountOverrideSource"
        updateDozeAmount()
        return true
    }

    private fun updateDozeAmount() {
        // Calculate new doze amount (linear)
        val newOutputLinearDozeAmount = hardDozeAmountOverride ?: inputLinearDozeAmount
        val changed = outputLinearDozeAmount != newOutputLinearDozeAmount

        // notify when the animation is starting
        if (newOutputLinearDozeAmount != 1.0f && newOutputLinearDozeAmount != 0.0f &&
                (outputLinearDozeAmount == 0.0f || outputLinearDozeAmount == 1.0f)) {
            // Let's notify the scroller that an animation started
            notifyAnimationStart(outputLinearDozeAmount == 1.0f)
        }

        // Update output doze amount
        outputLinearDozeAmount = newOutputLinearDozeAmount
        outputEasedDozeAmount = dozeAmountInterpolator.getInterpolation(outputLinearDozeAmount)
        logger.logUpdateDozeAmount(
                inputLinear = inputLinearDozeAmount,
                hardOverride = hardDozeAmountOverride,
                outputLinear = outputLinearDozeAmount,
                state = statusBarStateController.state,
                changed = changed)
        mStackScrollerController.setDozeAmount(outputEasedDozeAmount)
        updateHideAmount()
        if (changed && linear == 0.0f) {
        if (changed && outputLinearDozeAmount == 0.0f) {
            setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
            setNotificationsVisibleForExpansion(visible = false, animate = false,
                    increaseSpeed = false)
@@ -317,12 +350,13 @@ class NotificationWakeUpCoordinator @Inject constructor(
            // undefined state, so it's an indication that we should do state cleanup. We override
            // the doze amount to 0f (not dozing) so that the notifications are no longer hidden.
            // See: UnlockedScreenOffAnimationController.onFinishedWakingUp()
            setDozeAmount(0f, 0f, source = "Override: Shade->Shade (lock cancelled by unlock)")
            setHardDozeAmountOverride(
                    dozing = false, source = "Override: Shade->Shade (lock cancelled by unlock)")
            this.state = newState
            return
        }

        if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
        if (overrideDozeAmountIfAnimatingScreenOff()) {
            this.state = newState
            return
        }
@@ -332,7 +366,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
            return
        }

        maybeClearDozeAmountOverrideHidingNotifs()
        maybeClearHardDozeAmountOverrideHidingNotifs()

        this.state = newState
    }
@@ -360,10 +394,9 @@ class NotificationWakeUpCoordinator @Inject constructor(
    private fun overrideDozeAmountIfBypass(): Boolean {
        if (bypassController.bypassEnabled) {
            if (statusBarStateController.state == StatusBarState.KEYGUARD) {
                setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)",
                        hidesNotifsByOverride = true)
                setHardDozeAmountOverride(dozing = true, source = "Override: bypass (keyguard)")
            } else {
                setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
                setHardDozeAmountOverride(dozing = false, source = "Override: bypass (shade)")
            }
            return true
        }
@@ -377,8 +410,8 @@ class NotificationWakeUpCoordinator @Inject constructor(
     * This fixes bugs where the bypass state changing could result in stale overrides, hiding
     * notifications either on the inside screen or even after unlock.
     */
    private fun maybeClearDozeAmountOverrideHidingNotifs() {
        if (mNotifsHiddenByDozeAmountOverride) {
    private fun maybeClearHardDozeAmountOverrideHidingNotifs() {
        if (hardDozeAmountOverride == 1f) {
            val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
            val dozing = statusBarStateController.isDozing
            val bypass = bypassController.bypassEnabled
@@ -390,13 +423,13 @@ class NotificationWakeUpCoordinator @Inject constructor(
            // !dozing or !onKeyguard because those conditions should indicate that we intend
            // notifications to be visible, and thus it is safe to unhide them.
            val willRemove = (!onKeyguard || !dozing) && !bypass && !animating
            logger.logMaybeClearDozeAmountOverrideHidingNotifs(
            logger.logMaybeClearHardDozeAmountOverrideHidingNotifs(
                    willRemove = willRemove,
                    onKeyguard = onKeyguard, dozing = dozing,
                    bypass = bypass, animating = animating,
            )
            if (willRemove) {
                setDozeAmount(0f, 0f, source = "Removed: $mDozeAmountSource")
                clearHardDozeAmountOverride()
            }
        }
    }
@@ -409,10 +442,9 @@ class NotificationWakeUpCoordinator @Inject constructor(
     * @return Whether the doze amount was overridden because we are playing the screen off
     * animation. If true, the original doze amount should be ignored.
     */
    private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
    private fun overrideDozeAmountIfAnimatingScreenOff(): Boolean {
        if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
            setDozeAmount(1f, 1f, source = "Override: animating screen off",
                    hidesNotifsByOverride = true)
            setHardDozeAmountOverride(dozing = true, source = "Override: animating screen off")
            return true
        }

@@ -428,12 +460,12 @@ class NotificationWakeUpCoordinator @Inject constructor(
        }
        val target = if (mNotificationsVisible) 1.0f else 0.0f
        val visibilityAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target)
        visibilityAnimator.setInterpolator(Interpolators.LINEAR)
        visibilityAnimator.interpolator = Interpolators.LINEAR
        var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
        if (increaseSpeed) {
            duration = (duration.toFloat() / 1.5F).toLong()
        }
        visibilityAnimator.setDuration(duration)
        visibilityAnimator.duration = duration
        visibilityAnimator.start()
        mVisibilityAnimator = visibilityAnimator
    }
@@ -447,15 +479,15 @@ class NotificationWakeUpCoordinator @Inject constructor(
    }

    private fun handleAnimationFinished() {
        if (mLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
        if (outputLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
            mEntrySetToClearWhenFinished.forEach { it.setHeadsUpAnimatingAway(false) }
            mEntrySetToClearWhenFinished.clear()
        }
    }

    private fun updateHideAmount() {
        val linearAmount = min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
        val amount = min(1.0f - mVisibilityAmount, mDozeAmount)
        val linearAmount = min(1.0f - mLinearVisibilityAmount, outputLinearDozeAmount)
        val amount = min(1.0f - mVisibilityAmount, outputEasedDozeAmount)
        mStackScrollerController.setHideAmount(linearAmount, amount)
        notificationsFullyHidden = linearAmount == 1.0f
    }
@@ -473,7 +505,7 @@ class NotificationWakeUpCoordinator @Inject constructor(
    override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
        var animate = shouldAnimateVisibility()
        if (!isHeadsUp) {
            if (mLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
            if (outputLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
                if (entry.isRowDismissed) {
                    // if we animate, we see the shelf briefly visible. Instead we fully animate
                    // the notification and its background out
@@ -495,10 +527,12 @@ class NotificationWakeUpCoordinator @Inject constructor(
            dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking

    override fun dump(pw: PrintWriter, args: Array<out String>) {
        pw.println("mLinearDozeAmount: $mLinearDozeAmount")
        pw.println("mDozeAmount: $mDozeAmount")
        pw.println("mDozeAmountSource: $mDozeAmountSource")
        pw.println("mNotifsHiddenByDozeAmountOverride: $mNotifsHiddenByDozeAmountOverride")
        pw.println("inputLinearDozeAmount: $inputLinearDozeAmount")
        pw.println("inputEasedDozeAmount: $inputEasedDozeAmount")
        pw.println("hardDozeAmountOverride: $hardDozeAmountOverride")
        pw.println("hardDozeAmountOverrideSource: $hardDozeAmountOverrideSource")
        pw.println("outputLinearDozeAmount: $outputLinearDozeAmount")
        pw.println("outputEasedDozeAmount: $outputEasedDozeAmount")
        pw.println("mNotificationVisibleAmount: $mNotificationVisibleAmount")
        pw.println("mNotificationsVisible: $mNotificationsVisible")
        pw.println("mNotificationsVisibleForExpansion: $mNotificationsVisibleForExpansion")
+26 −14
Original line number Diff line number Diff line
@@ -24,48 +24,60 @@ class NotificationWakeUpCoordinatorLogger
constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
    private var lastSetDozeAmountLogWasFractional = false
    private var lastSetDozeAmountLogState = -1
    private var lastSetDozeAmountLogSource = "undefined"
    private var lastSetHardOverride: Float? = null
    private var lastOnDozeAmountChangedLogWasFractional = false

    fun logSetDozeAmount(
        linear: Float,
        eased: Float,
        source: String,
    fun logUpdateDozeAmount(
        inputLinear: Float,
        hardOverride: Float?,
        outputLinear: Float,
        state: Int,
        changed: Boolean,
    ) {
        // Avoid logging on every frame of the animation if important values are not changing
        val isFractional = linear != 1f && linear != 0f
        val isFractional = inputLinear != 1f && inputLinear != 0f
        if (
            lastSetDozeAmountLogWasFractional &&
                isFractional &&
                lastSetDozeAmountLogState == state &&
                lastSetDozeAmountLogSource == source
                lastSetHardOverride == hardOverride
        ) {
            return
        }
        lastSetDozeAmountLogWasFractional = isFractional
        lastSetDozeAmountLogState = state
        lastSetDozeAmountLogSource = source
        lastSetHardOverride = hardOverride

        buffer.log(
            TAG,
            DEBUG,
            {
                double1 = linear.toDouble()
                str2 = eased.toString()
                str3 = source
                double1 = inputLinear.toDouble()
                str1 = hardOverride.toString()
                str2 = outputLinear.toString()
                int1 = state
                bool1 = changed
            },
            {
                "setDozeAmount(linear=$double1, eased=$str2, source=$str3)" +
                "updateDozeAmount() inputLinear=$double1 hardOverride=$str1 outputLinear=$str2" +
                    " state=${StatusBarState.toString(int1)} changed=$bool1"
            }
        )
    }

    fun logMaybeClearDozeAmountOverrideHidingNotifs(
    fun logSetDozeAmountOverride(dozing: Boolean, source: String) {
        buffer.log(
            TAG,
            DEBUG,
            {
                bool1 = dozing
                str1 = source
            },
            { "setDozeAmountOverride(dozing=$bool1, source=\"$str1\")" }
        )
    }

    fun logMaybeClearHardDozeAmountOverrideHidingNotifs(
        willRemove: Boolean,
        onKeyguard: Boolean,
        dozing: Boolean,
@@ -80,7 +92,7 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
                    "willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" +
                        " bypass=$bypass animating=$animating"
            },
            { "maybeClearDozeAmountOverrideHidingNotifs() $str1" }
            { "maybeClearHardDozeAmountOverrideHidingNotifs() $str1" }
        )
    }

+28 −24
Original line number Diff line number Diff line
@@ -45,47 +45,51 @@ class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() {
    }

    @Test
    fun setDozeAmountWillThrottleFractionalUpdates() {
        logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
    fun updateDozeAmountWillThrottleFractionalInputUpdates() {
        logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false)
        verifyDidLog(1)
        logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
        verifyDidLog(1)
        logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
        verifyDidLog(0)
        logger.logSetDozeAmount(1f, 1f, "source1", StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(1f, null, 1f, StatusBarState.SHADE, changed = true)
        verifyDidLog(1)
    }

    @Test
    fun setDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
        logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
    fun updateDozeAmountWillIncludeFractionalUpdatesWhenStateChanges() {
        logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false)
        verifyDidLog(1)
        logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
        verifyDidLog(1)
        logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
        verifyDidLog(0)
        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.KEYGUARD, changed = false)
        logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.KEYGUARD, changed = false)
        verifyDidLog(1)
    }

    @Test
    fun setDozeAmountWillIncludeFractionalUpdatesWhenSourceChanges() {
        logger.logSetDozeAmount(0f, 0f, "source1", StatusBarState.SHADE, changed = false)
    fun updateDozeAmountWillIncludeFractionalUpdatesWhenHardOverrideChanges() {
        logger.logUpdateDozeAmount(0f, null, 0f, StatusBarState.SHADE, changed = false)
        verifyDidLog(1)
        logger.logSetDozeAmount(0.1f, 0.1f, "source1", StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.1f, null, 0.1f, StatusBarState.SHADE, changed = true)
        verifyDidLog(1)
        logger.logSetDozeAmount(0.2f, 0.2f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.3f, 0.3f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.4f, 0.4f, "source1", StatusBarState.SHADE, changed = true)
        logger.logSetDozeAmount(0.5f, 0.5f, "source1", StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.2f, null, 0.2f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.3f, null, 0.3f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.4f, null, 0.4f, StatusBarState.SHADE, changed = true)
        logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
        verifyDidLog(0)
        logger.logSetDozeAmount(0.5f, 0.5f, "source2", StatusBarState.SHADE, changed = false)
        logger.logUpdateDozeAmount(0.5f, 1f, 1f, StatusBarState.SHADE, changed = true)
        verifyDidLog(1)
        logger.logUpdateDozeAmount(0.5f, 0f, 0f, StatusBarState.SHADE, changed = true)
        verifyDidLog(1)
        logger.logUpdateDozeAmount(0.5f, null, 0.5f, StatusBarState.SHADE, changed = true)
        verifyDidLog(1)
    }

+13 −3
Original line number Diff line number Diff line
@@ -58,6 +58,8 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
    private var bypassEnabled: Boolean = false
    private var statusBarState: Int = StatusBarState.KEYGUARD
    private var dozeAmount: Float = 0f
    private val easedDozeAmount: Float
        get() = notificationWakeUpCoordinator.dozeAmountInterpolator.getInterpolation(dozeAmount)

    private fun setBypassEnabled(enabled: Boolean) {
        bypassEnabled = enabled
@@ -121,7 +123,11 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
    @Test
    fun setDozeToHalfWillHalfShowNotifications() {
        setDozeAmount(0.5f)
        verifyStackScrollerDozeAndHideAmount(dozeAmount = 0.5f, hideAmount = 0.5f)
        verifyStackScrollerDozeAndHideAmount(
            dozeAmount = easedDozeAmount,
            hideAmount = 0.5f,
            hideAmountEased = easedDozeAmount,
        )
        assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
    }

@@ -152,10 +158,14 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
        assertThat(notificationWakeUpCoordinator.statusBarState).isEqualTo(StatusBarState.SHADE)
    }

    private fun verifyStackScrollerDozeAndHideAmount(dozeAmount: Float, hideAmount: Float) {
    private fun verifyStackScrollerDozeAndHideAmount(
        dozeAmount: Float,
        hideAmount: Float,
        hideAmountEased: Float? = null,
    ) {
        // First verify that we did in-fact receive the correct values
        verify(stackScrollerController).setDozeAmount(dozeAmount)
        verify(stackScrollerController).setHideAmount(hideAmount, hideAmount)
        verify(stackScrollerController).setHideAmount(hideAmount, hideAmountEased ?: hideAmount)
        // Now verify that there was just this ONE call to each of these methods
        verify(stackScrollerController).setDozeAmount(anyFloat())
        verify(stackScrollerController).setHideAmount(anyFloat(), anyFloat())