AnimatedSharedAsState does not change value when bouncing

If the value doesn't change during overscroll then it shouldn't change
during bouncing either.

Test: atest ElementTest
Bug: 327257459
Flag: NA
Change-Id: I9b8b11830afdf7779bf8ac4a35600b113c0595e3
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 37d763b..5d1a7c5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -281,6 +281,15 @@
                     // relayout/redraw for nothing.
                     fromValue
                 } else {
+                    // In the case of bouncing, if the value remains constant during the overscroll,
+                    // we should use the value of the scene we are bouncing around.
+                    if (!canOverflow && transition is TransitionState.HasOverscrollProperties) {
+                        val bouncingScene = transition.bouncingScene
+                        if (bouncingScene != null) {
+                            return sceneValue(bouncingScene)
+                        }
+                    }
+
                     val progress =
                         if (canOverflow) transition.progress
                         else transition.progress.fastCoerceIn(0f, 1f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 0804761..597da9e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -33,11 +33,13 @@
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.PagerState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -545,7 +547,9 @@
         layoutWidth: Dp,
         layoutHeight: Dp,
         sceneTransitions: SceneTransitionsBuilder.() -> Unit,
-        firstScroll: Float
+        firstScroll: Float,
+        animatedFloatRange: ClosedFloatingPointRange<Float>,
+        onAnimatedFloat: (Float) -> Unit,
     ): MutableSceneTransitionLayoutStateImpl {
         // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
         // detected as a drag event.
@@ -568,10 +572,24 @@
                     key = TestScenes.SceneA,
                     userActions = mapOf(Swipe.Down to TestScenes.SceneB)
                 ) {
+                    animateSceneFloatAsState(
+                        value = animatedFloatRange.start,
+                        key = TestValues.Value1,
+                        false
+                    )
                     Spacer(Modifier.fillMaxSize())
                 }
                 scene(TestScenes.SceneB) {
+                    val animatedFloat by
+                        animateSceneFloatAsState(
+                            value = animatedFloatRange.endInclusive,
+                            key = TestValues.Value1,
+                            canOverflow = false
+                        )
                     Spacer(Modifier.element(TestElements.Foo).fillMaxSize())
+                    LaunchedEffect(Unit) {
+                        snapshotFlow { animatedFloat }.collect { onAnimatedFloat(it) }
+                    }
                 }
             }
         }
@@ -594,6 +612,7 @@
         val layoutWidth = 200.dp
         val layoutHeight = 400.dp
         val overscrollTranslateY = 10.dp
+        var animatedFloat = 0f
 
         val state =
             setupOverscrollScenario(
@@ -606,6 +625,8 @@
                     }
                 },
                 firstScroll = 0.5f, // Scroll 50%
+                animatedFloatRange = 0f..100f,
+                onAnimatedFloat = { animatedFloat = it },
             )
 
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
@@ -613,6 +634,7 @@
         val transition = state.currentTransition
         assertThat(transition).isNotNull()
         assertThat(transition!!.progress).isEqualTo(0.5f)
+        assertThat(animatedFloat).isEqualTo(50f)
 
         rule.onRoot().performTouchInput {
             // Scroll another 100%
@@ -623,6 +645,8 @@
         assertThat(transition.progress).isEqualTo(1.5f)
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)
+        // animatedFloat cannot overflow (canOverflow = false)
+        assertThat(animatedFloat).isEqualTo(100f)
 
         rule.onRoot().performTouchInput {
             // Scroll another 100%
@@ -633,6 +657,7 @@
         assertThat(transition.progress).isEqualTo(2.5f)
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
+        assertThat(animatedFloat).isEqualTo(100f)
     }
 
     @Test
@@ -715,6 +740,7 @@
     fun elementTransitionWithDistanceDuringOverscroll() {
         val layoutWidth = 200.dp
         val layoutHeight = 400.dp
+        var animatedFloat = 0f
         val state =
             setupOverscrollScenario(
                 layoutWidth = layoutWidth,
@@ -726,10 +752,13 @@
                     }
                 },
                 firstScroll = 1f, // 100% scroll
+                animatedFloatRange = 0f..100f,
+                onAnimatedFloat = { animatedFloat = it },
             )
 
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+        assertThat(animatedFloat).isEqualTo(100f)
 
         rule.onRoot().performTouchInput {
             // Scroll another 50%
@@ -738,11 +767,13 @@
 
         val transition = state.currentTransition
         assertThat(transition).isNotNull()
+        assertThat(animatedFloat).isEqualTo(100f)
 
         // Scroll 150% (100% scroll + 50% overscroll)
         assertThat(transition!!.progress).isEqualTo(1.5f)
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
+        assertThat(animatedFloat).isEqualTo(100f)
 
         rule.onRoot().performTouchInput {
             // Scroll another 100%
@@ -753,12 +784,14 @@
         assertThat(transition.progress).isEqualTo(2.5f)
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
+        assertThat(animatedFloat).isEqualTo(100f)
     }
 
     @Test
     fun elementTransitionWithDistanceDuringOverscrollBouncing() {
         val layoutWidth = 200.dp
         val layoutHeight = 400.dp
+        var animatedFloat = 0f
         val state =
             setupOverscrollScenario(
                 layoutWidth = layoutWidth,
@@ -776,10 +809,13 @@
                     }
                 },
                 firstScroll = 1f, // 100% scroll
+                animatedFloatRange = 0f..100f,
+                onAnimatedFloat = { animatedFloat = it },
             )
 
         val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
         fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+        assertThat(animatedFloat).isEqualTo(100f)
 
         rule.onRoot().performTouchInput {
             // Scroll another 50%
@@ -794,6 +830,7 @@
         assertThat(transition.progress).isEqualTo(1.5f)
         assertThat(state.currentOverscrollSpec).isNotNull()
         fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f))
+        assertThat(animatedFloat).isEqualTo(100f)
 
         // finger raised
         rule.onRoot().performTouchInput { up() }
@@ -802,7 +839,9 @@
         // value.
         rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f }
 
+        assertThat(transition.progress).isLessThan(1f)
         assertThat(state.currentOverscrollSpec).isNotNull()
         assertThat(transition.bouncingScene).isEqualTo(transition.toScene)
+        assertThat(animatedFloat).isEqualTo(100f)
     }
 }