Merge "Adding implementation of InstantAppResolver for quickstep" into ub-launcher3-master
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index d76cd25..dbc2763 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 9f4f8a1..54190ec 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -24,8 +24,4 @@
     android:alpha="0.0"
     android:visibility="invisible" >
 
-    <com.android.launcher3.uioverrides.WorkspaceCard
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
 </com.android.quickstep.RecentsView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index cf86176..6a76b45 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -33,8 +33,6 @@
     <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
 
-    <dimen name="workspace_overview_offset_x">-24dp</dimen>
-
     <!-- Launcher app transition -->
     <dimen name="content_trans_y">25dp</dimen>
     <dimen name="workspace_trans_y">80dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index b97669b..27b7f52 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -295,7 +295,7 @@
         boolean launchingCenterTask = launchedTaskIndex == centerTaskIndex;
         boolean isRtl = recentsView.isRtl();
         if (launchingCenterTask) {
-            if (launchedTaskIndex - 1 >= recentsView.getFirstTaskIndex()) {
+            if (launchedTaskIndex - 1 >= 0) {
                 TaskView adjacentPage1 = (TaskView) recentsView.getPageAt(launchedTaskIndex - 1);
                 ObjectAnimator adjacentTask1ScaleAndTranslate =
                         LauncherAnimUtils.ofPropertyValuesHolder(adjacentPage1,
@@ -317,7 +317,7 @@
                                         .build());
                 launcherAnimator.play(adjacentTask2ScaleAndTranslate);
             }
-        } else if (centerTaskIndex >= recentsView.getFirstTaskIndex()) {
+        } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
             TaskView centerTask = (TaskView) recentsView.getPageAt(centerTaskIndex);
             float translationX = Math.abs(v.getTranslationX());
@@ -329,7 +329,7 @@
                                     .build());
             launcherAnimator.play(centerTaskParallaxToRight);
             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - launchedTaskIndex);
