Using edge effect to trigger spring animation for all apps.

Bug: 72811152
Bug: 72059944

Change-Id: Ied7b51caa2fb48a2fda126d59e4eaf6a35edded3
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index 541c6bb..a55edfe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -31,7 +31,6 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -143,11 +142,6 @@
     }
 
     @Override
-    protected void initSprings() {
-        mSpringHandlers = new SpringAnimationHandler[0];
-    }
-
-    @Override
     protected float getShiftRange() {
         return getShiftRange(mLauncher);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index 2695054..c8d75dc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
 import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
 
 import android.animation.Animator;
@@ -27,7 +26,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.support.animation.SpringAnimation;
 import android.util.Log;
 import android.view.MotionEvent;
 
@@ -38,11 +36,9 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -52,8 +48,6 @@
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TouchInteractionService;
 
-import java.util.ArrayList;
-
 /**
  * Handles vertical touch gesture on the DragLayer
  */
@@ -112,8 +106,6 @@
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
 
-    private SpringAnimationHandler[] mSpringHandlers;
-
     public TwoStepSwipeController(Launcher l) {
         mLauncher = l;
         mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
@@ -156,29 +148,6 @@
         }
     }
 
-    private void initSprings() {
-        AllAppsContainerView appsView = mLauncher.getAppsView();
-
-        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
-        if (handler == null) {
-            mSpringHandlers = new SpringAnimationHandler[0];
-            return;
-        }
-
-        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
-        handlers.add(handler);
-
-        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
-        if (searchSpring != null) {
-            SpringAnimationHandler searchHandler =
-                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
-            searchHandler.add(searchSpring, true /* setDefaultValues */);
-            handlers.add(searchHandler);
-        }
-
-        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
-    }
-
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -214,10 +183,6 @@
 
             mDetector.setDetectableScrollConditions(
                     directionsToDetectScroll, ignoreSlopWhenSettling);
-
-            if (mSpringHandlers == null) {
-                initSprings();
-            }
         }
 
         if (mNoIntercept) {
@@ -230,9 +195,6 @@
 
     @Override
     public boolean onControllerTouchEvent(MotionEvent ev) {
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.addMovement(ev);
-        }
         return mDetector.onTouchEvent(ev);
     }
 
@@ -283,10 +245,6 @@
             mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
             updatePauseDetectorRangeFlag();
         }
-
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.skipToEnd();
-        }
     }
 
     private float getShiftRange() {
@@ -329,13 +287,6 @@
             targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
         }
 
-        if (fling && targetState == ALL_APPS) {
-            for (SpringAnimationHandler h : mSpringHandlers) {
-                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
-                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
-            }
-        }
-
         float endProgress;
 
         if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
diff --git a/res/layout/all_apps_rv_layout.xml b/res/layout/all_apps_rv_layout.xml
index 3c19f8c..c353b36 100644
--- a/res/layout/all_apps_rv_layout.xml
+++ b/res/layout/all_apps_rv_layout.xml
@@ -22,5 +22,4 @@
     android:layout_below="@id/search_container_all_apps"
     android:clipToPadding="false"
     android:descendantFocusability="afterDescendants"
-    android:focusable="true"
-    android:overScrollMode="never" />
+    android:focusable="true" />
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 26922ad..2cd9d1b 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -36,7 +36,6 @@
 import android.view.View;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.widget.RelativeLayout;
 
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.DeviceProfile;
@@ -50,7 +49,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -61,11 +59,12 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BottomUserEducationView;
 import com.android.launcher3.views.RecyclerViewFastScroller;
+import com.android.launcher3.views.SpringRelativeLayout;
 
 /**
  * The all apps view container.
  */
-public class AllAppsContainerView extends RelativeLayout implements DragSource,
+public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
         OnLongClickListener, Insettable, OnDeviceProfileChangeListener {
 
     private final Launcher mLauncher;
@@ -119,6 +118,10 @@
         // Attach a scrim to be drawn behind all-apps and hotseat
         new ColorScrim(this, Themes.getAttrColor(context, R.attr.allAppsScrimColor), DEACCEL_2)
                 .attach();
+
+        addSpringView(R.id.all_apps_header);
+        addSpringView(R.id.apps_list_view);
+        addSpringView(R.id.all_apps_tabs_view_pager);
     }
 
     public AllAppsStore getAppsStore() {
@@ -324,10 +327,6 @@
         }
     }
 
