Merge "Fix window transition when opening app from deep shortcut menu." into ub-launcher3-master
diff --git a/proguard.flags b/proguard.flags
index b8cade5..cac8930 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -102,6 +102,11 @@
     public <init>(...);
 }
 
+# InstantAppResolver
+-keep class com.android.quickstep.InstantAppResolverImpl {
+    public <init>(...);
+}
+
 -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
   *;
 }
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 6e62add..662069a 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/res/values/override.xml b/quickstep/res/values/override.xml
index ba99d81..2bd9f8f 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -16,5 +16,7 @@
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
   <string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
+
+  <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
 </resources>
 
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 14232ae..6fc1447 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/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index acd4fc1..8a5c55a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -16,6 +16,8 @@
 package com.android.launcher3.uioverrides;
 
 import com.android.launcher3.Launcher;
+import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.RecentsView;
 
 /**
  * Extension of overview state used for QuickScrub
@@ -23,12 +25,19 @@
 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;
 
     public FastOverviewState(int id) {
-        super(id, STATE_FLAGS);
+        super(id, QuickScrubController.QUICK_SWITCH_START_DURATION, STATE_FLAGS);
+    }
+
+    @Override
+    public void onStateTransitionEnd(Launcher launcher) {
+        super.onStateTransitionEnd(launcher);
+        RecentsView recentsView = launcher.getOverviewPanel();
+        recentsView.getQuickScrubController().onFinishedTransitionToQuickScrub();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 854fb4f..a88369d 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,31 +34,26 @@
 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);
+        this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
     }
 
-    protected OverviewState(int id, int stateFlags) {
-        super(id, ContainerType.TASKSWITCHER, OVERVIEW_TRANSITION_MS, stateFlags);
+    protected OverviewState(int id, int transitionDuration, int stateFlags) {
+        super(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
     }
 
     @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..eb14d60 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,21 @@
                 mTaskBeingDragged = null;
                 mSwipeDownEnabled = true;
 
-                int currentPage = mRecentsView.getCurrentPage();
-                if (currentPage == 0) {
-                    // User is on home tile
+                View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
+                if (view instanceof TaskView && mLauncher.getDragLayer().isEventOverView(view, ev)) {
+                    // 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/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/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index 214b3f3..84dfa45 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -77,6 +77,12 @@
         }
     }
 
+    public void finishAnimation() {
+        if (mValueAnimator != null && mValueAnimator.isRunning()) {
+            mValueAnimator.end();
+        }
+    }
+
     public ObjectAnimator getCurrentAnimation() {
         return mValueAnimator;
     }
diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
index b3ebd77..5871a6d 100644
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
@@ -23,6 +23,7 @@
 public abstract class BaseSwipeInteractionHandler extends InternalStateHandler {
 
     protected Runnable mGestureEndCallback;
+    protected boolean mIsGoingToHome;
 
     public void setGestureEndCallback(Runnable gestureEndCallback) {
         mGestureEndCallback = gestureEndCallback;
diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
new file mode 100644
index 0000000..b92678a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
@@ -0,0 +1,99 @@
+/*
+ * 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 android.annotation.TargetApi;
+import android.os.Build;
+import android.view.Choreographer;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+/**
+ * A TouchConsumer which defers all events on the UIThread until the consumer is created.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public class DeferredTouchConsumer implements TouchConsumer {
+
+    private final VelocityTracker mVelocityTracker;
+    private final DeferredTouchProvider mTouchProvider;
+
+    private MotionEventQueue mMyQueue;
+    private TouchConsumer mTarget;
+
+    public DeferredTouchConsumer(DeferredTouchProvider touchProvider) {
+        mVelocityTracker = VelocityTracker.obtain();
+        mTouchProvider = touchProvider;
+    }
+
+    @Override
+    public void accept(MotionEvent event) {
+        mTarget.accept(event);
+    }
+
+    @Override
+    public void reset() {
+        mTarget.reset();
+    }
+
+    @Override
+    public void updateTouchTracking(int interactionType) {
+        mTarget.updateTouchTracking(interactionType);
+    }
+
+    @Override
+    public void onQuickScrubEnd() {
+        mTarget.onQuickScrubEnd();
+    }
+
+    @Override
+    public void onQuickScrubProgress(float progress) {
+        mTarget.onQuickScrubProgress(progress);
+    }
+
+    @Override
+    public void preProcessMotionEvent(MotionEvent ev) {
+        mVelocityTracker.addMovement(ev);
+    }
+
+    @Override
+    public Choreographer getIntrimChoreographer(MotionEventQueue queue) {
+        mMyQueue = queue;
+        return null;
+    }
+
+    @Override
+    public void deferInit() {
+        mTarget = mTouchProvider.createTouchConsumer(mVelocityTracker);
+        mTarget.getIntrimChoreographer(mMyQueue);
+    }
+
+    @Override
+    public boolean forceToLauncherConsumer() {
+        return mTarget.forceToLauncherConsumer();
+    }
+
+    @Override
+    public boolean deferNextEventToMainThread() {
+        // If our target is still null, defer the next target as well
+        TouchConsumer target = mTarget;
+        return target == null ? true : target.deferNextEventToMainThread();
+    }
+
+    public interface DeferredTouchProvider {
+
+        TouchConsumer createTouchConsumer(VelocityTracker tracker);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
new file mode 100644
index 0000000..12757c0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.util.InstantAppResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of InstantAppResolver using platform APIs
+ */
+@SuppressWarnings("unused")
+public class InstantAppResolverImpl extends InstantAppResolver {
+
+    private static final String TAG = "InstantAppResolverImpl";
+    public static final String COMPONENT_CLASS_MARKER = "@instantapp";
+
+    private final PackageManager mPM;
+
+    public InstantAppResolverImpl(Context context)
+            throws NoSuchMethodException, ClassNotFoundException {
+        mPM = context.getPackageManager();
+    }
+
+    @Override
+    public boolean isInstantApp(ApplicationInfo info) {
+        return info.isInstantApp();
+    }
+
+    @Override
+    public boolean isInstantApp(AppInfo info) {
+        ComponentName cn = info.getTargetComponent();
+        return cn != null && cn.getClassName().equals(COMPONENT_CLASS_MARKER);
+    }
+
+    @Override
+    public List<ApplicationInfo> getInstantApps() {
+        try {
+            List<ApplicationInfo> result = new ArrayList<>();
+            for (InstantAppInfo iai : mPM.getInstantApps()) {
+                ApplicationInfo info = iai.getApplicationInfo();
+                if (info != null) {
+                    result.add(info);
+                }
+            }
+            return result;
+        } catch (SecurityException se) {
+            Log.w(TAG, "getInstantApps failed. Launcher may not be the default home app.", se);
+        } catch (Exception e) {
+            Log.e(TAG, "Error calling API: getInstantApps", e);
+        }
+        return super.getInstantApps();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 6e92d83..94b6faa 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -53,6 +53,8 @@
             ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_RESET =
             ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
+    private static final int ACTION_DEFER_INIT =
+            ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
 
     private final EventArray mEmptyArray = new EventArray();
     private final Object mExecutionLock = new Object();
@@ -76,10 +78,10 @@
     public MotionEventQueue(Choreographer choreographer, TouchConsumer consumer) {
         mMainChoreographer = choreographer;
         mConsumer = consumer;
-
         mCurrentChoreographer = mMainChoreographer;
         mCurrentRunnable = mMainFrameCallback;
-        setInterimChoreographerLocked(consumer.getIntrimChoreographer(this));
+
+        setInterimChoreographer(consumer.getIntrimChoreographer(this));
     }
 
     public void setInterimChoreographer(Choreographer choreographer) {
@@ -156,6 +158,9 @@
                         case ACTION_RESET:
                             mConsumer.reset();
                             break;
+                        case ACTION_DEFER_INIT:
+                            mConsumer.deferInit();
+                            break;
                         default:
                             Log.e(TAG, "Invalid virtual event: " + event.getAction());
                     }
@@ -204,6 +209,14 @@
         queueVirtualAction(ACTION_RESET, 0);
     }
 
+    public void deferInit() {
+        queueVirtualAction(ACTION_DEFER_INIT, 0);
+    }
+
+    public TouchConsumer getConsumer() {
+        return mConsumer;
+    }
+
     private static class EventArray extends ArrayList<MotionEvent> {
 
         public int lastEventAction = ACTION_CANCEL;
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index ff7d434..fca844c 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -319,13 +319,14 @@
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
     private void animateToProgress(float progress, long duration) {
+        mIsGoingToHome = Float.compare(progress, 1) == 0;
         ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
         anim.setInterpolator(Interpolators.SCROLL);
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
-                mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
-                        ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
+                mStateCallback.setState(mIsGoingToHome
+                        ? STATE_SCALED_SNAPSHOT_RECENTS : STATE_SCALED_SNAPSHOT_APP);
             }
         });
         anim.start();
diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
index 431fb30..3247312 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -29,6 +29,7 @@
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.graphics.BitmapInfo;
+import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.LauncherIcons;
 import com.android.systemui.shared.recents.model.IconLoader;
 import com.android.systemui.shared.recents.model.TaskKeyLruCache;
@@ -40,11 +41,13 @@
 public class NormalizedIconLoader extends IconLoader {
 
     private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
+    private final DrawableFactory mDrawableFactory;
     private LauncherIcons mLauncherIcons;
 
     public NormalizedIconLoader(Context context, TaskKeyLruCache<Drawable> iconCache,
             LruCache<ComponentName, ActivityInfo> activityInfoCache) {
         super(context, iconCache, activityInfoCache);
+        mDrawableFactory = DrawableFactory.get(context);
     }
 
     @Override
@@ -53,7 +56,7 @@
             BitmapInfo info = mDefaultIcons.get(userId);
             if (info == null) {
                 info = getBitmapInfo(Resources.getSystem()
-                        .getDrawable(android.R.drawable.sym_def_app_icon), userId);
+                        .getDrawable(android.R.drawable.sym_def_app_icon), userId, false);
                 mDefaultIcons.put(userId, info);
             }
 
@@ -63,22 +66,26 @@
 
     @Override
     protected Drawable createBadgedDrawable(Drawable drawable, int userId) {
-        return new FastBitmapDrawable(getBitmapInfo(drawable, userId));
+        return new FastBitmapDrawable(getBitmapInfo(drawable, userId, false));
     }
 