-            if (otherAdjacentTaskIndex >= recentsView.getFirstTaskIndex()
+            if (otherAdjacentTaskIndex >= 0
                     && otherAdjacentTaskIndex < recentsView.getPageCount()) {
                 TaskView otherAdjacentTask = (TaskView) recentsView.getPageAt(
                         otherAdjacentTaskIndex);
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/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index acd4fc1..c86c5af 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -23,7 +23,7 @@
 public class FastOverviewState extends OverviewState {
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
-            | FLAG_PAGE_BACKGROUNDS | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
+            | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
 
     private static final boolean DEBUG_DIFFERENT_UI = false;
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 854fb4f..4dfbf8d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 
 import android.graphics.Rect;
@@ -24,7 +24,6 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.RecentsView;
@@ -35,7 +34,7 @@
 public class OverviewState extends LauncherState {
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
-            | FLAG_DISABLE_RESTORE | FLAG_PAGE_BACKGROUNDS | FLAG_OVERVIEW_UI;
+            | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI;
 
     public OverviewState(int id) {
         this(id, STATE_FLAGS);
@@ -48,18 +47,13 @@
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
         Rect pageRect = new Rect();
-        RecentsView.getScaledDownPageRect(launcher.getDeviceProfile(), launcher, pageRect);
-        RecentsView rv = launcher.getOverviewPanel();
+        RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, pageRect);
 
         if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
-        float overlap = 0;
-        if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
-            overlap = launcher.getResources().getDimension(R.dimen.workspace_overview_offset_x);
-        }
-        return getScaleAndTranslationForPageRect(launcher, overlap, pageRect);
+        return getScaleAndTranslationForPageRect(launcher, pageRect);
     }
 
     @Override
@@ -85,40 +79,25 @@
     }
 
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
-        final int centerPage = launcher.getWorkspace().getNextPage();
-        return new PageAlphaProvider(ACCEL_2) {
+        return new PageAlphaProvider(DEACCEL_2) {
             @Override
             public float getPageAlpha(int pageIndex) {
-                return  pageIndex != centerPage ? 0 : 1f;
+                return 0;
             }
         };
     }
 
-    public static float[] getScaleAndTranslationForPageRect(Launcher launcher, float offsetX,
-            Rect pageRect) {
+    public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
         Workspace ws = launcher.getWorkspace();
         float childWidth = ws.getNormalChildWidth();
-        float childHeight = ws.getNormalChildHeight();
 
-        float scale = pageRect.height() / childHeight;
+        float scale = pageRect.width() / childWidth;
         Rect insets = launcher.getDragLayer().getInsets();
 
         float halfHeight = ws.getExpectedHeight() / 2;
         float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
         float translationY = pageRect.top - childTop;
 
-        // Align the workspace horizontally centered with the task rect
-        float halfWidth = ws.getExpectedWidth() / 2;
-        float childCenter = halfWidth -
-                scale * (halfWidth - ws.getPaddingLeft() - insets.left - childWidth / 2);
-        float translationX = pageRect.centerX() - childCenter;
-
-        if (launcher.<RecentsView>getOverviewPanel().isRtl()) {
-            translationX -= offsetX / scale;
-        } else {
-            translationX += offsetX / scale;
-        }
-
-        return new float[] {scale, translationX, translationY};
+        return new float[] {scale, 0, translationY};
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
index 468a561..958091b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -15,6 +15,13 @@
  */
 package com.android.launcher3.uioverrides;
 
+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.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -32,20 +39,13 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsView;
 import com.android.quickstep.TaskView;
 
-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.Interpolators.DEACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-
 /**
  * Touch controller for swipe interaction in Overview state
  */
@@ -132,28 +132,22 @@
                 mTaskBeingDragged = null;
                 mSwipeDownEnabled = true;
 
-                int currentPage = mRecentsView.getCurrentPage();
-                if (currentPage == 0) {
-                    // User is on home tile
+                View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
+                if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
+                        view instanceof TaskView) {
+                    // The tile can be dragged down to open the task.
+                    mTaskBeingDragged = (TaskView) view;
                     directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                    mStartingTarget = LauncherLogProto.ItemType.TASK;
+                } else if (isEventOverHotseat(ev)) {
+                    // The hotseat is being dragged
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                    mSwipeDownEnabled = false;
+                    mStartingTarget = ContainerType.HOTSEAT;
                 } else {
-                    View view = mRecentsView.getChildAt(currentPage);
-                    if (mLauncher.getDragLayer().isEventOverView(view, ev) &&
-                            view instanceof TaskView) {
-                        // The tile can be dragged down to open the task.
-                        mTaskBeingDragged = (TaskView) view;
-                        directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
-                        mStartingTarget = LauncherLogProto.ItemType.TASK;
-                    } else if (isEventOverHotseat(ev)) {
-                        // The hotseat is being dragged
-                        directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
-                        mSwipeDownEnabled = false;
-                        mStartingTarget = ContainerType.HOTSEAT;
-                    } else {
-                        mNoIntercept = true;
-                        mStartingTarget = ContainerType.WORKSPACE;
-                        return false;
-                    }
+                    mNoIntercept = true;
+                    mStartingTarget = ContainerType.WORKSPACE;
+                    return false;
                 }
             }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 92d071a..2a2e9c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -28,7 +28,6 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.PagedView;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
@@ -40,31 +39,26 @@
 
     private final Launcher mLauncher;
     private final RecentsView mRecentsView;
-    private final WorkspaceCard mWorkspaceCard;
 
     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 boolean mIsRecentsScrollingToFirstTask;
+    private boolean mIsRecentsSlidingInOrOut;
 
     public RecentsViewStateController(Launcher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
         mRecentsView.setStateController(this);
-
-        mWorkspaceCard = (WorkspaceCard) mRecentsView.getChildAt(0);
-        mWorkspaceCard.setup(launcher);
     }
 
     @Override
     public void setState(LauncherState state) {
-        mWorkspaceCard.setWorkspaceScrollingEnabled(state.overviewUi);
         setVisibility(state.overviewUi);
         setTransitionProgress(state.overviewUi ? 1 : 0);
         if (state.overviewUi) {
-            for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
+            for (int i = 0; i < mRecentsView.getPageCount(); i++) {
                 ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
             }
             mRecentsView.updateCurveProperties();
@@ -74,22 +68,19 @@
     @Override
     public void setStateWithAnimation(final LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        boolean settingEnabled = Utilities.getPrefs(mLauncher)
-            .getBoolean("pref_scroll_to_first_task_default_true", true);
-        mIsRecentsScrollingToFirstTask = mLauncher.isInState(NORMAL) && toState == OVERVIEW
-                && settingEnabled;
-        // TODO: Instead of animating the workspace translationX, move the contents
-        mWorkspaceCard.setWorkspaceScrollingEnabled(mIsRecentsScrollingToFirstTask);
+        LauncherState fromState = mLauncher.getStateManager().getState();
+        mIsRecentsSlidingInOrOut = fromState == NORMAL && toState.overviewUi
+                || fromState.overviewUi && toState == NORMAL;
 
         // Scroll to the workspace card before changing to the NORMAL state.
         int currPage = mRecentsView.getCurrentPage();
-        LauncherState fromState = mLauncher.getStateManager().getState();
         if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
             int maxSnapDuration = PagedView.SLOW_PAGE_SNAP_ANIMATION_DURATION;
             int durationPerPage = maxSnapDuration / 10;
             int snapDuration = Math.min(maxSnapDuration, durationPerPage * currPage);
             mRecentsView.snapToPage(0, snapDuration);
-            builder.setStartDelay(snapDuration);
+            // Let the snapping animation play for a bit before we translate off screen.
+            builder.setStartDelay(snapDuration / 4);
         }
 
         ObjectAnimator progressAnim =
@@ -100,7 +91,6 @@
 
             @Override
             public void onAnimationSuccess(Animator animator) {
-                mWorkspaceCard.setWorkspaceScrollingEnabled(toState.overviewUi);
                 mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
             }
         });
@@ -145,11 +135,14 @@
 
     private void onTransitionProgress() {
         applyProgress();
-        if (mIsRecentsScrollingToFirstTask) {
-            int scrollForFirstTask = mRecentsView.getScrollForPage(mRecentsView.getFirstTaskIndex());
-            int scrollForPage0 = mRecentsView.getScrollForPage(0);
-            mRecentsView.setScrollX((int) (mTransitionProgress.value * scrollForFirstTask
-                    + (1 - mTransitionProgress.value) * scrollForPage0));
+        if (mIsRecentsSlidingInOrOut) {
+            float interpolatedProgress = ACCEL.getInterpolation(mTransitionProgress.value);
+            // Slide in from the side as we swipe.
+            int translation = mRecentsView.getWidth();
+            if (mRecentsView.isRtl()) {
+                translation = -translation;
+            }
+            mRecentsView.setTranslationX(translation * (1 - interpolatedProgress));
         }
     }
 
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/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
deleted file mode 100644
index 8533502..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
+++ /dev/null
@@ -1,127 +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.uioverrides;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.RecentsView.PageCallbacks;
-import com.android.quickstep.RecentsView.ScrollState;
-
-public class WorkspaceCard extends View implements PageCallbacks, OnClickListener {
-
-    private final Rect mTempRect = new Rect();
-
-    private Launcher mLauncher;
-    private Workspace mWorkspace;
-
-    private float mLinearInterpolationForPage2 = 1;
-    private float mTranslateXPage0, mTranslateXPage1;
-    private float mExtraScrollShift;
-
-    private boolean mIsWorkspaceScrollingEnabled;
-
-    public WorkspaceCard(Context context) {
-        this(context, null);
-    }
-
-    public WorkspaceCard(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        setOnClickListener(this);
-    }
-
-    /**
-     * Draw nothing.
-     */
-    @Override
-    public void draw(Canvas canvas) { }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        // Initiate data
-        mLinearInterpolationForPage2 = RecentsView.getScaledDownPageRect(
-                mLauncher.getDeviceProfile(), mLauncher, mTempRect);
-
-        float[] scale = OverviewState.getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
-        mTranslateXPage0 = scale[1];
-        mTranslateXPage1 = OverviewState
-                .getScaleAndTranslationForPageRect(mLauncher,
-                        getResources().getDimension(R.dimen.workspace_overview_offset_x) / scale[0],
-                        mTempRect)[1];
-
-        mExtraScrollShift = 0;
-        if (mWorkspace != null && getWidth() > 0) {
-            float workspaceWidth = mWorkspace.getNormalChildWidth() * scale[0];
-            mExtraScrollShift = (workspaceWidth - getWidth()) / 2;
-            setScaleX(workspaceWidth / getWidth());
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        mLauncher.getStateManager().goToState(NORMAL);
-    }
-
-    public void setup(Launcher launcher) {
-        mLauncher = launcher;
-        mWorkspace = mLauncher.getWorkspace();
-    }
-
-    public void setWorkspaceScrollingEnabled(boolean isEnabled) {
-        mIsWorkspaceScrollingEnabled = isEnabled;
-    }
-
-    @Override
-    public int onPageScroll(ScrollState scrollState) {
-        float factor = scrollState.linearInterpolation;
-        float translateX = scrollState.distanceFromScreenCenter;
-        if (mIsWorkspaceScrollingEnabled) {
-            float shift = factor * (mTranslateXPage1 - mTranslateXPage0);
-            mWorkspace.setTranslationX(shift + mTranslateXPage0);
-            translateX += shift;
-        }
-
-        setTranslationX(translateX);
-
-        // If the workspace card is still the first page, shift all the other pages.
-        if (scrollState.linearInterpolation > mLinearInterpolationForPage2) {
-            scrollState.prevPageExtraWidth = 0;
-        } else if (mLinearInterpolationForPage2 > 0) {
-            scrollState.prevPageExtraWidth = mExtraScrollShift *
-                    (1 - scrollState.linearInterpolation / mLinearInterpolationForPage2);
-        } else {
-            scrollState.prevPageExtraWidth = mExtraScrollShift;
-        }
-        return SCROLL_TYPE_WORKSPACE;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 73cd503..9b2e822 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -23,8 +23,10 @@
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
 import static com.android.quickstep.RemoteRunnable.executeSafely;
+import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
@@ -61,6 +63,7 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -71,6 +74,8 @@
     private static final String TAG = "ActivityTouchConsumer";
 
     private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
+    private static final int[] DEFERRED_HIT_TARGETS = DEBUG_SHOW_OVERVIEW_BUTTON
+            ? new int[] {HIT_TARGET_BACK, HIT_TARGET_OVERVIEW} : new int[] {HIT_TARGET_BACK};
 
     private final RunningTaskInfo mRunningTask;
     private final RecentsModel mRecentsModel;
@@ -79,6 +84,7 @@
     private final MainThreadExecutor mMainThreadExecutor;
     private final Choreographer mBackgroundThreadChoreographer;
 
+    private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private int mActivePointerId = INVALID_POINTER_ID;
@@ -88,7 +94,6 @@
     private BaseSwipeInteractionHandler mInteractionHandler;
     private int mDisplayRotation;
     private Rect mStableInsets = new Rect();
-    private @HitTarget int mDownHitTarget = HIT_TARGET_NONE;
 
     private VelocityTracker mVelocityTracker;
     private MotionEventQueue mEventQueue;
@@ -105,7 +110,7 @@
         mISystemUiProxy = systemUiProxy;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadChoreographer = backgroundThreadChoreographer;
-        mDownHitTarget = downHitTarget;
+        mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
     }
 
     @Override
@@ -124,7 +129,7 @@
 
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
-                if (!isUsingScreenShot() && mDownHitTarget != HIT_TARGET_BACK) {
+                if (!isUsingScreenShot() && !mIsDeferredDownTarget) {
                     startTouchTrackingForWindowAnimation(ev.getEventTime());
                 }
 
@@ -166,7 +171,7 @@
 
                         if (isUsingScreenShot()) {
                             startTouchTrackingForScreenshotAnimation();
-                        } else if (mDownHitTarget == HIT_TARGET_BACK) {
+                        } else if (mIsDeferredDownTarget) {
                             // If we deferred starting the window animation on touch down, then
                             // start tracking now
                             startTouchTrackingForWindowAnimation(ev.getEventTime());
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
new file mode 100644
index 0000000..b60d1e2
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -0,0 +1,111 @@
+/*
+ * 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.quickstep;
+
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.states.InternalStateHandler;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Helper class to handle various atomic commands for switching between Overview.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class OverviewCommandHelper extends InternalStateHandler {
+
+    private final Context mContext;
+    private final ActivityManagerWrapper mAM;
+
+    public final Intent homeIntent;
+    public final ComponentName launcher;
+
+    private long mLastToggleTime;
+
+    public OverviewCommandHelper(Context context) {
+        mContext = context;
+        mAM = ActivityManagerWrapper.getInstance();
+
+        homeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setPackage(context.getPackageName())
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
+        launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+        // Clear the packageName as system can fail to dedupe it b/64108432
+        homeIntent.setComponent(launcher).setPackage(null);
+    }
+
+    public void onOverviewToggle() {
+        long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
+        mLastToggleTime = SystemClock.elapsedRealtime();
+
+        if (isOverviewAlmostVisible()) {
+            boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
+            startNonLauncherTask(isQuickTap ? 2 : 1);
+        } else {
+            Intent intent = addToIntent(new Intent(homeIntent));
+            mContext.startActivity(intent);
+            initWhenReady();
+        }
+    }
+
+    private void startNonLauncherTask(int backStackCount) {
+        for (RecentTaskInfo rti : mAM.getRecentTasks(backStackCount, UserHandle.myUserId())) {
+            backStackCount--;
+            if (backStackCount == 0) {
+                mAM.startActivityFromRecents(rti.id, null);
+            }
+        }
+    }
+
+    private boolean isOverviewAlmostVisible() {
+        if (clearReference()) {
+            return true;
+        }
+        if (!mAM.getRunningTask().topActivity.equals(launcher)) {
+            return false;
+        }
+        Launcher launcher = getLauncher();
+        return launcher != null && launcher.isStarted() && launcher.isInState(OVERVIEW);
+    }
+
+    private Launcher getLauncher() {
+        return (Launcher) LauncherAppState.getInstance(mContext).getModel().getCallback();
+    }
+
+    @Override
+    protected boolean init(Launcher launcher, boolean alreadyOnHome) {
+        AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
+        launcher.getStateManager().goToState(OVERVIEW, alreadyOnHome);
+        clearReference();
+        return false;
+    }
+
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 3c68281..4af89bf 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,7 +15,9 @@
  */
 package com.android.quickstep;
 
+import static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
+import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
 
 import android.content.Context;
 import android.os.Handler;
@@ -60,7 +62,7 @@
         }
     };
 
-    private static int sFlags;
+    private static int sFlags = DEBUG_SHOW_OVERVIEW_BUTTON ? FLAG_SHOW_OVERVIEW_BUTTON : 0;
 
     public static void setBackButtonVisible(Context context, boolean visible) {
         updateFlagOnUi(context, FLAG_HIDE_BACK_BUTTON, !visible);
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index f28d51c..dc1c0d2 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -22,10 +22,9 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 
 /**
  * Responds to quick scrub callbacks to page through and launch recent tasks.
@@ -52,6 +51,7 @@
     private boolean mInQuickScrub;
     private int mQuickScrubSection;
     private int mStartPage;
+    private boolean mStartedFromHome;
     private boolean mHasAlarmRun;
 
     public QuickScrubController(Launcher launcher, RecentsView recentsView) {
@@ -65,7 +65,8 @@
 
     public void onQuickScrubStart(boolean startingFromHome) {
         mInQuickScrub = true;
-        mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
+        mStartPage = 0;
+        mStartedFromHome = startingFromHome;
         mQuickScrubSection = 0;
         mHasAlarmRun = false;
         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
@@ -78,11 +79,7 @@
         }
         int page = mRecentsView.getNextPage();
         Runnable launchTaskRunnable = () -> {
-            if (page < mRecentsView.getFirstTaskIndex()) {
-                mRecentsView.getPageAt(page).performClick();
-            } else {
-                ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
-            }
+            ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
         };
         int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
                 * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
@@ -94,7 +91,7 @@
             launchTaskRunnable.run();
         }
         mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
-                ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
+                ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
                         ContainerType.WORKSPACE : ContainerType.APP);
     }
 
@@ -116,7 +113,7 @@
     }
 
     public void onQuickSwitch() {
-        for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
+        for (int i = 0; i < mRecentsView.getPageCount(); i++) {
             TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
             if (taskView.getTask().key.id != mRecentsView.getRunningTaskId()) {
                 Runnable launchTaskRunnable = () -> taskView.launchTask(true);
@@ -131,13 +128,13 @@
             }
         }
         mLauncher.getUserEventDispatcher().logActionOnControl(Touch.FLING,
-                ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
+                ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
                         ContainerType.WORKSPACE : ContainerType.APP);
     }
 
     public void snapToPageForCurrentQuickScrubSection() {
         if (mInQuickScrub) {
-            goToPageWithHaptic(mRecentsView.getFirstTaskIndex() + mQuickScrubSection);
+            goToPageWithHaptic(mQuickScrubSection);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 3e3b3b2..b4ce646 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -31,7 +31,6 @@
 import android.util.LruCache;
 import android.util.SparseArray;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Preconditions;
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 9ae41eb..fdfca84 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -17,9 +17,6 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.TaskView.CURVE_FACTOR;
-import static com.android.quickstep.TaskView.CURVE_INTERPOLATOR;
 
 import android.animation.LayoutTransition;
 import android.animation.LayoutTransition.TransitionListener;
@@ -52,7 +49,6 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
@@ -72,10 +68,6 @@
 
     private static final Rect sTempStableInsets = new Rect();
 
-    public static final int SCROLL_TYPE_NONE = 0;
-    public static final int SCROLL_TYPE_TASK = 1;
-    public static final int SCROLL_TYPE_WORKSPACE = 2;
-
     private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
 
     private final Launcher mLauncher;
@@ -152,7 +144,6 @@
                 mIsRtl = !mIsRtl;
             }
             setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
-            mScrollState.isRtl = mIsRtl;
         }
     }
 
@@ -274,7 +265,7 @@
     }
 
     public TaskView getTaskView(int taskId) {
-        for (int i = getFirstTaskIndex(); i < getChildCount(); i++) {
+        for (int i = 0; i < getChildCount(); i++) {
             TaskView tv = (TaskView) getChildAt(i);
             if (tv.getTask().key.id == taskId) {
                 return tv;
@@ -407,33 +398,6 @@
         return padding;
     }
 
-    /**
-     * Sets the {@param outRect} to match the position of the first tile such that it is scaled
-     * down to match the 2nd taskView.
-     * @return returns the factor which determines the scaling factor for the second task.
-     */
-    public static float getScaledDownPageRect(DeviceProfile dp, Context context, Rect outRect) {
-        getPageRect(dp, context, outRect);
-
-        int pageSpacing = context.getResources()
-                .getDimensionPixelSize(R.dimen.recents_page_spacing);
-        float halfScreenWidth = dp.widthPx * 0.5f;
-        float halfPageWidth = outRect.width() * 0.5f;
-        float pageCenter = outRect.right + pageSpacing + halfPageWidth;
-        float distanceFromCenter = Math.abs(halfScreenWidth - pageCenter);
-        float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
-        float linearInterpolation = Math.min(1, distanceFromCenter / distanceToReachEdge);
-
-        float scale = 1 - CURVE_INTERPOLATOR.getInterpolation(linearInterpolation) * CURVE_FACTOR;
-
-        int topMargin = context.getResources()
-                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-        outRect.top -= topMargin;
-        Utilities.scaleRectAboutCenter(outRect, scale);
-        outRect.top += (int) (scale * topMargin);
-        return linearInterpolation;
-    }
-
     public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
         Rect targetPadding = getPadding(grid, context);
         Rect insets = grid.getInsets();
@@ -474,8 +438,7 @@
         if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
             return;
         }
-        final int halfPageWidth = mScrollState.halfPageWidth = getNormalChildWidth() / 2;
-        mScrollState.lastScrollType = SCROLL_TYPE_NONE;
+        final int halfPageWidth = getNormalChildWidth() / 2;
         final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
         final int halfScreenWidth = getMeasuredWidth() / 2;
         final int pageSpacing = mPageSpacing;
@@ -484,11 +447,11 @@
         for (int i = 0; i < pageCount; i++) {
             View page = getPageAt(i);
             int pageCenter = page.getLeft() + halfPageWidth;
-            mScrollState.distanceFromScreenCenter = screenCenter - pageCenter;
+            float distanceFromScreenCenter = screenCenter - pageCenter;
             float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
             mScrollState.linearInterpolation = Math.min(1,
-                    Math.abs(mScrollState.distanceFromScreenCenter) / distanceToReachEdge);
-            mScrollState.lastScrollType = ((PageCallbacks) page).onPageScroll(mScrollState);
+                    Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
+            ((PageCallbacks) page).onPageScroll(mScrollState);
         }
     }
 
@@ -640,25 +603,17 @@
     public interface PageCallbacks {
 
         /**
-         * Updates the page UI based on scroll params and returns the type of scroll
-         * effect performed.
-         *
-         * @see #SCROLL_TYPE_NONE
-         * @see #SCROLL_TYPE_TASK
-         * @see #SCROLL_TYPE_WORKSPACE
+         * Updates the page UI based on scroll params.
          */
-        int onPageScroll(ScrollState scrollState);
+        default void onPageScroll(ScrollState scrollState) {};
     }
 
     public static class ScrollState {
 
-        public boolean isRtl;
-        public int lastScrollType;
-
-        public int halfPageWidth;
-        public float distanceFromScreenCenter;
+        /**
+         * The progress from 0 to 1, where 0 is the center
+         * of the screen and 1 is the edge of the screen.
+         */
         public float linearInterpolation;
-
-        public float prevPageExtraWidth;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 9b9c6f2..2cdd4e9 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -74,6 +74,7 @@
         mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
         mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
         mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
+        mPaint.setFilterBitmap(true);
     }
 
     public void bind() {
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index b407f75..e0b03b8 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,9 +16,6 @@
 
 package com.android.quickstep;
 
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
 import android.animation.TimeInterpolator;
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -47,17 +44,15 @@
  */
 public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
 
-    /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
-    public static final float CURVE_FACTOR = 0.25f;
-    /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
-    public static final TimeInterpolator CURVE_INTERPOLATOR
-            = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
+    /** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
+    private static final TimeInterpolator CURVE_INTERPOLATOR
+            = x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
 
     /**
      * The alpha of a black scrim on a page in the carousel as it leaves the screen.
      * In the resting position of the carousel, the adjacent pages have about half this scrim.
      */
-    private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
+    private static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
 
     private static final long SCALE_ICON_DURATION = 120;
 
@@ -163,34 +158,11 @@
     }
 
     @Override
-    public int onPageScroll(ScrollState scrollState) {
+    public void onPageScroll(ScrollState scrollState) {
         float curveInterpolation =
                 CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
-        float scale = 1 - curveInterpolation * CURVE_FACTOR;
-        setScaleX(scale);
-        setScaleY(scale);
-
-        // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
-        setTranslationZ(scale);
 
         mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
-
-        float translation =
-                scrollState.distanceFromScreenCenter * curveInterpolation * CURVE_FACTOR;
-
-        if (scrollState.lastScrollType == SCROLL_TYPE_WORKSPACE) {
-            // Make sure that the task cards do not overlap with the workspace card
-            float min = scrollState.halfPageWidth * (1 - scale);
-            if (scrollState.isRtl) {
-                setTranslationX(Math.min(translation, min) - scrollState.prevPageExtraWidth);
-            } else {
-                setTranslationX(Math.max(translation, -min) + scrollState.prevPageExtraWidth);
-            }
-        } else {
-            setTranslationX(translation);
-        }
-        scrollState.prevPageExtraWidth = 0;
-        return SCROLL_TYPE_TASK;
     }
 
     private static final class TaskOutlineProvider extends ViewOutlineProvider {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e5af3e5..12c2170 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -23,15 +23,12 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.Service;
-import android.content.ComponentName;
 import android.content.Intent;
-import android.content.pm.ResolveInfo;
 import android.graphics.PointF;
 import android.os.Build;
 import android.os.Handler;
@@ -62,6 +59,8 @@
 @TargetApi(Build.VERSION_CODES.O)
 public class TouchInteractionService extends Service {
 
+    public static final boolean DEBUG_SHOW_OVERVIEW_BUTTON = false;
+
     private static final SparseArray<String> sMotionEventNames;
 
     static {
@@ -132,6 +131,17 @@
             mEventQueue.onQuickScrubEnd();
             TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
         }
+
+        @Override
+        public void onOverviewToggle() {
+            mOverviewCommandHelper.onOverviewToggle();
+        }
+
+        @Override
+        public void onOverviewShown(boolean triggeredFromAltTab) { }
+
+        @Override
+        public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { }
     };
 
     private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
@@ -143,13 +153,11 @@
     }
 
     private ActivityManagerWrapper mAM;
-    private RunningTaskInfo mRunningTask;
     private RecentsModel mRecentsModel;
-    private Intent mHomeIntent;
-    private ComponentName mLauncher;
     private MotionEventQueue mEventQueue;
     private MainThreadExecutor mMainThreadExecutor;
     private ISystemUiProxy mISystemUiProxy;
+    private OverviewCommandHelper mOverviewCommandHelper;
 
     private Choreographer mMainThreadChoreographer;
     private Choreographer mBackgroundThreadChoreographer;
@@ -161,16 +169,7 @@
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.getInstance(this);
         mMainThreadExecutor = new MainThreadExecutor();
-
-        mHomeIntent = new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setPackage(getPackageName())
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        ResolveInfo info = getPackageManager().resolveActivity(mHomeIntent, 0);
-        mLauncher = new ComponentName(getPackageName(), info.activityInfo.name);
-        // Clear the packageName as system can fail to dedupe it b/64108432
-        mHomeIntent.setComponent(mLauncher).setPackage(null);
-
+        mOverviewCommandHelper = new OverviewCommandHelper(this);
         mMainThreadChoreographer = Choreographer.getInstance();
         mNoOpEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
         mEventQueue = mNoOpEventQueue;
@@ -195,19 +194,19 @@
     }
 
     private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
-        mRunningTask = mAM.getRunningTask();
+        RunningTaskInfo runningTaskInfo = mAM.getRunningTask();
 
         mEventQueue.reset();
 
-        if (mRunningTask == null) {
+        if (runningTaskInfo == null) {
             mEventQueue = mNoOpEventQueue;
-        } else if (mRunningTask.topActivity.equals(mLauncher)) {
+        } else if (runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
             mEventQueue = getLauncherEventQueue();
         } else {
             mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
-                    new OtherActivityTouchConsumer(this, mRunningTask, mRecentsModel,
-                    mHomeIntent, mISystemUiProxy, mMainThreadExecutor,
-                    mBackgroundThreadChoreographer, downHitTarget));
+                    new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
+                            mOverviewCommandHelper.homeIntent, mISystemUiProxy, mMainThreadExecutor,
+                            mBackgroundThreadChoreographer, downHitTarget));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index f49106e..bab8ef1 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -235,7 +235,9 @@
         mContext = context;
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
-        mInputConsumer.registerInputConsumer();
+        // Register the input consumer on the UI thread, to ensure that it runs after any pending
+        // unregister calls
+        mMainExecutor.execute(mInputConsumer::registerInputConsumer);
         initStateCallbacks();
     }
 
