Introduce a new Home animation for workspace and the outgoing task.

The values are currently the same for all display and orientation
configurations, but they might change before launch.

There are a couple known imperfections:
* Swiping out of a hotseat app with very low velocity doesn't look
  great
* Sometimes, if the window movement reaches its final location faster
  than the background is done scaling, there is a small snap in icon
  position

Bug: 298089923
Flag: ACONFIG com.android.launcher3.enable_scaling_reveal_home_animation DISABLED
Test: verified with the flag on and off

Change-Id: Id54c7f0a76f62108d8b92a3b5e78634fff64dbef
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 462d947..20c5248 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -90,6 +90,9 @@
     namespace: "launcher"
     description: "Enables full width two pane widget picker for tablets in landscape and portrait"
     bug: "315055849"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
 
 flag {
@@ -175,9 +178,6 @@
   namespace: "launcher"
   description: "When adding app widget through config activity, directly add it to workspace to reduce flicker"
   bug: "284236964"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
 }
 
 flag {
@@ -206,3 +206,10 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "enable_scaling_reveal_home_animation"
+    namespace: "launcher"
+    description: "Enables the Home gesture animation"
+    bug: "308801666"
+}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 569e95a..be532b4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -43,6 +43,7 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -215,7 +216,8 @@
     public static final int TASKBAR_TO_APP_DURATION = 600;
     // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
     // is solved.
-    public static final int TASKBAR_TO_HOME_DURATION = 300;
+    private static final int TASKBAR_TO_HOME_DURATION_FAST = 300;
+    private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000;
     protected static final int CONTENT_SCALE_DURATION = 350;
     protected static final int CONTENT_SCRIM_DURATION = 350;
 
@@ -1704,6 +1706,14 @@
         return new Pair(rectFSpringAnim, anim);
     }
 