-    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId) {
+    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
+            boolean isInstantApp) {
         if (mLauncherIcons == null) {
             mLauncherIcons = LauncherIcons.obtain(mContext);
         }
 
         // User version code O, so that the icon is always wrapped in an adaptive icon container.
         return mLauncherIcons.createBadgedIconBitmap(drawable, UserHandle.of(userId),
-                Build.VERSION_CODES.O);
+                Build.VERSION_CODES.O, isInstantApp);
     }
 
     @Override
-    protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId) {
-        return createBadgedDrawable(
-                activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), userId);
+    protected synchronized Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId) {
+        BitmapInfo bitmapInfo = getBitmapInfo(
+                activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
+                userId,
+                activityInfo.applicationInfo.isInstantApp());
+        return mDrawableFactory.newIcon(bitmapInfo, activityInfo);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 73cd503..c96f6d7 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -23,9 +23,11 @@
 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.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -37,6 +39,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Looper;
 import android.util.Log;
@@ -61,16 +64,20 @@
 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;
 
 /**
  * Touch consumer for handling events originating from an activity other than Launcher
  */
+@TargetApi(Build.VERSION_CODES.P)
 public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
     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 +86,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,24 +96,24 @@
     private BaseSwipeInteractionHandler mInteractionHandler;
     private int mDisplayRotation;
     private Rect mStableInsets = new Rect();
-    private @HitTarget int mDownHitTarget = HIT_TARGET_NONE;
 
     private VelocityTracker mVelocityTracker;
     private MotionEventQueue mEventQueue;
+    private boolean mIsGoingToHome;
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
             RecentsModel recentsModel, Intent homeIntent, ISystemUiProxy systemUiProxy,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
-            @HitTarget int downHitTarget) {
+            @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
         super(base);
         mRunningTask = runningTaskInfo;
         mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
-        mVelocityTracker = VelocityTracker.obtain();
+        mVelocityTracker = velocityTracker;
         mISystemUiProxy = systemUiProxy;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadChoreographer = backgroundThreadChoreographer;
-        mDownHitTarget = downHitTarget;
+        mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
     }
 
     @Override
