Added spring animations for tucking/untucking Floating Action Button

This change builds off of modifications from ag/24314702 to better suit animated tucking/untucking.
MenuViewLayer applies a clipBounds covering most of the screen excluding system/navigation bars
once the FAB begins a tucking animation. It does not remove the clip bounds until an untuck animation concludes.

Flag: ACONFIG com.android.systemui.Flags.floating_menu_animated_tuck ENABLED
Bug: 297556899
Test: adb sync system_ext && adb shell stop && adb shell start
Change-Id: Ic18e39486e24cd9080620174f9a3ecb298ebc515
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 08ecf09b..25ac486 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -3,10 +3,10 @@
 # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
 
 flag {
-    name: "floating_menu_overlaps_nav_bars_flag"
+    name: "floating_menu_animated_tuck"
     namespace: "accessibility"
-    description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
-    bug: "283768342"
+    description: "Sets up animations for tucking/untucking and adjusts clipbounds."
+    bug: "24592044"
 }
 
 flag {
@@ -14,4 +14,11 @@
     namespace: "accessibility"
     description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
     bug: "281150010"
+}
+
+flag {
+    name: "floating_menu_overlaps_nav_bars_flag"
+    namespace: "accessibility"
+    description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
+    bug: "283768342"
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index cd8bef1..ceddee8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -341,7 +341,16 @@
     void moveToEdgeAndHide() {
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
         final PointF position = mMenuView.getMenuPosition();
-        moveToPosition(getTuckedMenuPosition());
+        final PointF tuckedPosition = getTuckedMenuPosition();
+        if (Flags.floatingMenuAnimatedTuck()) {
+            flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X,
+                    Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY,
+                    FLING_FRICTION_SCALAR,
+                    createDefaultSpringForce(),
+                    tuckedPosition.x);
+        } else {
+            moveToPosition(tuckedPosition);
+        }
 
         // Keep the touch region let users could click extra space to pop up the menu view
         // from the screen edge
@@ -353,7 +362,24 @@
     void moveOutEdgeAndShow() {
         mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
 
-        mMenuView.onPositionChanged();
+        if (Flags.floatingMenuAnimatedTuck()) {
+            PointF position = mMenuView.getMenuPosition();
+            springMenuWith(DynamicAnimation.TRANSLATION_X,
+                    createDefaultSpringForce(),
+                    0,
+                    position.x,
+                    true
+            );
+            springMenuWith(DynamicAnimation.TRANSLATION_Y,
+                    createDefaultSpringForce(),
+                    0,
+                    position.y,
+                    true
+            );
+        } else {
+            mMenuView.onPositionChanged();
+        }
+
         mMenuView.onEdgeChangedIfNeeded();
     }
 
@@ -489,6 +515,12 @@
         return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
     }
 
+    private static SpringForce createDefaultSpringForce() {
+        return new SpringForce()
+                .setStiffness(SPRING_STIFFNESS)
+                .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO);
+    }
+
     static class MenuPositionProperty
             extends FloatPropertyCompat<MenuView> {
         private final DynamicAnimation.ViewProperty mProperty;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index ea5a56c..92c7259 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -71,6 +71,7 @@
 
     private final MenuAnimationController mMenuAnimationController;
     private OnTargetFeaturesChangeListener mFeaturesChangeListener;
+    private OnMoveToTuckedListener mMoveToTuckedListener;
 
     MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
         super(context);
@@ -138,6 +139,10 @@
         mFeaturesChangeListener = listener;
     }
 
+    void setMoveToTuckedListener(OnMoveToTuckedListener listener) {
+        mMoveToTuckedListener = listener;
+    }
+
     void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) {
         mTargetFeaturesView.addOnItemTouchListener(listener);
     }
