Swipe interaction changes on home screen

> Increasing the distance to travel for the first swipe
> Adding support for custom interpolators when building an animation
> When quickly swiping twice from home, finished the first animation

Change-Id: Ibc3c8667e9b927376fd99f08f0ca027f2398914b
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index b580992..2ac9323 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -89,7 +89,7 @@
     }
 
     @Override
-    public float getOverviewTranslationX(Launcher launcher) {
+    public float getOverviewTranslationFactor(Launcher launcher) {
         return 0;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 0b69c58..68322b0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -58,7 +58,7 @@
     }
 
     @Override
-    public float getOverviewTranslationX(Launcher launcher) {
+    public float getOverviewTranslationFactor(Launcher launcher) {
         return 0;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 9f648ed..1e006e5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -18,16 +18,24 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
 import android.view.MotionEvent;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.SysuiEventLogger;
 
@@ -36,6 +44,47 @@
  */
 public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
 
+    private static final float TOTAL_DISTANCE_MULTIPLIER = 2f;
+    private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
+
+    // Much be greater than LINEAR_SCALE_LIMIT;
+    private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
+
+    // Maximum amount to overshoot.
+    private static final float MAX_OVERSHOOT = 0.3f;
+
+    private static final double PI_BY_2 = Math.PI / 2;
+
+    private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
+
+    // If > 0, the animation progress is clamped at that value as long as user is dragging.
+    private float mClampProgressUpdate = -1;
+
+    // If true, we will finish the current animation instantly on second touch.
+    private boolean mFinishFastOnSecondTouch;
+
+    private final Interpolator mAllAppsDampedInterpolator = new Interpolator() {
+
+        private final double mAngleMultiplier = Math.PI /
+                (2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT));
+
+        @Override
+        public float getInterpolation(float v) {
+            if (v <= LINEAR_SCALE_LIMIT) {
+                return v * TOTAL_DISTANCE_MULTIPLIER;
+            }
+            float overshoot = (v - LINEAR_SCALE_LIMIT);
+            return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier));
+        }
+    };
+
+    private final Interpolator mOverviewBoundInterpolator = (v) -> {
+            if (v >= MAXIMUM_DISTANCE_FACTOR) {
+                return 1;
+            }
+            return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR);
+    };
+
     public PortraitStatesTouchController(Launcher l) {
         super(l, SwipeDetector.VERTICAL);
     }
@@ -43,6 +92,11 @@
     @Override
     protected boolean canInterceptTouch(MotionEvent ev) {
         if (mCurrentAnimation != null) {
+            if (mFinishFastOnSecondTouch) {
+                // TODO: Animate to finish instead.
+                mCurrentAnimation.getAnimationPlayer().end();
+            }
+
             // If we are already animating from a previous state, we can intercept.
             return true;
         }
@@ -100,17 +154,49 @@
         }
     }
 
+    private AnimatorSetBuilder getNormalToOverviewAnimation() {
+        mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator;
+
+        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
+
+        builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator);
+        return builder;
+    }
+
+    @Override
+    protected void updateProgress(float fraction) {
+        if (mClampProgressUpdate > 0) {
+            mCurrentAnimation.setPlayFraction(Math.min(fraction, mClampProgressUpdate));
+        } else {
+            super.updateProgress(fraction);
+        }
+    }
+
     @Override
     protected float initCurrentAnimation() {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
-        mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, maxAccuracy);
 
         float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
         float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
 
         float totalShift = endVerticalShift - startVerticalShift;