-    public SpringAnimationHandler getSpringAnimationHandler() {
-        return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
-    }
-
     private void rebindAdapters(boolean showTabs) {
         rebindAdapters(showTabs, false /* force */);
     }
@@ -470,7 +469,6 @@
 
         public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
-        final SpringAnimationHandler animationHandler;
         final AlphabeticalAppsList appsList;
         final Rect padding = new Rect();
         AllAppsRecyclerView recyclerView;
@@ -481,22 +479,19 @@
             adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
                     AllAppsContainerView.this, true);
             appsList.setAdapter(adapter);
-            animationHandler = adapter.getSpringAnimationHandler();
             layoutManager = adapter.getLayoutManager();
         }
 
         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
             appsList.updateItemFilter(matcher);
             recyclerView = (AllAppsRecyclerView) rv;
+            recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
             recyclerView.setApps(appsList, mUsingTabs);
             recyclerView.setLayoutManager(layoutManager);
             recyclerView.setAdapter(adapter);
             recyclerView.setHasFixedSize(true);
             // No animations will occur when changes occur to the items in this RecyclerView.
             recyclerView.setItemAnimator(null);
-            if (FeatureFlags.LAUNCHER3_PHYSICS && animationHandler != null) {
-                recyclerView.setSpringAnimationHandler(animationHandler);
-            }
             FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(recyclerView);
             recyclerView.addItemDecoration(focusedItemDecorator);
             adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index a61521c..b9443fd 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -18,8 +18,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.support.animation.DynamicAnimation;
-import android.support.animation.SpringAnimation;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
@@ -38,11 +36,8 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.List;
@@ -71,7 +66,6 @@
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
-    public static final int VIEW_TYPE_MASK_HAS_SPRINGS = VIEW_TYPE_MASK_ICON;
 
 
     public interface BindViewCallback {
@@ -195,8 +189,6 @@
     // The intent to send off to the market app, updated each time the search query changes.
     private Intent mMarketSearchIntent;
 
-    private final SpringAnimationHandler<ViewHolder> mSpringAnimationHandler;
-
     public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps, View.OnClickListener
             iconClickListener, View.OnLongClickListener iconLongClickListener, boolean springAnim) {
         Resources res = launcher.getResources();
@@ -209,21 +201,11 @@
         mLayoutInflater = LayoutInflater.from(launcher);
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-        if (FeatureFlags.LAUNCHER3_PHYSICS && springAnim) {
-            mSpringAnimationHandler = new SpringAnimationHandler<>(
-                    SpringAnimationHandler.Y_DIRECTION, new AllAppsSpringAnimationFactory());
-        } else {
-            mSpringAnimationHandler = null;
-        }
 
         mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
         mGridLayoutMgr.setSpanCount(mAppsPerRow);
     }
 
-    public SpringAnimationHandler getSpringAnimationHandler() {
-        return mSpringAnimationHandler;
-    }
-
     public static boolean isDividerViewType(int viewType) {
         return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
     }
@@ -344,22 +326,6 @@
     }
 
     @Override
-    public void onViewAttachedToWindow(ViewHolder holder) {
-        int type = holder.getItemViewType();
-        if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
-            mSpringAnimationHandler.add(holder.itemView, holder);
-        }
-    }
-
-    @Override
-    public void onViewDetachedFromWindow(ViewHolder holder) {
-        int type = holder.getItemViewType();
-        if (mSpringAnimationHandler != null && isViewType(type, VIEW_TYPE_MASK_HAS_SPRINGS)) {
-            mSpringAnimationHandler.remove(holder.itemView);
-        }
-    }
-
-    @Override
     public boolean onFailedToRecycleView(ViewHolder holder) {
         // Always recycle and we will reset the view when it is bound
         return true;
@@ -376,104 +342,4 @@
         return item.viewType;
     }
 