@@ -565,8 +567,13 @@
                 for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
                     if (app.mode == MODE_CLOSING) {
                         transaction.setMatrix(app.leash, mTmpMatrix)
-                                .setWindowCrop(app.leash, mClipRect)
-                                .show(app.leash);
+                                .setWindowCrop(app.leash, mClipRect);
+
+                        if (app.isNotInRecents) {
+                            transaction.setAlpha(app.leash, 1 - shift);
+                        }
+
+                        transaction.show(app.leash);
                     }
                 }
                 transaction.apply();
@@ -581,9 +588,8 @@
                 mLauncherTransitionController.setPlayFraction(shift);
 
                 // Make sure the window follows the first task if it moves, e.g. during quick scrub.
-                int firstTaskIndex = mRecentsView.getFirstTaskIndex();
-                View firstTask = mRecentsView.getPageAt(firstTaskIndex);
-                int scrollForFirstTask = mRecentsView.getScrollForPage(firstTaskIndex);
+                View firstTask = mRecentsView.getPageAt(0);
+                int scrollForFirstTask = mRecentsView.getScrollForPage(0);
                 int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
                 if (offsetFromFirstTask != 0) {
                     synchronized (mTargetRect) {
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/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 978b5a1..f507a88 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -31,21 +31,22 @@
             android:id="@+id/widgets_list_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:clipToPadding="false"
-            />
+            android:clipToPadding="false" />
 
         <!-- Fast scroller popup -->
         <TextView
             android:id="@+id/fast_scroller_popup"
             style="@style/FastScrollerPopup"
-            android:layout_gravity="top|end"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"
             android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
 
         <com.android.launcher3.views.RecyclerViewFastScroller
             android:id="@+id/fast_scroller"
             android:layout_width="@dimen/fastscroll_width"
             android:layout_height="match_parent"
-            android:layout_gravity="end"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"
             android:layout_marginEnd="@dimen/fastscroll_end_margin" />
     </com.android.launcher3.views.TopRoundedCornerView>
 </com.android.launcher3.widget.WidgetsFullSheet>
\ No newline at end of file
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fdf468c..375deb7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -19,11 +19,6 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
-import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -39,7 +34,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
-import android.app.AlertDialog;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
@@ -47,7 +41,6 @@
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.ContextWrapper;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
@@ -80,6 +73,7 @@
 import android.view.Menu;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
@@ -99,10 +93,8 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dynamicui.WallpaperColorInfo;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -115,6 +107,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.states.InternalStateHandler;
 import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -155,7 +148,7 @@
  * Default launcher application.
  */
 public class Launcher extends BaseActivity
-        implements LauncherExterns, View.OnClickListener, OnLongClickListener,
+        implements LauncherExterns, OnClickListener, OnLongClickListener,
                    LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
                    WallpaperColorInfo.OnThemeChangeListener {
     public static final String TAG = "Launcher";
@@ -170,8 +163,8 @@
     private static final int REQUEST_PICK_WALLPAPER = 10;
 
     private static final int REQUEST_BIND_APPWIDGET = 11;
-    private static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
-    private static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
+    public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
+    public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
 
     private static final int REQUEST_PERMISSION_CALL_PHONE = 14;
 
@@ -1017,7 +1010,7 @@
         BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.app_icon, parent, false);
         favorite.applyFromShortcutInfo(info);
-        favorite.setOnClickListener(this);
+        favorite.setOnClickListener(ItemClickHandler.INSTANCE);
         favorite.setOnFocusChangeListener(mFocusHandler);
         return favorite;
     }
@@ -1670,6 +1663,7 @@
      *
      * @param v The view representing the clicked shortcut.
      */
+    @Override
     public void onClick(View v) {
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
@@ -1702,21 +1696,6 @@
             }
             return;
         }