+
+        final AnimatorSetBuilder builder;
+
+        if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
+            builder = getNormalToOverviewAnimation();
+            totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
+            mClampProgressUpdate = MAXIMUM_DISTANCE_FACTOR;
+        } else {
+            builder = new AnimatorSetBuilder();
+            mClampProgressUpdate = -1;
+        }
+
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
+
         if (totalShift == 0) {
             totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
                     * OverviewState.getDefaultSwipeHeight(mLauncher);
@@ -119,10 +205,87 @@
     }
 
     @Override
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
+            mFinishFastOnSecondTouch = true;
+
+            // Update all apps interpolator
+            float currentFraction = mCurrentAnimation.getProgressFraction();
+            float absVelocity = Math.abs(velocity);
+            float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction);
+
+            if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) {
+
+                // TODO: Clean up these magic calculations
+                // Linearly interpolate the max value based on the velocity.
+                float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT :
+                        1 + (absVelocity - 1) * MAX_OVERSHOOT / 3,
+                        currentValue);
+                double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue);
+
+                if (expectedDuration != 0 && angleToPeak != 0) {
+
+                    float distanceLeft = 1 - currentFraction;
+                    mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
+                        float scaledF = (f - currentFraction) / distanceLeft;
+
+                        if (scaledF < 0.5f) {
+                            double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f;
+                            return (float) (maxValue * Math.sin(angle));
+                        }
+
+                        scaledF = ((scaledF - .5f) / .5f);
+                        double angle = PI_BY_2 + 3 * scaledF * PI_BY_2;
+                        float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1);
+                        return 1 + (float) (amplitude * Math.sin(angle));
+                    };
+
+                    animator.setDuration(expectedDuration).setInterpolator(LINEAR);
+                    return;
+                }
+            }
+
+            if (currentFraction < LINEAR_SCALE_LIMIT) {
+                mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
+                super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
+                        velocity, isFling);
+                return;
+            }
+            float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1;
+            float distanceLeft = 1 - currentFraction;
+
+            animator.setFloatValues(currentFraction, 1);
+            mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
+                float scaledF = (f - currentFraction) / distanceLeft;
+
+                double angle = scaledF * 1.5 * Math.PI;
+                float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue;
+                return 1 + (float) (amplitude * Math.sin(angle));
+            };
+            animator.setDuration(200).setInterpolator(LINEAR);
+            return;
+        }
+        mFinishFastOnSecondTouch = false;
+        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
+                velocity, isFling);
+    }
+
+    @Override
     protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
         super.onSwipeInteractionCompleted(targetState, logAction);
         if (mFromState == NORMAL && targetState == OVERVIEW) {
             SysuiEventLogger.writeDummyRecentsTransition(0);
         }
     }