-    /**
-     * Helper class to set the SpringAnimation values for an item in the adapter.
-     */
-    private class AllAppsSpringAnimationFactory
-            implements SpringAnimationHandler.AnimationFactory<ViewHolder> {
-        private static final float DEFAULT_MAX_VALUE_PX = 100;
-        private static final float DEFAULT_MIN_VALUE_PX = -DEFAULT_MAX_VALUE_PX;
-
-        // Damping ratio range is [0, 1]
-        private static final float SPRING_DAMPING_RATIO = 0.55f;
-
-        // Stiffness is a non-negative number.
-        private static final float MIN_SPRING_STIFFNESS = 580f;
-        private static final float MAX_SPRING_STIFFNESS = 900f;
-
-        // The amount by which each adjacent rows' stiffness will differ.
-        private static final float ROW_STIFFNESS_COEFFICIENT = 50f;
-
-        // The percentage by which we multiply each row to create the row factor.
-        private static final float ROW_PERCENTAGE = 0.3f;
-
-        @Override
-        public SpringAnimation initialize(ViewHolder vh) {
-            return SpringAnimationHandler.forView(vh.itemView, DynamicAnimation.TRANSLATION_Y, 0);
-        }
-
-        /**
-         * @param spring A new or recycled SpringAnimation.
-         * @param vh The ViewHolder that {@param spring} is related to.
-         */
-        @Override
-        public void update(SpringAnimation spring, ViewHolder vh) {
-            int appPosition = vh.getAdapterPosition();
-            int col = appPosition % mAppsPerRow;
-            int row = appPosition / mAppsPerRow;
-
-            int numTotalRows = mApps.getNumAppRows() - 1; // zero-based count
-            if (row > (numTotalRows / 2)) {
-                // Mirror the rows so that the top row acts the same as the bottom row.
-                row = Math.abs(numTotalRows - row);
-            }
-
-            calculateSpringValues(spring, row, col);
-        }
-
-        @Override
-        public void setDefaultValues(SpringAnimation spring) {
-            calculateSpringValues(spring, 0, mAppsPerRow / 2);
-        }
-
-        /**
-         * We manipulate the stiffness, min, and max values based on the items distance to the
-         * first row and the items distance to the center column to create the ^-shaped motion
-         * effect.
-         */
-        private void calculateSpringValues(SpringAnimation spring, int row, int col) {
-            float rowFactor = (1 + row) * ROW_PERCENTAGE;
-            float colFactor = getColumnFactor(col, mAppsPerRow);
-
-            float minValue = DEFAULT_MIN_VALUE_PX * (rowFactor + colFactor);
-            float maxValue = DEFAULT_MAX_VALUE_PX * (rowFactor + colFactor);
-
-            float stiffness = Utilities.boundToRange(
-                    MAX_SPRING_STIFFNESS - (row * ROW_STIFFNESS_COEFFICIENT),
-                    MIN_SPRING_STIFFNESS,
-                    MAX_SPRING_STIFFNESS);
-
-            spring.setMinValue(minValue)
-                    .setMaxValue(maxValue)
-                    .getSpring()
-                    .setStiffness(stiffness)
-                    .setDampingRatio(SPRING_DAMPING_RATIO);
-        }
-
-        /**
-         * Increase the column factor as the distance increases between the column and the center
-         * column(s).
-         */
-        private float getColumnFactor(int col, int numCols) {
-            float centerColumn = numCols / 2;
-            int distanceToCenter = (int) Math.abs(col - centerColumn);
-
-            boolean evenNumberOfColumns = numCols % 2 == 0;
-            if (evenNumberOfColumns && col < centerColumn) {
-                distanceToCenter -= 1;
-            }
-
-            float factor = 0;
-            while (distanceToCenter > 0) {
-                if (distanceToCenter == 1) {
-                    factor += 0.2f;
-                } else {
-                    factor += 0.1f;
-                }
-                --distanceToCenter;
-            }
-
-            return factor;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 4a393c9..a7447b7 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -17,14 +17,12 @@
 
 import static android.view.View.MeasureSpec.UNSPECIFIED;
 
-import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
-import android.util.Property;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -35,12 +33,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.SpringAnimationHandler;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider;
-import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -64,23 +58,6 @@
     private AllAppsBackgroundDrawable mEmptySearchBackground;
     private int mEmptySearchBackgroundTopOffset;
 
-    private SpringAnimationHandler mSpringAnimationHandler;
-    private OverScrollHelper mOverScrollHelper;
-    private SwipeDetector mPullDetector;
-    private float mContentTranslationY = 0;
-    public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
-            new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
-                @Override
-                public Float get(AllAppsRecyclerView allAppsRecyclerView) {
-                    return allAppsRecyclerView.getContentTranslationY();
-                }
-
-                @Override
-                public void set(AllAppsRecyclerView allAppsRecyclerView, Float y) {
-                    allAppsRecyclerView.setContentTranslationY(y);
-                }
-            };
-
     public AllAppsRecyclerView(Context context) {
         this(context, null);
     }
@@ -99,30 +76,9 @@
         Resources res = getResources();
         mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                 R.dimen.all_apps_empty_search_bg_top_offset);