@@ -307,8 +312,11 @@
     void updateMenuMoveToTucked(boolean isMoveToTucked) {
         mIsMoveToTucked = isMoveToTucked;
         mMenuViewModel.updateMenuMoveToTucked(isMoveToTucked);
+        if (mMoveToTuckedListener != null) {
+            mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
+        }
 
-        if (Flags.floatingMenuOverlapsNavBarsFlag()) {
+        if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
             if (isMoveToTucked) {
                 final float halfWidth = getMenuWidth() / 2.0f;
                 final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
@@ -428,4 +436,11 @@
          */
         void onChange(List<AccessibilityTarget> newTargetFeatures);
     }
+
+    /**
+     * Interface containing a callback for when MoveToTucked changes.
+     */
+    interface OnMoveToTuckedListener {
+        void onMoveToTuckedChanged(boolean moveToTucked);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 89ce065..4865fce 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -281,7 +281,7 @@
                 : new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
     }
 
-    private Rect getWindowAvailableBounds() {
+    public Rect getWindowAvailableBounds() {
         final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
         final WindowInsets windowInsets = windowMetrics.getWindowInsets();
         final Insets insets = windowInsets.getInsetsIgnoringVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index fbca022..ff3a9e3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -79,7 +79,8 @@
  */
 @SuppressLint("ViewConstructor")
 class MenuViewLayer extends FrameLayout implements
-        ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener, ComponentCallbacks {
+        ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener, ComponentCallbacks,
+        MenuView.OnMoveToTuckedListener {
     private static final int SHOW_MESSAGE_DELAY_MS = 3000;
 
     private final WindowManager mWindowManager;
@@ -211,6 +212,7 @@
         mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
                 mDismissAnimationController);
         mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
+        mMenuView.setMoveToTuckedListener(this);
 
         mMessageView = new MenuMessageView(context);
 
@@ -232,6 +234,10 @@
         addView(mMenuView, LayerIndex.MENU_VIEW);
         addView(mDismissView, LayerIndex.DISMISS_VIEW);
         addView(mMessageView, LayerIndex.MESSAGE_VIEW);
+
+        if (Flags.floatingMenuAnimatedTuck()) {
+            setClipChildren(true);
+        }
     }
 
     @Override
@@ -354,6 +360,24 @@
         mShouldShowDockTooltip = !hasSeenTooltip;
     }
 
+    public void onMoveToTuckedChanged(boolean moveToTuck) {
+        if (Flags.floatingMenuOverlapsNavBarsFlag()) {
+            if (moveToTuck) {
+                final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+                final int[] location = getLocationOnScreen();
+                bounds.offset(
+                        location[0],
+                        location[1]
+                );
+
+                setClipBounds(bounds);
+            }
+            // Instead of clearing clip bounds when moveToTuck is false,
+            // wait until the spring animation finishes.
+        }
+        // Function is a no-operation if flag is disabled.
+    }
+
     private void onSpringAnimationsEndAction() {
         if (mShouldShowDockTooltip) {
             mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance));
@@ -364,6 +388,11 @@
             mMenuAnimationController.startTuckedAnimationPreview();
         }
 
+        if (Flags.floatingMenuAnimatedTuck()) {
+            if (!mMenuView.isMoveToTucked()) {
+                setClipBounds(null);
+            }
+        }
         if (Flags.floatingMenuImeDisplacementAnimation()) {
             mMenuView.onArrivalAtPosition();
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 2e75480..834dccb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -26,6 +26,9 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.graphics.PointF;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -39,6 +42,7 @@
 import androidx.dynamicanimation.animation.SpringForce;
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.settings.SecureSettings;
@@ -70,6 +74,10 @@
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Mock
     private AccessibilityManager mAccessibilityManager;
 
@@ -223,6 +231,24 @@
         verifyZeroInteractions(onSpringAnimationsEndCallback);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
+    public void tuck_animates() {
+        mMenuAnimationController.cancelAnimations();
+        mMenuAnimationController.moveToEdgeAndHide();
+        assertThat(mMenuAnimationController.getAnimation(
+                DynamicAnimation.TRANSLATION_X).isRunning()).isTrue();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK)
+    public void untuck_animates() {
+        mMenuAnimationController.cancelAnimations();
+        mMenuAnimationController.moveOutEdgeAndShow();
+        assertThat(mMenuAnimationController.getAnimation(
+                DynamicAnimation.TRANSLATION_X).isRunning()).isTrue();
+    }
+
     private void setupAndRunSpringAnimations() {
         final float stiffness = 700f;
         final float dampingRatio = 0.85f;