+
+    private static class InterpolatorWrapper implements Interpolator {
+
+        public TimeInterpolator baseInterpolator = LINEAR;
+
+        @Override
+        public float getInterpolation(float v) {
+            return baseInterpolator.getInterpolation(v);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index b68a3d8..ae747d8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,32 +16,30 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_FACTOR;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.view.View;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.PagedView;
-import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.Interpolators;
-import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.views.RecentsView;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.quickstep.views.LauncherRecentsView;
 
+@TargetApi(Build.VERSION_CODES.O)
 public class RecentsViewStateController implements StateHandler {
 
     private final Launcher mLauncher;
-    private final RecentsView mRecentsView;
-
-    private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::onTransitionProgress);
-    // The fraction representing the visibility of the RecentsView. This allows delaying the
-    // overall transition while the RecentsView is being shown or hidden.
-    private final AnimatedFloat mVisibilityMultiplier = new AnimatedFloat(this::onVisibilityProgress);
+    private final LauncherRecentsView mRecentsView;
 
     public RecentsViewStateController(Launcher launcher) {
         mLauncher = launcher;
@@ -50,14 +48,12 @@
 
     @Override
     public void setState(LauncherState state) {
-        setVisibility(state.overviewUi);
-        setTransitionProgress(state.overviewUi ? 1 : 0);
+        mRecentsView.setAlpha(state.overviewUi ? 1 : 0);
+        updateVisibility(mRecentsView, isAccessibilityEnabled(mLauncher));
+        mRecentsView.setTranslationFactor(state.getOverviewTranslationFactor(mLauncher));
         if (state.overviewUi) {
             mRecentsView.resetTaskVisuals();
         }
-        float overviewTranslationX = state.getOverviewTranslationX(mLauncher);
-        int direction = mRecentsView.isRtl() ? -1 : 1;
-        mRecentsView.setTranslationX(overviewTranslationX * direction);
     }
 
     @Override
@@ -76,73 +72,20 @@
             builder.setStartDelay(snapDuration / 4);
         }
 
-        ObjectAnimator progressAnim =
-                mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
-        progressAnim.setDuration(config.duration);
-        progressAnim.setInterpolator(Interpolators.LINEAR);
-        builder.play(progressAnim);
+        PropertySetter setter = config.getProperSetter(builder);
+        setter.setFloat(mRecentsView, TRANSLATION_FACTOR,
+                toState.getOverviewTranslationFactor(mLauncher),
+                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
+        setter.setViewAlpha(mRecentsView, toState.overviewUi ? 1 : 0, LINEAR);
 
-        ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
-        visibilityAnim.setDuration(config.duration);
-        visibilityAnim.setInterpolator(Interpolators.LINEAR);
-        builder.play(visibilityAnim);
-
-        int direction = mRecentsView.isRtl() ? -1 : 1;
-        float fromTranslationX = fromState.getOverviewTranslationX(mLauncher) * direction;
-        float toTranslationX = toState.getOverviewTranslationX(mLauncher) * direction;
-        ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_X,
-                fromTranslationX, toTranslationX);
-        translationXAnim.setDuration(config.duration);
-        translationXAnim.setInterpolator(Interpolators.ACCEL);
         if (toState.overviewUi) {
-            translationXAnim.addUpdateListener(valueAnimator -> {
+            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+            updateAnim.addUpdateListener(valueAnimator -> {
                 // While animating into recents, update the visible task data as needed
                 mRecentsView.loadVisibleTaskData();
             });
+            updateAnim.setDuration(config.duration);
+            builder.play(updateAnim);
         }
-        builder.play(translationXAnim);
-    }
-
-    public void setVisibility(boolean isVisible) {
-        mVisibilityMultiplier.cancelAnimation();
-        mRecentsView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
-        mVisibilityMultiplier.updateValue(isVisible ? 1 : 0);
-    }
-
-    public ObjectAnimator animateVisibility(boolean isVisible) {
-        ObjectAnimator anim = mVisibilityMultiplier.animateToValue(isVisible ? 1 : 0);
-        if (isVisible) {
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    mRecentsView.setVisibility(View.VISIBLE);
-                }
-            });
-        } else {
-            anim.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    mRecentsView.setVisibility(View.GONE);
-                }
-            });
-        }
-        return anim;
-    }
-
-    public void setTransitionProgress(float progress) {
-        mTransitionProgress.cancelAnimation();
-        mTransitionProgress.updateValue(progress);
-    }
-
-    private void onTransitionProgress() {
-        applyProgress();
-    }
-
-    private void onVisibilityProgress() {
-        applyProgress();
-    }
-
-    private void applyProgress() {
-        mRecentsView.setAlpha(mTransitionProgress.value * mVisibilityMultiplier.value);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 7989e84..d490f82 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
@@ -30,7 +31,9 @@
 import android.graphics.Shader.TileMode;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
 import android.widget.FrameLayout;
 
 import com.android.launcher3.DeviceProfile;
@@ -41,14 +44,31 @@
 /**
  * {@link RecentsView} used in Launcher activity
  */
+@TargetApi(Build.VERSION_CODES.O)
 public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
 
+    public static final FloatProperty<LauncherRecentsView> TRANSLATION_FACTOR =
+            new FloatProperty<LauncherRecentsView>("translationFactor") {
+
+                @Override
+                public void setValue(LauncherRecentsView view, float v) {
+                    view.setTranslationFactor(v);
+                }
+
+                @Override
+                public Float get(LauncherRecentsView view) {
+                    return view.mTranslationFactor;
+                }
+            };
+
     private Bitmap mScrim;
     private Paint mFadePaint;
     private Shader mFadeShader;
     private Matrix mFadeMatrix;
     private boolean mScrimOnLeft;
 
+    private float mTranslationFactor;
+
     public LauncherRecentsView(Context context) {
         this(context, null);
     }
@@ -131,4 +151,21 @@
     protected void onAllTasksRemoved() {
         mActivity.getStateManager().goToState(NORMAL);
     }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        int width = right - left;
+        setTranslationX(mTranslationFactor * (mIsRtl ? -width : width));
+    }
+
+    public void setTranslationFactor(float translationFactor) {
+        mTranslationFactor = translationFactor;
+        setTranslationX(translationFactor * (mIsRtl ? -getWidth() : getWidth()));
+    }
+
+    public float getTranslationFactor() {
+        return mTranslationFactor;
+    }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 6185844..5c16ca9 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -180,8 +180,8 @@
         return new float[] {1, 0, 0};
     }
 