+    public static int getTaskbarToHomeDuration() {
+        if (enableScalingRevealHomeAnimation()) {
+            return TASKBAR_TO_HOME_DURATION_SLOW;
+        } else {
+            return TASKBAR_TO_HOME_DURATION_FAST;
+        }
+    }
+
     /**
      * Remote animation runner for animation from the app to Launcher, including recents.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 1e861d2..36ce049 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -191,7 +191,7 @@
                         ? TRANSIENT_TASKBAR_TRANSITION_DURATION
                         : (!isVisible
                                 ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
-                                : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION));
+                                : QuickstepTransitionManager.getTaskbarToHomeDuration()));
     }
 
     @Nullable
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 8d83716..a14e3fd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -193,7 +193,7 @@
                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
                     if (!mShouldDelayLauncherStateAnim) {
                         if (toState == LauncherState.NORMAL) {
-                            applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
+                            applyState(QuickstepTransitionManager.getTaskbarToHomeDuration());
                         } else {
                             applyState();
                         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 577eba6..b6002e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
 
 import android.content.Context;
@@ -28,6 +29,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
+import com.android.quickstep.util.BaseDepthController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 /**
@@ -105,8 +107,14 @@
             return context.getDeviceProfile().bottomSheetDepth;
         } else {
             // The scrim fades in at approximately 50% of the swipe gesture.
-            // This means that the depth should be greater than 1, in order to fully zoom out.
-            return 2f;
+            if (enableScalingRevealHomeAnimation()) {
+                // This means that the depth should be twice of what we want, in order to fully zoom
+                // out during the visible portion of the animation.
+                return BaseDepthController.DEPTH_60_PERCENT;
+            } else {
+                // This means that the depth should be greater than 1, in order to fully zoom out.
+                return 2f;
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index d11a08b..6a25c21 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides.states;
 
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
@@ -26,6 +27,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.quickstep.util.BaseDepthController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
 
@@ -90,13 +92,14 @@
 
     @Override
     protected float getDepthUnchecked(Context context) {
-        if (isDesktopModeSupported()) {
-            if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
-                // Don't blur the background while freeform tasks are visible
-                return 0;
-            }
+        if (isDesktopModeSupported() && Launcher.getLauncher(context).areFreeformTasksVisible()) {
+            // Don't blur the background while freeform tasks are visible
+            return BaseDepthController.DEPTH_0_PERCENT;
+        } else if (enableScalingRevealHomeAnimation()) {
+            return BaseDepthController.DEPTH_70_PERCENT;
+        } else {
+            return 1f;
         }
-        return 1;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 7650235..8c2efc2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.wm.shell.Flags.enableSplitContextual;
 
@@ -29,6 +30,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Themes;
+import com.android.quickstep.util.BaseDepthController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -186,8 +188,14 @@
 
     @Override
     protected float getDepthUnchecked(Context context) {
-        //TODO revert when b/178661709 is fixed
-        return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0;
+        // TODO(178661709): revert to always scaled
+        if (enableScalingRevealHomeAnimation()) {
+            return SystemProperties.getBoolean("ro.launcher.depth.overview", true)
+                    ? BaseDepthController.DEPTH_70_PERCENT
+                    : BaseDepthController.DEPTH_0_PERCENT;
+        } else {
+            return SystemProperties.getBoolean("ro.launcher.depth.overview", true) ? 1 : 0;
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 8c92c7d..b401868 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -37,7 +37,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
@@ -59,6 +58,7 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AllAppsSwipeController;
@@ -108,7 +108,8 @@
 
             // We sync the scrim fade with the taskbar animation duration to avoid any flickers for
             // taskbar icons disappearing before hotseat icons show up.
-            float scrimUpperBoundFromSplit = TASKBAR_TO_HOME_DURATION / (float) config.duration;
+            float scrimUpperBoundFromSplit =
+                    QuickstepTransitionManager.getTaskbarToHomeDuration() / (float) config.duration;
             config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
             config.setInterpolator(ANIM_SCRIM_FADE,
                     fromState == OVERVIEW_SPLIT_SELECT
@@ -136,7 +137,8 @@
 
                 // Sync scroll so that it ends before or at the same time as the taskbar animation.
                 if (mActivity.getDeviceProfile().isTaskbarPresent) {
-                    config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
+                    config.duration = Math.min(
+                            config.duration, QuickstepTransitionManager.getTaskbarToHomeDuration());
                 }
                 overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
             } else {
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index a9d8afc..f678bea 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -17,6 +17,7 @@
 
 import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.Utilities.mapBoundToRange;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
@@ -46,6 +47,7 @@
 import com.android.launcher3.views.FloatingView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.views.FloatingWidgetView;
@@ -296,9 +298,15 @@
 
         @Override
         public void playAtomicAnimation(float velocity) {
-            new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
-                    getViewIgnoredInWorkspaceRevealAnimation())
-                    .start();
+            if (enableScalingRevealHomeAnimation()) {
+                if (mActivity != null) {
+                    new ScalingWorkspaceRevealAnim(mActivity).start();
+                }
+            } else {
+                new StaggeredWorkspaceAnim(mActivity, velocity, true /* animateOverviewScrim */,
+                        getViewIgnoredInWorkspaceRevealAnimation())
+                        .start();
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 99f564c..5d6bb1d 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+
 import android.app.WallpaperManager;
 import android.os.IBinder;
 import android.util.FloatProperty;
