Merge "Add logging for adding pending icons to the workspace." into sc-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 009ca27..827eb7d 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -693,7 +693,7 @@
                             matrix.postTranslate(windowTransX0, windowTransY0);
                         }
 
-                        floatingView.update(floatingIconBounds, mIconAlpha.value, percent, 0f,
+                        floatingView.update(mIconAlpha.value, 255, floatingIconBounds, percent, 0f,
                                 mWindowRadius.value * scale, true /* isOpening */);
                         builder.withMatrix(matrix)
                                 .withWindowCrop(crop)
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index dbb8272..1b59c49 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1252,7 +1252,7 @@
             HomeAnimationFactory homeAnimationFactory) {
         RectFSpringAnim anim =
                 super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
-        anim.addOnUpdateListener((r, p) -> {
+        anim.addOnUpdateListener((v, r, p) -> {
             updateSysUiFlags(Math.max(p, mCurrentShift.value));
         });
         anim.addAnimatorListener(new AnimationSuccessListener() {
@@ -1395,7 +1395,9 @@
         mActivityInterface.onTransitionCancelled(wasVisible);
 
         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
-        mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+        if (mActivity != null) {
+            mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+        }
     }
 
     protected void switchToScreenshot() {
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index ec1cc4a..f5698f7 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -48,6 +48,7 @@
 import android.view.SurfaceControl.Transaction;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
@@ -56,6 +57,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.AppCloseConfig;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.TransformParams.BuilderProxy;
@@ -298,7 +300,8 @@
         }
 
         @Override
-        public void update(RectF currentRect, float progress, float radius) {
+        public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
+                 float radius) {
             if (mSurfaceControl != null) {
                 currentRect.roundOut(mTempRect);
                 Transaction t = new Transaction();
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 311ac83..5ecd385 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -15,26 +15,45 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.Utilities.dpToPx;
+import static com.android.launcher3.Utilities.mapToRange;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
+import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.RectF;
 import android.os.UserHandle;
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.views.FloatingIconView;
+import com.android.quickstep.util.AppCloseConfig;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.system.InputConsumerController;
 
 /**
@@ -66,24 +85,41 @@
             } else {
                 workspaceView = null;
             }
-            final RectF iconLocation = new RectF();
             boolean canUseWorkspaceView =
                     workspaceView != null && workspaceView.isAttachedToWindow();
-            FloatingIconView floatingIconView = canUseWorkspaceView
-                    ? FloatingIconView.getFloatingIconView(mActivity, workspaceView,
-                    true /* hideOriginal */, iconLocation, false /* isOpening */)
-                    : null;
 
             mActivity.getRootView().setForceHideBackArrow(true);
             mActivity.setHintUserWillBeActive();
 
             if (canUseWorkspaceView) {
+                final ResourceProvider rp = DynamicResource.provider(mActivity);
+                final float transY = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
+                float dpPerSecond = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp_per_s));
+                final float launcherAlphaMax =
+                        rp.getFloat(R.dimen.swipe_up_launcher_alpha_max_progress);
+
+                RectF iconLocation = new RectF();
+                FloatingIconView floatingIconView = getFloatingIconView(mActivity, workspaceView,
+                        true /* hideOriginal */, iconLocation, false /* isOpening */);
+
                 // We want the window alpha to be 0 once this threshold is met, so that the
                 // FolderIconView can be seen morphing into the icon shape.
                 float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
                 homeAnimFactory = new LauncherHomeAnimationFactory() {
+
+                    // There is a delay in loading the icon, so we need to keep the window
+                    // opaque until it is ready.
+                    private boolean mIsFloatingIconReady = false;
+
+                    private @Nullable ValueAnimator mBounceBackAnimator;
+
                     @Override
                     public RectF getWindowTargetRect() {
+                        if (PROTOTYPE_APP_CLOSE.get()) {
+                            // We want the target rect to be at this offset position, so that all
+                            // launcher content can spring back upwards.
+                            floatingIconView.setPositionOffsetY(transY);
+                        }
                         return iconLocation;
                     }
 
@@ -92,17 +128,92 @@
                         anim.addAnimatorListener(floatingIconView);
                         floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
                         floatingIconView.setFastFinishRunnable(anim::end);
+                        if (PROTOTYPE_APP_CLOSE.get()) {
+                            mBounceBackAnimator = bounceBackToRestingPosition();
+                            // Use a spring to put drag layer translation back to 0.
+                            anim.addAnimatorListener(new AnimatorListenerAdapter() {
+                                @Override
+                                public void onAnimationEnd(Animator animation) {
+                                    floatingIconView.setPositionOffsetY(0);
+                                    mBounceBackAnimator.start();
+                                }
+                            });
+
+                            Workspace workspace = mActivity.getWorkspace();
+                            workspace.setPivotToScaleWithSelf(mActivity.getHotseat());
+                        }
+                    }
+
+                    private ValueAnimator bounceBackToRestingPosition() {
+                        DragLayer dl = mActivity.getDragLayer();
+                        Workspace workspace = mActivity.getWorkspace();
+                        Hotseat hotseat = mActivity.getHotseat();
+
+                        final float startValue = transY;
+                        final float endValue = 0;
+                        // Ensures the velocity is always aligned with the direction.
+                        float pixelPerSecond = Math.abs(dpPerSecond)
+                                * Math.signum(endValue - transY);
+
+                        ValueAnimator springTransY = new SpringAnimationBuilder(dl.getContext())
+                                .setStiffness(rp.getFloat(R.dimen.swipe_up_trans_y_stiffness))
+                                .setDampingRatio(rp.getFloat(R.dimen.swipe_up_trans_y_damping))
+                                .setMinimumVisibleChange(1f)
+                                .setStartValue(startValue)
+                                .setEndValue(endValue)
+                                .setStartVelocity(pixelPerSecond)
+                                .build(dl, VIEW_TRANSLATE_Y);
+                        springTransY.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                dl.setTranslationY(0f);
+                                dl.setAlpha(1f);
+                                SCALE_PROPERTY.set(workspace, 1f);
+                                SCALE_PROPERTY.set(hotseat, 1f);
+                            }
+                        });
+                        return springTransY;
                     }
 
                     @Override
-                    public void update(RectF currentRect, float progress, float radius) {
-                        floatingIconView.update(currentRect, 1f, progress, windowAlphaThreshold,
-                                radius, false);
+                    public boolean keepWindowOpaque() {
+                        if (mIsFloatingIconReady || floatingIconView.isVisibleToUser()) {
+                            mIsFloatingIconReady = true;
+                            return false;
+                        }
+                        return true;
+                    }
+
+                    @Override
+                    public void update(@Nullable AppCloseConfig config, RectF currentRect,
+                            float progress, float radius) {
+                        int fgAlpha = 255;
+                        if (config != null && PROTOTYPE_APP_CLOSE.get()) {
+                            DragLayer dl = mActivity.getDragLayer();
+                            float translationY = config.getWorkspaceTransY();
+                            dl.setTranslationY(translationY);
+
+                            float alpha = mapToRange(progress, 0, launcherAlphaMax, 0, 1f, LINEAR);
+                            dl.setAlpha(Math.min(alpha, 1f));
+
+                            float scale = Math.min(1f, config.getWorkspaceScale());
+                            SCALE_PROPERTY.set(mActivity.getWorkspace(), scale);
+                            SCALE_PROPERTY.set(mActivity.getHotseat(), scale);
+                            SCALE_PROPERTY.set(mActivity.getAppsView(), scale);
+
+                            progress = config.getInterpolatedProgress();
+                            fgAlpha = config.getFgAlpha();
+                        }
+                        floatingIconView.update(1f, fgAlpha, currentRect, progress,
+                                windowAlphaThreshold, radius, false);
                     }
 
                     @Override
                     public void onCancel() {
                         floatingIconView.fastFinish();
+                        if (mBounceBackAnimator != null) {
+                            mBounceBackAnimator.cancel();
+                        }
                     }
                 };
             } else {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index a8c09dc..0f34a72 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
 
 import android.animation.Animator;
 import android.content.Context;
@@ -26,6 +27,7 @@
 import android.graphics.RectF;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
@@ -35,7 +37,9 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.AppCloseConfig;
 import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.RectFSpringAnim2;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.TransformParams.BuilderProxy;
@@ -149,7 +153,10 @@
 
         public void setAnimation(RectFSpringAnim anim) { }
 
-        public void update(RectF currentRect, float progress, float radius) { }
+        public boolean keepWindowOpaque() { return false; }
+
+        public void update(@Nullable AppCloseConfig config, RectF currentRect, float progress,
+                float radius) { }
 
         public void onCancel() { }
 
@@ -199,7 +206,14 @@
         homeToWindowPositionMap.invert(windowToHomePositionMap);
         windowToHomePositionMap.mapRect(startRect);
 
-        RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext);
+        RectFSpringAnim anim;
+        if (PROTOTYPE_APP_CLOSE.get()) {
+            anim = new RectFSpringAnim2(startRect, targetRect, mContext,
+                    mTaskViewSimulator.getCurrentCornerRadius(),
+                    cropRectF.width() / 2f);
+        } else {
+            anim = new RectFSpringAnim(startRect, targetRect, mContext);
+        }
         homeAnimationFactory.setAnimation(anim);
 
         SpringAnimationRunner runner = new SpringAnimationRunner(
@@ -259,18 +273,26 @@
         }
 
         @Override