-
-        mOverScrollHelper = new OverScrollHelper();
-        mPullDetector = new SwipeDetector(getContext(), mOverScrollHelper, SwipeDetector.VERTICAL);
-        mPullDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, true);
-
         mNumAppsPerRow = LauncherAppState.getIDP(context).numColumns;
     }
 
-    public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
-        if (FeatureFlags.LAUNCHER3_PHYSICS) {
-            mSpringAnimationHandler = springAnimationHandler;
-            addOnScrollListener(new SpringMotionOnScrollListener());
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent e) {
-        mPullDetector.onTouchEvent(e);
-        if (FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null) {
-            mSpringAnimationHandler.addMovement(e);
-        }
-        return super.onTouchEvent(e);
-    }
-
     /**
      * Sets the list of apps in this view, used to determine the fastscroll position.
      */
@@ -170,26 +126,6 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        canvas.translate(0, mContentTranslationY);
-        super.dispatchDraw(canvas);
-        canvas.translate(0, -mContentTranslationY);
-    }
-
-    public float getContentTranslationY() {
-        return mContentTranslationY;
-    }
-
-    /**
-     * Use this method instead of calling {@link #setTranslationY(float)}} directly to avoid drawing
-     * on top of other Views.
-     */
-    public void setContentTranslationY(float y) {
-        mContentTranslationY = y;
-        invalidate();
-    }
-
-    @Override
     protected boolean verifyDrawable(Drawable who) {
         return who == mEmptySearchBackground || super.verifyDrawable(who);
     }
@@ -231,8 +167,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent e) {
-        mPullDetector.onTouchEvent(e);
-        boolean result = super.onInterceptTouchEvent(e) || mOverScrollHelper.isInOverScroll();
+        boolean result = super.onInterceptTouchEvent(e);
         if (!result && e.getAction() == MotionEvent.ACTION_DOWN
                 && mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
             mEmptySearchBackground.setHotspot(e.getX(), e.getY());
@@ -480,114 +415,4 @@
                 y + mEmptySearchBackground.getIntrinsicHeight());
     }
 