-    public float getOverviewTranslationX(Launcher launcher) {
-        return launcher.getDragLayer().getMeasuredWidth();
+    public float getOverviewTranslationFactor(Launcher launcher) {
+        return 1;
     }
 
     public void onStateEnabled(Launcher launcher) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index fbd23d1..9be123f 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -3,6 +3,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -55,8 +56,6 @@
         }
     };
 
-    public static final float PARALLAX_COEFFICIENT = .125f;
-
     private AllAppsContainerView mAppsView;
 
     private final Launcher mLauncher;
@@ -169,7 +168,7 @@
         ObjectAnimator anim =
                 ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
         anim.setDuration(config.duration);
-        anim.setInterpolator(interpolator);
+        anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
         anim.addListener(getProgressAnimatorListener());
 
         builder.play(anim);
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index 7cd9651..9191048 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -17,6 +17,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.util.SparseArray;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.LauncherAnimUtils;
 
@@ -27,7 +29,12 @@
  */
 public class AnimatorSetBuilder {
 
+    public static final int ANIM_VERTICAL_PROGRESS = 0;
+    public static final int ANIM_OVERVIEW_TRANSLATION = 1;
+
     protected final ArrayList<Animator> mAnims = new ArrayList<>();
+
+    private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
     private long mStartDelay = 0;
 
     /**
@@ -49,4 +56,12 @@
         anim.setStartDelay(mStartDelay);
         return anim;
     }
+
+    public Interpolator getInterpolator(int animId, Interpolator fallback) {
+        return mInterpolators.get(animId, fallback);
+    }
+
+    public void setInterpolator(int animId, Interpolator interpolator) {
+        mInterpolators.put(animId, interpolator);
+    }
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index db53634..a22f450 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -19,6 +19,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -140,10 +141,14 @@
     @Override
     public boolean onDrag(float displacement, float velocity) {
         float deltaProgress = mProgressMultiplier * displacement;
-        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+        updateProgress(deltaProgress + mStartProgress);
         return true;
     }
 
+    protected void updateProgress(float fraction) {
+        mCurrentAnimation.setPlayFraction(fraction);
+    }
+
     @Override
     public void onDragEnd(float velocity, boolean fling) {
         final int logAction;
@@ -173,7 +178,7 @@
                 startProgress = 1;
             } else {
                 startProgress = Utilities.boundToRange(
-                        progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+                        progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
                 duration = SwipeDetector.calculateDuration(velocity,
                         endProgress - Math.max(progress, 0));
             }
@@ -184,7 +189,7 @@
                 startProgress = 0;
             } else {
                 startProgress = Utilities.boundToRange(
-                        progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+                        progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
                 duration = SwipeDetector.calculateDuration(velocity,
                         Math.min(progress, 1) - endProgress);
             }
@@ -193,10 +198,16 @@
         mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
         ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
         anim.setFloatValues(startProgress, endProgress);
-        anim.setDuration(duration).setInterpolator(scrollInterpolatorForVelocity(velocity));
+        updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
         anim.start();
     }
 
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        animator.setDuration(expectedDuration)
+                .setInterpolator(scrollInterpolatorForVelocity(velocity));
+    }
+
     protected int getDirectionForLog() {
         return mToState.ordinal > mFromState.ordinal ? Direction.UP : Direction.DOWN;
     }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
index c05d30c..121f13e 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsState.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -33,6 +32,8 @@
  */
 public class AllAppsState extends LauncherState {
 
+    private static final float PARALLAX_COEFFICIENT = .125f;
+
     private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY
             | FLAG_SHOW_SCRIM | FLAG_ALL_APPS_SCRIM;
 
@@ -75,8 +76,7 @@
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
         return new float[] { 1f, 0,
-                -launcher.getAllAppsController().getShiftRange()
-                        * AllAppsTransitionController.PARALLAX_COEFFICIENT};
+                -launcher.getAllAppsController().getShiftRange() * PARALLAX_COEFFICIENT};
     }
 
     @Override