@@ -124,7 +132,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 +174,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());
@@ -369,6 +377,7 @@
         if (mInteractionHandler != null) {
             final BaseSwipeInteractionHandler handler = mInteractionHandler;
             mInteractionHandler = null;
+            mIsGoingToHome = handler.mIsGoingToHome;
             mMainThreadExecutor.execute(handler::reset);
         }
     }
@@ -423,4 +432,15 @@
            }
         }
     }
+
+    @Override
+    public boolean forceToLauncherConsumer() {
+        return mIsGoingToHome;
+    }
+
+    @Override
+    public boolean deferNextEventToMainThread() {
+        // TODO: Consider also check if the eventQueue is using mainThread of not.
+        return mInteractionHandler != null;
+    }
 }
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..0ccd7f2 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.
@@ -35,8 +34,7 @@
  */
 public class QuickScrubController implements OnAlarmListener {
 
-    public static final int QUICK_SWITCH_START_DURATION = 133;
-    public static final int QUICK_SWITCH_SNAP_DURATION = 120;
+    public static final int QUICK_SWITCH_START_DURATION = 210;
 
     private static final boolean ENABLE_AUTO_ADVANCE = true;
     private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -51,8 +49,10 @@
 
     private boolean mInQuickScrub;
     private int mQuickScrubSection;
-    private int mStartPage;
+    private boolean mStartedFromHome;
     private boolean mHasAlarmRun;
+    private boolean mQuickSwitched;
+    private boolean mFinishedTransitionToQuickScrub;
 
     public QuickScrubController(Launcher launcher, RecentsView recentsView) {
         mLauncher = launcher;
@@ -65,9 +65,13 @@
 
     public void onQuickScrubStart(boolean startingFromHome) {
         mInQuickScrub = true;
-        mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
+        mStartedFromHome = startingFromHome;
         mQuickScrubSection = 0;
         mHasAlarmRun = false;
+        mQuickSwitched = false;
+        mFinishedTransitionToQuickScrub = false;
+
+        snapToNextTaskIfAvailable();
         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
     }
 
@@ -78,11 +82,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 +94,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);
     }
 