-
-        Object tag = v.getTag();
-        if (tag instanceof ShortcutInfo) {
-            onClickAppShortcut(v);
-        } else if (tag instanceof FolderInfo) {
-            if (v instanceof FolderIcon) {
-                onClickFolderIcon(v);
-            }
-        } else if (tag instanceof AppInfo) {
-            startAppShortcutOrInfoActivity(v);
-        } else if (tag instanceof LauncherAppWidgetInfo) {
-            if (v instanceof PendingAppWidgetHostView) {
-                onClickPendingWidget((PendingAppWidgetHostView) v);
-            }
-        }
     }
 
     @SuppressLint("ClickableViewAccessibility")
@@ -1725,173 +1704,6 @@
     }
 
     /**
-     * Event handler for the app widget view which has not fully restored.
-     */
-    public void onClickPendingWidget(final PendingAppWidgetHostView v) {
-        if (mIsSafeModeEnabled) {
-            Toast.makeText(this, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-            return;
-        }
-
-        final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
-        if (v.isReadyForClickSetup()) {
-            LauncherAppWidgetProviderInfo appWidgetInfo =
-                    mAppWidgetManager.findProvider(info.providerName, info.user);
-            if (appWidgetInfo == null) {
-                return;
-            }
-            WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
-
-            if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
-                if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
-                    // This should not happen, as we make sure that an Id is allocated during bind.
-                    return;
-                }
-                addFlowHandler.startBindFlow(this, info.appWidgetId, info,
-                        REQUEST_BIND_PENDING_APPWIDGET);
-            } else {
-                addFlowHandler.startConfigActivity(this, info, REQUEST_RECONFIGURE_APPWIDGET);
-            }
-        } else {
-            final String packageName = info.providerName.getPackageName();
-            onClickPendingAppItem(v, packageName, info.installProgress >= 0);
-        }
-    }
-
-    private void onClickPendingAppItem(final View v, final String packageName,
-            boolean downloadStarted) {
-        if (downloadStarted) {
-            // If the download has started, simply direct to the market app.
-            startMarketIntentForPackage(v, packageName);
-            return;
-        }
-        new AlertDialog.Builder(this)
-            .setTitle(R.string.abandoned_promises_title)
-            .setMessage(R.string.abandoned_promise_explanation)
-            .setPositiveButton(R.string.abandoned_search, new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialogInterface, int i) {
-                    startMarketIntentForPackage(v, packageName);
-                }
-            })
-            .setNeutralButton(R.string.abandoned_clean_this,
-                new DialogInterface.OnClickListener() {
-                    public void onClick(DialogInterface dialog, int id) {
-                        final UserHandle user = Process.myUserHandle();
-                        mWorkspace.removeAbandonedPromise(packageName, user);
-                    }
-                })
-            .create().show();
-    }
-
-    private void startMarketIntentForPackage(View v, String packageName) {
-        ItemInfo item = (ItemInfo) v.getTag();
-        Intent intent = new PackageManagerHelper(v.getContext()).getMarketIntent(packageName);
-        startActivitySafely(v, intent, item);
-    }
-
-    /**
-     * Event handler for an app shortcut click.
-     *
-     * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
-     */
-    protected void onClickAppShortcut(final View v) {
-        if (LOGD) Log.d(TAG, "onClickAppShortcut");
-        Object tag = v.getTag();
-        if (!(tag instanceof ShortcutInfo)) {
-            throw new IllegalArgumentException("Input must be a Shortcut");
-        }
-
-        // Open shortcut
-        final ShortcutInfo shortcut = (ShortcutInfo) tag;
-
-        if (shortcut.isDisabled()) {
-            final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
-            if ((disabledFlags &
-                    ~FLAG_DISABLED_SUSPENDED &
-                    ~FLAG_DISABLED_QUIET_USER) == 0) {
-                // If the app is only disabled because of the above flags, launch activity anyway.
-                // Framework will tell the user why the app is suspended.
-            } else {
-                if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
-                    // Use a message specific to this shortcut, if it has one.
-                    Toast.makeText(this, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
-                    return;
-                }
-                // Otherwise just use a generic error message.
-                int error = R.string.activity_not_available;
-                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
-                    error = R.string.safemode_shortcut_error;
-                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
-                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
-                    error = R.string.shortcut_not_available;
-                }
-                Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
-                return;
-            }
-        }
-
-        // Check for abandoned promise
-        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
-            String packageName = shortcut.intent.getComponent() != null ?
-                    shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
-            if (!TextUtils.isEmpty(packageName)) {
-                onClickPendingAppItem(v, packageName,
-                        shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
-                return;
-            }
-        }
-
-        // Start activities
-        startAppShortcutOrInfoActivity(v);
-    }
-
-    private void startAppShortcutOrInfoActivity(View v) {
-        ItemInfo item = (ItemInfo) v.getTag();
-        Intent intent;
-        if (item instanceof PromiseAppInfo) {
-            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
-            intent = promiseAppInfo.getMarketIntent(this);
-        } else {
-            intent = item.getIntent();
-        }
-        if (intent == null) {
-            throw new IllegalArgumentException("Input must have a valid intent");
-        }
-        if (item instanceof ShortcutInfo) {
-            ShortcutInfo si = (ShortcutInfo) item;
-            if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
-                    && intent.getAction() == Intent.ACTION_VIEW) {
-                // make a copy of the intent that has the package set to null
-                // we do this because the platform sometimes disables instant
-                // apps temporarily (triggered by the user) and fallbacks to the
-                // web ui. This only works though if the package isn't set
-                intent = new Intent(intent);
-                intent.setPackage(null);
-            }
-        }
-        startActivitySafely(v, intent, item);
-    }
-
-    /**
-     * Event handler for a folder icon click.
-     *
-     * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
-     */
-    protected void onClickFolderIcon(View v) {
-        if (LOGD) Log.d(TAG, "onClickFolder");
-        if (!(v instanceof FolderIcon)){
-            throw new IllegalArgumentException("Input must be a FolderIcon");
-        }
-
-        Folder folder = ((FolderIcon) v).getFolder();
-        if (!folder.isOpen() && !folder.isDestroyed()) {
-            // Open the requested folder
-            folder.animateOpen();
-        }
-    }
-
-    /**
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
@@ -2081,45 +1893,25 @@
             }
         }
 
-        CellLayout.CellInfo longClickCellInfo = null;
-        View itemUnderLongClick = null;
-        if (v.getTag() instanceof ItemInfo) {
-            ItemInfo info = (ItemInfo) v.getTag();
-            longClickCellInfo = new CellLayout.CellInfo(v, info);
-            itemUnderLongClick = longClickCellInfo.cell;
-            mPendingRequestArgs = null;
-        }
-
         // The hotseat touch handling does not go through Workspace, and we always allow long press
         // on hotseat items.
         if (!mDragController.isDragging()) {
-            if (itemUnderLongClick == null) {
-                // User long pressed on empty space
-                if (mWorkspace.isPageRearrangeEnabled()) {
-                    mWorkspace.startReordering(v);
-                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                            Action.Direction.NONE, ContainerType.OVERVIEW);
-                } else {
-                    if (ignoreLongPressToOverview) {
-                        return false;
-                    }
-                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                            Action.Direction.NONE, ContainerType.WORKSPACE,
-                            mWorkspace.getCurrentPage());
-                    UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
-                }
-                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            // User long pressed on empty space
+            if (mWorkspace.isPageRearrangeEnabled()) {
+                mWorkspace.startReordering(v);
+                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                        Action.Direction.NONE, ContainerType.OVERVIEW);
             } else {
-                final boolean isAllAppsButton =
-                        !FeatureFlags.NO_ALL_APPS_ICON && isHotseatLayout(v) &&
-                                mDeviceProfile.inv.isAllAppsButtonRank(mHotseat.getOrderInHotseat(
-                                        longClickCellInfo.cellX, longClickCellInfo.cellY));
-                if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
-                    // User long pressed on an item
-                    mWorkspace.startDrag(longClickCellInfo, new DragOptions());
+                if (ignoreLongPressToOverview) {
+                    return false;
                 }
+                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                        Action.Direction.NONE, ContainerType.WORKSPACE,
+                        mWorkspace.getCurrentPage());
+                UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
             }
+            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
         }
         return true;
     }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index ec608ca..8588c7a 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -79,7 +79,7 @@
      * A message to display when the user tries to start a disabled shortcut.
      * This is currently only used for deep shortcuts.
      */