-    private class SpringMotionOnScrollListener extends RecyclerView.OnScrollListener {
-
-        @Override
-        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
-            if (mOverScrollHelper.isInOverScroll()) {
-                // OverScroll will handle animating the springs.
-                return;
-            }
-
-            // We only start the spring animation when we hit the top/bottom, to ensure
-            // that all of the animations start at the same time.
-            if (dy < 0 && !canScrollVertically(-1)) {
-                mSpringAnimationHandler.animateToFinalPosition(0, 1);
-            } else if (dy > 0 && !canScrollVertically(1)) {
-                mSpringAnimationHandler.animateToFinalPosition(0, -1);
-            }
-        }
-    }
-
-    private class OverScrollHelper implements SwipeDetector.Listener {
-
-        private static final float MAX_RELEASE_VELOCITY = 5000; // px / s
-        private static final float MAX_OVERSCROLL_PERCENTAGE = 0.07f;
-
-        private boolean mIsInOverScroll;
-
-        // We use this value to calculate the actual amount the user has overscrolled.
-        private float mFirstDisplacement = 0;
-
-        private boolean mAlreadyScrollingUp;
-        private int mFirstScrollYOnScrollUp;
-
-        @Override
-        public void onDragStart(boolean start) {
-        }
-
-        @Override
-        public boolean onDrag(float displacement, float velocity) {
-            boolean isScrollingUp = displacement > 0;
-            if (isScrollingUp) {
-                if (!mAlreadyScrollingUp) {
-                    mFirstScrollYOnScrollUp = getCurrentScrollY();
-                    mAlreadyScrollingUp = true;
-                }
-            } else {
-                mAlreadyScrollingUp = false;
-            }
-
-            // Only enter overscroll if the user is interacting with the RecyclerView directly
-            // and if one of the following criteria are met:
-            // - User scrolls down when they're already at the bottom.
-            // - User starts scrolling up, hits the top, and continues scrolling up.
-            boolean wasInOverScroll = mIsInOverScroll;
-            mIsInOverScroll = !mScrollbar.isDraggingThumb() &&
-                    ((!canScrollVertically(1) && displacement < 0) ||
-                    (!canScrollVertically(-1) && isScrollingUp && mFirstScrollYOnScrollUp != 0));
-
-            if (wasInOverScroll && !mIsInOverScroll) {
-                // Exit overscroll. This can happen when the user is in overscroll and then
-                // scrolls the opposite way.
-                reset(false /* shouldSpring */);
-            } else if (mIsInOverScroll) {
-                if (Float.compare(mFirstDisplacement, 0) == 0) {
-                    // Because users can scroll before entering overscroll, we need to
-                    // subtract the amount where the user was not in overscroll.
-                    mFirstDisplacement = displacement;
-                }
-                float overscrollY = displacement - mFirstDisplacement;
-                setContentTranslationY(getDampedOverScroll(overscrollY));
-            }
-
-            return mIsInOverScroll;
-        }
-
-        @Override
-        public void onDragEnd(float velocity, boolean fling) {
-           reset(mIsInOverScroll  /* shouldSpring */);
-        }
-
-        private void reset(boolean shouldSpring) {
-            float y = getContentTranslationY();
-            if (Float.compare(y, 0) != 0) {
-                if (mSpringAnimationHandler != null && shouldSpring) {
-                    // We calculate our own velocity to give the springs the desired effect.
-                    float velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
-                    // We want to negate the velocity because we are moving to 0 from -1 due to the
-                    // downward motion. (y-axis -1 is above 0).
-                    mSpringAnimationHandler.animateToPositionWithVelocity(0, -1, -velocity);
-                }
-
-                ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
-                        AllAppsRecyclerView.CONTENT_TRANS_Y, 0)
-                        .setDuration(100)
-                        .start();
-            }
-            mIsInOverScroll = false;
-            mFirstDisplacement = 0;
-            mFirstScrollYOnScrollUp = 0;
-            mAlreadyScrollingUp = false;
-        }
-
-        public boolean isInOverScroll() {
-            return mIsInOverScroll;
-        }
-
-        private float getDampedOverScroll(float y) {
-            return OverScroll.dampedScroll(y, getHeight());
-        }
-    }
-
 }
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
deleted file mode 100644
index 29a2430..0000000
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2017 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.launcher3.anim;
-
-import android.support.animation.FloatPropertyCompat;
-import android.support.animation.SpringAnimation;
-import android.support.animation.SpringForce;
-import android.support.annotation.IntDef;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-
-import com.android.launcher3.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-
-/**
- * Handler class that manages springs for a set of views that should all move based on the same
- * {@link MotionEvent}s.
- *
- * Supports setting either X or Y velocity on the list of springs added to this handler.
- */
-public class SpringAnimationHandler<T> {
-
-    private static final String TAG = "SpringAnimationHandler";
-    private static final boolean DEBUG = false;
-
-    private static final float VELOCITY_DAMPING_FACTOR = 0.175f;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({Y_DIRECTION, X_DIRECTION})
-    public @interface Direction {}
-    public static final int Y_DIRECTION = 0;
-    public static final int X_DIRECTION = 1;
-    private int mVelocityDirection;
-
-    private VelocityTracker mVelocityTracker;
-    private float mCurrentVelocity = 0;
-    private boolean mShouldComputeVelocity = false;
-
-    private AnimationFactory<T> mAnimationFactory;
-
-    private ArrayList<SpringAnimation> mAnimations = new ArrayList<>();
-
-    /**
-     * @param direction Either {@link #X_DIRECTION} or {@link #Y_DIRECTION}.
-     *                  Determines which direction we use to calculate and set the velocity.
-     * @param factory   The AnimationFactory is responsible for initializing and updating the
-     *                  SpringAnimations added to this class.
-     */
-    public SpringAnimationHandler(@Direction int direction, AnimationFactory<T> factory) {
-        mVelocityDirection = direction;
-        mAnimationFactory = factory;
-    }
-
-    /**
-     * Adds a spring to the list of springs handled by this class.
-     * @param spring The new spring to be added.
-     * @param setDefaultValues If True, sets the spring to the default
-     *                         {@link AnimationFactory} values.
-     */
-    public void add(SpringAnimation spring, boolean setDefaultValues) {
-        if (setDefaultValues) {
-            mAnimationFactory.setDefaultValues(spring);
-        }
-        spring.setStartVelocity(mCurrentVelocity);
-        mAnimations.add(spring);
-    }
-
-    public AnimationFactory<T> getFactory() {
-        return mAnimationFactory;
-    }
-
-    /**
-     * Adds a new or recycled animation to the list of springs handled by this class.
-     *
-     * @param view The view the spring is attached to.
-     * @param object Used to initialize and update the spring.
-     */
-    public void add(View view, T object) {
-        SpringAnimation spring = (SpringAnimation) view.getTag(R.id.spring_animation_tag);
-        if (spring == null) {
-            spring = mAnimationFactory.initialize(object);
-            view.setTag(R.id.spring_animation_tag, spring);
-        }
-        mAnimationFactory.update(spring, object);
-        add(spring, false /* setDefaultValues */);
-    }
-
-    /**
-     * Stops and removes the spring attached to {@param view}.
-     */
-    public void remove(View view) {
-        remove((SpringAnimation) view.getTag(R.id.spring_animation_tag));
-    }
-
-    public void remove(SpringAnimation animation) {
-        if (animation.canSkipToEnd()) {
-            animation.skipToEnd();
-        }
-        mAnimations.remove(animation);
-    }
-
-    public void addMovement(MotionEvent event) {
-        int action = event.getActionMasked();
-        if (DEBUG) Log.d(TAG, "addMovement#action=" + action);
-        switch (action) {
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_DOWN:
-                reset();
-                break;
-        }
-
-        getVelocityTracker().addMovement(event);
-        mShouldComputeVelocity = true;
-    }
-
-    public void animateToFinalPosition(float position, int startValue) {
-        animateToFinalPosition(position, startValue, mShouldComputeVelocity);
-    }
-
-    /**
-     * @param position The final animation position.
-     * @param startValue < 0 if scrolling from start to end; > 0 if scrolling from end to start
-     *                   The magnitude of the number changes how the spring will move.
-     * @param setVelocity If true, we set the velocity to {@link #mCurrentVelocity} before
-     *                    starting the animation.
-     */
-    private void animateToFinalPosition(float position, int startValue, boolean setVelocity) {
-        if (DEBUG) {
-            Log.d(TAG, "animateToFinalPosition#position=" + position + ", startValue=" + startValue);
-        }
-
-        if (mShouldComputeVelocity) {
-            mCurrentVelocity = computeVelocity();
-        }
-
-        int size = mAnimations.size();
-        for (int i = 0; i < size; ++i) {
-            mAnimations.get(i).setStartValue(startValue);
-            if (setVelocity) {
-                mAnimations.get(i).setStartVelocity(mCurrentVelocity);
-            }
-            mAnimations.get(i).animateToFinalPosition(position);
-        }
-
-        reset();
-    }
-
-    /**
-     * Similar to {@link #animateToFinalPosition(float, int)}, but used in cases where we want to
-     * manually set the velocity.
-     */
-    public void animateToPositionWithVelocity(float position, int startValue, float velocity) {
-        if (DEBUG) {
-            Log.d(TAG, "animateToPosition#pos=" + position + ", start=" + startValue
-                    + ", velocity=" + velocity);
-        }
-
-        mCurrentVelocity = velocity;
-        mShouldComputeVelocity = false;
-        animateToFinalPosition(position, startValue, true);
-    }
-
-
-    public boolean isRunning() {
-        // All the animations run at the same time so we can just check the first one.
-        return !mAnimations.isEmpty() && mAnimations.get(0).isRunning();
-    }
-
-    public void skipToEnd() {
-        if (DEBUG) Log.d(TAG, "setStartVelocity#skipToEnd");
-        if (DEBUG) Log.v(TAG, "setStartVelocity#skipToEnd", new Exception());
-
-        int size = mAnimations.size();
-        for (int i = 0; i < size; ++i) {
-            if (mAnimations.get(i).canSkipToEnd()) {
-                mAnimations.get(i).skipToEnd();
-            }
-        }
-    }
-
-    public void reset() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-        mCurrentVelocity = 0;
-        mShouldComputeVelocity = false;
-    }
-
-
-    private float computeVelocity() {
-        getVelocityTracker().computeCurrentVelocity(1000 /* millis */);
-
-        float velocity = isVerticalDirection()
-                ? getVelocityTracker().getYVelocity()
-                : getVelocityTracker().getXVelocity();
-        velocity *= VELOCITY_DAMPING_FACTOR;
-
-        if (DEBUG) Log.d(TAG, "computeVelocity=" + velocity);
-        return velocity;
-    }
-
-    private boolean isVerticalDirection() {
-        return mVelocityDirection == Y_DIRECTION;
-    }
-
-    private VelocityTracker getVelocityTracker() {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        return mVelocityTracker;
-    }
-
-    /**
-     * This interface is used to initialize and update the SpringAnimations added to the
-     * {@link SpringAnimationHandler}.
-     *
-     * @param <T> The object that each SpringAnimation is attached to.
-     */
-    public interface AnimationFactory<T> {
-
-        /**
-         * Initializes a new Spring for {@param object}.
-         */
-        SpringAnimation initialize(T object);
-
-        /**
-         * Updates the value of {@param spring} based on {@param object}.
-         */
-        void update(SpringAnimation spring, T object);
-
-        /**
-         * Sets the factory default values for the given {@param spring}.
-         */
-        void setDefaultValues(SpringAnimation spring);
-    }
-
-    /**
-     * Helper method to create a new SpringAnimation for {@param view}.
-     */
-    public static SpringAnimation forView(View view, FloatPropertyCompat property, float finalPos) {
-        SpringAnimation spring = new SpringAnimation(view, property, finalPos);
-        spring.setSpring(new SpringForce(finalPos));
-        return spring;
-    }
-
-}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index e494bea..28645dc 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -35,8 +35,6 @@
     public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false;