-        public void onUpdate(RectF currentRect, float progress) {
+        public void onUpdate(@Nullable AppCloseConfig config, RectF currentRect, float progress) {
             mHomeAnim.setPlayFraction(progress);
             mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
 
             mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
             float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
+            float alpha = getWindowAlpha(progress);
+            if (config != null && PROTOTYPE_APP_CLOSE.get()) {
+                alpha = config.getWindowAlpha();
+                cornerRadius = config.getCornerRadius();
+            }
+            if (mAnimationFactory.keepWindowOpaque()) {
+                alpha = 1f;
+            }
             mTransformParams
-                    .setTargetAlpha(getWindowAlpha(progress))
+                    .setTargetAlpha(alpha)
                     .setCornerRadius(cornerRadius);
-
             mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this));
-            mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
+            mAnimationFactory.update(config, currentRect, progress,
+                    mMatrix.mapRadius(cornerRadius));
         }
 
         @Override
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 8fc453d..1aa64fa 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -52,6 +52,7 @@
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.SwipeUpAnimationLogic;
 import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
+import com.android.quickstep.util.AppCloseConfig;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
@@ -306,11 +307,12 @@
                 }
 
                 @Override
-                public void update(RectF rect, float progress, float radius) {
+                public void update(@Nullable AppCloseConfig config, RectF rect, float progress,
+                        float radius) {
                     mFakeIconView.setVisibility(View.VISIBLE);
                     mFakeIconView.update(rect, progress,
                             1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
-                            radius,
+                            radius, 255,
                             false, /* isOpening */
                             mFakeIconView, mDp,
                             false /* isVerticalBarLayout */);
diff --git a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java b/quickstep/src/com/android/quickstep/util/AppCloseConfig.java
new file mode 100644
index 0000000..bec3379
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AppCloseConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.annotation.FloatRange;
+import android.annotation.IntRange;
+
+/*
+ * Adds getter methods to {@link MultiValueUpdateListener} specific to app close animation,
+ * so that the entire animation can be defined in one place.
+ */
+public abstract class AppCloseConfig extends MultiValueUpdateListener {
+
+    /**
+     * Returns the translation y of the workspace contents.
+     */
+    public abstract float getWorkspaceTransY();
+
+    /*
+     * Returns the scale of the workspace contents.
+     */
+    public abstract float getWorkspaceScale();
+
+    /*
+     * Returns the alpha of the window.
+     */
+    public abstract @FloatRange(from = 0, to = 1) float getWindowAlpha();
+
+    /*
+     * Returns the alpha of the foreground layer of an adaptive icon.
+     */
+    public abstract @IntRange(from = 0, to = 255) int getFgAlpha();
+
+    /*
+     * Returns the corner radius of the window and icon.
+     */
+    public abstract float getCornerRadius();
+
+    /*
+     * Returns the interpolated progress of the animation.
+     */
+    public abstract float getInterpolatedProgress();
+
+}
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
index e5d2c53..02ec68a 100644
--- a/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -20,6 +20,7 @@
 import android.graphics.PointF;
 import android.graphics.RectF;
 
+import androidx.annotation.Nullable;
 import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
 import androidx.dynamicanimation.animation.FloatPropertyCompat;
 import androidx.dynamicanimation.animation.SpringAnimation;
@@ -241,7 +242,7 @@
                         mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight);
             }
             for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