@@ -102,7 +102,9 @@
         int quickScrubSection = Math.round(progress * NUM_QUICK_SCRUB_SECTIONS);
         if (quickScrubSection != mQuickScrubSection) {
             int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection;
-            goToPageWithHaptic(pageToGoTo);
+            if (mFinishedTransitionToQuickScrub) {
+                goToPageWithHaptic(pageToGoTo);
+            }
             if (ENABLE_AUTO_ADVANCE) {
                 if (quickScrubSection == NUM_QUICK_SCRUB_SECTIONS || quickScrubSection == 0) {
                     mAutoAdvanceAlarm.setAlarm(mHasAlarmRun
@@ -116,36 +118,45 @@
     }
 
     public void onQuickSwitch() {
-        for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
-            TaskView taskView = (TaskView) mRecentsView.getPageAt(i);
-            if (taskView.getTask().key.id != mRecentsView.getRunningTaskId()) {
-                Runnable launchTaskRunnable = () -> taskView.launchTask(true);
-                if (mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION)) {
-                    // Snap to the new page then launch it
-                    mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
-                } else {
-                    // No need to move page, just launch task directly
-                    launchTaskRunnable.run();
-                }
-                break;
-            }
-        }
-        mLauncher.getUserEventDispatcher().logActionOnControl(Touch.FLING,
-                ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
-                        ContainerType.WORKSPACE : ContainerType.APP);
+        mQuickSwitched = true;
+        quickSwitchIfReady();
     }
 
-    public void snapToPageForCurrentQuickScrubSection() {
-        if (mInQuickScrub) {
-            goToPageWithHaptic(mRecentsView.getFirstTaskIndex() + mQuickScrubSection);
+    public void onFinishedTransitionToQuickScrub() {
+        mFinishedTransitionToQuickScrub = true;
+        quickSwitchIfReady();
+    }
+
+    /**
+     * Immediately launches the current task (which we snapped to in onQuickScrubStart) if we've
+     * gotten the onQuickSwitch callback and the transition to quick scrub has completed.
+     */
+    private void quickSwitchIfReady() {
+        if (mQuickSwitched && mFinishedTransitionToQuickScrub) {
+            onQuickScrubEnd();
+            mLauncher.getUserEventDispatcher().logActionOnControl(Touch.FLING,
+                    ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
+                            ContainerType.WORKSPACE : ContainerType.APP);
+        }
+    }
+
+    public void snapToNextTaskIfAvailable() {
+        if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
+            int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
+            goToPageWithHaptic(toPage, QUICK_SWITCH_START_DURATION);
         }
     }
 
     private void goToPageWithHaptic(int pageToGoTo) {
-        pageToGoTo = Utilities.boundToRange(pageToGoTo, mStartPage, mRecentsView.getPageCount() - 1);
+        goToPageWithHaptic(pageToGoTo, -1);
+    }
+
+    private void goToPageWithHaptic(int pageToGoTo, int overrideDuration) {
+        pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
         if (pageToGoTo != mRecentsView.getNextPage()) {
-            int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
-                    * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
+            int duration = overrideDuration > -1 ? overrideDuration
+                    : Math.abs(pageToGoTo - mRecentsView.getNextPage())
+                            * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
             mRecentsView.snapToPage(pageToGoTo, duration);
             mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -158,7 +169,7 @@
         if (mQuickScrubSection == NUM_QUICK_SCRUB_SECTIONS
                 && currPage < mRecentsView.getPageCount() - 1) {
             goToPageWithHaptic(currPage + 1);
-        } else if (mQuickScrubSection == 0 && currPage > mStartPage) {
+        } else if (mQuickScrubSection == 0 && currPage > 0) {
             goToPageWithHaptic(currPage - 1);
         }
         mHasAlarmRun = true;
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..3cd1179 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;
@@ -94,7 +86,7 @@
     private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
-            for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
+            for (int i = 0; i < getChildCount(); i++) {
                 final TaskView taskView = (TaskView) getChildAt(i);
                 if (taskView.getTask().key.id == taskId) {
                     taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
@@ -105,7 +97,6 @@
     };
 
     private RecentsViewStateController mStateController;
-    private int mFirstTaskIndex;
 
     private final RecentsModel mModel;
     private int mLoadPlanId = -1;
@@ -152,7 +143,6 @@
                 mIsRtl = !mIsRtl;
             }
             setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
-            mScrollState.isRtl = mIsRtl;
         }
     }
 
@@ -161,7 +151,7 @@
     }
 
     public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
+        for (int i = 0; i < getChildCount(); i++) {
             final TaskView taskView = (TaskView) getChildAt(i);
             if (taskView.getTask().key.id == taskId) {
                 taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
@@ -203,7 +193,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         Resources res = getResources();
-        mFirstTaskIndex = getPageCount();
         mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
     }
 
@@ -264,17 +253,13 @@
         }
     }
 
-    public int getFirstTaskIndex() {
-        return mFirstTaskIndex;
-    }
-
     public boolean isTaskViewVisible(TaskView tv) {
         // For now, just check if it's the active task or an adjacent task
         return Math.abs(indexOfChild(tv) - getNextPage()) <= 1;
     }
 
     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;
@@ -325,7 +310,7 @@
         final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
         setLayoutTransition(null);
 
-        final int requiredChildCount = tasks.size() + mFirstTaskIndex;
+        final int requiredChildCount = tasks.size();
         for (int i = getChildCount(); i < requiredChildCount; i++) {
             final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
             addView(taskView);
@@ -340,8 +325,8 @@
         setLayoutTransition(mLayoutTransition);
 
         // Rebind and reset all task views
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            final int pageIndex = tasks.size() - i - 1 + mFirstTaskIndex;
+        for (int i = requiredChildCount - 1; i >= 0; i--) {
+            final int pageIndex = requiredChildCount - i - 1;
             final Task task = tasks.get(i);
             final TaskView taskView = (TaskView) getChildAt(pageIndex);
             taskView.bind(task);
@@ -354,7 +339,7 @@
         applyIconScale(false /* animate */);
 
         if (oldChildCount != getChildCount()) {
-            mQuickScrubController.snapToPageForCurrentQuickScrubSection();
+            mQuickScrubController.snapToNextTaskIfAvailable();
         }
     }
 
@@ -407,33 +392,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 +432,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 +441,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);
         }
     }
 