-    // When enabled allows use of physics based motions in the Launcher.
-    public static final boolean LAUNCHER3_PHYSICS = true;
     // When enabled allows use of spring motions on the icons.
     public static final boolean LAUNCHER3_SPRING_ICONS = true;
 
diff --git a/src/com/android/launcher3/util/VerticalSwipeController.java b/src/com/android/launcher3/util/VerticalSwipeController.java
index a647378..ae5bfd5 100644
--- a/src/com/android/launcher3/util/VerticalSwipeController.java
+++ b/src/com/android/launcher3/util/VerticalSwipeController.java
@@ -18,12 +18,10 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.support.animation.SpringAnimation;
 import android.util.Log;
 import android.view.MotionEvent;
 
@@ -31,13 +29,10 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.touch.SwipeDetector.Direction;
 
-import java.util.ArrayList;
 
 /**
  * Handles vertical touch gesture on the DragLayer allowing transitioning from
@@ -68,8 +63,6 @@
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
 
-    protected SpringAnimationHandler[] mSpringHandlers;
-
     public VerticalSwipeController(Launcher l, LauncherState baseState) {
         this(l, baseState, ALL_APPS, SwipeDetector.VERTICAL);
     }
@@ -104,29 +97,6 @@
         }
     }
 
-    protected void initSprings() {
-        AllAppsContainerView appsView = mLauncher.getAppsView();
-
-        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
-        if (handler == null) {
-            mSpringHandlers = new SpringAnimationHandler[0];
-            return;
-        }
-
-        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
-        handlers.add(handler);
-
-        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
-        if (searchSpring != null) {
-            SpringAnimationHandler searchHandler =
-                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
-            searchHandler.add(searchSpring, true /* setDefaultValues */);
-            handlers.add(searchHandler);
-        }
-
-        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
-    }
-
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -155,10 +125,6 @@
 
             mDetector.setDetectableScrollConditions(
                     directionsToDetectScroll, ignoreSlopWhenSettling);