-    CharSequence disabledMessage;
+    public CharSequence disabledMessage;
 
     public int status;
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7c42b48..f329f5e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -81,6 +81,7 @@
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -935,10 +936,9 @@
             Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
         }
 
-        if (!(child instanceof Folder)) {
-            child.setHapticFeedbackEnabled(false);
-            child.setOnLongClickListener(mLongClickListener);
-        }
+        child.setHapticFeedbackEnabled(false);
+        child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
+
         if (child instanceof DropTarget) {
             mDragController.addDropTarget((DropTarget) child);
         }
@@ -1635,7 +1635,6 @@
                 new DragPreviewProvider(child), options);
     }
 
-
     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
             DragPreviewProvider previewProvider, DragOptions dragOptions) {
         child.clearFocus();
@@ -2970,8 +2969,9 @@
                         + "Workspace#onDropCompleted. Please file a bug. ");
             }
         }
-        if (d.cancelled && mDragInfo != null && mDragInfo.cell != null) {
-            mDragInfo.cell.setVisibility(VISIBLE);
+        View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
+        if (d.cancelled && cell != null) {
+            cell.setVisibility(VISIBLE);
         }
         mDragInfo = null;
     }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 2cd8b1d..4398f6e 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherSettings;
@@ -358,29 +359,14 @@
             mDragInfo.dragType = DragType.WIDGET;
         }
 