@@ -33,6 +35,9 @@
  * Utility class for applying depth effect
  */
 public class BaseDepthController {
+    public static final float DEPTH_0_PERCENT = 0f;
+    public static final float DEPTH_60_PERCENT = 0.6f;
+    public static final float DEPTH_70_PERCENT = 0.7f;
 
     private static final FloatProperty<BaseDepthController> DEPTH =
             new FloatProperty<BaseDepthController>("depth") {
@@ -127,10 +132,14 @@
         float depth = mDepth;
         IBinder windowToken = mLauncher.getRootView().getWindowToken();
         if (windowToken != null) {
-            // The API's full zoom-out is three times larger than the zoom-out we apply to the
-            // icons. To keep the two consistent throughout the animation while keeping Launcher's
-            // concept of full depth unchanged, we divide the depth by 3 here.
-            mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
+            if (enableScalingRevealHomeAnimation()) {
+                mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+            } else {
+                // The API's full zoom-out is three times larger than the zoom-out we apply to the
+                // icons. To keep the two consistent throughout the animation while keeping
+                // Launcher's concept of full depth unchanged, we divide the depth by 3 here.
+                mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
+            }
         }
 
         if (!BlurUtils.supportsBlursOnWindows()) {
@@ -150,8 +159,15 @@
         boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
         boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg && !mPauseBlurs;
 
+        float blurAmount;
+        if (enableScalingRevealHomeAnimation()) {
+            blurAmount = mapDepthToBlur(depth);
+        } else {
+            blurAmount = depth;
+        }
         mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs
-                ? 0 : (int) (depth * mMaxBlurRadius);
+                ? 0 : (int) (blurAmount * mMaxBlurRadius);
+
         SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
                 .setBackgroundBlurRadius(mSurface, mCurrentBlur)
                 .setOpaque(mSurface, isSurfaceOpaque);
@@ -197,4 +213,12 @@
             applyDepthAndBlur();
         }
     }
+
+    /**
+     * Maps depth values to blur amounts as a percentage of the max blur.
+     * The blur percentage grows linearly with depth, and maxes out at 30% depth.
+     */
+    private static float mapDepthToBlur(float depth) {
+        return Math.min(3 * depth, 1f);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
index 245dde2..c39056d 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.animation.Animator;
@@ -104,6 +106,8 @@
     private float mCurrentScaleProgress;
     private FlingSpringAnim mRectXAnim;
     private FlingSpringAnim mRectYAnim;
+    private SpringAnimation mRectXSpring;
+    private SpringAnimation mRectYSpring;
     private SpringAnimation mRectScaleAnim;
     private boolean mAnimsStarted;
     private boolean mRectXAnimEnded;
@@ -166,27 +170,47 @@
     }
 
     public void onTargetPositionChanged() {
-        if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
-            mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
-        }
+        if (enableScalingRevealHomeAnimation()) {
+            if (mRectXSpring != null) {
+                mRectXSpring.animateToFinalPosition(mTargetRect.centerX());
+            }
 
-        if (mRectYAnim != null) {
-            switch (mTracking) {
-                case TRACKING_TOP:
-                    if (mRectYAnim.getTargetPosition() != mTargetRect.top) {
-                        mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
-                    }
-                    break;
-                case TRACKING_BOTTOM:
-                    if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
-                        mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
-                    }
-                    break;
-                case TRACKING_CENTER:
-                    if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
-                        mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY());
-                    }
-                    break;
+            if (mRectYSpring != null) {
+                switch (mTracking) {
+                    case TRACKING_TOP:
+                        mRectYSpring.animateToFinalPosition(mTargetRect.top);
+                        break;
+                    case TRACKING_BOTTOM:
+                        mRectYSpring.animateToFinalPosition(mTargetRect.bottom);
+                        break;
+                    case TRACKING_CENTER:
+                        mRectYSpring.animateToFinalPosition(mTargetRect.centerY());
+                        break;
+                }
+            }
+        } else {
+            if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) {
+                mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX());
+            }
+
+            if (mRectYAnim != null) {
+                switch (mTracking) {
+                    case TRACKING_TOP:
+                        if (mRectYAnim.getTargetPosition() != mTargetRect.top) {
+                            mRectYAnim.updatePosition(mCurrentY, mTargetRect.top);
+                        }
+                        break;
+                    case TRACKING_BOTTOM:
+                        if (mRectYAnim.getTargetPosition() != mTargetRect.bottom) {
+                            mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom);
+                        }
+                        break;
+                    case TRACKING_CENTER:
+                        if (mRectYAnim.getTargetPosition() != mTargetRect.centerY()) {
+                            mRectYAnim.updatePosition(mCurrentY, mTargetRect.centerY());
+                        }
+                        break;
+                }
             }
         }
     }