@@ -499,9 +456,9 @@
     private void loadVisibleTaskData() {
         RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
         int centerPageIndex = getPageNearestToCenterOfScreen();
-        int lower = Math.max(mFirstTaskIndex, centerPageIndex - 2);
+        int lower = Math.max(0, centerPageIndex - 2);
         int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
+        for (int i = 0; i < getChildCount(); i++) {
             TaskView taskView = (TaskView) getChildAt(i);
             Task task = taskView.getTask();
             boolean visible = lower <= i && i <= upper;
@@ -523,7 +480,7 @@
     public void onTaskDismissed(TaskView taskView) {
         ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
         removeView(taskView);
-        if (getTaskCount() == 0) {
+        if (getChildCount() == 0) {
             mLauncher.getStateManager().goToState(NORMAL);
         }
     }
@@ -533,10 +490,6 @@
         setCurrentPage(0);
     }
 
-    public int getTaskCount() {
-        return getChildCount() - mFirstTaskIndex;
-    }
-
     public int getRunningTaskId() {
         return mRunningTaskId;
     }
@@ -560,17 +513,17 @@
      */
     public void showTask(int runningTaskId) {
         boolean needsReload = false;
-        if (getTaskCount() == 0) {
+        if (getChildCount() == 0) {
             needsReload = true;
             // Add an empty view for now
             setLayoutTransition(null);
             final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
                     .inflate(R.layout.task, this, false);
-            addView(taskView, mFirstTaskIndex);
+            addView(taskView, 0);
             setLayoutTransition(mLayoutTransition);
         }
         mRunningTaskId = runningTaskId;
-        setCurrentPage(mFirstTaskIndex);
+        setCurrentPage(0);
         if (!needsReload) {
             needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
         }
@@ -579,9 +532,7 @@
         } else {
             loadVisibleTaskData();
         }
-        if (mCurrentPage >= mFirstTaskIndex) {
-            getPageAt(mCurrentPage).setAlpha(0);
-        }
+        getPageAt(mCurrentPage).setAlpha(0);
     }
 
     public QuickScrubController getQuickScrubController() {
@@ -598,7 +549,7 @@
 
     private void applyIconScale(boolean animate) {
         float scale = mFirstTaskIconScaledDown ? 0 : 1;
-        TaskView firstTask = (TaskView) getChildAt(mFirstTaskIndex);
+        TaskView firstTask = (TaskView) getChildAt(0);
         if (firstTask != null) {
             if (animate) {
                 firstTask.animateIconToScale(scale);
@@ -640,25 +591,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/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/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index f35f6a6..768fbda 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -21,8 +21,6 @@
 import android.view.Choreographer;
 import android.view.MotionEvent;
 
-import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
@@ -64,4 +62,14 @@
     default Choreographer getIntrimChoreographer(MotionEventQueue queue) {
         return null;
     }
+
+    default void deferInit() { }
+
+    default boolean deferNextEventToMainThread() {
+        return false;
+    }
+
+    default boolean forceToLauncherConsumer() {
+        return false;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e5af3e5..04740d9 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,17 +21,13 @@
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 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 static com.android.launcher3.LauncherState.NORMAL;
 
 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;
@@ -42,11 +38,13 @@
 import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.R;
 import com.android.launcher3.uioverrides.UiFactory;
@@ -62,6 +60,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 +132,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,17 +154,14 @@
     }
 
     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;
-    private MotionEventQueue mNoOpEventQueue;
 
     @Override
     public void onCreate() {
@@ -161,19 +169,9 @@
         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;
+        mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
 
         sConnected = true;
 
@@ -195,31 +193,45 @@
     }
 
     private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
-        mRunningTask = mAM.getRunningTask();
-
         mEventQueue.reset();
-
-        if (mRunningTask == null) {
-            mEventQueue = mNoOpEventQueue;
-        } else if (mRunningTask.topActivity.equals(mLauncher)) {
-            mEventQueue = getLauncherEventQueue();
-        } else {
+        TouchConsumer oldConsumer = mEventQueue.getConsumer();
+        if (oldConsumer.deferNextEventToMainThread()) {
             mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
-                    new OtherActivityTouchConsumer(this, mRunningTask, mRecentsModel,
-                    mHomeIntent, mISystemUiProxy, mMainThreadExecutor,
-                    mBackgroundThreadChoreographer, downHitTarget));
+                    new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
+                            oldConsumer.forceToLauncherConsumer(), v)));
+            mEventQueue.deferInit();
+        } else {
+            mEventQueue = new MotionEventQueue(
+                    mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
         }
     }
 
-    private MotionEventQueue getLauncherEventQueue() {
+    private TouchConsumer getCurrentTouchConsumer(
+            @HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
+        RunningTaskInfo runningTaskInfo = mAM.getRunningTask();
+
+        if (runningTaskInfo == null && !forceToLauncher) {
+            return mNoOpTouchConsumer;
+        } else if (forceToLauncher ||
+                runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
+            return getLauncherConsumer();
+        } else {
+            if (tracker == null) {
+                tracker = VelocityTracker.obtain();
+            }
+            return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
+                            mOverviewCommandHelper.homeIntent, mISystemUiProxy, mMainThreadExecutor,
+                            mBackgroundThreadChoreographer, downHitTarget, tracker);
+        }
+    }
+
+    private TouchConsumer getLauncherConsumer() {
         Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
         if (launcher == null) {
-            return mNoOpEventQueue;
+            return mNoOpTouchConsumer;
         }
-
         View target = launcher.getDragLayer();
-        return new MotionEventQueue(mMainThreadChoreographer,
-                new LauncherTouchConsumer(launcher, target));
+        return new LauncherTouchConsumer(launcher, target);
     }
 
     private static class LauncherTouchConsumer implements TouchConsumer {
@@ -304,13 +316,12 @@
             if (TouchConsumer.isInteractionQuick(interactionType)) {
                 Runnable action = () -> {
                     Runnable onComplete = null;
-                    if (interactionType == INTERACTION_QUICK_SCRUB) {
-                        mQuickScrubController.onQuickScrubStart(true);
-                    } else if (interactionType == INTERACTION_QUICK_SWITCH) {
+                    if (interactionType == INTERACTION_QUICK_SWITCH) {
                         onComplete = mQuickScrubController::onQuickSwitch;
                     }
-                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, 0,
-                            QUICK_SWITCH_START_DURATION, onComplete);
+                    LauncherState fromState = mLauncher.getStateManager().getState();
+                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, onComplete);
+                    mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
                 };
 
                 if (mLauncher.getWorkspace().runOnOverlayHidden(action)) {
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index ede0ad2..0d308a0 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -74,6 +74,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
@@ -468,6 +469,7 @@
         mLauncherLayoutListener.setHandler(this);
         onLauncherLayoutChanged();
 
+        final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
         // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
         // "Recents" activity for app transition tests for the app-to-recents case.
         final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
@@ -475,8 +477,11 @@
         builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
                 "com.android.systemui.recents.RecentsActivity");
         builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
-                mLauncherFrameDrawnTime - mTouchTimeMs);
+                transitionDelay);
         mMetricsLogger.write(builder);
+        if (LatencyTrackerCompat.isEnabled(mContext)) {
+            LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
+        }
     }
 
     public void updateInteractionType(@InteractionType int interactionType) {
@@ -588,9 +593,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) {
@@ -721,13 +725,14 @@
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
     private void animateToProgress(float progress, long duration) {
+        mIsGoingToHome = Float.compare(progress, 1) == 0;
         ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
         anim.setInterpolator(Interpolators.SCROLL);
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
-                setStateOnUiThread((Float.compare(mCurrentShift.value, 0) == 0)
-                        ? STATE_SCALED_CONTROLLER_APP : STATE_SCALED_CONTROLLER_RECENTS);
+                setStateOnUiThread(mIsGoingToHome ?
+                        STATE_SCALED_CONTROLLER_RECENTS : STATE_SCALED_CONTROLLER_APP);
             }
         });
         anim.start();
