[Flexiglass] Update the y position of HUNs

Use the y position from the placeholder to place HUNs in the NSSL.

Bug: 339181697
Test: check if HUNs are displayed correctly in Gone, Shade, and QuickSettings
Test: atest StackScrollAlgorithmTest
Flag: com.android.systemui.scene_container

Change-Id: I407e0c598f3ca4365a1309e2d81e4e628002f4d5
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 12ca997..104bf31 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -145,6 +145,7 @@
                     }
                     // Note: boundsInWindow doesn't scroll off the screen
                     stackScrollView.setHeadsUpTop(boundsInWindow.top)
+                    stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
                 }
     )
 }
@@ -470,7 +471,11 @@
             )
         }
         if (shouldIncludeHeadsUpSpace) {
-            HeadsUpNotificationSpace(stackScrollView = stackScrollView, viewModel = viewModel)
+            HeadsUpNotificationSpace(
+                stackScrollView = stackScrollView,
+                viewModel = viewModel,
+                modifier = Modifier.padding(top = topPadding)
+            )
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 2d5d259..42ec2d2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -408,7 +408,7 @@
         HeadsUpNotificationSpace(
             stackScrollView = notificationStackScrollView,
             viewModel = notificationsPlaceholderViewModel,
-            modifier = Modifier.align(Alignment.BottomCenter),
+            modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding(),
             isPeekFromBottom = true,
         )
         NotificationScrollingStack(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index fbddc06..7c3072d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -66,6 +66,8 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private float mStackTop;
     private float mStackCutoff;
+    private float mHeadsUpTop;
+    private float mHeadsUpBottom;
     private int mScrollY;
     private float mOverScrollTopAmount;
     private float mOverScrollBottomAmount;
@@ -377,6 +379,30 @@
         this.mStackCutoff = stackCutoff;
     }
 
+    /** y coordinate of the top position of a pinned HUN */
+    public float getHeadsUpTop() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+        return mHeadsUpTop;
+    }
+
+    /** @see #getHeadsUpTop() */
+    public void setHeadsUpTop(float mHeadsUpTop) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        this.mHeadsUpTop = mHeadsUpTop;
+    }
+
+    /** the bottom-most y position where we can draw pinned HUNs  */
+    public float getHeadsUpBottom() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+        return mHeadsUpBottom;
+    }
+
+    /** @see #getHeadsUpBottom() */
+    public void setHeadsUpBottom(float headsUpBottom) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mHeadsUpBottom = headsUpBottom;
+    }
+
     public int getScrollY() {
         return mScrollY;
     }
@@ -784,7 +810,9 @@
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("mStackTop=" + mStackTop);
-        pw.println("mStackCutoff" + mStackCutoff);
+        pw.println("mStackCutoff=" + mStackCutoff);
+        pw.println("mHeadsUpTop=" + mHeadsUpTop);
+        pw.println("mHeadsUpBottom=" + mHeadsUpBottom);
         pw.println("mTopPadding=" + mTopPadding);
         pw.println("mStackTopMargin=" + mStackTopMargin);
         pw.println("mStackTranslation=" + mStackTranslation);
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 1789ad6..f630521 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
@@ -834,7 +834,7 @@
             y = (int) mAmbientState.getStackCutoff();
             drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "getStackCutoff() = " + y);
 
-            y = (int) mScrollViewFields.getHeadsUpTop();
+            y = (int) mAmbientState.getHeadsUpTop();
             drawDebugInfo(canvas, y, Color.GREEN, /* label= */ "getHeadsUpTop() = " + y);
 
             y += getTopHeadsUpHeight();
@@ -1222,7 +1222,12 @@
 
     @Override
     public void setHeadsUpTop(float headsUpTop) {
-        mScrollViewFields.setHeadsUpTop(headsUpTop);
+        mAmbientState.setHeadsUpTop(headsUpTop);
+    }
+
+    @Override
+    public void setHeadsUpBottom(float headsUpBottom) {
+        mAmbientState.setHeadsUpBottom(headsUpBottom);
     }
 
     @Override