-                onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress);
+                onUpdateListener.onUpdate(null, mCurrentRect, mCurrentScaleProgress);
             }
         }
     }
@@ -266,7 +267,7 @@
     }
 
     public interface OnUpdateListener {
-        void onUpdate(RectF currentRect, float progress);
+        void onUpdate(@Nullable AppCloseConfig values, RectF currentRect, float progress);
 
         default void onCancel() { }
     }
diff --git a/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java
new file mode 100644
index 0000000..95d56aa
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/RectFSpringAnim2.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2021 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 static com.android.launcher3.Utilities.dpToPx;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.PathParser;
+import android.util.Property;
+import android.view.animation.Interpolator;
+
+import androidx.core.view.animation.PathInterpolatorCompat;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DynamicResource;
+import com.android.systemui.plugins.ResourceProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Applies spring forces to animate from a starting rect to a target rect,
+ * while providing update callbacks to the caller.
+ */
+public class RectFSpringAnim2 extends RectFSpringAnim {
+
+    private static final FloatPropertyCompat<RectFSpringAnim2> RECT_CENTER_X =
+            new FloatPropertyCompat<RectFSpringAnim2>("rectCenterXSpring") {
+                @Override
+                public float getValue(RectFSpringAnim2 anim) {
+                    return anim.mCurrentCenterX;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim2 anim, float currentCenterX) {
+                    anim.mCurrentCenterX = currentCenterX;
+                    anim.onUpdate();
+                }
+            };
+
+    private static final FloatPropertyCompat<RectFSpringAnim2> RECT_Y =
+            new FloatPropertyCompat<RectFSpringAnim2>("rectYSpring") {
+                @Override
+                public float getValue(RectFSpringAnim2 anim) {
+                    return anim.mCurrentY;
+                }
+
+                @Override
+                public void setValue(RectFSpringAnim2 anim, float y) {
+                    anim.mCurrentY = y;
+                    anim.onUpdate();
+                }
+            };
+
+    private static final Property<RectFSpringAnim2, Float> PROGRESS =
+            new Property<RectFSpringAnim2, Float>(Float.class, "rectFProgress") {
+                @Override
+                public Float get(RectFSpringAnim2 rectFSpringAnim) {
+                    return rectFSpringAnim.mProgress;
+                }
+
+                @Override
+                public void set(RectFSpringAnim2 rectFSpringAnim, Float progress) {
+                    rectFSpringAnim.mProgress = progress;
+                    rectFSpringAnim.onUpdate();
+                }
+            };
+
+    private final RectF mStartRect;
+    private final RectF mTargetRect;
+    private final RectF mCurrentRect = new RectF();
+    private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>();
+    private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>();
+
+    private float mCurrentCenterX;
+    private float mCurrentY;
+
+    private float mTargetX;
+    private float mTargetY;
+
+    // If true, tracking the bottom of the rects, else tracking the top.
+    private final boolean mTrackingBottomY;
+    private float mProgress;
+    private SpringAnimation mRectXAnim;
+    private SpringAnimation mRectYAnim;
+    private ValueAnimator mRectScaleAnim;
+    private boolean mAnimsStarted;
+    private boolean mRectXAnimEnded;
+    private boolean mRectYAnimEnded;
+    private boolean mRectScaleAnimEnded;
+
+    private final float mXDamping;
+    private final float mXStiffness;
+
+    private final float mYDamping;
+    private float mYStiffness;
+
+    private long mDuration;
+
+    private final Interpolator mCloseInterpolator;
+
+    private AppCloseConfig mValues;
+    final float mStartRadius;
+    final float mEndRadius;
+
+    final float mHomeTransYEnd;
+    final float mScaleStart;
+
+    public RectFSpringAnim2(RectF startRect, RectF targetRect, Context context, float startRadius,
+            float endRadius) {
+        super(startRect, targetRect, context);
+        mStartRect = startRect;
+        mTargetRect = targetRect;
+
+        mTrackingBottomY = startRect.bottom < targetRect.bottom;
+        mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top;
+        mCurrentCenterX = mStartRect.centerX();
+
+        mTargetY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top;
+        mTargetX = mTargetRect.centerX();
+
+        ResourceProvider rp = DynamicResource.provider(context);
+        mXDamping = rp.getFloat(R.dimen.swipe_up_rect_2_x_damping_ratio);
+        mXStiffness = rp.getFloat(R.dimen.swipe_up_rect_2_x_stiffness);
+
+        mYDamping = rp.getFloat(R.dimen.swipe_up_rect_2_y_damping_ratio);
+        mYStiffness = rp.getFloat(R.dimen.swipe_up_rect_2_y_stiffness);
+        mDuration = Math.round(rp.getFloat(R.dimen.swipe_up_duration));
+
+        mHomeTransYEnd = dpToPx(rp.getFloat(R.dimen.swipe_up_trans_y_dp));
+        mScaleStart = rp.getFloat(R.dimen.swipe_up_scale_start);
+
+
+        if (!mTrackingBottomY) {
+            mYStiffness *= rp.getFloat(R.dimen.swipe_up_rect_2_y_stiffness_low_swipe_multiplier);
+            mDuration *= rp.getFloat(R.dimen.swipe_up_low_swipe_duration_multiplier);
+        }
+
+        mCloseInterpolator = getAppCloseInterpolator(context);
+
+        // End on a "round-enough" radius so that the shape reveal doesn't have to do too much
+        // rounding at the end of the animation.
+        mStartRadius = startRadius;
+        mEndRadius = endRadius;
+
+        setCanRelease(true);
+    }
+
+    public void onTargetPositionChanged() {
+        if (mRectXAnim != null && mTargetX != mTargetRect.centerX()) {
+            mTargetX = mTargetRect.centerX();
+            mRectXAnim.animateToFinalPosition(mTargetX);
+        }
+
+        if (mRectYAnim != null) {
+            if (mTrackingBottomY && mTargetY != mTargetRect.bottom) {
+                mTargetY = mTargetRect.bottom;
+                mRectYAnim.animateToFinalPosition(mTargetY);
+            } else if (!mTrackingBottomY && mTargetY != mTargetRect.top) {
+                mTargetY = mTargetRect.top;
+                mRectYAnim.animateToFinalPosition(mTargetY);
+            }
+        }
+    }
+
+    public void addOnUpdateListener(OnUpdateListener onUpdateListener) {
+        mOnUpdateListeners.add(onUpdateListener);
+    }
+
+    public void addAnimatorListener(Animator.AnimatorListener animatorListener) {
+        mAnimatorListeners.add(animatorListener);
+    }
+
+    /**
+     * Starts the fling/spring animation.
+     * @param context The activity context.
+     * @param velocityPxPerMs Velocity of swipe in px/ms.
+     */
+    public void start(Context context, PointF velocityPxPerMs) {
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context);
+
+        mRectXAnim = new SpringAnimation(this, RECT_CENTER_X)
+                .setStartValue(mCurrentCenterX)
+                .setMinValue(Math.min(0, mCurrentCenterX))
+                .setMaxValue(Math.max(dp.widthPx, mCurrentCenterX))
+                .setStartVelocity(velocityPxPerMs.x * 1000)
+                .setSpring(new SpringForce(mTargetX)
+                        .setStiffness(mXStiffness)
+                        .setDampingRatio(mXDamping));
+        mRectXAnim.addEndListener(((animation, canceled, centerX, velocityX) -> {
+            mRectXAnimEnded = true;
+            maybeOnEnd();
+        }));
+
+        mRectYAnim = new SpringAnimation(this, RECT_Y)
+                .setStartValue(mCurrentY)
+                .setMinValue(Math.min(0, mCurrentY))
+                .setMaxValue(Math.max(dp.heightPx, mCurrentY))
+                .setStartVelocity(velocityPxPerMs.y * 1000)
+                .setSpring(new SpringForce(mTargetY)
+                        .setStiffness(mYStiffness)
+                        .setDampingRatio(mYDamping));
+        mRectYAnim.addEndListener(((animation, canceled, centerY, velocityY) -> {
+            mRectYAnimEnded = true;
+            maybeOnEnd();
+        }));
+
+        mRectScaleAnim = ObjectAnimator.ofFloat(this, PROGRESS, 0, 1f)
+                .setDuration(mDuration);
+        mRectScaleAnim.setInterpolator(mCloseInterpolator);
+        mRectScaleAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mRectScaleAnimEnded = true;
+                maybeOnEnd();
+            }
+        });
+
+        mValues = buildConfig();
+        mRectScaleAnim.addUpdateListener(mValues);
+
+        setCanRelease(false);
+        mAnimsStarted = true;
+
+        mRectXAnim.start();
+        mRectYAnim.start();
+        mRectScaleAnim.start();
+        for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+            animatorListener.onAnimationStart(null);
+        }
+    }
+
+    private AppCloseConfig buildConfig() {
+        return new AppCloseConfig() {
+            FloatProp mHomeTransY = new FloatProp(0, mHomeTransYEnd, 0, mDuration, LINEAR);
+            FloatProp mHomeScale = new FloatProp(mScaleStart, 1f, 0, mDuration, LINEAR);
+            FloatProp mWindowFadeOut = new FloatProp(1f, 0f, 0, 116, LINEAR);
+            // There should be a slight overlap b/w window fading out and fg fading in.
+            // (fg startDelay < window fade out duration)
+            FloatProp mFgFadeIn = new FloatProp(0, 255f, 100, mDuration - 100, LINEAR);
+            FloatProp mRadius = new FloatProp(mStartRadius, mEndRadius, 0, mDuration, LINEAR);
+            FloatProp mThreePointInterpolation = new FloatProp(0, 1, 0, mDuration, LINEAR);
+
+            @Override
+            public float getWorkspaceTransY() {
+                return mHomeTransY.value;
+            }
+
+            @Override
+            public float getWorkspaceScale() {
+                return mHomeScale.value;
+            }
+
+            @Override
+            public float getWindowAlpha() {
+                return mWindowFadeOut.value;
+            }
+
+            @Override
+            public int getFgAlpha() {
+                return (int) mFgFadeIn.value;
+            }
+
+            @Override
+            public float getCornerRadius() {
+                return mRadius.value;
+            }
+
+            @Override
+            public float getInterpolatedProgress() {
+                return mThreePointInterpolation.value;
+            }
+
+            @Override
+            public void onUpdate(float percent) {}
+        };
+    }
+
+    public void end() {
+        if (mAnimsStarted) {
+            if (mRectXAnim.canSkipToEnd()) {
+                mRectXAnim.skipToEnd();
+            }
+            if (mRectYAnim.canSkipToEnd()) {
+                mRectYAnim.skipToEnd();
+            }
+            mRectScaleAnim.end();
+        }
+        mRectXAnimEnded = true;
+        mRectYAnimEnded = true;
+        mRectScaleAnimEnded = true;
+        maybeOnEnd();
+    }
+
+    private boolean isEnded() {
+        return mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded;
+    }
+
+    private void onUpdate() {
+        if (isEnded()) {
+            // Prevent further updates from being called. This can happen between callbacks for
+            // ending the x/y/scale animations.
+            return;
+        }
+
+        if (!mOnUpdateListeners.isEmpty()) {
+            float rectProgress = mProgress;
+            float currentWidth = Utilities.mapRange(rectProgress, mStartRect.width(),
+                    mTargetRect.width());
+            float currentHeight = Utilities.mapRange(rectProgress, mStartRect.height(),
+                    mTargetRect.height());
+            if (mTrackingBottomY) {
+                mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY - currentHeight,
+                        mCurrentCenterX + currentWidth / 2, mCurrentY);
+            } else {
+                mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY,
+                        mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight);
+            }
+
+            float currentPlayTime = mRectScaleAnimEnded ? mRectScaleAnim.getDuration()
+                    : mRectScaleAnim.getCurrentPlayTime();
+            float linearProgress = Math.min(1f, currentPlayTime / mRectScaleAnim.getDuration());
+            for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+                onUpdateListener.onUpdate(mValues, mCurrentRect, linearProgress);
+            }
+        }
+    }
+
+    private void maybeOnEnd() {
+        if (mAnimsStarted && isEnded()) {
+            mAnimsStarted = false;
+            setCanRelease(true);
+            for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
+                animatorListener.onAnimationEnd(null);
+            }
+        }
+    }
+
+    public void cancel() {
+        if (mAnimsStarted) {
+            for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+                onUpdateListener.onCancel();
+            }
+        }
+        end();
+    }
+
+    private Interpolator getAppCloseInterpolator(Context context) {
+        ResourceProvider rp = DynamicResource.provider(context);
+        String path = String.format("M 0,0 C %f, %f, %f, %f, %f, %f C %f, %f, %f, %f, 1, 1",
+                rp.getFloat(R.dimen.c1_a),
+                rp.getFloat(R.dimen.c1_b),
+                rp.getFloat(R.dimen.c1_c),
+                rp.getFloat(R.dimen.c1_d),
+                rp.getFloat(R.dimen.mp_x),
+                rp.getFloat(R.dimen.mp_y),
+                rp.getFloat(R.dimen.c2_a),
+                rp.getFloat(R.dimen.c2_b),
+                rp.getFloat(R.dimen.c2_c),
+                rp.getFloat(R.dimen.c2_d));
+        return PathInterpolatorCompat.create(PathParser.createPathFromPathData(path));
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 1df459e..de6c4f5 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
@@ -220,6 +221,9 @@
      * @param totalRows Total number of rows.
      */
     private void addStaggeredAnimationForView(View v, int row, int totalRows) {
+        if (PROTOTYPE_APP_CLOSE.get()) {
+            return;
+        }
         // Invert the rows, because we stagger starting from the bottom of the screen.
         int invertedRow = totalRows - row;
         // Add 1 to the inverted row so that the bottom most row has a start delay.
diff --git a/res/values/config.xml b/res/values/config.xml
index db98811..b75af7f 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -134,8 +134,45 @@
     <item name="swipe_up_rect_scale_stiffness" type="dimen" format="float">200</item>
 
     <item name="swipe_up_rect_xy_fling_friction" type="dimen" format="float">1.5</item>
+
+    <item name="swipe_up_scale_start"  type="dimen" format="float">0.98</item>
+    <item name="swipe_up_duration"  type="dimen" format="float">500</item>
+
+    <item name="swipe_up_trans_y_dp"  type="dimen" format="float">3</item>
+    <item name="swipe_up_trans_y_dp_per_s" type="dimen" format="float">3</item>
+
+    <item name="swipe_up_trans_y_damping" type="dimen" format="float">0.4</item>
+    <item name="swipe_up_trans_y_stiffness" type="dimen" format="float">200</item>
+
     <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>
+    <item name="swipe_up_rect_xy_stiffness" type="dimen" format="float">100</item>
+
+
+    <item name="swipe_up_rect_2_x_damping_ratio" type="dimen" format="float">1</item>
+    <item name="swipe_up_rect_2_x_stiffness" type="dimen" format="float">350</item>
+
+    <item name="swipe_up_rect_2_y_damping_ratio" type="dimen" format="float">1</item>
+    <item name="swipe_up_rect_2_y_stiffness" type="dimen" format="float">700</item>
+
+    <item name="swipe_up_rect_2_y_stiffness_low_swipe_multiplier" type="dimen" format="float">1</item>
+    <item name="swipe_up_low_swipe_duration_multiplier"  type="dimen" format="float">1</item>
+
+    <item name="swipe_up_launcher_alpha_max_progress" type="dimen" format="float">0.85</item>
+
+
+    <item name="c1_a" type="dimen" format="float">0.05</item>
+    <item name="c1_b" type="dimen" format="float">0</item>
+    <item name="c1_c" type="dimen" format="float">0.133333</item>
+    <item name="c1_d" type="dimen" format="float">0.06</item>
+
+    <item name="mp_x" type="dimen" format="float">0.166666</item>
+    <item name="mp_y" type="dimen" format="float">.4</item>
+
+    <item name="c2_a" type="dimen" format="float">0.208333</item>
+    <item name="c2_b" type="dimen" format="float">.82</item>
+    <item name="c2_c" type="dimen" format="float">.25</item>
+    <item name="c2_d" type="dimen" format="float">1</item>
+
 
     <item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="staggered_stiffness" type="dimen" format="float">150</item>
@@ -151,35 +188,32 @@
     <dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
 
     <array name="dynamic_resources">
-        <item>@dimen/all_apps_spring_damping_ratio</item>
-        <item>@dimen/all_apps_spring_stiffness</item>
+        <item>@dimen/swipe_up_duration</item>
+        <item>@dimen/swipe_up_scale_start</item>
+        <item>@dimen/swipe_up_trans_y_dp</item>
+        <item>@dimen/swipe_up_trans_y_dp_per_s</item>
+        <item>@dimen/swipe_up_trans_y_damping</item>
+        <item>@dimen/swipe_up_trans_y_stiffness</item>
+        <item>@dimen/swipe_up_rect_2_x_damping_ratio</item>
+        <item>@dimen/swipe_up_rect_2_x_stiffness</item>
+        <item>@dimen/swipe_up_rect_2_y_damping_ratio</item>
+        <item>@dimen/swipe_up_rect_2_y_stiffness</item>
+        <item>@dimen/swipe_up_launcher_alpha_max_progress</item>
+        <item>@dimen/swipe_up_rect_2_y_stiffness_low_swipe_multiplier</item>
+        <item>@dimen/swipe_up_low_swipe_duration_multiplier</item>
 
-        <item>@dimen/dismiss_task_trans_y_damping_ratio</item>
-        <item>@dimen/dismiss_task_trans_y_stiffness</item>
+        <item>@dimen/c1_a</item>
+        <item>@dimen/c1_b</item>
+        <item>@dimen/c1_c</item>
+        <item>@dimen/c1_d</item>
 
-        <item>@dimen/dismiss_task_trans_x_damping_ratio</item>
-        <item>@dimen/dismiss_task_trans_x_stiffness</item>
+        <item>@dimen/mp_x</item>
+        <item>@dimen/mp_y</item>
 
-        <item>@dimen/horizontal_spring_damping_ratio</item>
-        <item>@dimen/horizontal_spring_stiffness</item>
-
-        <item>@dimen/swipe_up_rect_scale_damping_ratio</item>
-        <item>@dimen/swipe_up_rect_scale_stiffness</item>
-
-        <item>@dimen/swipe_up_rect_xy_fling_friction</item>
-        <item>@dimen/swipe_up_rect_xy_damping_ratio</item>
-        <item>@dimen/swipe_up_rect_xy_stiffness</item>
-
-        <item>@dimen/staggered_damping_ratio</item>
-        <item>@dimen/staggered_stiffness</item>
-        <item>@dimen/unlock_staggered_velocity_dp_per_s</item>
-
-        <item>@dimen/swipe_up_fling_min_visible_change</item>
-        <item>@dimen/swipe_up_y_overshoot</item>
-
-        <item>@dimen/hint_scale_damping_ratio</item>
-        <item>@dimen/hint_scale_stiffness</item>
-        <item>@dimen/hint_scale_velocity_dp_per_s</item>
+        <item>@dimen/c2_a</item>
+        <item>@dimen/c2_b</item>
+        <item>@dimen/c2_c</item>
+        <item>@dimen/c2_d</item>
     </array>
 
     <string-array name="live_wallpapers_remove_sysui_scrims">
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 10091a1..5d941a7 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3323,6 +3323,18 @@
         }
     }
 