-        CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info);
-
         Rect pos = new Rect();
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
         mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
-
-        Folder folder = Folder.getOpen(mLauncher);
-        if (folder != null) {
-            if (!folder.getItemsInReadingOrder().contains(item)) {
-                folder.close(true);
-                folder = null;
-            }
-        }
-
         mLauncher.getDragController().addDragListener(this);
 
         DragOptions options = new DragOptions();
         options.isAccessibleDrag = true;
-        if (folder != null) {
-            folder.startDrag(cellInfo.cell, options);
-        } else {
-            mLauncher.getWorkspace().startDrag(cellInfo, options);
-        }
+        ItemLongClickListener.beginDrag(item, mLauncher, info, options);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 26922ad..3fe5d7a 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -34,26 +34,19 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 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;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.ItemInfo;
 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;
 import com.android.launcher3.graphics.ColorScrim;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -61,12 +54,13 @@
 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,
-        OnLongClickListener, Insettable, OnDeviceProfileChangeListener {
+public class AllAppsContainerView extends SpringRelativeLayout implements DragSource,
+        Insettable, OnDeviceProfileChangeListener {
 
     private final Launcher mLauncher;
     private final AdapterHolder[] mAH;
@@ -119,6 +113,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() {
@@ -249,37 +247,6 @@
     }
 
     @Override
-    public boolean onLongClick(final View v) {
-        // When we have exited all apps or are in transition, disregard long clicks
-        if (!mLauncher.isInState(LauncherState.ALL_APPS) ||
-                mLauncher.getWorkspace().isSwitchingState()) return false;
-        // Return if global dragging is not enabled or we are already dragging
-        if (!mLauncher.isDraggingEnabled()) return false;
-        if (mLauncher.getDragController().isDragging()) return false;
-
-        // Start the drag
-        final DragController dragController = mLauncher.getDragController();
-        dragController.addDragListener(new DragController.DragListener() {
-            @Override
-            public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-                v.setVisibility(INVISIBLE);
-            }
-
-            @Override
-            public void onDragEnd() {
-                v.setVisibility(VISIBLE);
-                dragController.removeDragListener(this);
-            }
-        });
-
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        DragOptions options = new DragOptions();
-        options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
-        mLauncher.getWorkspace().beginDragShared(v, this, options);
-        return false;
-    }
-
-    @Override
     public void onDropCompleted(View target, DragObject d, boolean success) { }
 
     @Override
@@ -324,10 +291,6 @@
         }
     }
 
-    public SpringAnimationHandler getSpringAnimationHandler() {
-        return mUsingTabs ? null : mAH[AdapterHolder.MAIN].animationHandler;
-    }
-
     private void rebindAdapters(boolean showTabs) {
         rebindAdapters(showTabs, false /* force */);
     }
@@ -470,7 +433,6 @@
 
         public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
-        final SpringAnimationHandler animationHandler;
         final AlphabeticalAppsList appsList;
         final Rect padding = new Rect();
         AllAppsRecyclerView recyclerView;
@@ -478,25 +440,21 @@
 
         AdapterHolder(boolean isWork) {
             appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
-            adapter = new AllAppsGridAdapter(mLauncher, appsList, mLauncher,
-                    AllAppsContainerView.this, true);
+            adapter = new AllAppsGridAdapter(mLauncher, appsList);
             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..27fc53a 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,10 @@
 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.touch.ItemClickHandler;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.List;
@@ -71,7 +68,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 {
@@ -182,8 +178,6 @@
     private final AlphabeticalAppsList mApps;
     private final GridLayoutManager mGridLayoutMgr;
     private final GridSpanSizer mGridSizer;
-    private final View.OnClickListener mIconClickListener;
-    private final View.OnLongClickListener mIconLongClickListener;
 
     private final int mAppsPerRow;
 
@@ -195,10 +189,7 @@
     // 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) {
+    public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
         Resources res = launcher.getResources();
         mLauncher = launcher;
         mApps = apps;
@@ -207,23 +198,11 @@
         mGridLayoutMgr = new AppsGridLayoutManager(launcher);
         mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
         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);
     }
@@ -270,8 +249,8 @@
             case VIEW_TYPE_ICON:
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.all_apps_icon, parent, false);
-                icon.setOnClickListener(mIconClickListener);
-                icon.setOnLongClickListener(mIconLongClickListener);
+                icon.setOnClickListener(ItemClickHandler.INSTANCE);
+                icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
                 icon.setLongPressTimeout(ViewConfiguration.getLongPressTimeout());
                 icon.setOnFocusChangeListener(mIconFocusListener);
 
@@ -344,22 +323,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 +339,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/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index b3310c7..2b42429 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -86,7 +85,7 @@
 /**
  * Represents a set of icons chosen by the user or generated by the system.
  */
-public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener,
+public class Folder extends AbstractFloatingView implements DragSource,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
         View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
     private static final String TAG = "Launcher.Folder";
@@ -253,13 +252,6 @@
         mFooterHeight = mFooter.getMeasuredHeight();
     }
 
-    public void onClick(View v) {
-        Object tag = v.getTag();
-        if (tag instanceof ShortcutInfo) {
-            mLauncher.onClick(v);
-        }
-    }
-
     public boolean onLongClick(View v) {
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return true;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 13926db..6c94273 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
@@ -159,14 +160,14 @@
                 .inflate(resId, group, false);
 
         icon.setClipToPadding(false);
-        icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
+        icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
         icon.mFolderName.setText(folderInfo.title);
         icon.mFolderName.setCompoundDrawablePadding(0);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
         lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
 
         icon.setTag(folderInfo);
-        icon.setOnClickListener(launcher);
+        icon.setOnClickListener(ItemClickHandler.INSTANCE);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
         icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index a468cb5..fa7565a 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -237,7 +238,7 @@
                 R.layout.folder_application, null, false);
         textView.applyFromShortcutInfo(item);
         textView.setHapticFeedbackEnabled(false);