@@ -4894,6 +4899,7 @@
      * @param bottomBarHeight the height of the bar on the bottom
      */
     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
         mStackScrollAlgorithm.setHeadsUpAppearHeightBottom(height);
         mStateAnimator.setHeadsUpAppearHeightBottom(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index 97ec391..383d8b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -32,8 +32,6 @@
 class ScrollViewFields {
     /** Used to produce the clipping path */
     var scrimClippingShape: ShadeScrimShape? = null
-    /** Y coordinate in view pixels of the top of the HUN */
-    var headsUpTop: Float = 0f
     /** Whether the notifications are scrolled all the way to the top (i.e. when freshly opened) */
     var isScrolledToTop: Boolean = true
 
@@ -74,7 +72,6 @@
     fun dump(pw: IndentingPrintWriter) {
         pw.printSection("StackViewStates") {
             pw.println("scrimClippingShape", scrimClippingShape)
-            pw.println("headsUpTop", headsUpTop)
             pw.println("isScrolledToTop", isScrolledToTop)
         }
     }
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 4282fa2..b801e5d 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
@@ -79,9 +79,7 @@
     private int mHeadsUpAppearHeightBottom;
     private int mHeadsUpCyclingPadding;
 
-    public StackScrollAlgorithm(
-            Context context,
-            ViewGroup hostView) {
+    public StackScrollAlgorithm(Context context, ViewGroup hostView) {
         mHostView = hostView;
         initView(context);
     }
@@ -865,7 +863,10 @@
 
         // Move the tracked heads up into position during the appear animation, by interpolating
         // between the HUN inset (where it will appear as a HUN) and the end position in the shade
-        float headsUpTranslation = mHeadsUpInset - ambientState.getStackTopMargin();
+        float headsUpTranslation =
+                SceneContainerFlag.isEnabled()
+                        ? ambientState.getHeadsUpTop()
+                        : mHeadsUpInset - ambientState.getStackTopMargin();
         ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
         if (trackedHeadsUpRow != null) {
             ExpandableViewState childState = trackedHeadsUpRow.getViewState();
@@ -894,21 +895,44 @@
             boolean isTopEntry = topHeadsUpEntry == row;
             float unmodifiedEndLocation = childState.getYTranslation() + childState.height;
             if (mIsExpanded) {
-                if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
-                        childState.headsUpIsVisible, row.showingPulsing(),
-                        ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
-                    // Ensure that the heads up is always visible even when scrolled off.
-                    // NSSL y starts at top of screen in non-split-shade, but below the qs offset
-                    // in split shade, so we only need to inset by the scrim padding in split shade.
-                    // TODO(b/332574413) get the clamp inset from HeadsUpNotificationPlaceholder
-                    final float clampInset = ambientState.getUseSplitShade()
-                            ? mNotificationScrimPadding : mQuickQsOffsetHeight;
-                    clampHunToTop(clampInset, ambientState.getStackTranslation(),
-                            row.getCollapsedHeight(), childState);
-                    if (isTopEntry && row.isAboveShelf()) {
-                        // the first hun can't get off screen.
-                        clampHunToMaxTranslation(ambientState, row, childState);
-                        childState.hidden = false;
+                if (SceneContainerFlag.isEnabled()) {
+                    if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+                            childState.headsUpIsVisible, row.showingPulsing(),
+                            ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
+                        clampHunToTop(
+                                /* headsUpTop = */ headsUpTranslation,
+                                /* collapsedHeight = */ row.getCollapsedHeight(),
+                                /* viewState = */ childState
+                        );
+                        if (isTopEntry && row.isAboveShelf()) {
+                            clampHunToMaxTranslation(
+                                    /* headsUpTop =  */ headsUpTranslation,
+                                    /* headsUpBottom =  */ ambientState.getHeadsUpBottom(),
+                                    /* viewState = */ childState
+                            );
+                            updateCornerRoundnessForPinnedHun(row, ambientState.getStackTop());
+                            childState.hidden = false;
+                        }
+                    }
+                } else {
+                    if (shouldHunBeVisibleWhenScrolled(row.mustStayOnScreen(),
+                            childState.headsUpIsVisible, row.showingPulsing(),
+                            ambientState.isOnKeyguard(), row.getEntry().isStickyAndNotDemoted())) {
+                        // Ensure that the heads up is always visible even when scrolled off.
+                        // NSSL y starts at top of screen in non-split-shade, but below the qs
+                        // offset
+                        // in split shade, so we only need to inset by the scrim padding in split
+                        // shade.
+                        final float clampInset = ambientState.getUseSplitShade()
+                                ? mNotificationScrimPadding : mQuickQsOffsetHeight;
+                        clampHunToTop(clampInset, ambientState.getStackTranslation(),
+                                row.getCollapsedHeight(), childState);
+                        if (isTopEntry && row.isAboveShelf()) {
+                            // the first hun can't get off screen.
+                            clampHunToMaxTranslation(ambientState, row, childState);
+                            updateCornerRoundnessForPinnedHun(row, ambientState.getStackY());
+                            childState.hidden = false;
+                        }
                     }
                 }
             }
@@ -1005,9 +1029,13 @@
     @VisibleForTesting
     void clampHunToTop(float clampInset, float stackTranslation, float collapsedHeight,
             ExpandableViewState viewState) {
+        SceneContainerFlag.assertInLegacyMode();
+        clampHunToTop(clampInset + stackTranslation, collapsedHeight, viewState);
+    }
 
-        final float newTranslation = Math.max(clampInset + stackTranslation,
-                viewState.getYTranslation());
+    @VisibleForTesting
+    void clampHunToTop(float headsUpTop, float collapsedHeight, ExpandableViewState viewState) {
+        final float newTranslation = Math.max(headsUpTop, viewState.getYTranslation());
 
         // Transition from collapsed pinned state to fully expanded state
         // when the pinned HUN approaches its actual location (when scrolling back to top).
@@ -1020,9 +1048,12 @@
     // while the rest of notifications are scrolled offscreen.
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
             ExpandableViewState childState) {
+        SceneContainerFlag.assertInLegacyMode();
         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
-        final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
-                + ambientState.getStackTranslation();
+        final float maxShelfPosition =
+                ambientState.getInnerHeight()
+                        + ambientState.getTopPadding()
+                        + ambientState.getStackTranslation();
         maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
 
         final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
@@ -1030,13 +1061,20 @@
         childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
                 - newTranslation);
         childState.setYTranslation(newTranslation);
+    }
 
+    private void clampHunToMaxTranslation(float headsUpTop, float headsUpBottom,
+            ExpandableViewState viewState) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        final float maxHeight = headsUpTop - headsUpBottom;
+        viewState.setYTranslation(Math.min(headsUpTop, viewState.getYTranslation()));
+        viewState.height = (int) Math.min(maxHeight, viewState.height);
+    }
+
+    private void updateCornerRoundnessForPinnedHun(ExpandableNotificationRow row, float stackTop) {
         // Animate pinned HUN bottom corners to and from original roundness.
         final float originalCornerRadius =
                 row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
-        final float stackTop = SceneContainerFlag.isEnabled()
-                ? ambientState.getStackTop()
-                : ambientState.getStackY();
         final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
                 stackTop, getMaxAllowedChildHeight(row), originalCornerRadius);
         row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 762c507..6226fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -59,6 +59,9 @@
     /** set the y position in px of the top of the HUN in this view's coordinates */
     fun setHeadsUpTop(headsUpTop: Float)
 
+    /** set the bottom-most y position in px, where we can draw HUNs in this view's coordinates */
+    fun setHeadsUpBottom(headsUpBottom: Float)
+
     /** set whether the view has been scrolled all the way to the top */
     fun setScrolledToTop(scrolledToTop: Boolean)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index c2a7b52..b7ebebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -126,6 +126,19 @@
     }
 
     @Test
+    @EnableSceneContainer
+    fun resetViewStates_defaultHun_yTranslationIsHeadsUpTop() {
+        val headsUpTop = 200f
+        ambientState.headsUpTop = headsUpTop
+
+        whenever(notificationRow.isPinned).thenReturn(true)
+        whenever(notificationRow.isHeadsUp).thenReturn(true)
+
+        resetViewStates_hunYTranslationIs(headsUpTop)
+    }
+
+    @Test
+    @DisableSceneContainer
     fun resetViewStates_defaultHun_yTranslationIsInset() {
         whenever(notificationRow.isPinned).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
@@ -133,6 +146,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun resetViewStates_defaultHunWithStackMargin_changesHunYTranslation() {
         whenever(notificationRow.isPinned).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
@@ -140,7 +154,7 @@
     }
 
     @Test
-    @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests
+    @DisableSceneContainer
     fun resetViewStates_defaultHunWhenShadeIsOpening_yTranslationIsInset() {
         whenever(notificationRow.isPinned).thenReturn(true)
         whenever(notificationRow.isHeadsUp).thenReturn(true)
@@ -153,6 +167,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_hunAnimatingAway_yTranslationIsInset() {
         whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -160,6 +175,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     @DisableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_hunAnimatingAway_StackMarginChangesHunYTranslation() {
         whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -167,6 +183,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_defaultHun_newHeadsUpAnim_yTranslationIsInset() {
         whenever(notificationRow.isPinned).thenReturn(true)
@@ -175,6 +192,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_defaultHunWithStackMargin_newHeadsUpAnim_changesHunYTranslation() {
         whenever(notificationRow.isPinned).thenReturn(true)
@@ -183,7 +201,98 @@
     }
 
     @Test
-    @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests
+    @EnableSceneContainer
+    fun resetViewStates_defaultHunInShade_stackTopEqualsHunTop_hunHasFullHeight() {
+        // Given: headsUpTop == stackTop -> haven't scrolled the stack yet
+        val headsUpTop = 150f
+        val collapsedHeight = 100
+        val intrinsicHeight = 300
+        fakeHunInShade(
+            headsUpTop = headsUpTop,
+            stackTop = headsUpTop,
+            collapsedHeight = collapsedHeight,
+            intrinsicHeight = intrinsicHeight,
+        )
+
+        // When
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        // Then: HUN is at the headsUpTop
+        assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+        // And: HUN has its full height
+        assertThat(notificationRow.viewState.height).isEqualTo(intrinsicHeight)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun resetViewStates_defaultHunInShade_stackTopGreaterThanHeadsUpTop_hunClampedToHeadsUpTop() {
+        // Given: headsUpTop < stackTop -> scrolled the stack a little bit
+        val stackTop = -25f
+        val headsUpTop = 150f
+        val collapsedHeight = 100
+        val intrinsicHeight = 300
+        fakeHunInShade(
+            headsUpTop = headsUpTop,
+            stackTop = stackTop,
+            collapsedHeight = collapsedHeight,
+            intrinsicHeight = intrinsicHeight
+        )
+
+        // When
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        // Then: HUN is translated to the headsUpTop
+        assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+        // And: HUN is clipped to the available space
+        // newTranslation = max(150, -25)
+        // distToReal = 150 - (-25)
+        // height = max(300 - 175, 100)
+        assertThat(notificationRow.viewState.height).isEqualTo(125)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun resetViewStates_defaultHunInShade_stackOverscrolledHun_hunClampedToHeadsUpTop() {
+        // Given: headsUpTop << stackTop -> stack has fully overscrolled the HUN
+        val stackTop = -500f
+        val headsUpTop = 150f
+        val collapsedHeight = 100
+        val intrinsicHeight = 300
+        fakeHunInShade(
+            headsUpTop = headsUpTop,
+            stackTop = stackTop,
+            collapsedHeight = collapsedHeight,
+            intrinsicHeight = intrinsicHeight
+        )
+
+        // When
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        // Then: HUN is translated to the headsUpTop
+        assertThat(notificationRow.viewState.yTranslation).isEqualTo(headsUpTop)
+        // And: HUN is clipped to its collapsed height
+        assertThat(notificationRow.viewState.height).isEqualTo(collapsedHeight)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun resetViewStates_defaultHun_showingQS_hunTranslatedToHeadsUpTop() {
+        // Given: the shade is open and scrolled to the bottom to show the QuickSettings
+        val headsUpTop = 2000f
+        fakeHunInShade(
+            headsUpTop = headsUpTop,
+            stackTop = 2600f, // stack scrolled below the screen
+            stackCutoff = 4000f,
+            collapsedHeight = 100,
+            intrinsicHeight = 300
+        )
+        whenever(notificationRow.isAboveShelf).thenReturn(true)
+
+        resetViewStates_hunYTranslationIs(headsUpTop)
+    }
+
+    @Test
+    @DisableSceneContainer
     @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_defaultHun_showingQS_newHeadsUpAnim_hunTranslatedToMax() {
         // Given: the shade is open and scrolled to the bottom to show the QuickSettings
@@ -200,7 +309,7 @@
     }
 
     @Test
-    @DisableSceneContainer // TODO(b/332574413) cover hun bounds integration with tests
+    @DisableSceneContainer
     @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_hunAnimatingAway_showingQS_newHeadsUpAnim_hunTranslatedToBottomOfScreen() {
         // Given: the shade is open and scrolled to the bottom to show the QuickSettings
@@ -245,6 +354,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_hunAnimatingAwayWhileDozing_yTranslationIsInset() {
         whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -255,6 +365,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     @EnableFlags(NotificationsImprovedHunAnimation.FLAG_NAME)
     fun resetViewStates_hunAnimatingAwayWhileDozing_hasStackMargin_changesHunYTranslation() {
         whenever(notificationRow.isHeadsUpAnimatingAway).thenReturn(true)
@@ -744,8 +855,7 @@
         expandableViewState.yTranslation = 50f
 
         stackScrollAlgorithm.clampHunToTop(
-            /* quickQsOffsetHeight= */ 10f,
-            /* stackTranslation= */ 0f,
+            /* headsUpTop= */ 10f,
             /* collapsedHeight= */ 1f,
             expandableViewState
         )
@@ -760,8 +870,7 @@
         expandableViewState.yTranslation = -10f
 
         stackScrollAlgorithm.clampHunToTop(
-            /* quickQsOffsetHeight= */ 10f,
-            /* stackTranslation= */ 0f,
+            /* headsUpTop= */ 10f,
             /* collapsedHeight= */ 1f,
             expandableViewState
         )
@@ -777,8 +886,7 @@
         expandableViewState.yTranslation = -100f
 
         stackScrollAlgorithm.clampHunToTop(
-            /* quickQsOffsetHeight= */ 10f,
-            /* stackTranslation= */ 0f,
+            /* headsUpTop= */ 10f,
             /* collapsedHeight= */ 10f,
             expandableViewState
         )
@@ -796,8 +904,7 @@
         expandableViewState.yTranslation = 5f
 
         stackScrollAlgorithm.clampHunToTop(
-            /* quickQsOffsetHeight= */ 10f,
-            /* stackTranslation= */ 0f,
+            /* headsUpTop= */ 10f,
             /* collapsedHeight= */ 10f,
             expandableViewState
         )
@@ -1309,6 +1416,33 @@
 
         expect.that(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
     }
+
+    /** fakes the notification row under test, to be a HUN in a fully opened shade */
+    private fun fakeHunInShade(
+        headsUpTop: Float,
+        collapsedHeight: Int,
+        intrinsicHeight: Int,
+        stackTop: Float,
+        stackCutoff: Float = 2000f,
+        fullStackHeight: Float = 3000f
+    ) {
+        ambientState.headsUpTop = headsUpTop
+        ambientState.stackTop = stackTop
+        ambientState.stackCutoff = stackCutoff
+
+        // shade is fully open
+        ambientState.expansionFraction = 1.0f
+        with(fullStackHeight) {
+            ambientState.stackHeight = this
+            ambientState.stackEndHeight = this
+        }
+        stackScrollAlgorithm.setIsExpanded(true)
+
+        whenever(notificationRow.mustStayOnScreen()).thenReturn(true)
+        whenever(notificationRow.isHeadsUp).thenReturn(true)
+        whenever(notificationRow.collapsedHeight).thenReturn(collapsedHeight)
+        whenever(notificationRow.intrinsicHeight).thenReturn(intrinsicHeight)
+    }
 }
 
 private fun mockExpandableNotificationRow(): ExpandableNotificationRow {