+    /**
+     * Set the given view's pivot point to match the workspace's, so that it scales together. Since
+     * both this view and workspace can move, transform the point manually instead of using
+     * dragLayer.getDescendantCoordRelativeToSelf and related methods.
+     */
+    public void setPivotToScaleWithSelf(View sibling) {
+        sibling.setPivotY(getPivotY() + getTop()
+                - sibling.getTop() - sibling.getTranslationY());
+        sibling.setPivotX(getPivotX() + getLeft()
+                - sibling.getLeft() - sibling.getTranslationX());
+    }
+
     @Override
     public int getExpectedHeight() {
         return getMeasuredHeight() <= 0 || !mIsLayoutValid
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index f4986f4..ed854dc 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -119,7 +119,7 @@
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
         }
 
-        setPivotToScaleWithWorkspace(hotseat);
+        mWorkspace.setPivotToScaleWithSelf(hotseat);
         float hotseatScale = hotseatScaleAndTranslation.scale;
         if (shouldSpring) {
             PendingAnimation pa = (PendingAnimation) propertySetter;
@@ -156,18 +156,6 @@
         }
     }
 
-    /**
-     * Set the given view's pivot point to match the workspace's, so that it scales together. Since
-     * both this view and workspace can move, transform the point manually instead of using
-     * dragLayer.getDescendantCoordRelativeToSelf and related methods.
-     */
-    private void setPivotToScaleWithWorkspace(View sibling) {
-        sibling.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop()
-                - sibling.getTop() - sibling.getTranslationY());
-        sibling.setPivotX(mWorkspace.getPivotX() + mWorkspace.getLeft()
-                - sibling.getLeft() - sibling.getTranslationX());
-    }
-
     public void setScrim(PropertySetter propertySetter, LauncherState state,
             StateAnimationConfig config) {
         Scrim workspaceDragScrim = mLauncher.getDragLayer().getWorkspaceDragScrim();
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 7a38937..11e831e 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -27,7 +27,6 @@
 
 import com.android.launcher3.Utilities;
 
-
 /**
  * Common interpolators used in Launcher
  */
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 44e3138..272dfc2 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -233,6 +233,9 @@
     public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag("NOTIFY_CRASHES", false,
             "Sends a notification whenever launcher encounters an uncaught exception.");
 
+    public static final BooleanFlag PROTOTYPE_APP_CLOSE = getDebugFlag(
+            "PROTOTYPE_APP_CLOSE", false, "Enables new app close");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 4e82336..a66b3f9 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -15,10 +15,13 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.Utilities.boundToRange;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 
+import static java.lang.Math.max;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -143,10 +146,9 @@
     /**
      * Update the icon UI to match the provided parameters during an animation frame
      */
-    public void update(RectF rect, float progress, float shapeProgressStart,
-            float cornerRadius, boolean isOpening, View container,
-            DeviceProfile dp, boolean isVerticalBarLayout) {
-
+    public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+            int fgIconAlpha, boolean isOpening, View container, DeviceProfile dp,
+            boolean isVerticalBarLayout) {
         MarginLayoutParams lp = (MarginLayoutParams) container.getLayoutParams();
 
         float dX = mIsRtl
@@ -166,7 +168,7 @@
             return;
         }
 
-        update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale,
+        update(rect, progress, shapeProgressStart, cornerRadius, fgIconAlpha, isOpening, scale,
                 minSize, lp, isVerticalBarLayout, dp);
 
         container.setPivotX(0);
@@ -178,8 +180,8 @@
     }
 
     private void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