-        textView.setOnClickListener(mFolder);
+        textView.setOnClickListener(ItemClickHandler.INSTANCE);
         textView.setOnLongClickListener(mFolder);
         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
         textView.setOnKeyListener(mKeyListener);
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index bc75268..42ba191 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -164,7 +164,7 @@
     @Override
     public void drawInternal(Canvas canvas, Rect bounds) {
         if (mRanFinishAnimation) {
-            super.draw(canvas);
+            super.drawInternal(canvas, bounds);
             return;
         }
 
@@ -178,7 +178,7 @@
 
         int saveCount = canvas.save();
         canvas.scale(mIconScale, mIconScale, bounds.exactCenterX(), bounds.exactCenterY());
-        super.draw(canvas);
+        super.drawInternal(canvas, bounds);
         canvas.restoreToCount(saveCount);
     }
 
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b3ef7bb..e427a81 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -77,6 +77,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Themes;
 
@@ -907,12 +908,9 @@
 
     @Override
     public boolean onLongClick(View v) {
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
         // Return early if not the correct view
         if (!(v.getParent() instanceof DeepShortcutView)) return false;
-        // Return early if global dragging is not enabled
-        if (!mLauncher.isDraggingEnabled()) return false;
-        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
-        if (mLauncher.getDragController().isDragging()) return false;
 
         // Long clicked on a shortcut.
         DeepShortcutView sv = (DeepShortcutView) v.getParent();
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 450a690..9ad266b 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.touch.ItemClickHandler;
 
 /**
  * A {@link android.widget.FrameLayout} that contains a {@link DeepShortcutView}.
@@ -120,7 +121,7 @@
         mBubbleText.setText(usingLongLabel ? longLabel : mDetail.getShortLabel());
 
         // TODO: Add the click handler to this view directly and not the child view.
-        mBubbleText.setOnClickListener(Launcher.getLauncher(getContext()));
+        mBubbleText.setOnClickListener(ItemClickHandler.INSTANCE);
         mBubbleText.setOnLongClickListener(container);
         mBubbleText.setOnTouchListener(container);
     }
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index d3c0fef..0a2c3e4 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -56,8 +56,8 @@
         sScheduler.schedule(this);
     }
 
-    public void clearReference() {
-        sScheduler.clearReference(this);
+    public boolean clearReference() {
+        return sScheduler.clearReference(this);
     }
 
     public static boolean handleCreate(Launcher launcher, Intent intent) {
@@ -125,10 +125,12 @@
             return false;
         }
 
-        public synchronized void clearReference(InternalStateHandler handler) {
+        public synchronized boolean clearReference(InternalStateHandler handler) {
             if (mPendingHandler.get() == handler) {
                 mPendingHandler.clear();
+                return true;
             }
+            return false;
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
new file mode 100644
index 0000000..f2f5592
--- /dev/null
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -0,0 +1,232 @@
+/*
+ * 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.touch;
+
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
+import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.os.Process;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Toast;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
+import com.android.launcher3.widget.WidgetAddFlowHandler;
+
+/**
+ * Class for handling clicks on workspace and all-apps items
+ */
+public class ItemClickHandler {
+
+    /**
+     * Instance used for click handling on items
+     */
+    public static final OnClickListener INSTANCE = ItemClickHandler::onClick;
+
+    private static void onClick(View v) {
+        // Make sure that rogue clicks don't get through while allapps is launching, or after the
+        // view has detached (it's possible for this to happen if the view is removed mid touch).
+        if (v.getWindowToken() == null) {
+            return;
+        }
+
+        Launcher launcher = Launcher.getLauncher(v.getContext());
+        if (!launcher.getWorkspace().isFinishedSwitchingState()) {
+            return;
+        }
+
+        Object tag = v.getTag();
+        if (tag instanceof ShortcutInfo) {
+            onClickAppShortcut(v, (ShortcutInfo) tag, launcher);
+        } else if (tag instanceof FolderInfo) {
+            if (v instanceof FolderIcon) {
+                onClickFolderIcon(v);
+            }
+        } else if (tag instanceof AppInfo) {
+            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
+        } else if (tag instanceof LauncherAppWidgetInfo) {
+            if (v instanceof PendingAppWidgetHostView) {
+                onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
+            }
+        }
+    }
+
+    /**
+     * Event handler for a folder icon click.
+     *
+     * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
+     */
+    private static void onClickFolderIcon(View v) {
+        Folder folder = ((FolderIcon) v).getFolder();
+        if (!folder.isOpen() && !folder.isDestroyed()) {
+            // Open the requested folder
+            folder.animateOpen();
+        }
+    }
+
+    /**
+     * Event handler for the app widget view which has not fully restored.
+     */
+    private static void onClickPendingWidget(PendingAppWidgetHostView v, Launcher launcher) {
+        if (launcher.getPackageManager().isSafeMode()) {
+            Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
+        if (v.isReadyForClickSetup()) {
+            LauncherAppWidgetProviderInfo appWidgetInfo = AppWidgetManagerCompat
+                    .getInstance(launcher).findProvider(info.providerName, info.user);
+            if (appWidgetInfo == null) {
+                return;
+            }
+            WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
+
+            if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+                if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
+                    // This should not happen, as we make sure that an Id is allocated during bind.
+                    return;
+                }
+                addFlowHandler.startBindFlow(launcher, info.appWidgetId, info,
+                        REQUEST_BIND_PENDING_APPWIDGET);
+            } else {
+                addFlowHandler.startConfigActivity(launcher, info, REQUEST_RECONFIGURE_APPWIDGET);
+            }
+        } else {
+            final String packageName = info.providerName.getPackageName();
+            onClickPendingAppItem(v, launcher, packageName, info.installProgress >= 0);
+        }
+    }
+
+    private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
+            boolean downloadStarted) {
+        if (downloadStarted) {
+            // If the download has started, simply direct to the market app.
+            startMarketIntentForPackage(v, launcher, packageName);
+            return;
+        }
+        new AlertDialog.Builder(launcher)
+                .setTitle(R.string.abandoned_promises_title)
+                .setMessage(R.string.abandoned_promise_explanation)
+                .setPositiveButton(R.string.abandoned_search,
+                        (d, i) -> startMarketIntentForPackage(v, launcher, packageName))
+                .setNeutralButton(R.string.abandoned_clean_this,
+                        (d, i) -> launcher.getWorkspace()
+                                .removeAbandonedPromise(packageName, Process.myUserHandle()))
+                .create().show();
+    }
+
+    private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
+        ItemInfo item = (ItemInfo) v.getTag();
+        Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
+        launcher.startActivitySafely(v, intent, item);
+    }
+
+    /**
+     * Event handler for an app shortcut click.
+     *
+     * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
+     */
+    private static void onClickAppShortcut(View v, ShortcutInfo shortcut, Launcher launcher) {
+        if (shortcut.isDisabled()) {
+            final int disabledFlags = shortcut.runtimeStatusFlags & ShortcutInfo.FLAG_DISABLED_MASK;
+            if ((disabledFlags &
+                    ~FLAG_DISABLED_SUSPENDED &
+                    ~FLAG_DISABLED_QUIET_USER) == 0) {
+                // If the app is only disabled because of the above flags, launch activity anyway.
+                // Framework will tell the user why the app is suspended.
+            } else {
+                if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
+                    // Use a message specific to this shortcut, if it has one.
+                    Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
+                    return;
+                }
+                // Otherwise just use a generic error message.
+                int error = R.string.activity_not_available;
+                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
+                    error = R.string.safemode_shortcut_error;
+                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
+                    error = R.string.shortcut_not_available;
+                }
+                Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        // Check for abandoned promise
+        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
+            String packageName = shortcut.intent.getComponent() != null ?
+                    shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
+            if (!TextUtils.isEmpty(packageName)) {
+                onClickPendingAppItem(v, launcher, packageName,
+                        shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE));
+                return;
+            }
+        }
+
+        // Start activities
+        startAppShortcutOrInfoActivity(v, shortcut, launcher);
+    }
+
+    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
+        Intent intent;
+        if (item instanceof PromiseAppInfo) {
+            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+            intent = promiseAppInfo.getMarketIntent(launcher);
+        } else {
+            intent = item.getIntent();
+        }
+        if (intent == null) {
+            throw new IllegalArgumentException("Input must have a valid intent");
+        }
+        if (item instanceof ShortcutInfo) {
+            ShortcutInfo si = (ShortcutInfo) item;
+            if (si.hasStatusFlag(ShortcutInfo.FLAG_SUPPORTS_WEB_UI)
+                    && intent.getAction() == Intent.ACTION_VIEW) {
+                // make a copy of the intent that has the package set to null
+                // we do this because the platform sometimes disables instant
+                // apps temporarily (triggered by the user) and fallbacks to the
+                // web ui. This only works though if the package isn't set
+                intent = new Intent(intent);
+                intent.setPackage(null);
+            }
+        }
+        launcher.startActivitySafely(v, intent, item);
+    }
+}
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
new file mode 100644
index 0000000..f10a695
--- /dev/null
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -0,0 +1,118 @@
+/*
+ * 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.touch;
+
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+
+import android.view.View;
+import android.view.View.OnLongClickListener;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.folder.Folder;
+
+/**
+ * Class to handle long-clicks on workspace items and start drag as a result.
+ */
+public class ItemLongClickListener {
+
+    public static OnLongClickListener INSTANCE_WORKSPACE =
+            ItemLongClickListener::onWorkspaceItemLongClick;
+
+    public static OnLongClickListener INSTANCE_ALL_APPS =
+            ItemLongClickListener::onAllAppsItemLongClick;
+
+    private static boolean onWorkspaceItemLongClick(View v) {
+        Launcher launcher = Launcher.getLauncher(v.getContext());
+        if (!canStartDrag(launcher)) return false;
+        if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
+        if (!(v.getTag() instanceof ItemInfo)) return false;
+
+        launcher.setWaitingForResult(null);
+        beginDrag(v, launcher, (ItemInfo) v.getTag(), new DragOptions());
+        return true;
+    }
+
+    public static void beginDrag(View v, Launcher launcher, ItemInfo info,
+            DragOptions dragOptions) {
+        if (info.container >= 0) {
+            Folder folder = Folder.getOpen(launcher);
+            if (folder != null) {
+                if (!folder.getItemsInReadingOrder().contains(v)) {
+                    folder.close(true);
+                } else {
+                    folder.startDrag(v, dragOptions);
+                    return;
+                }
+            }
+        }
+
+        CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
+        launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
+    }
+
+    private static boolean onAllAppsItemLongClick(View v) {
+        Launcher launcher = Launcher.getLauncher(v.getContext());
+        if (!canStartDrag(launcher)) return false;
+        // When we have exited all apps or are in transition, disregard long clicks
+        if (!launcher.isInState(LauncherState.ALL_APPS) ||
+                launcher.getWorkspace().isSwitchingState()) return false;
+
+        // Start the drag
+        final DragController dragController = launcher.getDragController();
+        dragController.addDragListener(new DragController.DragListener() {
+            @Override
+            public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+                v.setVisibility(INVISIBLE);
+            }
+
+            @Override
+            public void onDragEnd() {
+                v.setVisibility(VISIBLE);
+                dragController.removeDragListener(this);
+            }
+        });
+
+        DeviceProfile grid = launcher.getDeviceProfile();
+        DragOptions options = new DragOptions();
+        options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
+        launcher.getWorkspace().beginDragShared(v, launcher.getAppsView(), options);
+        return false;
+    }
+
+    public static boolean canStartDrag(Launcher launcher) {
+        if (launcher == null) {
+            return false;
+        }
+        // We prevent dragging when we are loading the workspace as it is possible to pick up a view
+        // that is subsequently removed from the workspace in startBinding().
+        if (launcher.isWorkspaceLocked()) return false;
+        // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+        if (launcher.getDragController().isDragging()) return false;
+
+        return 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
diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java
index 3ba8ca3..7888b08 100644
--- a/src/com/android/launcher3/views/TopRoundedCornerView.java
+++ b/src/com/android/launcher3/views/TopRoundedCornerView.java
@@ -29,7 +29,7 @@
 /**
  * View with top rounded corners.
  */
-public class TopRoundedCornerView extends FrameLayout {
+public class TopRoundedCornerView extends SpringRelativeLayout {
 
     private final RectF mRect = new RectF();
     private final Path mClipPath = new Path();
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index c51842d..10708d6 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -32,6 +32,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.ColorScrim;
+import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.SystemUiController;
@@ -71,7 +72,7 @@
 
     @Override
     public final boolean onLongClick(View v) {
-        if (!mLauncher.isDraggingEnabled()) return false;
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
 
         if (v instanceof WidgetCell) {
             return beginDraggingWidget((WidgetCell) v);
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 6970833..961799d 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
@@ -83,7 +84,7 @@
 
         setElevation(getResources().getDimension(R.dimen.pending_widget_elevation));
         updateAppWidget(null);
-        setOnClickListener(mLauncher);
+        setOnClickListener(ItemClickHandler.INSTANCE);
 
         if (info.pendingItemInfo == null) {
             info.pendingItemInfo = new PackageItemInfo(info.providerName.getPackageName());
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index b31feed..a622624 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -70,6 +70,9 @@
         mRecyclerView.setAdapter(mAdapter);
         mAdapter.setApplyBitmapDeferred(true, mRecyclerView);
 
+        TopRoundedCornerView springLayout = (TopRoundedCornerView) mContent;
+        springLayout.addSpringView(R.id.widgets_list_view);
+        mRecyclerView.setEdgeEffectFactory(springLayout.createEdgeEffectFactory());
         onWidgetsBound();
     }
 
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 7d1da88..011aa22 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.ui;
 
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -37,22 +34,27 @@
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
 import android.view.MotionEvent;
+
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testcomponent.AppWidgetNoConfig;
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
+
+import org.junit.Before;
+
 import java.util.Locale;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import org.junit.Before;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 /**
  * Base class for all instrumentation tests providing various utility methods.
@@ -63,6 +65,7 @@
     public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
 
     public static final long DEFAULT_UI_TIMEOUT = 3000;
+    public static final long LARGE_UI_TIMEOUT = 10000;
     public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5;
 
     protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
@@ -95,8 +98,13 @@
     protected UiObject2 openAllApps() {
         mDevice.waitForIdle();
         if (FeatureFlags.NO_ALL_APPS_ICON) {
-            // clicking on the page indicator brings up all apps tray on non tablets.
-            findViewById(R.id.page_indicator).click();
+            UiObject2 hotseat = mDevice.wait(
+                    Until.findObject(getSelectorForId(R.id.hotseat)), 2500);
+            Point start = hotseat.getVisibleCenter();
+            int endY = (int) (mDevice.getDisplayHeight() * 0.1f);
+            // 100 px/step
+            mDevice.swipe(start.x, start.y, start.x, endY, (start.y - endY) / 100);
+
         } else {
             mDevice.wait(Until.findObject(
                     By.desc(mTargetContext.getString(R.string.all_apps_button_label))),
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
new file mode 100644
index 0000000..ccee7da
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 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.ui;
+
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Condition;
+import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.LauncherActivityRule;
+import com.android.launcher3.util.rule.ShellCommandRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertTrue;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class WorkTabTest extends AbstractLauncherUiTest {
+    @Rule
+    public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
+    @Rule
+    public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
+
+    private int mProfileUserId;
+
+    @Before
+    public void createWorkProfile() throws Exception {
+        String output =
+                mDevice.executeShellCommand(
+                        "pm create-user --profileOf 0 --managed TestProfile");
+        assertTrue("Failed to create work profile", output.startsWith("Success"));
+
+        String[] tokens = output.split("\\s+");
+        mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
+
+        mDevice.executeShellCommand("am start-user " + mProfileUserId);
+    }
+
+    @After
+    public void removeWorkProfile() throws Exception {
+        mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+    }
+
+    @Test
+    public void workTabExists() {
+        mActivityMonitor.startLauncher();
+
+        // Open all apps and wait for load complete
+        final UiObject2 appsContainer = openAllApps();
+        assertTrue(Wait.atMost(Condition.minChildCount(appsContainer, 2), DEFAULT_UI_TIMEOUT));
+
+        assertTrue("Personal tab is missing",
+                mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_personal)),
+                        LARGE_UI_TIMEOUT));
+        assertTrue("Work tab is missing",
+                mDevice.wait(Until.hasObject(getSelectorForId(R.id.tab_work)), LARGE_UI_TIMEOUT));
+    }
+}
\ No newline at end of file