@@ -748,7 +753,7 @@
     }
 
     private void invalidateHandler() {
-        mCurrentShift.cancelAnimation();
+        mCurrentShift.finishAnimation();
 
         if (mGestureEndCallback != null) {
             mGestureEndCallback.run();
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 5354edf..3873a81 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -58,7 +58,7 @@
     private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
 
     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
-    private final Bitmap mBitmap;
+    protected Bitmap mBitmap;
     protected final int mIconColor;
 
     private boolean mIsPressed;
@@ -324,10 +324,9 @@
         return new MyConstantState(mBitmap, mIconColor);
     }
 
-    private static class MyConstantState extends ConstantState {
-        private final Bitmap mBitmap;
-        private final int mIconColor;
-
+    protected static class MyConstantState extends ConstantState {
+        protected final Bitmap mBitmap;
+        protected final int mIconColor;
 
         public MyConstantState(Bitmap bitmap, int color) {
             mBitmap = bitmap;
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index f4ae62a..ab73074 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -641,11 +641,8 @@
                     // Load the full res icon for the application, but if useLowResIcon is set, then
                     // only keep the low resolution icon instead of the larger full-sized icon
                     BitmapInfo iconInfo = li.createBadgedIconBitmap(
-                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion);
-                    if (mInstantAppResolver.isInstantApp(appInfo)) {
-                        li.badgeWithDrawable(iconInfo.icon,
-                                mContext.getDrawable(R.drawable.ic_instant_app_badge));
-                    }
+                            appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion,
+                            mInstantAppResolver.isInstantApp(appInfo));
                     li.recycle();
 
                     Bitmap lowResIcon =  generateLowResIcon(iconInfo.icon);
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index d5813f3..950a8ac 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -157,11 +157,6 @@
     }
 
     private void goToState(LauncherState state, boolean animated, long delay,
-            Runnable onCompleteRunnable) {
-        goToState(state, animated, delay, -1, onCompleteRunnable);
-    }
-
-    public void goToState(LauncherState state, boolean animated, long delay, long overrideDuration,
             final Runnable onCompleteRunnable) {
         if (mLauncher.isInState(state)) {
             if (mConfig.mCurrentAnimation == null) {
@@ -208,9 +203,6 @@
         // Since state NORMAL can be reached from multiple states, just assume that the
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
-        if (overrideDuration > -1) {
-            mConfig.duration = overrideDuration;
-        }
 
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(
                 state, new AnimatorSetBuilder(), onCompleteRunnable);
@@ -333,6 +325,10 @@
     }
 
     public void moveToRestState() {
+        if (mConfig.mCurrentAnimation != null && mConfig.userControlled) {
+            // The user is doing something. Lets not mess it up
+            return;
+        }
         if (mState.disableRestore) {
             goToState(getRestState());
             // Reset history
diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java
index 3393469..34a4e2d 100644
--- a/src/com/android/launcher3/graphics/DrawableFactory.java
+++ b/src/com/android/launcher3/graphics/DrawableFactory.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.graphics;
 
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -70,6 +71,10 @@
         return drawable;
     }
 
+    public FastBitmapDrawable newIcon(BitmapInfo info, ActivityInfo target) {
+        return new FastBitmapDrawable(info);
+    }
+
     /**
      * Returns a FastBitmapDrawable with the icon.
      */
@@ -80,7 +85,6 @@
         return new PreloadIconDrawable(info, mPreloadProgressPath, context);
     }
 
-
     protected Path getPreloadProgressPath(Context context) {
         if (Utilities.ATLEAST_OREO) {
             try {
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index 4a9cdd9..2e9ff23 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -104,6 +104,7 @@
 
     private IconNormalizer mNormalizer;
     private ShadowGenerator mShadowGenerator;
+    private Drawable mWrapperIcon;
 
     // sometimes we store linked lists of these things
     private LauncherIcons next;
@@ -172,6 +173,16 @@
      * The bitmap is also visually normalized with other icons.
      */
     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
+        return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
+    }
+
+    /**
+     * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
+     * view or workspace. The icon is badged for {@param user}.
+     * The bitmap is also visually normalized with other icons.
+     */
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
+            boolean isInstantApp) {
         float[] scale = new float[1];
         icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
         Bitmap bitmap = createIconBitmap(icon, scale[0]);
@@ -190,6 +201,9 @@
             } else {
                 result = createIconBitmap(badged, 1f);
             }
+        } else if (isInstantApp) {
+            badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
+            result = bitmap;
         } else {
             result = bitmap;
         }
@@ -213,8 +227,11 @@
         float scale = 1f;
         if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
             boolean[] outShape = new boolean[1];
-            AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
-                    mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+            if (mWrapperIcon == null) {
+                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
+                        .mutate();
+            }
+            AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
             dr.setBounds(0, 0, 1, 1);
             scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
             if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
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