-            boolean isOpening, float scale, float minSize, MarginLayoutParams parentLp,
-            boolean isVerticalBarLayout, DeviceProfile dp) {
+            int fgIconAlpha, boolean isOpening, float scale, float minSize,
+            MarginLayoutParams parentLp, boolean isVerticalBarLayout, DeviceProfile dp) {
         float dX = mIsRtl
                 ? rect.left - (dp.widthPx - parentLp.getMarginStart() - parentLp.width)
                 : rect.left - parentLp.getMarginStart();
@@ -187,9 +189,9 @@
 
         // shapeRevealProgress = 1 when progress = shapeProgressStart + SHAPE_PROGRESS_DURATION
         float toMax = isOpening ? 1 / SHAPE_PROGRESS_DURATION : 1f;
-        float shapeRevealProgress = Utilities.boundToRange(mapToRange(
-                Math.max(shapeProgressStart, progress), shapeProgressStart, 1f, 0, toMax,
-                LINEAR), 0, 1);
+
+        float shapeRevealProgress = boundToRange(mapToRange(max(shapeProgressStart, progress),
+                shapeProgressStart, 1f, 0, toMax, LINEAR), 0, 1);
 
         if (isVerticalBarLayout) {
             mOutline.right = (int) (rect.width() / scale);
@@ -231,6 +233,8 @@
                 sTmpRect.offset(diffX, diffY);
                 mForeground.setBounds(sTmpRect);
             } else {
+                mForeground.setAlpha(fgIconAlpha);
+
                 // Spring the foreground relative to the icon's movement within the DragLayer.
                 int diffX = (int) (dX / dp.availableWidthPx * FG_TRANS_X_FACTOR);
                 int diffY = (int) (dY / dp.availableHeightPx * FG_TRANS_Y_FACTOR);
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 96268ce..d49320b 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -100,6 +100,8 @@
     private ListenerView mListenerView;
     private Runnable mFastFinishRunnable;
 
+    private float mIconOffsetY;
+
     public FloatingIconView(Context context) {
         this(context, null);
     }
@@ -136,16 +138,18 @@
 
     /**
      * Positions this view to match the size and location of {@param rect}.
-     * @param alpha The alpha to set this view.
+     * @param alpha The alpha[0, 1] of the entire floating view.
+     * @param fgIconAlpha The alpha[0-255] of the foreground layer of the icon (if applicable).
      * @param progress A value from [0, 1] that represents the animation progress.
      * @param shapeProgressStart The progress value at which to start the shape reveal.
      * @param cornerRadius The corner radius of {@param rect}.
+     * @param isOpening True if view is used for app open animation, false for app close animation.
      */
-    public void update(RectF rect, float alpha, float progress, float shapeProgressStart,
-            float cornerRadius, boolean isOpening) {
+    public void update(float alpha, int fgIconAlpha, RectF rect, float progress,
+            float shapeProgressStart, float cornerRadius, boolean isOpening) {
         setAlpha(alpha);
-        mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening,
-                this, mLauncher.getDeviceProfile(), mIsVerticalBarLayout);
+        mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, fgIconAlpha,
+                isOpening, this, mLauncher.getDeviceProfile(), mIsVerticalBarLayout);
     }
 
     @Override
@@ -478,11 +482,19 @@
     @Override
     public void onAnimationRepeat(Animator animator) {}
 
+    /**
+     * Offsets and updates the position of this view by {@param y}.
+     */
+    public void setPositionOffsetY(float y) {
+        mIconOffsetY = y;
+        onGlobalLayout();
+    }
+
     @Override
     public void onGlobalLayout() {
-        if (mOriginalIcon.isAttachedToWindow() && mPositionOut != null) {
-            getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening,
-                    sTmpRectF);
+        if (mOriginalIcon != null && mOriginalIcon.isAttachedToWindow() && mPositionOut != null) {
+            getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, sTmpRectF);
+            sTmpRectF.offset(0, mIconOffsetY);
             if (!sTmpRectF.equals(mPositionOut)) {
                 updatePosition(sTmpRectF, (InsettableFrameLayout.LayoutParams) getLayoutParams());
                 if (mOnTargetChangeRunnable != null) {
@@ -617,6 +629,7 @@
         mClipIconView.recycle();
         mBtvDrawable.setBackground(null);
         mFastFinishRunnable = null;
+        mIconOffsetY = 0;
     }
 
     private static class IconLoadResult {