-
-            if (mSpringHandlers == null) {
-                initSprings();
-            }
         }
 
         if (mNoIntercept) {
@@ -173,9 +139,6 @@
 
     @Override
     public boolean onControllerTouchEvent(MotionEvent ev) {
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.addMovement(ev);
-        }
         return mDetector.onTouchEvent(ev);
     }
 
@@ -198,10 +161,6 @@
             mCurrentAnimation.pause();
             mStartProgress = mCurrentAnimation.getProgressFraction();
         }
-
-        for (SpringAnimationHandler h : mSpringHandlers) {
-            h.skipToEnd();
-        }
     }
 
     protected boolean isTransitionFlipped() {
@@ -244,13 +203,6 @@
             }
         }
 
-        if (fling && targetState == mTargetState) {
-            for (SpringAnimationHandler h : mSpringHandlers) {
-                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
-                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
-            }
-        }
-
         mCurrentAnimation.setEndAction(() -> {
             mLauncher.getStateManager().goToState(targetState, false);
             onTransitionComplete(fling, targetState == mToState);
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
new file mode 100644
index 0000000..090b3e6
--- /dev/null
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2018 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.launcher3.views;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.support.animation.FloatPropertyCompat;
+import android.support.animation.SpringAnimation;
+import android.support.animation.SpringForce;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.EdgeEffectFactory;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.View;
+import android.widget.EdgeEffect;
+import android.widget.RelativeLayout;
+
+import static android.support.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY;
+import static android.support.animation.SpringForce.STIFFNESS_LOW;
+import static android.support.animation.SpringForce.STIFFNESS_MEDIUM;
+
+public class SpringRelativeLayout extends RelativeLayout {
+
+    private static final float STIFFNESS = (STIFFNESS_MEDIUM + STIFFNESS_LOW) / 2;
+    private static final float DAMPING_RATIO = DAMPING_RATIO_MEDIUM_BOUNCY;
+    private static final float VELOCITY_MULTIPLIER = 0.3f;
+
+    private static final FloatPropertyCompat<SpringRelativeLayout> DAMPED_SCROLL =
+            new FloatPropertyCompat<SpringRelativeLayout>("value") {
+
+                @Override
+                public float getValue(SpringRelativeLayout object) {
+                    return object.mDampedScrollShift;
+                }
+
+                @Override
+                public void setValue(SpringRelativeLayout object, float value) {
+                    object.setDampedScrollShift(value);
+                }
+            };
+
+    private final SparseBooleanArray mSpringViews = new SparseBooleanArray();
+    private final SpringAnimation mSpring;
+
+    private float mDampedScrollShift = 0;
+
+    public SpringRelativeLayout(Context context) {
+        this(context, null);
+    }
+
+    public SpringRelativeLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mSpring = new SpringAnimation(this, DAMPED_SCROLL, 0);
+        mSpring.setSpring(new SpringForce(0)
+                .setStiffness(STIFFNESS)
+                .setDampingRatio(DAMPING_RATIO));
+    }
+
+    public void addSpringView(int id) {
+        mSpringViews.put(id, true);
+    }
+
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        if (mDampedScrollShift != 0 && mSpringViews.get(child.getId())) {
+            canvas.translate(0, mDampedScrollShift);
+            boolean result = super.drawChild(canvas, child, drawingTime);
+            canvas.translate(0, -mDampedScrollShift);
+            return result;
+        }
+        return super.drawChild(canvas, child, drawingTime);
+    }
+
+    private void setDampedScrollShift(float shift) {
+        if (shift != mDampedScrollShift) {
+            mDampedScrollShift = shift;
+            invalidate();
+        }
+    }
+
+    private void finishScrollWithVelocity(float velocity) {
+        mSpring.setStartVelocity(velocity);
+        mSpring.setStartValue(mDampedScrollShift);
+        mSpring.start();
+    }
+
+    public EdgeEffectFactory createEdgeEffectFactory() {
+        return new SpringEdgeEffectFactory();
+    }
+
+    private class SpringEdgeEffectFactory extends EdgeEffectFactory {
+
+        @NonNull @Override
+        protected EdgeEffect createEdgeEffect(RecyclerView view, int direction) {
+            switch (direction) {
+                case DIRECTION_TOP:
+                    return new SpringEdgeEffect(getContext(), +VELOCITY_MULTIPLIER);
+                case DIRECTION_BOTTOM:
+                    return new SpringEdgeEffect(getContext(), -VELOCITY_MULTIPLIER);
+            }
+            return super.createEdgeEffect(view, direction);
+        }
+    }
+
+    private class SpringEdgeEffect extends EdgeEffect {
+
+        private final float mVelocityMultiplier;
+
+        private float mDistance;
+
+        public SpringEdgeEffect(Context context, float velocityMultiplier) {
+            super(context);
+            mVelocityMultiplier = velocityMultiplier;
+        }
+
+        @Override
+        public boolean draw(Canvas canvas) {
+            return false;
+        }
+
+        @Override
+        public void onAbsorb(int velocity) {
+            finishScrollWithVelocity(velocity * mVelocityMultiplier);
+        }
+
+        @Override
+        public void onPull(float deltaDistance, float displacement) {
+            mDistance += deltaDistance * (mVelocityMultiplier / 3f);
+            setDampedScrollShift(mDistance * getHeight());
+        }
+
+        @Override
+        public void onRelease() {
+            mDistance = 0;
+            finishScrollWithVelocity(0);
+        }
+    }
+}
\ No newline at end of file