@@ -215,59 +239,126 @@
             maybeOnEnd();
         });
 
-        // We dampen the user velocity here to keep the natural feeling and to prevent the
-        // rect from straying too from a linear path.
-        final float xVelocityPxPerS = velocityPxPerMs.x * 1000;
-        final float yVelocityPxPerS = velocityPxPerMs.y * 1000;
-        final float dampedXVelocityPxPerS = OverScroll.dampedScroll(
-                Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS);
-        final float dampedYVelocityPxPerS = OverScroll.dampedScroll(
-                Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS);
-
+        float xVelocityPxPerS = velocityPxPerMs.x * 1000;
+        float yVelocityPxPerS = velocityPxPerMs.y * 1000;
         float startX = mCurrentCenterX;
         float endX = mTargetRect.centerX();
-        float minXValue = Math.min(startX, endX);
-        float maxXValue = Math.max(startX, endX);
-
-        mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
-                dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX, mStiffnessX,
-                onXEndListener);
-
         float startY = mCurrentY;
         float endY = getTrackedYFromRect(mTargetRect);
-        float minYValue = Math.min(startY, endY);
-        float maxYValue = Math.max(startY, endY);
-        mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY, dampedYVelocityPxPerS,
-                mMinVisChange, minYValue, maxYValue, mDampingY, mStiffnessY, onYEndListener);
-
         float minVisibleChange = Math.abs(1f / mStartRect.height());
-        ResourceProvider rp = DynamicResource.provider(context);
-        float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
 
-        // Increase the stiffness for devices where we want the window size to transform quicker.
-        boolean shouldUseHigherStiffness = profile != null
-                && (profile.isLandscape || profile.isTablet);
-        float stiffness = shouldUseHigherStiffness
-                ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
-                : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+        if (enableScalingRevealHomeAnimation()) {
+            ResourceProvider rp = DynamicResource.provider(context);
+            long minVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_x_px_per_s);
+            long maxVelocityXPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_x_px_per_s);
+            long minVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_min_velocity_y_px_per_s);
+            long maxVelocityYPxPerS = rp.getInt(R.dimen.swipe_up_max_velocity_y_px_per_s);
+            float fallOffFactor = rp.getFloat(R.dimen.swipe_up_max_velocity_fall_off_factor);
 
-        mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
-                .setSpring(new SpringForce(1f)
-                .setDampingRatio(damping)
-                .setStiffness(stiffness))
-                .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
-                .setMaxValue(1f)
-                .setMinimumVisibleChange(minVisibleChange)
-                .addEndListener((animation, canceled, value, velocity) -> {
-                    mRectScaleAnimEnded = true;
-                    maybeOnEnd();
-                });
+            // We want the actual initial velocity to never dip below the minimum, and to taper off
+            // once it's above the soft cap so that we can prevent the window from flying off
+            // screen, while maintaining a natural feel.
+            xVelocityPxPerS = adjustVelocity(
+                    xVelocityPxPerS, minVelocityXPxPerS, maxVelocityXPxPerS, fallOffFactor);
+            yVelocityPxPerS = adjustVelocity(
+                    yVelocityPxPerS, minVelocityYPxPerS, maxVelocityYPxPerS, fallOffFactor);
 
-        setCanRelease(false);
-        mAnimsStarted = true;
+            float stiffnessX = rp.getFloat(R.dimen.swipe_up_rect_x_stiffness);
+            float dampingX = rp.getFloat(R.dimen.swipe_up_rect_x_damping_ratio);
+            mRectXSpring =
+                    new SpringAnimation(this, RECT_CENTER_X)
+                            .setSpring(
+                                    new SpringForce(endX)
+                                            .setStiffness(stiffnessX)
+                                            .setDampingRatio(dampingX)
+                            ).setStartValue(startX)
+                            .setStartVelocity(xVelocityPxPerS)
+                            .addEndListener(onXEndListener);
 
-        mRectXAnim.start();
-        mRectYAnim.start();
+            float stiffnessY = rp.getFloat(R.dimen.swipe_up_rect_y_stiffness);
+            float dampingY = rp.getFloat(R.dimen.swipe_up_rect_y_damping_ratio);
+            mRectYSpring =
+                    new SpringAnimation(this, RECT_Y)
+                            .setSpring(
+                                    new SpringForce(endY)
+                                            .setStiffness(stiffnessY)
+                                            .setDampingRatio(dampingY)
+                            )
+                            .setStartValue(startY)
+                            .setStartVelocity(yVelocityPxPerS)
+                            .addEndListener(onYEndListener);
+
+            float stiffnessZ = rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness_v2);
+            float dampingZ = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio_v2);
+            mRectScaleAnim =
+                    new SpringAnimation(this, RECT_SCALE_PROGRESS)
+                            .setSpring(
+                                    new SpringForce(1f)
+                                            .setStiffness(stiffnessZ)
+                                            .setDampingRatio(dampingZ))
+                            .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
+                            .setMinimumVisibleChange(minVisibleChange)
+                            .addEndListener((animation, canceled, value, velocity) -> {
+                                mRectScaleAnimEnded = true;
+                                maybeOnEnd();
+                            });
+
+            setCanRelease(false);
+            mAnimsStarted = true;
+
+            mRectXSpring.start();
+            mRectYSpring.start();
+        } else {
+            // We dampen the user velocity here to keep the natural feeling and to prevent the
+            // rect from straying too from a linear path.
+            final float dampedXVelocityPxPerS = OverScroll.dampedScroll(
+                    Math.abs(xVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(xVelocityPxPerS);
+            final float dampedYVelocityPxPerS = OverScroll.dampedScroll(
+                    Math.abs(yVelocityPxPerS), mMaxVelocityPxPerS) * Math.signum(yVelocityPxPerS);
+
+            float minXValue = Math.min(startX, endX);
+            float maxXValue = Math.max(startX, endX);
+
+            mRectXAnim = new FlingSpringAnim(this, context, RECT_CENTER_X, startX, endX,
+                    dampedXVelocityPxPerS, mMinVisChange, minXValue, maxXValue, mDampingX,
+                    mStiffnessX, onXEndListener);
+
+            float minYValue = Math.min(startY, endY);
+            float maxYValue = Math.max(startY, endY);
+            mRectYAnim = new FlingSpringAnim(this, context, RECT_Y, startY, endY,
+                    dampedYVelocityPxPerS, mMinVisChange, minYValue, maxYValue, mDampingY,
+                    mStiffnessY, onYEndListener);
+
+            ResourceProvider rp = DynamicResource.provider(context);
+            float damping = rp.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio);
+
+            // Increase the stiffness for devices where we want the window size to transform
+            // quicker.
+            boolean shouldUseHigherStiffness = profile != null
+                    && (profile.isLandscape || profile.isTablet);
+            float stiffness = shouldUseHigherStiffness
+                    ? rp.getFloat(R.dimen.swipe_up_rect_scale_higher_stiffness)
+                    : rp.getFloat(R.dimen.swipe_up_rect_scale_stiffness);
+
+            mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
+                    .setSpring(new SpringForce(1f)
+                            .setDampingRatio(damping)
+                            .setStiffness(stiffness))
+                    .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
+                    .setMaxValue(1f)
+                    .setMinimumVisibleChange(minVisibleChange)
+                    .addEndListener((animation, canceled, value, velocity) -> {
+                        mRectScaleAnimEnded = true;
+                        maybeOnEnd();
+                    });
+
+            setCanRelease(false);
+            mAnimsStarted = true;
+
+            mRectXAnim.start();
+            mRectYAnim.start();
+        }
+
         mRectScaleAnim.start();
         for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
             animatorListener.onAnimationStart(null);
@@ -276,8 +367,17 @@
 
     public void end() {
         if (mAnimsStarted) {
-            mRectXAnim.end();
-            mRectYAnim.end();
+            if (enableScalingRevealHomeAnimation()) {
+                if (mRectXSpring.canSkipToEnd()) {
+                    mRectXSpring.skipToEnd();
+                }
+                if (mRectYSpring.canSkipToEnd()) {
+                    mRectYSpring.skipToEnd();
+                }
+            } else {
+                mRectXAnim.end();
+                mRectYAnim.end();
+            }
             if (mRectScaleAnim.canSkipToEnd()) {
                 mRectScaleAnim.skipToEnd();
             }
@@ -357,6 +457,32 @@
         end();
     }
 
+    /**
+     * Modify the given velocity so that it's never below the minimum value, and falls off by the
+     * given factor once it goes above the maximum value.
+     * In order for the max soft cap to be enforced, the fall-off factor must be >1.
+     */
+    private static float adjustVelocity(float velocity, long min, long max, float factor) {
+        float sign = Math.signum(velocity);
+        float magnitude = Math.abs(velocity);
+
+        // If the absolute velocity is less than the min, bump it up.
+        if (magnitude < min) {
+            return min * sign;
+        }
+
+        // If the absolute velocity falls between min and max, or the fall-off factor is invalid,
+        // do nothing.
+        if (magnitude <= max || factor <= 1) {
+            return velocity;
+        }
+
+        // Scale the excess velocity by the fall-off factor.
+        float excess = magnitude - max;
+        float scaled = (float) Math.pow(excess, 1f / factor);
+        return (max + scaled) * sign;
+    }
+
     public interface OnUpdateListener {
         /**
          * Called when an update is made to the animation.
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
new file mode 100644
index 0000000..33736ad
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.view.View
+import com.android.app.animation.Interpolators
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.Launcher
+import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE
+import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
+import com.android.launcher3.LauncherState
+import com.android.launcher3.anim.AnimatorListeners
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.anim.PropertySetter
+import com.android.launcher3.states.StateAnimationConfig
+import com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER
+import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
+import com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM
+import com.android.launcher3.uioverrides.QuickstepLauncher
+import com.android.quickstep.views.RecentsView
+
+/**
+ * Creates an animation where the workspace and hotseat fade in while revealing from the center of
+ * the screen outwards radially. This is used in conjunction with the swipe up to home animation.
+ */
+class ScalingWorkspaceRevealAnim(launcher: Launcher) {
+    companion object {
+        private const val FADE_DURATION_MS = 200L
+        private const val SCALE_DURATION_MS = 1000L
+        private const val MAX_ALPHA = 1f
+        private const val MIN_ALPHA = 0f
+        private const val MAX_SIZE = 1f
+        private const val MIN_SIZE = 0.85f
+    }
+
+    private val animation = PendingAnimation(SCALE_DURATION_MS)
+
+    init {
+        // Make sure the starting state is right for the animation.
+        val config = StateAnimationConfig()
+        config.animFlags = SKIP_OVERVIEW.or(SKIP_DEPTH_CONTROLLER).or(SKIP_SCRIM)
+        config.duration = 0
+        launcher.stateManager
+            .createAtomicAnimation(LauncherState.BACKGROUND_APP, LauncherState.NORMAL, config)
+            .start()
+        launcher
+            .getOverviewPanel<RecentsView<QuickstepLauncher, LauncherState>>()
+            .forceFinishScroller()
+        launcher.workspace.stateTransitionAnimation.setScrim(
+            PropertySetter.NO_ANIM_PROPERTY_SETTER,
+            LauncherState.BACKGROUND_APP,
+            config
+        )
+
+        val workspace = launcher.workspace
+        val hotseat = launcher.hotseat
+
+        // Scale the Workspace and Hotseat around the same pivot.
+        animation.addFloat(
+            workspace,
+            WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
+            MIN_SIZE,
+            MAX_SIZE,
+            EMPHASIZED,
+        )
+        workspace.setPivotToScaleWithSelf(hotseat)
+        animation.addFloat(
+            hotseat,
+            HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_WORKSPACE_STATE],
+            MIN_SIZE,
+            MAX_SIZE,
+            EMPHASIZED,
+        )
+
+        // Fade in quickly at the beginning of the animation, so the content doesn't look like it's
+        // popping into existence out of nowhere.
+        val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS
+        workspace.alpha = MIN_ALPHA
+        animation.setViewAlpha(
+            workspace,
+            MAX_ALPHA,
+            Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+        )
+        hotseat.alpha = MIN_ALPHA
+        animation.setViewAlpha(
+            hotseat,
+            MAX_ALPHA,
+            Interpolators.clampToProgress(LINEAR, 0f, fadeClamp)
+        )
+
+        // Match the Wallpaper animation to the rest of the content.
+        val depthController = (launcher as? QuickstepLauncher)?.depthController
+        val depthConfig = StateAnimationConfig()
+        depthConfig.setInterpolator(StateAnimationConfig.ANIM_DEPTH, EMPHASIZED)
+        depthController?.setStateWithAnimation(LauncherState.NORMAL, depthConfig, animation)
+
+        // Needed to avoid text artefacts during the scale animation.
+        workspace.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+        hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+        animation.addListener(
+            AnimatorListeners.forEndCallback(
+                Runnable {
+                    workspace.setLayerType(View.LAYER_TYPE_NONE, null)
+                    hotseat.setLayerType(View.LAYER_TYPE_NONE, null)
+                }
+            )
+        )
+    }
+
+    fun start() {
+        animation.buildAnim().start()
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 6b3199f..d6d6a11 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -65,7 +65,7 @@
     // Should be used for animations running alongside this StaggeredWorkspaceAnim.
     public static final int DURATION_MS = 250;
     public static final int DURATION_TASKBAR_MS =
-            QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
+            QuickstepTransitionManager.getTaskbarToHomeDuration();
 
     private static final float MAX_VELOCITY_PX_PER_S = 22f;
 
diff --git a/res/values/config.xml b/res/values/config.xml
index 21d6024..048d6cc 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -159,6 +159,9 @@
     <item name="swipe_up_rect_scale_damping_ratio" type="dimen" format="float">0.75</item>
     <item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
     <item name="swipe_up_rect_scale_higher_stiffness" type="dimen" format="float">400</item>
+    <!-- Flag: enableScalingRevealHomeAnimation() -->
+    <item name="swipe_up_rect_scale_damping_ratio_v2" type="dimen" format="float">0.8</item>
+    <item name="swipe_up_rect_scale_stiffness_v2" type="dimen" format="float">650</item>
 
     <item name="swipe_up_rect_xy_fling_friction" type="dimen" format="float">1.5</item>
 
@@ -166,6 +169,11 @@
 
     <item name="swipe_up_rect_xy_damping_ratio" type="dimen" format="float">0.8</item>
     <item name="swipe_up_rect_xy_stiffness" type="dimen" format="float">200</item>
+    <!-- Flag: enableScalingRevealHomeAnimation() -->
+    <item name="swipe_up_rect_x_damping_ratio" type="dimen" format="float">0.965</item>
+    <item name="swipe_up_rect_x_stiffness" type="dimen" format="float">300</item>
+    <item name="swipe_up_rect_y_damping_ratio" type="dimen" format="float">0.95</item>
+    <item name="swipe_up_rect_y_stiffness" type="dimen" format="float">190</item>
 
     <!-- Taskbar -->
     <!-- This is a float because it is converted to dp later in DeviceProfile -->
@@ -190,6 +198,12 @@
     <dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
     <dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
     <dimen name="swipe_up_max_velocity">7.619dp</dimen>
+    <!-- Flag: enableScalingRevealHomeAnimation() -->
+    <item name="swipe_up_min_velocity_x_px_per_s" type="dimen" format="integer">300</item>
+    <item name="swipe_up_max_velocity_x_px_per_s" type="dimen" format="integer">500</item>
+    <item name="swipe_up_min_velocity_y_px_per_s" type="dimen" format="integer">2000</item>
+    <item name="swipe_up_max_velocity_y_px_per_s" type="dimen" format="integer">5000</item>
+    <item name="swipe_up_max_velocity_fall_off_factor" type="dimen" format="float">1.4</item>
 
     <array name="dynamic_resources">
         <item>@dimen/swipe_up_scale_start</item>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4b4bdc2..9afbc64 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -18,6 +18,7 @@
 
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.Flags.enableOverviewIconMenu;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
@@ -436,7 +437,11 @@
             if (isMultiDisplay && !ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH.get()) {
                 // TODO(b/259893832): Revert to use maxWallpaperScale to calculate bottomSheetDepth
                 // when screen recorder bug is fixed.
-                bottomSheetDepth = 1f;
+                if (enableScalingRevealHomeAnimation()) {
+                    bottomSheetDepth = 0.3f;
+                } else {
+                    bottomSheetDepth = 1f;
+                }
             } else {
                 // The goal is to set wallpaper to zoom at workspaceContentScale when in AllApps.
                 // When depth is 0, wallpaper zoom is set to maxWallpaperScale.
diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt
index aafaaa0..6ff47ae 100644
--- a/src/com/android/launcher3/states/EditModeState.kt
+++ b/src/com/android/launcher3/states/EditModeState.kt
@@ -16,6 +16,7 @@
 package com.android.launcher3.states
 
 import android.content.Context
+import com.android.launcher3.Flags.enableScalingRevealHomeAnimation
 import com.android.launcher3.Launcher
 import com.android.launcher3.LauncherState
 import com.android.launcher3.logging.StatsLogManager
@@ -25,6 +26,8 @@
 class EditModeState(id: Int) : LauncherState(id, StatsLogManager.LAUNCHER_STATE_HOME, STATE_FLAGS) {
 
     companion object {
+        const val DEPTH_15_PERCENT = 0.15f
+
         private val STATE_FLAGS =
             (FLAG_MULTI_PAGE or
                 FLAG_WORKSPACE_INACCESSIBLE or
@@ -40,7 +43,11 @@
     }
 
     override fun <T> getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? {
-        return 0.5f
+        if (enableScalingRevealHomeAnimation()) {
+            return DEPTH_15_PERCENT
+        } else {
+            return 0.5f
+        }
     }
 
     override fun getWorkspaceScaleAndTranslation(launcher: Launcher): ScaleAndTranslation {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index 4cfced8..bf2fb30 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.states;
 
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 
 import android.content.Context;
@@ -34,6 +35,8 @@
     private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
             | FLAG_HAS_SYS_UI_SCRIM;
 
+    public static final float DEPTH_5_PERCENT = 0.05f;
+
     public HintState(int id) {
         this(id, LAUNCHER_STATE_HOME);
     }
@@ -49,7 +52,11 @@
 
     @Override
     protected float getDepthUnchecked(Context context) {
-        return 0.15f;
+        if (enableScalingRevealHomeAnimation()) {
+            return DEPTH_5_PERCENT;
+        } else {
+            return 0.15f;
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 3286afb..2e57ed8 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.states;
 
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 
 import android.content.Context;
@@ -33,6 +34,8 @@
             | FLAG_WORKSPACE_INACCESSIBLE | FLAG_DISABLE_RESTORE
             | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_WORKSPACE_HAS_BACKGROUNDS;
 
+    public static final float DEPTH_15_PERCENT = 0.15f;
+
     public SpringLoadedState(int id) {
         super(id, LAUNCHER_STATE_HOME, STATE_FLAGS);
     }
@@ -62,7 +65,11 @@
 
     @Override
     protected float getDepthUnchecked(Context context) {
-        return 0.5f;
+        if (enableScalingRevealHomeAnimation()) {
+            return DEPTH_15_PERCENT;
+        } else {
+            return 0.5f;
+        }
     }
 
     @Override