Merge "Adding support for prefenrece search in QuickStep" 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..9006831 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
new file mode 100644
index 0000000..c416844
--- /dev/null
+++ b/quickstep/res/layout/fallback_recents_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+     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.
+-->
+<com.android.quickstep.RecentsRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.android.quickstep.FallbackRecentsView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:theme="@style/HomeScreenElementTheme"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false" />
+
+</com.android.quickstep.RecentsRootView>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 9f4f8a1..54a90cf 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -14,7 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.RecentsView
+<com.android.quickstep.views.LauncherRecentsView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@style/HomeScreenElementTheme"
     android:layout_width="match_parent"
@@ -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
+</com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 91b6aa3..0ac2b11 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -13,12 +13,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.quickstep.views.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:elevation="4dp">
 
-    <com.android.quickstep.TaskThumbnailView
+    <com.android.quickstep.views.TaskThumbnailView
         android:id="@+id/snapshot"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -29,4 +29,4 @@
         android:layout_width="@dimen/task_thumbnail_icon_size"
         android:layout_height="@dimen/task_thumbnail_icon_size"
         android:layout_gravity="top|center_horizontal" />
-</com.android.quickstep.TaskView>
\ No newline at end of file
+</com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index 6e3fb4f..b846665 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -14,7 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.TaskMenuView
+<com.android.quickstep.views.TaskMenuView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/bg_popup_item_width"
     android:layout_height="wrap_content"
@@ -33,4 +33,4 @@
             android:paddingTop="18dp"
             android:drawablePadding="8dp"
             android:gravity="center_horizontal"/>
-</com.android.quickstep.TaskMenuView>
\ No newline at end of file
+</com.android.quickstep.views.TaskMenuView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index cf86176..8497191 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,8 +16,6 @@
 
 <resources>
 
-    <dimen name="options_menu_icon_size">24dp</dimen>
-
     <dimen name="task_thumbnail_top_margin">24dp</dimen>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
     <dimen name="task_menu_background_radius">12dp</dimen>
@@ -33,8 +31,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 b97669b..ba0cbfa 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -32,6 +32,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -58,8 +59,9 @@
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -79,10 +81,14 @@
 
     private static final String TAG = "LauncherTransition";
     private static final int REFRESH_RATE_MS = 16;
+    private static final int STATUS_BAR_TRANSITION_DURATION = 120;
 
     private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
             "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
 
+    private static final int APP_LAUNCH_DURATION = 500;
+    // Use a shorter duration for x or y translation to create a curve effect
+    private static final int APP_LAUNCH_CURVED_DURATION = 233;
     private static final int RECENTS_LAUNCH_DURATION = 336;
     private static final int LAUNCHER_RESUME_START_DELAY = 100;
     private static final int CLOSING_TRANSITION_DURATION_MS = 350;
@@ -156,6 +162,7 @@
     @Override
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
+            TaskView taskView = findTaskViewToLaunch(launcher, v);
             try {
                 RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
                     @Override
@@ -165,8 +172,8 @@
                         // processed before the next frame.
                         postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
                             final boolean removeTrackingView;
-                            LauncherTransitionAnimator animator =
-                                    composeRecentsLaunchAnimator(v, targets);
+                            LauncherTransitionAnimator animator = composeRecentsLaunchAnimator(
+                                    taskView == null ? v : taskView, targets);
                             if (animator != null) {
                                 // We are animating the task view directly, do not remove it after
                                 removeTrackingView = false;
@@ -206,8 +213,10 @@
                     }
                 };
 
-                return ActivityOptionsCompat.makeRemoteAnimation(
-                        new RemoteAnimationAdapterCompat(runner, 500, 380));
+                int duration = taskView != null ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
+                int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
+                return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+                        runner, duration, statusBarTransitionDelay));
             } catch (NoClassDefFoundError e) {
                 // Gracefully fall back to default launch options if the user's platform doesn't
                 // have the latest changes.
@@ -217,10 +226,56 @@
     }
 
     /**
+     * Try to find a TaskView that corresponds with the component of the launched view.
+     *
+     * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
+     * Otherwise, we will assume we are using a normal app transition, but it's possible that the
+     * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
+     */
+    private TaskView findTaskViewToLaunch(Launcher launcher, View v) {
+        if (v instanceof TaskView) {
+            return (TaskView) v;
+        }
+        if (!launcher.isInState(LauncherState.OVERVIEW)) {
+            return null;
+        }
+        if (v.getTag() instanceof ItemInfo) {
+            ItemInfo itemInfo = (ItemInfo) v.getTag();
+            ComponentName componentName = itemInfo.getTargetComponent();
+            if (componentName != null) {
+                RecentsView recentsView = launcher.getOverviewPanel();
+                for (int i = 0; i < recentsView.getChildCount(); i++) {
+                    TaskView taskView = (TaskView) recentsView.getPageAt(i);
+                    if (recentsView.isTaskViewVisible(taskView)) {
+                        Task task = taskView.getTask();
+                        if (componentName.equals(task.key.getComponent())) {
+                            return taskView;
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * Composes the animations for a launch from the recents list if possible.
      */
     private LauncherTransitionAnimator composeRecentsLaunchAnimator(View v,
             RemoteAnimationTargetCompat[] targets) {
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
+        MutableBoolean skipLauncherChanges = new MutableBoolean(!launcherClosing);
+        if (v instanceof TaskView) {
+            // We already found a task view to launch, so use that for the animation.
+            TaskView taskView = (TaskView) v;
+            return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
+                    getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
+        }
+
+        // It's possible that the launched view can still be resolved to a visible task view, check
+        // the task id of the opening task and see if we can find a match.
+
         // Ensure recents is actually visible
         if (!mLauncher.getStateManager().getState().overviewUi) {
             return null;
@@ -242,7 +297,6 @@
 
         // If the opening task id is not currently visible in overview, then fall back to normal app
         // icon launch animation
-        RecentsView recentsView = mLauncher.getOverviewPanel();
         TaskView taskView = recentsView.getTaskView(openingTaskId);
         if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
             return null;
@@ -251,7 +305,6 @@
         // Found a visible recents task that matches the opening app, lets launch the app from there
         Animator launcherAnim;
         AnimatorListenerAdapter windowAnimEndListener;
-        boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING);
         if (launcherClosing) {
             launcherAnim = getRecentsLauncherAnimator(recentsView, taskView);
             windowAnimEndListener = new AnimatorListenerAdapter() {
@@ -275,7 +328,6 @@
             };
         }
 
-        MutableBoolean skipLauncherChanges = new MutableBoolean(!launcherClosing);
         Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
         windowAnim.addListener(windowAnimEndListener);
         return new LauncherTransitionAnimator(launcherAnim, windowAnim, skipLauncherChanges);
@@ -295,7 +347,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 +369,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 +381,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);
@@ -587,8 +639,8 @@
 
         // Adjust the duration to change the "curve" of the app icon to the center.
         boolean isBelowCenterY = lp.topMargin < centerY;
-        x.setDuration(isBelowCenterY ? 500 : 233);
-        y.setDuration(isBelowCenterY ? 233 : 500);
+        x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
+        y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
         x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
         y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
         appIconAnimatorSet.play(x);
@@ -601,7 +653,7 @@
         float scale = Math.max(maxScaleX, maxScaleY);
         ObjectAnimator scaleAnim = ObjectAnimator
                 .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
-        scaleAnim.setDuration(500).setInterpolator(Interpolators.EXAGGERATED_EASE);
+        scaleAnim.setDuration(APP_LAUNCH_DURATION).setInterpolator(Interpolators.EXAGGERATED_EASE);
         appIconAnimatorSet.play(scaleAnim);
 
         // Fade out the app icon.
@@ -619,7 +671,13 @@
      */
     private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
         Rect bounds = new Rect();
-        if (v instanceof BubbleTextView) {
+        boolean isDeepShortcutTextView = v instanceof DeepShortcutTextView
+                && v.getParent() != null && v.getParent() instanceof DeepShortcutView;
+        if (isDeepShortcutTextView) {
+            // Deep shortcut views have their icon drawn in a sibling view.
+            DeepShortcutView view = (DeepShortcutView) v.getParent();
+            mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds);
+        } else if (v instanceof BubbleTextView) {
             ((BubbleTextView) v).getIconBounds(bounds);
         } else {
             mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
@@ -630,7 +688,7 @@
         Matrix matrix = new Matrix();
 
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
-        appAnimator.setDuration(500);
+        appAnimator.setDuration(APP_LAUNCH_DURATION);
         appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             boolean isFirstFrame = true;
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index ea03a76..efa83e4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -58,7 +59,8 @@
 
     @Override
     public String getDescription(Launcher launcher) {
-        return launcher.getString(R.string.all_apps_button_label);
+        AllAppsContainerView appsView = launcher.getAppsView();
+        return appsView.getDescription();
     }
 
     @Override
@@ -88,6 +90,11 @@
     }
 
     @Override
+    public float getOverviewTranslationX(Launcher launcher) {
+        return 0;
+    }
+
+    @Override
     public LauncherState getHistoryForState(LauncherState previousState) {
         return previousState == OVERVIEW ? OVERVIEW : NORMAL;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index a55edfe..97ac3e6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -36,7 +36,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.VerticalSwipeController;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
 
 class EventLogTags {
     private EventLogTags() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index acd4fc1..9541d0d 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.views.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..09acb1d 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,10 +24,9 @@
 
 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;
+import com.android.quickstep.views.RecentsView;
 
 /**
  * Definition for overview state
@@ -35,31 +34,31 @@
 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
+    public float getOverviewTranslationX(Launcher launcher) {
+        return 0;
     }
 
     @Override
@@ -85,40 +84,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..c8b54ad 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -15,10 +15,14 @@
  */
 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.scrollInterpolatorForVelocity;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -32,19 +36,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;
+import com.android.quickstep.PendingAnimation;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 
 /**
  * Touch controller for swipe interaction in Overview state
@@ -65,6 +63,7 @@
     private final RecentsView mRecentsView;
     private final int[] mTempCords = new int[2];
 
+    private PendingAnimation mPendingAnimation;
     private AnimatorPlaybackController mCurrentAnimation;
     private boolean mCurrentAnimationIsGoingUp;
 
@@ -132,28 +131,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;
                 }
             }
 
@@ -185,6 +177,11 @@
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setPlayFraction(0);
         }
+        if (mPendingAnimation != null) {
+            mPendingAnimation.finish(false);
+            mPendingAnimation = null;
+        }
+
         mCurrentAnimationIsGoingUp = goingUp;
         float range = mLauncher.getAllAppsController().getShiftRange();
         long maxDuration = (long) (2 * range);
@@ -201,19 +198,11 @@
             }
         } else {
             if (goingUp) {
-                AnimatorSet anim = new AnimatorSet();
-                ObjectAnimator translate = ObjectAnimator.ofFloat(
-                        mTaskBeingDragged, View.TRANSLATION_Y, -mTaskBeingDragged.getBottom());
-                translate.setInterpolator(LINEAR);
-                translate.setDuration(maxDuration);
-                anim.play(translate);
-
-                ObjectAnimator alpha = ObjectAnimator.ofFloat(mTaskBeingDragged, View.ALPHA, 0);
-                alpha.setInterpolator(DEACCEL_1_5);
-                alpha.setDuration(maxDuration);
-                anim.play(alpha);
-                mCurrentAnimation = AnimatorPlaybackController.wrap(anim, maxDuration);
-                mEndDisplacement = -mTaskBeingDragged.getBottom();
+                mPendingAnimation = mRecentsView
+                        .createTaskDismissAnimation(mTaskBeingDragged, maxDuration);
+                mCurrentAnimation = AnimatorPlaybackController
+                        .wrap(mPendingAnimation.anim, maxDuration);
+                mEndDisplacement = -mTaskBeingDragged.getHeight();
             } else {
                 AnimatorSet anim = new AnimatorSet();
                 // TODO: Setup a zoom animation
@@ -299,15 +288,17 @@
     }
 
     private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
+        if (mPendingAnimation != null) {
+            mPendingAnimation.finish(wasSuccess);
+            mPendingAnimation = null;
+        }
         if (mTaskBeingDragged == null) {
             LauncherState state = wasSuccess ?
                     (mCurrentAnimationIsGoingUp ? ALL_APPS : NORMAL) : OVERVIEW;
             mLauncher.getStateManager().goToState(state, false);
 
         } else if (wasSuccess) {
-            if (mCurrentAnimationIsGoingUp) {
-                mRecentsView.onTaskDismissed(mTaskBeingDragged);
-            } else {
+            if (!mCurrentAnimationIsGoingUp) {
                 mTaskBeingDragged.launchTask(false);
                 mLauncher.getUserEventDispatcher().logTaskLaunch(logAction,
                         Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 92d071a..b68a3d8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -28,88 +27,80 @@
 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;
 import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.TaskView;
+import com.android.quickstep.views.RecentsView;
 
 public class RecentsViewStateController implements StateHandler {
 
     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;
-
     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++) {
-                ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
-            }
-            mRecentsView.updateCurveProperties();
+            mRecentsView.resetTaskVisuals();
         }
+        float overviewTranslationX = state.getOverviewTranslationX(mLauncher);
+        int direction = mRecentsView.isRtl() ? -1 : 1;
+        mRecentsView.setTranslationX(overviewTranslationX * direction);
     }
 
     @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);
 
         // Scroll to the workspace card before changing to the NORMAL state.
-        int currPage = mRecentsView.getCurrentPage();
         LauncherState fromState = mLauncher.getStateManager().getState();
+        int currPage = mRecentsView.getCurrentPage();
         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 =
                 mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
         progressAnim.setDuration(config.duration);
         progressAnim.setInterpolator(Interpolators.LINEAR);
-        progressAnim.addListener(new AnimationSuccessListener() {
-
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mWorkspaceCard.setWorkspaceScrollingEnabled(toState.overviewUi);
-                mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
-            }
-        });
         builder.play(progressAnim);
 
         ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
         visibilityAnim.setDuration(config.duration);
         visibilityAnim.setInterpolator(Interpolators.LINEAR);
         builder.play(visibilityAnim);
+
+        int direction = mRecentsView.isRtl() ? -1 : 1;
+        float fromTranslationX = fromState.getOverviewTranslationX(mLauncher) * direction;
+        float toTranslationX = toState.getOverviewTranslationX(mLauncher) * direction;
+        ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_X,
+                fromTranslationX, toTranslationX);
+        translationXAnim.setDuration(config.duration);
+        translationXAnim.setInterpolator(Interpolators.ACCEL);
+        if (toState.overviewUi) {
+            translationXAnim.addUpdateListener(valueAnimator -> {
+                // While animating into recents, update the visible task data as needed
+                mRecentsView.loadVisibleTaskData();
+            });
+        }
+        builder.play(translationXAnim);
     }
 
     public void setVisibility(boolean isVisible) {
@@ -145,12 +136,6 @@
 
     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));
-        }
     }
 
     private void onVisibilityProgress() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index a8bcb11..9051cfb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 
-import android.graphics.PointF;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
@@ -30,13 +29,10 @@
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RecentsView;
+import com.android.quickstep.views.RecentsView;
 
 public class UiFactory {
 
-    private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
-            "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
-
     public static TouchController[] createTouchControllers(Launcher launcher) {
         if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
             return new TouchController[] {
@@ -60,10 +56,6 @@
                 new RecentsViewStateController(launcher)};
     }
 
-    public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
-        OptionsPopupView.show(launcher, touchPoint.x, touchPoint.y);
-    }
-
     public static void onLauncherStateOrFocusChanged(Launcher launcher) {
         boolean shouldBackButtonBeVisible = launcher == null
                 || !launcher.isInState(NORMAL)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
deleted file mode 100644
index 8533502..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Workspace;
-import com.android.quickstep.RecentsView;
-import com.android.quickstep.RecentsView.PageCallbacks;
-import com.android.quickstep.RecentsView.ScrollState;
-
-public class WorkspaceCard extends View implements PageCallbacks, OnClickListener {
-
-    private final Rect mTempRect = new Rect();
-
-    private Launcher mLauncher;
-    private Workspace mWorkspace;
-
-    private float mLinearInterpolationForPage2 = 1;
-    private float mTranslateXPage0, mTranslateXPage1;
-    private float mExtraScrollShift;
-
-    private boolean mIsWorkspaceScrollingEnabled;
-
-    public WorkspaceCard(Context context) {
-        this(context, null);
-    }
-
-    public WorkspaceCard(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        setOnClickListener(this);
-    }
-
-    /**
-     * Draw nothing.
-     */
-    @Override
-    public void draw(Canvas canvas) { }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        // Initiate data
-        mLinearInterpolationForPage2 = RecentsView.getScaledDownPageRect(
-                mLauncher.getDeviceProfile(), mLauncher, mTempRect);
-
-        float[] scale = OverviewState.getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
-        mTranslateXPage0 = scale[1];
-        mTranslateXPage1 = OverviewState
-                .getScaleAndTranslationForPageRect(mLauncher,
-                        getResources().getDimension(R.dimen.workspace_overview_offset_x) / scale[0],
-                        mTempRect)[1];
-
-        mExtraScrollShift = 0;
-        if (mWorkspace != null && getWidth() > 0) {
-            float workspaceWidth = mWorkspace.getNormalChildWidth() * scale[0];
-            mExtraScrollShift = (workspaceWidth - getWidth()) / 2;
-            setScaleX(workspaceWidth / getWidth());
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        mLauncher.getStateManager().goToState(NORMAL);
-    }
-
-    public void setup(Launcher launcher) {
-        mLauncher = launcher;
-        mWorkspace = mLauncher.getWorkspace();
-    }
-
-    public void setWorkspaceScrollingEnabled(boolean isEnabled) {
-        mIsWorkspaceScrollingEnabled = isEnabled;
-    }
-
-    @Override
-    public int onPageScroll(ScrollState scrollState) {
-        float factor = scrollState.linearInterpolation;
-        float translateX = scrollState.distanceFromScreenCenter;
-        if (mIsWorkspaceScrollingEnabled) {
-            float shift = factor * (mTranslateXPage1 - mTranslateXPage0);
-            mWorkspace.setTranslationX(shift + mTranslateXPage0);
-            translateX += shift;
-        }
-
-        setTranslationX(translateX);
-
-        // If the workspace card is still the first page, shift all the other pages.
-        if (scrollState.linearInterpolation > mLinearInterpolationForPage2) {
-            scrollState.prevPageExtraWidth = 0;
-        } else if (mLinearInterpolationForPage2 > 0) {
-            scrollState.prevPageExtraWidth = mExtraScrollShift *
-                    (1 - scrollState.linearInterpolation / mLinearInterpolationForPage2);
-        } else {
-            scrollState.prevPageExtraWidth = mExtraScrollShift;
-        }
-        return SCROLL_TYPE_WORKSPACE;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/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/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
new file mode 100644
index 0000000..22f6e0c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/FallbackRecentsView.java
@@ -0,0 +1,57 @@
+/*
+ * 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.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.quickstep.views.RecentsView;
+
+public class FallbackRecentsView extends RecentsView<RecentsActivity> implements Insettable {
+
+    public FallbackRecentsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setOverviewStateEnabled(true);
+    }
+
+    @Override
+    protected void onAllTasksRemoved() {
+        mActivity.finish();
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        Rect padding = getPadding(dp, getContext());
+        verticalCenter(padding, dp);
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+    }
+
+    public static void verticalCenter(Rect padding, DeviceProfile dp) {
+        Rect insets = dp.getInsets();
+        int totalSpace = (padding.top + padding.bottom - insets.top - insets.bottom) / 2;
+        padding.top = insets.top + totalSpace;
+        padding.bottom = insets.bottom + totalSpace;
+    }
+}
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..89c9d16 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -17,7 +17,6 @@
 
 
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
-import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SWITCH;
 import static com.android.quickstep.TouchConsumer.isInteractionQuick;
 
@@ -51,6 +50,8 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -319,13 +320,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..f875bb7 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -16,19 +16,20 @@
 package com.android.quickstep;
 
 import android.annotation.TargetApi;
+import android.app.ActivityManager.TaskDescription;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.os.UserHandle;
 import android.util.LruCache;
 import android.util.SparseArray;
 
 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, 0, false);
                 mDefaultIcons.put(userId, info);
             }
 
@@ -62,23 +65,31 @@
     }
 
     @Override
-    protected Drawable createBadgedDrawable(Drawable drawable, int userId) {
-        return new FastBitmapDrawable(getBitmapInfo(drawable, userId));
+    protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
+        return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
+                false));
     }
 
-    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId) {
+    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
+            int primaryColor, boolean isInstantApp) {
         if (mLauncherIcons == null) {
             mLauncherIcons = LauncherIcons.obtain(mContext);
         }
 
+        mLauncherIcons.setWrapperBackgroundColor(primaryColor);
         // 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 Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId,
+            TaskDescription desc) {
+        BitmapInfo bitmapInfo = getBitmapInfo(
+                activityInfo.loadUnbadgedIcon(mContext.getPackageManager()),
+                userId,
+                desc.getPrimaryColor(),
+                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..031624a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -0,0 +1,120 @@
+/*
+ * 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 static com.android.quickstep.TouchInteractionService.DEBUG_SHOW_OVERVIEW_BUTTON;
+
+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 static final boolean DEBUG_START_FALLBACK_ACTIVITY = DEBUG_SHOW_OVERVIEW_BUTTON;
+
+    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() {
+        if (DEBUG_START_FALLBACK_ACTIVITY) {
+            mContext.startActivity(new Intent(mContext, RecentsActivity.class)
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+            return;
+        }
+
+        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/PendingAnimation.java b/quickstep/src/com/android/quickstep/PendingAnimation.java
new file mode 100644
index 0000000..d22ef61
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/PendingAnimation.java
@@ -0,0 +1,53 @@
+/*
+ * 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.animation.AnimatorSet;
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to keep track of a running animation.
+ *
+ * This class allows attaching end callbacks to an animation is intended to be used with
+ * {@link com.android.launcher3.anim.AnimatorPlaybackController}, since in that case
+ * AnimationListeners are not properly dispatched.
+ */
+@TargetApi(Build.VERSION_CODES.O)
+public class PendingAnimation {
+
+    private final ArrayList<Consumer<Boolean>> mEndListeners = new ArrayList<>();
+
+    public final AnimatorSet anim;
+
+    public PendingAnimation(AnimatorSet anim) {
+        this.anim = anim;
+    }
+
+    public void finish(boolean isSuccess) {
+        for (Consumer<Boolean> listeners : mEndListeners) {
+            listeners.accept(isSuccess);
+        }
+        mEndListeners.clear();
+    }
+
+    public void addEndListener(Consumer<Boolean> listener) {
+        mEndListeners.add(listener);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index f28d51c..a154b29 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -19,13 +19,14 @@
 import android.view.HapticFeedbackConstants;
 
 import com.android.launcher3.Alarm;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseActivity;
 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;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 
 /**
  * Responds to quick scrub callbacks to page through and launch recent tasks.
@@ -35,8 +36,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;
@@ -47,15 +47,17 @@
 
     private final Alarm mAutoAdvanceAlarm;
     private final RecentsView mRecentsView;
-    private final Launcher mLauncher;
+    private final BaseActivity mActivity;
 
     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;
+    public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
+        mActivity = activity;
         mRecentsView = recentsView;
         if (ENABLE_AUTO_ADVANCE) {
             mAutoAdvanceAlarm = new Alarm();
@@ -65,10 +67,14 @@
 
     public void onQuickScrubStart(boolean startingFromHome) {
         mInQuickScrub = true;
-        mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
+        mStartedFromHome = startingFromHome;
         mQuickScrubSection = 0;
         mHasAlarmRun = false;
-        mLauncher.getUserEventDispatcher().resetActionDurationMillis();
+        mQuickSwitched = false;
+        mFinishedTransitionToQuickScrub = false;
+
+        snapToNextTaskIfAvailable();
+        mActivity.getUserEventDispatcher().resetActionDurationMillis();
     }
 
     public void onQuickScrubEnd() {
@@ -78,11 +84,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;
@@ -93,8 +95,8 @@
             // No page move needed, just launch it
             launchTaskRunnable.run();
         }
-        mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
-                ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
+        mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
+                ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
                         ContainerType.WORKSPACE : ContainerType.APP);
     }
 
@@ -102,7 +104,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 +120,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();
+            mActivity.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 +171,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/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index f92d773..598c34d 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,34 +15,29 @@
  */
 package com.android.quickstep;
 
-import android.app.ListActivity;
 import android.os.Bundle;
-import android.os.UserHandle;
-import android.support.annotation.Nullable;
-import android.widget.ArrayAdapter;
 
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
-import com.android.systemui.shared.recents.model.RecentsTaskLoader;
-import com.android.systemui.shared.recents.model.Task;
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
 
 /**
  * A simple activity to show the recently launched tasks
  */
-public class RecentsActivity extends ListActivity {
-
-    private ArrayAdapter<Task> mAdapter;
+public class RecentsActivity extends BaseActivity {
 
     @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
+    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
-        plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
-                UserHandle.myUserId());
+        // In case we are reusing IDP, create a copy so that we dont conflict with Launcher
+        // activity.
+        LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+        setDeviceProfile(appState != null
+                ? appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this)
+                : new InvariantDeviceProfile(this).getDeviceProfile(this));
 
-        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
-        mAdapter.addAll(plan.getTaskStack().getTasks());
-        setListAdapter(mAdapter);
+        setContentView(R.layout.fallback_recents_activity);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 3e3b3b2..392b73f 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;
@@ -211,13 +210,13 @@
 
     public void onStart() {
         mRecentsTaskLoader.startLoader(mContext);
-//        mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
+        mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(true);
     }
 
     public void onTrimMemory(int level) {
         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
             // We already stop the loader in UI_HIDDEN, so stop the high res loader as well
-//            mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
+            mRecentsTaskLoader.getHighResThumbnailLoader().setVisible(false);
         }
         mRecentsTaskLoader.onTrimMemory(level);
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsRootView.java b/quickstep/src/com/android/quickstep/RecentsRootView.java
new file mode 100644
index 0000000..3c69dbf
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsRootView.java
@@ -0,0 +1,56 @@
+/*
+ * 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.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.R;
+import com.android.launcher3.util.Themes;
+
+public class RecentsRootView extends InsettableFrameLayout {
+
+    private final BaseActivity mActivity;
+
+    public RecentsRootView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mActivity = BaseActivity.fromContext(context);
+    }
+
+    @TargetApi(23)
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        // Update device profile before notifying the children.
+        mActivity.getDeviceProfile().updateInsets(insets);
+        setInsets(insets);
+        return true; // I'll take it from here
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // If the insets haven't changed, this is a no-op. Avoid unnecessary layout caused by
+        // modifying child layout params.
+        if (!insets.equals(mInsets)) {
+            super.setInsets(insets);
+        }
+        setBackground(insets.top == 0 ? null
+                : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
deleted file mode 100644
index 9ae41eb..0000000
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ /dev/null
@@ -1,664 +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.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;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.Shader.TileMode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.SparseBooleanArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-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;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-import java.util.ArrayList;
-
-/**
- * A list of recent tasks.
- */
-public class RecentsView extends PagedView implements Insettable, OnSharedPreferenceChangeListener {
-
-    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;
-    private QuickScrubController mQuickScrubController;
-    private final ScrollState mScrollState = new ScrollState();
-    private boolean mOverviewStateEnabled;
-    private boolean mTaskStackListenerRegistered;
-    private LayoutTransition mLayoutTransition;
-    private Runnable mNextPageSwitchRunnable;
-
-    private float mFastFlingVelocity;
-
-    /**
-     * TODO: Call reloadIdNeeded in onTaskStackChanged.
-     */
-    private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
-        @Override
-        public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
-            for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-                final TaskView taskView = (TaskView) getChildAt(i);
-                if (taskView.getTask().key.id == taskId) {
-                    taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
-                    return;
-                }
-            }
-        }
-    };
-
-    private RecentsViewStateController mStateController;
-    private int mFirstTaskIndex;
-
-    private final RecentsModel mModel;
-    private int mLoadPlanId = -1;
-
-    // Only valid until the launcher state changes to NORMAL
-    private int mRunningTaskId = -1;
-
-    private Bitmap mScrim;
-    private Paint mFadePaint;
-    private Shader mFadeShader;
-    private Matrix mFadeMatrix;
-    private boolean mScrimOnLeft;
-
-    private boolean mFirstTaskIconScaledDown = false;
-    private SparseBooleanArray mPrevVisibleTasks = new SparseBooleanArray();
-
-    public RecentsView(Context context) {
-        this(context, null);
-    }
-
-    public RecentsView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
-        enableFreeScroll(true);
-        setupLayoutTransition();
-        setClipToOutline(true);
-
-        mLauncher = Launcher.getLauncher(context);
-        mQuickScrubController = new QuickScrubController(mLauncher, this);
-        mModel = RecentsModel.getInstance(context);
-
-        onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
-        if (s.equals(PREF_FLIP_RECENTS)) {
-            mIsRtl = Utilities.isRtl(getResources());
-            if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
-                mIsRtl = !mIsRtl;
-            }
-            setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
-            mScrollState.isRtl = mIsRtl;
-        }
-    }
-
-    public boolean isRtl() {
-        return mIsRtl;
-    }
-
-    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-            final TaskView taskView = (TaskView) getChildAt(i);
-            if (taskView.getTask().key.id == taskId) {
-                taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
-                taskView.setAlpha(1);
-                return taskView;
-            }
-        }
-        return null;
-    }
-
-    private void setupLayoutTransition() {
-        // We want to show layout transitions when pages are deleted, to close the gap.
-        // TODO: We should this manually so we can control the animation (fill in the gap as the
-        // dismissing task is being tracked, and also so we can update the visible task data during
-        // the transition. For now, the workaround is to expand the visible tasks to load.
-        mLayoutTransition = new LayoutTransition();
-        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
-        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
-
-        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
-        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
-        mLayoutTransition.addTransitionListener(new TransitionListener() {
-            @Override
-            public void startTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
-                    View view, int i) {
-                loadVisibleTaskData();
-            }
-
-            @Override
-            public void endTransition(LayoutTransition layoutTransition, ViewGroup viewGroup,
-                    View view, int i) {
-                loadVisibleTaskData();
-            }
-        });
-        setLayoutTransition(mLayoutTransition);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        Resources res = getResources();
-        mFirstTaskIndex = getPageCount();
-        mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
-    }
-
-    @Override
-    protected void onWindowVisibilityChanged(int visibility) {
-        super.onWindowVisibilityChanged(visibility);
-        updateTaskStackListenerState();
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        updateTaskStackListenerState();
-        Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        updateTaskStackListenerState();
-        Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        mInsets.set(insets);
-        DeviceProfile dp = Launcher.getLauncher(getContext()).getDeviceProfile();
-        Rect padding = getPadding(dp, getContext());
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        lp.bottomMargin = padding.bottom;
-        setLayoutParams(lp);
-
-        setPadding(padding.left, padding.top, padding.right, 0);
-
-        if (dp.isVerticalBarLayout()) {
-            boolean wasScrimOnLeft = mScrimOnLeft;
-            mScrimOnLeft = dp.isSeascape();
-
-            if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
-                Drawable scrim = getContext().getDrawable(mScrimOnLeft
-                        ? R.drawable.recents_horizontal_scrim_left
-                        : R.drawable.recents_horizontal_scrim_right);
-                if (scrim instanceof BitmapDrawable) {
-                    mScrim = ((BitmapDrawable) scrim).getBitmap();
-                    mFadePaint = new Paint();
-                    mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
-                    mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
-                    mFadeMatrix = new Matrix();
-                } else {
-                    mScrim = null;
-                }
-            }
-        } else {
-            mScrim = null;
-            mFadePaint = null;
-            mFadeShader = null;
-            mFadeMatrix = null;
-        }
-    }
-
-    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++) {
-            TaskView tv = (TaskView) getChildAt(i);
-            if (tv.getTask().key.id == taskId) {
-                return tv;
-            }
-        }
-        return null;
-    }
-
-    public void setStateController(RecentsViewStateController stateController) {
-        mStateController = stateController;
-    }
-
-    public RecentsViewStateController getStateController() {
-        return mStateController;
-    }
-
-    public void setOverviewStateEnabled(boolean enabled) {
-        mOverviewStateEnabled = enabled;
-        updateTaskStackListenerState();
-    }
-
-    public void setNextPageSwitchRunnable(Runnable r) {
-        mNextPageSwitchRunnable = r;
-    }
-
-    @Override
-    protected void onPageEndTransition() {
-        super.onPageEndTransition();
-        if (mNextPageSwitchRunnable != null) {
-            mNextPageSwitchRunnable.run();
-            mNextPageSwitchRunnable = null;
-        }
-    }
-
-    private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
-        final RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
-        TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
-        if (stack == null) {
-            removeAllViews();
-            return;
-        }
-
-        int oldChildCount = getChildCount();
-
-        // Ensure there are as many views as there are tasks in the stack (adding and trimming as
-        // necessary)
-        final LayoutInflater inflater = LayoutInflater.from(getContext());
-        final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
-        setLayoutTransition(null);
-
-        final int requiredChildCount = tasks.size() + mFirstTaskIndex;
-        for (int i = getChildCount(); i < requiredChildCount; i++) {
-            final TaskView taskView = (TaskView) inflater.inflate(R.layout.task, this, false);
-            addView(taskView);
-        }
-        while (getChildCount() > requiredChildCount) {
-            final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
-            final Task task = taskView.getTask();
-            removeView(taskView);
-            loader.unloadTaskData(task);
-//            loader.getHighResThumbnailLoader().onTaskInvisible(task);
-        }
-        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;
-            final Task task = tasks.get(i);
-            final TaskView taskView = (TaskView) getChildAt(pageIndex);
-            taskView.bind(task);
-            taskView.resetVisualProperties();
-        }
-        updateCurveProperties();
-        // Reload the set of visible task's data
-        mPrevVisibleTasks.clear();
-        loadVisibleTaskData();
-        applyIconScale(false /* animate */);
-
-        if (oldChildCount != getChildCount()) {
-            mQuickScrubController.snapToPageForCurrentQuickScrubSection();
-        }
-    }
-
-    private void updateTaskStackListenerState() {
-        boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
-                && getWindowVisibility() == VISIBLE;
-        if (registerStackListener != mTaskStackListenerRegistered) {
-            if (registerStackListener) {
-                ActivityManagerWrapper.getInstance()
-                        .registerTaskStackListener(mTaskStackListener);
-                reloadIfNeeded();
-            } else {
-                ActivityManagerWrapper.getInstance()
-                        .unregisterTaskStackListener(mTaskStackListener);
-            }
-            mTaskStackListenerRegistered = registerStackListener;
-        }
-    }
-
-    private static Rect getPadding(DeviceProfile profile, Context context) {
-        WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
-        Rect padding = new Rect(profile.workspacePadding);
-
-        float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
-        float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
-
-        float overviewHeight, overviewWidth;
-        if (profile.isVerticalBarLayout()) {
-            float scrimLength = context.getResources()
-                    .getDimension(R.dimen.recents_page_fade_length);
-            float maxPadding = Math.max(padding.left, padding.right);
-
-            // Use the same padding on both sides for symmetry.
-            float availableWidth = taskWidth - 2 * Math.max(maxPadding, scrimLength);
-            float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
-                    - sTempStableInsets.top;
-            float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
-            overviewHeight = taskHeight * scaledRatio;
-            overviewWidth = taskWidth * scaledRatio;
-
-        } else {
-            overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
-                    - sTempStableInsets.top;
-            overviewWidth = taskWidth * overviewHeight / taskHeight;
-        }
-
-        padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
-                - Math.round(overviewHeight);
-        padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
-        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();
-        outRect.set(
-                targetPadding.left + insets.left,
-                targetPadding.top + insets.top,
-                grid.widthPx - targetPadding.right - insets.right,
-                grid.heightPx - targetPadding.bottom - insets.bottom);
-        outRect.top += context.getResources()
-                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-    }
-
-    @Override
-    protected boolean computeScrollHelper() {
-        boolean scrolling = super.computeScrollHelper();
-        boolean isFlingingFast = false;
-        updateCurveProperties();
-        if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
-            if (scrolling) {
-                // Check if we are flinging quickly to disable high res thumbnail loading
-                isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
-            }
-
-            // After scrolling, update the visible task's data
-            loadVisibleTaskData();
-        }
-
-        // Update the high res thumbnail loader
-        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
-//        loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
-        return scrolling;
-    }
-
-    /**
-     * Scales and adjusts translation of adjacent pages as if on a curved carousel.
-     */
-    public void updateCurveProperties() {
-        if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
-            return;
-        }
-        final int halfPageWidth = mScrollState.halfPageWidth = getNormalChildWidth() / 2;
-        mScrollState.lastScrollType = SCROLL_TYPE_NONE;
-        final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
-        final int halfScreenWidth = getMeasuredWidth() / 2;
-        final int pageSpacing = mPageSpacing;
-
-        final int pageCount = getPageCount();
-        for (int i = 0; i < pageCount; i++) {
-            View page = getPageAt(i);
-            int pageCenter = page.getLeft() + halfPageWidth;
-            mScrollState.distanceFromScreenCenter = screenCenter - pageCenter;
-            float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
-            mScrollState.linearInterpolation = Math.min(1,
-                    Math.abs(mScrollState.distanceFromScreenCenter) / distanceToReachEdge);
-            mScrollState.lastScrollType = ((PageCallbacks) page).onPageScroll(mScrollState);
-        }
-    }
-
-    /**
-     * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
-     * and unloads the associated task data for tasks that are no longer visible.
-     */
-    private void loadVisibleTaskData() {
-        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
-        int centerPageIndex = getPageNearestToCenterOfScreen();
-        int lower = Math.max(mFirstTaskIndex, centerPageIndex - 2);
-        int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
-        for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
-            TaskView taskView = (TaskView) getChildAt(i);
-            Task task = taskView.getTask();
-            boolean visible = lower <= i && i <= upper;
-            if (visible) {
-                if (!mPrevVisibleTasks.get(i)) {
-                    loader.loadTaskData(task);
-//                    loader.getHighResThumbnailLoader().onTaskVisible(task);
-                }
-            } else {
-                if (mPrevVisibleTasks.get(i)) {
-                    loader.unloadTaskData(task);
-//                    loader.getHighResThumbnailLoader().onTaskInvisible(task);
-                }
-            }
-            mPrevVisibleTasks.put(i, visible);
-        }
-    }
-
-    public void onTaskDismissed(TaskView taskView) {
-        ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
-        removeView(taskView);
-        if (getTaskCount() == 0) {
-            mLauncher.getStateManager().goToState(NORMAL);
-        }
-    }
-
-    public void reset() {
-        mRunningTaskId = -1;
-        setCurrentPage(0);
-    }
-
-    public int getTaskCount() {
-        return getChildCount() - mFirstTaskIndex;
-    }
-
-    public int getRunningTaskId() {
-        return mRunningTaskId;
-    }
-
-    /**
-     * Reloads the view if anything in recents changed.
-     */
-    public void reloadIfNeeded() {
-        if (!mModel.isLoadPlanValid(mLoadPlanId)) {
-            mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
-        }
-    }
-
-    /**
-     * Ensures that the first task in the view represents {@param task} and reloads the view
-     * if needed. This allows the swipe-up gesture to assume that the first tile always
-     * corresponds to the correct task.
-     * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
-     * is called.
-     * Also scrolls the view to this task
-     */
-    public void showTask(int runningTaskId) {
-        boolean needsReload = false;
-        if (getTaskCount() == 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);
-            setLayoutTransition(mLayoutTransition);
-        }
-        mRunningTaskId = runningTaskId;
-        setCurrentPage(mFirstTaskIndex);
-        if (!needsReload) {
-            needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
-        }
-        if (needsReload) {
-            mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
-        } else {
-            loadVisibleTaskData();
-        }
-        if (mCurrentPage >= mFirstTaskIndex) {
-            getPageAt(mCurrentPage).setAlpha(0);
-        }
-    }
-
-    public QuickScrubController getQuickScrubController() {
-        return mQuickScrubController;
-    }
-
-    public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
-        if (mFirstTaskIconScaledDown == isScaledDown) {
-            return;
-        }
-        mFirstTaskIconScaledDown = isScaledDown;
-        applyIconScale(animate);
-    }
-
-    private void applyIconScale(boolean animate) {
-        float scale = mFirstTaskIconScaledDown ? 0 : 1;
-        TaskView firstTask = (TaskView) getChildAt(mFirstTaskIndex);
-        if (firstTask != null) {
-            if (animate) {
-                firstTask.animateIconToScale(scale);
-            } else {
-                firstTask.setIconScale(scale);
-            }
-        }
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mScrim == null) {
-            super.draw(canvas);
-            return;
-        }
-
-        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
-
-        int length = mScrim.getWidth();
-        int height = getHeight();
-        int saveCount = canvas.getSaveCount();
-
-        int scrimLeft;
-        if (mScrimOnLeft) {
-            scrimLeft = getScrollX();
-        } else {
-            scrimLeft = getScrollX() + getWidth() - length;
-        }
-        canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
-        super.draw(canvas);
-
-        mFadeMatrix.setTranslate(scrimLeft, 0);
-        mFadeShader.setLocalMatrix(mFadeMatrix);
-        mFadePaint.setShader(mFadeShader);
-        canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
-        canvas.restoreToCount(saveCount);
-    }
-
-    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
-         */
-        int onPageScroll(ScrollState scrollState);
-    }
-
-    public static class ScrollState {
-
-        public boolean isRtl;
-        public int lastScrollType;
-
-        public int halfPageWidth;
-        public float distanceFromScreenCenter;
-        public float linearInterpolation;
-
-        public float prevPageExtraWidth;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/SimpleTaskView.java b/quickstep/src/com/android/quickstep/SimpleTaskView.java
deleted file mode 100644
index 8425fa3..0000000
--- a/quickstep/src/com/android/quickstep/SimpleTaskView.java
+++ /dev/null
@@ -1,52 +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.quickstep;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-
-/**
- * A simple view which keeps its size proportional to the display size
- */
-public class SimpleTaskView extends View {
-
-    private static final Point sTempPoint = new Point();
-
-    public SimpleTaskView(Context context) {
-        super(context);
-    }
-
-    public SimpleTaskView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SimpleTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-        getContext().getSystemService(WindowManager.class)
-                .getDefaultDisplay().getRealSize(sTempPoint);
-
-        int width = (int) ((float) height * sTempPoint.x / sTempPoint.y);
-        setMeasuredDimension(width, height);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index fb14fb8..eebfb91 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep;
 
-import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -37,6 +36,8 @@
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.util.InstantAppResolver;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
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..a522ef1 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,15 +38,18 @@
 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;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -62,6 +61,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 +133,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 +155,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 +170,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 +194,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 +317,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..af09842 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -71,9 +71,12 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.quickstep.TouchConsumer.InteractionType;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
 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 +471,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 +479,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 +595,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 +727,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 +755,7 @@
     }
 
     private void invalidateHandler() {
-        mCurrentShift.cancelAnimation();
+        mCurrentShift.finishAnimation();
 
         if (mGestureEndCallback != null) {
             mGestureEndCallback.run();
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
new file mode 100644
index 0000000..7989e84
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -0,0 +1,134 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+/**
+ * {@link RecentsView} used in Launcher activity
+ */
+public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
+
+    private Bitmap mScrim;
+    private Paint mFadePaint;
+    private Shader mFadeShader;
+    private Matrix mFadeMatrix;
+    private boolean mScrimOnLeft;
+
+    public LauncherRecentsView(Context context) {
+        this(context, null);
+    }
+
+    public LauncherRecentsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets.set(insets);
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        Rect padding = getPadding(dp, getContext());
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        lp.bottomMargin = padding.bottom;
+        setLayoutParams(lp);
+
+        setPadding(padding.left, padding.top, padding.right, 0);
+
+        if (dp.isVerticalBarLayout()) {
+            boolean wasScrimOnLeft = mScrimOnLeft;
+            mScrimOnLeft = dp.isSeascape();
+
+            if (mScrim == null || wasScrimOnLeft != mScrimOnLeft) {
+                Drawable scrim = getContext().getDrawable(mScrimOnLeft
+                        ? R.drawable.recents_horizontal_scrim_left
+                        : R.drawable.recents_horizontal_scrim_right);
+                if (scrim instanceof BitmapDrawable) {
+                    mScrim = ((BitmapDrawable) scrim).getBitmap();
+                    mFadePaint = new Paint();
+                    mFadePaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
+                    mFadeShader = new BitmapShader(mScrim, TileMode.CLAMP, TileMode.REPEAT);
+                    mFadeMatrix = new Matrix();
+                } else {
+                    mScrim = null;
+                }
+            }
+        } else {
+            mScrim = null;
+            mFadePaint = null;
+            mFadeShader = null;
+            mFadeMatrix = null;
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mScrim == null) {
+            super.draw(canvas);
+            return;
+        }
+
+        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
+
+        int length = mScrim.getWidth();
+        int height = getHeight();
+        int saveCount = canvas.getSaveCount();
+
+        int scrimLeft;
+        if (mScrimOnLeft) {
+            scrimLeft = getScrollX();
+        } else {
+            scrimLeft = getScrollX() + getWidth() - length;
+        }
+        canvas.saveLayer(scrimLeft, 0, scrimLeft + length, height, null, flags);
+        super.draw(canvas);
+
+        mFadeMatrix.setTranslate(scrimLeft, 0);
+        mFadeShader.setLocalMatrix(mFadeMatrix);
+        mFadePaint.setShader(mFadeShader);
+        canvas.drawRect(scrimLeft, 0, scrimLeft + length, height, mFadePaint);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    protected void onAllTasksRemoved() {
+        mActivity.getStateManager().goToState(NORMAL);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
new file mode 100644
index 0000000..23e6e5b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -0,0 +1,588 @@
+/*
+ * 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.quickstep.views;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.PendingAnimation;
+import com.android.quickstep.QuickScrubController;
+import com.android.quickstep.RecentsModel;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.ArrayList;
+
+/**
+ * A list of recent tasks.
+ */
+@TargetApi(Build.VERSION_CODES.P)
+public abstract class RecentsView<T extends BaseActivity>
+        extends PagedView implements OnSharedPreferenceChangeListener {
+
+    private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
+
+    private static final Rect sTempStableInsets = new Rect();
+
+    protected final T mActivity;
+    private final QuickScrubController mQuickScrubController;
+    private final float mFastFlingVelocity;
+    private final RecentsModel mModel;
+
+    private final ScrollState mScrollState = new ScrollState();
+    // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
+    private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
+
+    /**
+     * TODO: Call reloadIdNeeded in onTaskStackChanged.
+     */
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+        @Override
+        public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+            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);
+                    return;
+                }
+            }
+        }
+    };
+
+    private int mLoadPlanId = -1;
+
+    // Only valid until the launcher state changes to NORMAL
+    private int mRunningTaskId = -1;
+
+    private boolean mFirstTaskIconScaledDown = false;
+
+    private boolean mOverviewStateEnabled;
+    private boolean mTaskStackListenerRegistered;
+    private Runnable mNextPageSwitchRunnable;
+
+    private PendingAnimation mPendingAnimation;
+
+    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
+        enableFreeScroll(true);
+        setClipToOutline(true);
+
+        mFastFlingVelocity = getResources()
+                .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
+        mActivity = (T) BaseActivity.fromContext(context);
+        mQuickScrubController = new QuickScrubController(mActivity, this);
+        mModel = RecentsModel.getInstance(context);
+
+        onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        if (s.equals(PREF_FLIP_RECENTS)) {
+            mIsRtl = Utilities.isRtl(getResources());
+            if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
+                mIsRtl = !mIsRtl;
+            }
+            setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+        }
+    }
+
+    public boolean isRtl() {
+        return mIsRtl;
+    }
+
+    public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
+        for (int i = 0; i < getChildCount(); i++) {
+            final TaskView taskView = (TaskView) getChildAt(i);
+            if (taskView.getTask().key.id == taskId) {
+                taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
+                taskView.setAlpha(1);
+                return taskView;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        updateTaskStackListenerState();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateTaskStackListenerState();
+        Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        updateTaskStackListenerState();
+        Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+
+        // Clear the task data for the removed child if it was visible
+        Task task = ((TaskView) child).getTask();
+        if (mHasVisibleTaskData.get(task.key.id)) {
+            mHasVisibleTaskData.delete(task.key.id);
+            RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+            loader.unloadTaskData(task);
+            loader.getHighResThumbnailLoader().onTaskInvisible(task);
+        }
+    }
+
+    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 = 0; i < getChildCount(); i++) {
+            TaskView tv = (TaskView) getChildAt(i);
+            if (tv.getTask().key.id == taskId) {
+                return tv;
+            }
+        }
+        return null;
+    }
+
+    public void setOverviewStateEnabled(boolean enabled) {
+        mOverviewStateEnabled = enabled;
+        updateTaskStackListenerState();
+    }
+
+    public void setNextPageSwitchRunnable(Runnable r) {
+        mNextPageSwitchRunnable = r;
+    }
+
+    @Override
+    protected void onPageEndTransition() {
+        super.onPageEndTransition();
+        if (mNextPageSwitchRunnable != null) {
+            mNextPageSwitchRunnable.run();
+            mNextPageSwitchRunnable = null;
+        }
+    }
+
+    private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
+        if (mPendingAnimation != null) {
+            mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
+            return;
+        }
+        TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
+        if (stack == null) {
+            removeAllViews();
+            return;
+        }
+
+        int oldChildCount = getChildCount();
+
+        // Ensure there are as many views as there are tasks in the stack (adding and trimming as
+        // necessary)
+        final LayoutInflater inflater = LayoutInflater.from(getContext());
+        final ArrayList<Task> tasks = new ArrayList<>(stack.getTasks());
+
+        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);
+        }
+        while (getChildCount() > requiredChildCount) {
+            final TaskView taskView = (TaskView) getChildAt(getChildCount() - 1);
+            removeView(taskView);
+        }
+
+        // Unload existing visible task data
+        unloadVisibleTaskData();
+
+        // Rebind and reset all task views
+        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);
+        }
+        resetTaskVisuals();
+        applyIconScale(false /* animate */);
+
+        if (oldChildCount != getChildCount()) {
+            mQuickScrubController.snapToNextTaskIfAvailable();
+        }
+    }
+
+    public void resetTaskVisuals() {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            ((TaskView) getChildAt(i)).resetVisualProperties();
+        }
+
+        updateCurveProperties();
+        // Update the set of visible task's data
+        loadVisibleTaskData();
+    }
+
+    private void updateTaskStackListenerState() {
+        boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
+                && getWindowVisibility() == VISIBLE;
+        if (registerStackListener != mTaskStackListenerRegistered) {
+            if (registerStackListener) {
+                ActivityManagerWrapper.getInstance()
+                        .registerTaskStackListener(mTaskStackListener);
+                reloadIfNeeded();
+            } else {
+                ActivityManagerWrapper.getInstance()
+                        .unregisterTaskStackListener(mTaskStackListener);
+            }
+            mTaskStackListenerRegistered = registerStackListener;
+        }
+    }
+
+    protected static Rect getPadding(DeviceProfile profile, Context context) {
+        WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
+        Rect padding = new Rect(profile.workspacePadding);
+
+        float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
+        float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
+
+        float overviewHeight, overviewWidth;
+        if (profile.isVerticalBarLayout()) {
+            float scrimLength = context.getResources()
+                    .getDimension(R.dimen.recents_page_fade_length);
+            float maxPadding = Math.max(padding.left, padding.right);
+
+            // Use the same padding on both sides for symmetry.
+            float availableWidth = taskWidth - 2 * Math.max(maxPadding, scrimLength);
+            float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
+                    - sTempStableInsets.top;
+            float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+            overviewHeight = taskHeight * scaledRatio;
+            overviewWidth = taskWidth * scaledRatio;
+
+        } else {
+            overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+                    - sTempStableInsets.top;
+            overviewWidth = taskWidth * overviewHeight / taskHeight;
+        }
+
+        padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
+                - Math.round(overviewHeight);
+        padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+        return padding;
+    }
+
+    public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
+        Rect targetPadding = getPadding(grid, context);
+        Rect insets = grid.getInsets();
+        outRect.set(
+                targetPadding.left + insets.left,
+                targetPadding.top + insets.top,
+                grid.widthPx - targetPadding.right - insets.right,
+                grid.heightPx - targetPadding.bottom - insets.bottom);
+        outRect.top += context.getResources()
+                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+    }
+
+    @Override
+    protected boolean computeScrollHelper() {
+        boolean scrolling = super.computeScrollHelper();
+        boolean isFlingingFast = false;
+        updateCurveProperties();
+        if (scrolling || (mTouchState == TOUCH_STATE_SCROLLING)) {
+            if (scrolling) {
+                // Check if we are flinging quickly to disable high res thumbnail loading
+                isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity;
+            }
+
+            // After scrolling, update the visible task's data
+            loadVisibleTaskData();
+        }
+
+        // Update the high res thumbnail loader
+        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        loader.getHighResThumbnailLoader().setFlingingFast(isFlingingFast);
+        return scrolling;
+    }
+
+    /**
+     * Scales and adjusts translation of adjacent pages as if on a curved carousel.
+     */
+    public void updateCurveProperties() {
+        if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
+            return;
+        }
+        final int halfPageWidth = getNormalChildWidth() / 2;
+        final int screenCenter = mInsets.left + getPaddingLeft() + getScrollX() + halfPageWidth;
+        final int halfScreenWidth = getMeasuredWidth() / 2;
+        final int pageSpacing = mPageSpacing;
+
+        final int pageCount = getPageCount();
+        for (int i = 0; i < pageCount; i++) {
+            View page = getPageAt(i);
+            float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
+            float distanceFromScreenCenter = screenCenter - pageCenter;
+            float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+            mScrollState.linearInterpolation = Math.min(1,
+                    Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
+            ((PageCallbacks) page).onPageScroll(mScrollState);
+        }
+    }
+
+    /**
+     * Iterates through all thet asks, and loads the associated task data for newly visible tasks,
+     * and unloads the associated task data for tasks that are no longer visible.
+     */
+    public void loadVisibleTaskData() {
+        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        int centerPageIndex = getPageNearestToCenterOfScreen();
+        int lower = Math.max(0, centerPageIndex - 2);
+        int upper = Math.min(centerPageIndex + 2, getChildCount() - 1);
+        int numChildren = getChildCount();
+
+        // Update the task data for the in/visible children
+        for (int i = 0; i < numChildren; i++) {
+            TaskView taskView = (TaskView) getChildAt(i);
+            Task task = taskView.getTask();
+            boolean visible = lower <= i && i <= upper;
+            if (visible) {
+                if (!mHasVisibleTaskData.get(task.key.id)) {
+                    loader.loadTaskData(task);
+                    loader.getHighResThumbnailLoader().onTaskVisible(task);
+                }
+                mHasVisibleTaskData.put(task.key.id, visible);
+            } else {
+                if (mHasVisibleTaskData.get(task.key.id)) {
+                    loader.unloadTaskData(task);
+                    loader.getHighResThumbnailLoader().onTaskInvisible(task);
+                }
+                mHasVisibleTaskData.delete(task.key.id);
+            }
+        }
+    }
+
+    /**
+     * Unloads any associated data from the currently visible tasks
+     */
+    private void unloadVisibleTaskData() {
+        RecentsTaskLoader loader = mModel.getRecentsTaskLoader();
+        for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
+            if (mHasVisibleTaskData.valueAt(i)) {
+                TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
+                Task task = taskView.getTask();
+                loader.unloadTaskData(task);
+                loader.getHighResThumbnailLoader().onTaskInvisible(task);
+            }
+        }
+        mHasVisibleTaskData.clear();
+    }
+
+
+    protected abstract void onAllTasksRemoved();
+
+    public void reset() {
+        unloadVisibleTaskData();
+        mRunningTaskId = -1;
+        setCurrentPage(0);
+    }
+
+    /**
+     * Reloads the view if anything in recents changed.
+     */
+    public void reloadIfNeeded() {
+        if (!mModel.isLoadPlanValid(mLoadPlanId)) {
+            mLoadPlanId = mModel.loadTasks(mRunningTaskId, this::applyLoadPlan);
+        }
+    }
+
+    /**
+     * Ensures that the first task in the view represents {@param task} and reloads the view
+     * if needed. This allows the swipe-up gesture to assume that the first tile always
+     * corresponds to the correct task.
+     * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
+     * is called.
+     * Also scrolls the view to this task
+     */
+    public void showTask(int runningTaskId) {
+        boolean needsReload = false;
+        if (getChildCount() == 0) {
+            needsReload = true;
+            // Add an empty view for now
+            final TaskView taskView = (TaskView) LayoutInflater.from(getContext())
+                    .inflate(R.layout.task, this, false);
+            addView(taskView, 0);
+        }
+        mRunningTaskId = runningTaskId;
+        setCurrentPage(0);
+        if (!needsReload) {
+            needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
+        }
+        if (needsReload) {
+            mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+        } else {
+            loadVisibleTaskData();
+        }
+        getPageAt(mCurrentPage).setAlpha(0);
+    }
+
+    public QuickScrubController getQuickScrubController() {
+        return mQuickScrubController;
+    }
+
+    public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
+        if (mFirstTaskIconScaledDown == isScaledDown) {
+            return;
+        }
+        mFirstTaskIconScaledDown = isScaledDown;
+        applyIconScale(animate);
+    }
+
+    private void applyIconScale(boolean animate) {
+        float scale = mFirstTaskIconScaledDown ? 0 : 1;
+        TaskView firstTask = (TaskView) getChildAt(0);
+        if (firstTask != null) {
+            if (animate) {
+                firstTask.animateIconToScale(scale);
+            } else {
+                firstTask.setIconScale(scale);
+            }
+        }
+    }
+
+    public interface PageCallbacks {
+
+        /**
+         * Updates the page UI based on scroll params.
+         */
+        default void onPageScroll(ScrollState scrollState) {};
+    }
+
+    public static class ScrollState {
+
+        /**
+         * 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 PendingAnimation createTaskDismissAnimation(TaskView taskView, long duration) {
+        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+            throw new IllegalStateException("Another pending animation is still running");
+        }
+        AnimatorSet anim = new AnimatorSet();
+        PendingAnimation pendingAnimation = new PendingAnimation(anim);
+
+        int count = getChildCount();
+        if (count == 0) {
+            return pendingAnimation;
+        }
+
+        int[] oldScroll = new int[count];
+        getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
+
+        int[] newScroll = new int[count];
+        getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
+
+        int maxScrollDiff = 0;
+        int lastPage = mIsRtl ? 0 : count - 1;
+        if (getChildAt(lastPage) == taskView) {
+            if (count > 1) {
+                int secondLastPage = mIsRtl ? 1 : count - 2;
+                maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage];
+            }
+        }
+
+        boolean needsCurveUpdates = false;
+        for (int i = 0; i < count; i++) {
+            View child = getChildAt(i);
+            if (child == taskView) {
+                addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
+                addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
+                        duration, LINEAR, anim);
+            } else {
+                int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
+                if (scrollDiff != 0) {
+                    addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
+                            duration, ACCEL, anim);
+                    needsCurveUpdates = true;
+                }
+            }
+        }
+
+        if (needsCurveUpdates) {
+            ValueAnimator va = ValueAnimator.ofFloat(0, 1);
+            va.addUpdateListener((a) -> updateCurveProperties());
+            anim.play(va);
+        }
+
+        // Add a tiny bit of translation Z, so that it draws on top of other views
+        taskView.setTranslationZ(0.1f);
+
+        mPendingAnimation = pendingAnimation;
+        mPendingAnimation.addEndListener((isSuccess) -> {
+           if (isSuccess) {
+               ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+               removeView(taskView);
+               if (getChildCount() == 0) {
+                   onAllTasksRemoved();
+               }
+           }
+           resetTaskVisuals();
+           mPendingAnimation = null;
+        });
+        return pendingAnimation;
+    }
+
+    private static void addAnim(ObjectAnimator anim, long duration,
+            TimeInterpolator interpolator, AnimatorSet set) {
+        anim.setDuration(duration).setInterpolator(interpolator);
+        set.play(anim);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
similarity index 98%
rename from quickstep/src/com/android/quickstep/TaskMenuView.java
rename to quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 6bbcb37..94f440d 100644
--- a/quickstep/src/com/android/quickstep/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
+package com.android.quickstep.views;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -40,6 +40,8 @@
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
+import com.android.quickstep.TaskSystemShortcut;
+import com.android.quickstep.TaskUtils;
 
 /**
  * Contains options for a recent task when long-pressing its icon.
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
similarity index 96%
rename from quickstep/src/com/android/quickstep/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 9b9c6f2..87bb53b 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
+package com.android.quickstep.views;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -33,9 +33,10 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -74,6 +75,7 @@
         mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
         mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
         mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
+        mPaint.setFilterBitmap(true);
     }
 
     public void bind() {
@@ -151,7 +153,8 @@
             } else {
                 final Configuration configuration =
                         getContext().getApplicationContext().getResources().getConfiguration();
-                final DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
+                final DeviceProfile profile = BaseActivity.fromContext(getContext())
+                        .getDeviceProfile();
                 if (configuration.orientation == mThumbnailData.orientation) {
                     // If we are in the same orientation as the screenshot, just scale it to the
                     // width of the task view
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
similarity index 76%
rename from quickstep/src/com/android/quickstep/TaskView.java
rename to quickstep/src/com/android/quickstep/views/TaskView.java
index b407f75..7a575ad 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep;
-
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+package com.android.quickstep.views;
 
 import android.animation.TimeInterpolator;
 import android.app.ActivityOptions;
@@ -33,8 +30,8 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.quickstep.RecentsView.PageCallbacks;
-import com.android.quickstep.RecentsView.ScrollState;
+import com.android.quickstep.views.RecentsView.PageCallbacks;
+import com.android.quickstep.views.RecentsView.ScrollState;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -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;
 
@@ -159,38 +154,16 @@
         setScaleY(1f);
         setTranslationX(0f);
         setTranslationY(0f);
+        setTranslationZ(0);
         setAlpha(1f);
     }
 
     @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/res/layout/longpress_options_menu.xml b/res/layout/longpress_options_menu.xml
similarity index 96%
rename from quickstep/res/layout/longpress_options_menu.xml
rename to res/layout/longpress_options_menu.xml
index 9cf0fcf..71d117a 100644
--- a/quickstep/res/layout/longpress_options_menu.xml
+++ b/res/layout/longpress_options_menu.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.uioverrides.OptionsPopupView
+<com.android.launcher3.views.OptionsPopupView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
@@ -94,4 +94,4 @@
 
     </FrameLayout>
 
-</com.android.launcher3.uioverrides.OptionsPopupView>
\ No newline at end of file
+</com.android.launcher3.views.OptionsPopupView>
\ No newline at end of file
diff --git a/res/layout/overview_panel.xml b/res/layout/overview_panel.xml
index c795b81..bdd5d23 100644
--- a/res/layout/overview_panel.xml
+++ b/res/layout/overview_panel.xml
@@ -14,61 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.uioverrides.OverviewPanel
+<Space
       xmlns:android="http://schemas.android.com/apk/res/android"
-      android:theme="@style/HomeScreenElementTheme"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:layout_gravity="center_horizontal|bottom"
-      android:gravity="top"
-      android:orientation="horizontal">
-
-    <TextView
-        android:id="@+id/wallpaper_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/ic_wallpaper"
-        android:drawableTint="?attr/workspaceTextColor"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:stateListAnimator="@animator/overview_button_anim"
-        android:text="@string/wallpaper_button_text"
-        android:textAllCaps="true"
-        android:textColor="?attr/workspaceTextColor"
-        android:textSize="12sp" />
-
-    <TextView
-        android:id="@+id/widget_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/ic_widget"
-        android:drawableTint="?attr/workspaceTextColor"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:stateListAnimator="@animator/overview_button_anim"
-        android:text="@string/widget_button_text"
-        android:textAllCaps="true"
-        android:textColor="?attr/workspaceTextColor"
-        android:textSize="12sp" />
-
-    <TextView
-        android:id="@+id/settings_button"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:drawablePadding="4dp"
-        android:drawableTop="@drawable/ic_setting"
-        android:drawableTint="?attr/workspaceTextColor"
-        android:fontFamily="sans-serif-condensed"
-        android:gravity="center_horizontal"
-        android:stateListAnimator="@animator/overview_button_anim"
-        android:text="@string/settings_button_text"
-        android:textAllCaps="true"
-        android:textColor="?attr/workspaceTextColor"
-        android:textSize="12sp" />
-
-</com.android.launcher3.uioverrides.OverviewPanel>
\ No newline at end of file
+      android:layout_width="0dp"
+      android:layout_height="0dp" />
\ No newline at end of file
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 21ff55e..379e9d0 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -17,6 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:focusable="true"
     android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_bottom_padding"
     android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
     android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 40ffffe..6cf23ad 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -25,7 +25,6 @@
     <dimen name="fastscroll_popup_text_size">24dp</dimen>
 
     <!-- Dynamic grid -->
-    <dimen name="dynamic_grid_overview_bar_item_width">120dp</dimen>
     <dimen name="dynamic_grid_min_page_indicator_size">48dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">4dp</dimen>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 9f4233c..f8f9c2a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -23,10 +23,6 @@
     <dimen name="dynamic_grid_min_page_indicator_size">24dp</dimen>
     <dimen name="dynamic_grid_page_indicator_line_height">1dp</dimen>
     <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
-    <dimen name="dynamic_grid_overview_min_icon_zone_height">80dp</dimen>
-    <dimen name="dynamic_grid_overview_max_icon_zone_height">120dp</dimen>
-    <dimen name="dynamic_grid_overview_bar_item_width">80dp</dimen>
-    <dimen name="dynamic_grid_overview_bar_spacer_width">25dp</dimen>
     <dimen name="dynamic_grid_workspace_top_padding">8dp</dimen>
     <dimen name="dynamic_grid_workspace_page_spacing">8dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
@@ -227,5 +223,5 @@
     <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
 
 <!-- Overview -->
-    <dimen name="options_menu_icon_size">48dp</dimen>
+    <dimen name="options_menu_icon_size">24dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cb7dde9..d971a36 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -80,6 +80,9 @@
 
     <!-- All applications label -->
     <string name="all_apps_button_label">Apps list</string>
+    <string name="all_apps_button_personal_label">Personal apps list</string>
+    <string name="all_apps_button_work_label">Work apps list</string>
+
     <!-- Label for button in all applications label to go back home (to the workspace / desktop)
          for accessibilty (spoken when the button gets focus). -->
     <string name="all_apps_home_button_label">Home</string>
@@ -169,8 +172,6 @@
     <string name="settings_button_text">Home settings</string>
     <!-- Message shown when a feature is disabled by the administrator -->
     <string name="msg_disabled_by_admin">Disabled by your admin</string>
-    <!-- Text for custom accessibility action to go to the overview mode, where users can look and change the overall UI of the launcher. -->
-    <string name="accessibility_action_overview">Overview</string>
 
     <!-- Strings for settings -->
     <!-- Title for Notification dots setting. Tapping this will link to the system Notifications settings screen where the user can turn off notification dots globally. [CHAR LIMIT=50] -->
@@ -280,15 +281,6 @@
     <!-- Accessibility action to move an item from folder to workspace. [CHAR_LIMIT=30] -->
     <string name="action_move_to_workspace">Move to Home screen</string>
 
-    <!-- Accessibility action to move an homescreen to the left. [CHAR_LIMIT=30] -->
-    <string name="action_move_screen_left">Move screen to left</string>
-
-    <!-- Accessibility action to move an homescreen to the right. [CHAR_LIMIT=30] -->
-    <string name="action_move_screen_right">Move screen to right</string>
-
-    <!-- Accessibility confirmation when a screen was moved. -->
-    <string name="screen_moved">Screen moved</string>
-
     <!-- Accessibility action to resize a widget. [CHAR_LIMIT=30] -->
     <string name="action_resize">Resize</string>
 
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 12db3b6..77a430b 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.graphics.Point;
+import android.view.Display;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
@@ -102,4 +104,17 @@
             mDPChangeListeners.get(i).onDeviceProfileChanged(mDeviceProfile);
         }
     }
+
+    /**
+     * Sets the device profile, adjusting it accordingly in case of multi-window
+     */
+    protected void setDeviceProfile(DeviceProfile dp) {
+        mDeviceProfile = dp;
+        if (isInMultiWindowModeCompat()) {
+            Display display = getWindowManager().getDefaultDisplay();
+            Point mwSize = new Point();
+            display.getSize(mwSize);
+            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 734aec3..7979082 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -292,7 +292,7 @@
             ViewCompat.setAccessibilityDelegate(this, null);
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
             getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            setOnClickListener(mLauncher);
+            setOnClickListener(null);
         } else {
             if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
                     !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index ea52324..13971ad 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -515,12 +515,6 @@
         }
     }
 
-    public boolean shouldIgnoreLongPressToOverview(float touchX) {
-        boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx;
-        boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx);
-        return !isMultiWindowMode && (touchedLhsEdge || touchedRhsEdge);
-    }
-
     private static Context getContext(Context c, int orientation) {
         Configuration context = new Configuration(c.getResources().getConfiguration());
         context.orientation = orientation;
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/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 03043f2..211a756 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -63,14 +63,6 @@
         return mContent;
     }
 
-    /**
-     * Registers the specified listener on the cell layout of the hotseat.
-     */
-    @Override
-    public void setOnLongClickListener(OnLongClickListener l) {
-        mContent.setOnLongClickListener(l);
-    }
-
     /* Get the orientation invariant order of the item in the hotseat for persistence. */
     int getOrderInHotseat(int x, int y) {
         return mHasVerticalHotseat ? (mContent.getCountY() - y - 1) : x;
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/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e460911..f63cce5 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -22,6 +22,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Point;
+import android.support.annotation.VisibleForTesting;
 import android.util.DisplayMetrics;
 import android.util.Xml;
 import android.view.Display;
@@ -88,17 +89,18 @@
 
     public Point defaultWallpaperSize;
 
+    @VisibleForTesting
     public InvariantDeviceProfile() {
     }
 
-    public InvariantDeviceProfile(InvariantDeviceProfile p) {
+    private InvariantDeviceProfile(InvariantDeviceProfile p) {
         this(p.name, p.minWidthDps, p.minHeightDps, p.numRows, p.numColumns,
                 p.numFolderRows, p.numFolderColumns,
                 p.iconSize, p.landscapeIconSize, p.iconTextSize, p.numHotseatIcons,
                 p.defaultLayoutId, p.demoModeLayoutId);
     }
 
-    InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
+    private InvariantDeviceProfile(String n, float w, float h, int r, int c, int fr, int fc,
             float is, float lis, float its, int hs, int dlId, int dmlId) {
         name = n;
         minWidthDps = w;
@@ -116,7 +118,7 @@
     }
 
     @TargetApi(23)
-    InvariantDeviceProfile(Context context) {
+    public InvariantDeviceProfile(Context context) {
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = wm.getDefaultDisplay();
         DisplayMetrics dm = new DisplayMetrics();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 375deb7..0bbb90c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -31,7 +31,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.appwidget.AppWidgetHostView;
@@ -48,8 +47,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -64,17 +61,12 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.ActionMode;
-import android.view.Display;
-import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
 import android.view.KeyboardShortcutInfo;
 import android.view.LayoutInflater;
 import android.view.Menu;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
@@ -109,7 +101,6 @@
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.UiFactory;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -126,6 +117,7 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -147,10 +139,8 @@
 /**
  * Default launcher application.
  */
-public class Launcher extends BaseActivity
-        implements LauncherExterns, OnClickListener, OnLongClickListener,
-                   LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
-                   WallpaperColorInfo.OnThemeChangeListener {
+public class Launcher extends BaseActivity implements LauncherExterns, LauncherModel.Callbacks,
+        LauncherProviderChangeListener, WallpaperColorInfo.OnThemeChangeListener {
     public static final String TAG = "Launcher";
     static final boolean LOGD = false;
 
@@ -228,7 +218,7 @@
     AllAppsTransitionController mAllAppsController;
 
     // UI and state for the overview panel
-    private ViewGroup mOverviewPanel;
+    private View mOverviewPanel;
 
     @Thunk boolean mWorkspaceLoading = true;
 
@@ -261,8 +251,6 @@
      */
     private PendingRequestArgs mPendingRequestArgs;
 
-    private final PointF mLastDispatchTouchEvent = new PointF();
-
     public ViewGroupFocusHelper mFocusHandler;
     private boolean mAppLaunchSuccess;
 
@@ -411,13 +399,7 @@
 
     private void initDeviceProfile(InvariantDeviceProfile idp) {
         // Load configuration-specific DeviceProfile
-        mDeviceProfile = idp.getDeviceProfile(this);
-        if (isInMultiWindowModeCompat()) {
-            Display display = getWindowManager().getDefaultDisplay();
-            Point mwSize = new Point();
-            display.getSize(mwSize);
-            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
-        }
+        setDeviceProfile(idp.getDeviceProfile(this));
         mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true);
     }
 
@@ -952,6 +934,7 @@
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
         mOverviewPanel = findViewById(R.id.overview_panel);
+        mHotseat = findViewById(R.id.hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
@@ -960,15 +943,6 @@
         // Setup the drag layer
         mDragLayer.setup(this, mDragController);
 
-        // Setup the hotseat
-        mHotseat = (Hotseat) findViewById(R.id.hotseat);
-        if (mHotseat != null) {
-            mHotseat.setOnLongClickListener(this);
-        }
-
-        // Setup the workspace
-        mWorkspace.setHapticFeedbackEnabled(false);
-        mWorkspace.setOnLongClickListener(this);
         mWorkspace.setup(mDragController);
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
         // default state, otherwise we will update to the wrong offsets in RTL
@@ -1222,7 +1196,7 @@
         return mHotseat;
     }
 
-    public <T extends ViewGroup> T getOverviewPanel() {
+    public <T extends View> T getOverviewPanel() {
         return (T) mOverviewPanel;
     }
 
@@ -1659,51 +1633,6 @@
     }
 
     /**
-     * Launches the intent referred by the clicked shortcut.
-     *
-     * @param v The view representing the clicked shortcut.
-     */
-    @Override
-    public void onClick(View v) {
-        // Make sure that rogue clicks don't get through while allapps is launching, or after the
-        // view has detached (it's possible for this to happen if the view is removed mid touch).
-        if (v.getWindowToken() == null) {
-            return;
-        }
-
-        if (!mWorkspace.isFinishedSwitchingState()) {
-            return;
-        }
-
-        if (v instanceof Workspace) {
-            if (isInState(OVERVIEW)) {
-                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Touch.TAP,
-                        LauncherLogProto.Action.Direction.NONE,
-                        LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
-                mStateManager.goToState(NORMAL);
-            }
-            return;
-        }
-
-        if (v instanceof CellLayout) {
-            if (isInState(OVERVIEW)) {
-                int page = mWorkspace.indexOfChild(v);
-                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Touch.TAP,
-                        LauncherLogProto.Action.Direction.NONE,
-                        LauncherLogProto.ContainerType.OVERVIEW, page);
-                mWorkspace.snapToPageFromOverView(page);
-                mStateManager.goToState(NORMAL);
-            }
-            return;
-        }
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    public boolean onTouch(View v, MotionEvent event) {
-        return false;
-    }
-
-    /**
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
@@ -1860,62 +1789,6 @@
         return mAppLaunchSuccess;
     }
 
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        mLastDispatchTouchEvent.set(ev.getX(), ev.getY());
-        return super.dispatchTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        if (!isDraggingEnabled()) return false;
-        if (isWorkspaceLocked()) return false;
-        if (!isInState(NORMAL) && !isInState(OVERVIEW)) return false;
-
-        boolean ignoreLongPressToOverview =
-                mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEvent.x);
-
-        if (v instanceof Workspace) {
-            if (!isInState(OVERVIEW)) {
-                if (!mWorkspace.isTouchActive() && !ignoreLongPressToOverview) {
-                    getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                            Action.Direction.NONE, ContainerType.WORKSPACE,
-                            mWorkspace.getCurrentPage());
-                    UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
-                    mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                    return true;
-                } else {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
-
-        // The hotseat touch handling does not go through Workspace, and we always allow long press
-        // on hotseat items.
-        if (!mDragController.isDragging()) {
-            // User long pressed on empty space
-            if (mWorkspace.isPageRearrangeEnabled()) {
-                mWorkspace.startReordering(v);
-                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                        Action.Direction.NONE, ContainerType.OVERVIEW);
-            } else {
-                if (ignoreLongPressToOverview) {
-                    return false;
-                }
-                getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
-                        Action.Direction.NONE, ContainerType.WORKSPACE,
-                        mWorkspace.getCurrentPage());
-                UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
-            }
-            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
-                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-        }
-        return true;
-    }
-
     boolean isHotseatLayout(View layout) {
         // TODO: Remove this method
         return mHotseat != null && layout != null &&
@@ -2628,8 +2501,7 @@
 
                 // Setting the touch point to (-1, -1) will show the options popup in the center of
                 // the screen.
-                mLastDispatchTouchEvent.set(-1, -1);
-                UiFactory.onWorkspaceLongPress(this, mLastDispatchTouchEvent);
+                OptionsPopupView.show(this, -1, -1);
             }
             return true;
         }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 4e6bcdc..e5d8f47 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -165,6 +165,10 @@
         return 1f;
     }
 
+    public float getOverviewTranslationX(Launcher launcher) {
+        return launcher.getDragLayer().getMeasuredWidth();
+    }
+
     public void onStateEnabled(Launcher launcher) {
         dispatchWindowStateChanged(launcher);
     }
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/PagedView.java b/src/com/android/launcher3/PagedView.java
index d39ec3e..6e8cacd 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -19,10 +19,7 @@
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.LayoutTransition;
-import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -30,8 +27,6 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -48,7 +43,6 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.util.Thunk;
@@ -62,7 +56,9 @@
 public abstract class PagedView<T extends View & PageIndicator> extends ViewGroup {
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
+
     protected static final int INVALID_PAGE = -1;
+    protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
     public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
     public static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
@@ -105,31 +101,21 @@
     private VelocityTracker mVelocityTracker;
     protected int mPageSpacing = 0;
 
-    private float mParentDownMotionX;
-    private float mParentDownMotionY;
     private float mDownMotionX;
     private float mDownMotionY;
-    private float mDownScrollX;
-    private float mDragViewBaselineLeft;
     private float mLastMotionX;
     private float mLastMotionXRemainder;
-    private float mLastMotionY;
     private float mTotalMotionX;
 
-    private boolean mCancelTap;
-
     private int[] mPageScrolls;
 
     protected final static int TOUCH_STATE_REST = 0;
     protected final static int TOUCH_STATE_SCROLLING = 1;
     protected final static int TOUCH_STATE_PREV_PAGE = 2;
     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
-    protected final static int TOUCH_STATE_REORDERING = 4;
 
     protected int mTouchState = TOUCH_STATE_REST;
 
-    protected OnLongClickListener mLongClickListener;
-
     protected int mTouchSlop;
     private int mMaximumVelocity;
     protected boolean mAllowOverScroll = true;
@@ -153,26 +139,6 @@
     @Thunk int mPageIndicatorViewId;
     protected T mPageIndicator;
 
-    // Reordering
-    // We use the min scale to determine how much to expand the actually PagedView measured
-    // dimensions such that when we are zoomed out, the view is not clipped
-    private static int REORDERING_DROP_REPOSITION_DURATION = 200;
-    @Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
-    private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
-
-    @Thunk View mDragView;
-    private Runnable mSidePageHoverRunnable;
-    @Thunk int mSidePageHoverIndex = -1;
-    // This variable's scope is only for the duration of startReordering() and endReordering()
-    private boolean mReorderingStarted = false;
-    // This variable's scope is for the duration of startReordering() and after the zoomIn()
-    // animation after endReordering()
-    private boolean mIsReordering;
-    // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
-    private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
-    private int mPostReorderingPreZoomInRemainingAnimationCount;
-    private Runnable mPostReorderingPreZoomInRunnable;
-
     // Convenience/caching
     private static final Matrix sTmpInvMatrix = new Matrix();
     private static final float[] sTmpPoint = new float[2];
@@ -237,47 +203,6 @@
         }
     }
 
-    // Convenience methods to map points from self to parent and vice versa
-    private float[] mapPointFromViewToParent(View v, float x, float y) {
-        sTmpPoint[0] = x;
-        sTmpPoint[1] = y;
-        v.getMatrix().mapPoints(sTmpPoint);
-        sTmpPoint[0] += v.getLeft();
-        sTmpPoint[1] += v.getTop();
-        return sTmpPoint;
-    }
-    private float[] mapPointFromParentToView(View v, float x, float y) {
-        sTmpPoint[0] = x - v.getLeft();
-        sTmpPoint[1] = y - v.getTop();
-        v.getMatrix().invert(sTmpInvMatrix);
-        sTmpInvMatrix.mapPoints(sTmpPoint);
-        return sTmpPoint;
-    }
-
-    private void updateDragViewTranslationDuringDrag() {
-        if (mDragView != null) {
-            float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
-                    (mDragViewBaselineLeft - mDragView.getLeft());
-            float y = mLastMotionY - mDownMotionY;
-            mDragView.setTranslationX(x);
-            mDragView.setTranslationY(y);
-
-            if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
-                    + x + ", " + y);
-        }
-    }
-
-    @Override
-    public void setScaleX(float scaleX) {
-        super.setScaleX(scaleX);
-        if (isReordering(true)) {
-            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
-            mLastMotionX = p[0];
-            mLastMotionY = p[1];
-            updateDragViewTranslationDuringDrag();
-        }
-    }
-
     public T getPageIndicator() {
         return mPageIndicator;
     }
@@ -383,9 +308,6 @@
         // Update the page indicator (when we aren't reordering)
         if (mPageIndicator != null) {
             mPageIndicator.setPageDescription(getPageIndicatorDescription());
-            if (!isReordering(false)) {
-                mPageIndicator.setActiveMarker(getNextPage());
-            }
         }
     }
     protected void pageBeginTransition() {
@@ -421,21 +343,6 @@
         mWasInOverscroll = false;
     }
 
-    /**
-     * Registers the specified listener on each page contained in this workspace.
-     *
-     * @param l The listener used to respond to long clicks.
-     */
-    @Override
-    public void setOnLongClickListener(OnLongClickListener l) {
-        mLongClickListener = l;
-        final int count = getPageCount();
-        for (int i = 0; i < count; i++) {
-            getPageAt(i).setOnLongClickListener(l);
-        }
-        super.setOnLongClickListener(l);
-    }
-
     protected int getUnboundedScrollX() {
         return mUnboundedScrollX;
     }
@@ -490,14 +397,6 @@
             mOverScrollX = x;
             super.scrollTo(x, y);
         }
-
-        // Update the last motion events when scrolling
-        if (isReordering(true)) {
-            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
-            mLastMotionX = p[0];
-            mLastMotionY = p[1];
-            updateDragViewTranslationDuringDrag();
-        }
     }
 
     private void sendScrollAccessibilityEvent() {
@@ -549,7 +448,6 @@
                 pageEndTransition();
             }
 
-            onPostReorderingAnimationCompleted();
             if (isAccessibilityEnabled(getContext())) {
                 // Notify the user when the page changes
                 announceForAccessibility(getCurrentPageDescription());
@@ -644,43 +542,13 @@
         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
         final int childCount = getChildCount();
 
-        final int startIndex = mIsRtl ? childCount - 1 : 0;
-        final int endIndex = mIsRtl ? -1 : childCount;
-        final int delta = mIsRtl ? -1 : 1;
-
-        int verticalPadding = getPaddingTop() + getPaddingBottom();
-
-        int scrollOffsetLeft = mInsets.left + getPaddingLeft();
-        int childLeft = scrollOffsetLeft;
-
         boolean pageScrollChanged = false;
         if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
             mPageScrolls = new int[childCount];
             pageScrollChanged = true;
         }
-
-        for (int i = startIndex; i != endIndex; i += delta) {
-            final View child = getPageAt(i);
-            if (child.getVisibility() != View.GONE) {
-                int childTop = getPaddingTop() + mInsets.top;
-                childTop += (getMeasuredHeight() - mInsets.top - mInsets.bottom - verticalPadding
-                        - child.getMeasuredHeight()) / 2;
-
-                final int childWidth = child.getMeasuredWidth();
-                final int childHeight = child.getMeasuredHeight();
-
-                if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
-                child.layout(childLeft, childTop,
-                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
-
-                final int pageScroll = childLeft - scrollOffsetLeft;
-                if (mPageScrolls[i] != pageScroll) {
-                    pageScrollChanged = true;
-                    mPageScrolls[i] = pageScroll;
-                }
-
-                childLeft += childWidth + mPageSpacing + getChildGap();
-            }
+        if (getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC)) {
+            pageScrollChanged = true;
         }
 
         final LayoutTransition transition = getLayoutTransition();
@@ -716,10 +584,51 @@
             setCurrentPage(getNextPage());
         }
         mChildCountOnLastLayout = childCount;
+    }
 
-        if (isReordering(true)) {
-            updateDragViewTranslationDuringDrag();
+    /**
+     * Initializes {@code outPageScrolls} with scroll positions for view at that index. The length
+     * of {@code outPageScrolls} should be same as the the childCount
+     *
+     */
+    protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
+            ComputePageScrollsLogic scrollLogic) {
+        final int childCount = getChildCount();
+
+        final int startIndex = mIsRtl ? childCount - 1 : 0;
+        final int endIndex = mIsRtl ? -1 : childCount;
+        final int delta = mIsRtl ? -1 : 1;
+
+        int verticalPadding = getPaddingTop() + getPaddingBottom();
+
+        int scrollOffsetLeft = mInsets.left + getPaddingLeft();
+        int childLeft = scrollOffsetLeft;
+        boolean pageScrollChanged = false;
+
+        for (int i = startIndex; i != endIndex; i += delta) {
+            final View child = getPageAt(i);
+            if (scrollLogic.shouldIncludeView(child)) {
+                int childTop = getPaddingTop() + mInsets.top;
+                childTop += (getMeasuredHeight() - mInsets.top - mInsets.bottom - verticalPadding
+                        - child.getMeasuredHeight()) / 2;
+                final int childWidth = child.getMeasuredWidth();
+
+                if (layoutChildren) {
+                    final int childHeight = child.getMeasuredHeight();
+                    child.layout(childLeft, childTop,
+                            childLeft + child.getMeasuredWidth(), childTop + childHeight);
+                }
+
+                final int pageScroll = childLeft - scrollOffsetLeft;
+                if (outPageScrolls[i] != pageScroll) {
+                    pageScrollChanged = true;
+                    outPageScrolls[i] = pageScroll;
+                }
+
+                childLeft += childWidth + mPageSpacing + getChildGap();
+            }
         }
+        return pageScrollChanged;
     }
 
     protected int getChildGap() {
@@ -756,11 +665,13 @@
 
     @Override
     public void onViewAdded(View child) {
+        super.onViewAdded(child);
         dispatchPageCountChanged();
     }
 
     @Override
     public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
         mCurrentPage = validateNewPage(mCurrentPage);
         dispatchPageCountChanged();
     }
@@ -937,12 +848,7 @@
                 // Remember location of down touch
                 mDownMotionX = x;
                 mDownMotionY = y;
-                mDownScrollX = getScrollX();
                 mLastMotionX = x;
-                mLastMotionY = y;
-                float[] p = mapPointFromViewToParent(this, x, y);
-                mParentDownMotionX = p[0];
-                mParentDownMotionY = p[1];
                 mLastMotionXRemainder = 0;
                 mTotalMotionX = 0;
                 mActivePointerId = ev.getPointerId(0);
@@ -990,6 +896,10 @@
         return mTouchState != TOUCH_STATE_REST;
     }
 
+    public boolean isHandlingTouch() {
+        return mTouchState != TOUCH_STATE_REST;
+    }
+
     protected void determineScrollingStart(MotionEvent ev) {
         determineScrollingStart(ev, 1.0f);
     }
@@ -1101,22 +1011,12 @@
         dampedOverScroll(amount);
     }
 
-    /**
-     * return true if freescroll has been enabled, false otherwise
-     */
-    protected void enableFreeScroll() {
-        enableFreeScroll(false);
-    }
 
     protected void enableFreeScroll(boolean settleOnPageInFreeScroll) {
         setEnableFreeScroll(true);
         mSettleOnPageInFreeScroll = settleOnPageInFreeScroll;
     }
 
-    protected void disableFreeScroll() {
-        setEnableFreeScroll(false);
-    }
-
     private void setEnableFreeScroll(boolean freeScroll) {
         boolean wasFreeScroll = mFreeScroll;
         mFreeScroll = freeScroll;
@@ -1134,27 +1034,6 @@
         mAllowOverScroll = enable;
     }
 
-    private int getNearestHoverOverPageIndex() {
-        if (mDragView != null) {
-            int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
-                    + mDragView.getTranslationX());
-            int minDistance = Integer.MAX_VALUE;
-            int minIndex = indexOfChild(mDragView);
-            int maxPageNo = getChildCount() - 1;
-            for (int i = 0; i <= maxPageNo; i++) {
-                View page = getPageAt(i);
-                int pageX = (page.getLeft() + page.getMeasuredWidth() / 2);
-                int d = Math.abs(dragX - pageX);
-                if (d < minDistance) {
-                    minIndex = i;
-                    minDistance = d;
-                }
-            }
-            return minIndex;
-        }
-        return -1;
-    }
-
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
@@ -1178,11 +1057,7 @@
 
             // Remember where the motion event started
             mDownMotionX = mLastMotionX = ev.getX();
-            mDownMotionY = mLastMotionY = ev.getY();
-            mDownScrollX = getScrollX();
-            float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
-            mParentDownMotionX = p[0];
-            mParentDownMotionY = p[1];
+            mDownMotionY = ev.getY();
             mLastMotionXRemainder = 0;
             mTotalMotionX = 0;
             mActivePointerId = ev.getPointerId(0);
@@ -1215,82 +1090,6 @@
                 } else {
                     awakenScrollBars();
                 }
-            } else if (mTouchState == TOUCH_STATE_REORDERING) {
-                // Update the last motion position
-                mLastMotionX = ev.getX();
-                mLastMotionY = ev.getY();
-
-                // Update the parent down so that our zoom animations take this new movement into
-                // account
-                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
-                mParentDownMotionX = pt[0];
-                mParentDownMotionY = pt[1];
-                updateDragViewTranslationDuringDrag();
-
-                // Find the closest page to the touch point
-                final int dragViewIndex = indexOfChild(mDragView);
-
-                if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
-                if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
-                if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
-                if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
-
-                final int pageUnderPointIndex = getNearestHoverOverPageIndex();
-                // Do not allow any page to be moved to 0th position.
-                if (pageUnderPointIndex > 0 && pageUnderPointIndex != indexOfChild(mDragView)) {
-                    if (0 <= pageUnderPointIndex && pageUnderPointIndex <= getPageCount() - 1 &&
-                            pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
-                        mSidePageHoverIndex = pageUnderPointIndex;
-                        mSidePageHoverRunnable = new Runnable() {
-                            @Override
-                            public void run() {
-                                // Setup the scroll to the correct page before we swap the views
-                                snapToPage(pageUnderPointIndex);
-
-                                // For each of the pages between the paged view and the drag view,
-                                // animate them from the previous position to the new position in
-                                // the layout (as a result of the drag view moving in the layout)
-                                int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
-                                int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
-                                        dragViewIndex + 1 : pageUnderPointIndex;
-                                int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
-                                        dragViewIndex - 1 : pageUnderPointIndex;
-                                for (int i = lowerIndex; i <= upperIndex; ++i) {
-                                    View v = getChildAt(i);
-                                    // dragViewIndex < pageUnderPointIndex, so after we remove the
-                                    // drag view all subsequent views to pageUnderPointIndex will
-                                    // shift down.
-                                    int oldX = getChildOffset(i);
-                                    int newX = getChildOffset(i + shiftDelta);
-
-                                    // Animate the view translation from its old position to its new
-                                    // position
-                                    ObjectAnimator anim = (ObjectAnimator) v.getTag();
-                                    if (anim != null) {
-                                        anim.cancel();
-                                    }
-
-                                    v.setTranslationX(oldX - newX);
-                                    anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0);
-                                    anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
-                                    anim.start();
-                                    v.setTag(anim);
-                                }
-
-                                removeView(mDragView);
-                                addView(mDragView, pageUnderPointIndex);
-                                mSidePageHoverIndex = -1;
-                                if (mPageIndicator != null) {
-                                    mPageIndicator.setActiveMarker(getNextPage());
-                                }
-                            }
-                        };
-                        postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
-                    }
-                } else {
-                    removeCallbacks(mSidePageHoverRunnable);
-                    mSidePageHoverIndex = -1;
-                }
             } else {
                 determineScrollingStart(ev);
             }
@@ -1391,25 +1190,8 @@
                 } else {
                     snapToDestination();
                 }
-            } else if (mTouchState == TOUCH_STATE_REORDERING) {
-                // Update the last motion position
-                mLastMotionX = ev.getX();
-                mLastMotionY = ev.getY();
-
-                // Update the parent down so that our zoom animations take this new movement into
-                // account
-                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
-                mParentDownMotionX = pt[0];
-                mParentDownMotionY = pt[1];
-                updateDragViewTranslationDuringDrag();
-            } else {
-                if (!mCancelTap) {
-                    onUnhandledTap(ev);
-                }
             }
 
-            // Remove the callback to wait for the side page hover timeout
-            removeCallbacks(mSidePageHoverRunnable);
             // End any intermediate reordering states
             resetTouchState();
             break;
@@ -1437,8 +1219,6 @@
 
     private void resetTouchState() {
         releaseVelocityTracker();
-        endReordering();
-        mCancelTap = false;
         mTouchState = TOUCH_STATE_REST;
         mActivePointerId = INVALID_POINTER;
     }
@@ -1452,10 +1232,6 @@
     protected void onScrollInteractionEnd() {
     }
 
-    protected void onUnhandledTap(MotionEvent ev) {
-        Launcher.getLauncher(getContext()).onClick(this);
-    }
-
     @Override
     public boolean onGenericMotionEvent(MotionEvent event) {
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -1512,7 +1288,6 @@
             // TODO: Make this decision more intelligent.
             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
             mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
-            mLastMotionY = ev.getY(newPointerIndex);
             mLastMotionXRemainder = 0;
             mActivePointerId = ev.getPointerId(newPointerIndex);
             if (mVelocityTracker != null) {
@@ -1689,139 +1464,6 @@
         if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
     }
 
-    @Override
-    public boolean performLongClick() {
-        mCancelTap = true;
-        return super.performLongClick();
-    }
-
-    public static class SavedState extends BaseSavedState {
-        int currentPage = -1;
-
-        SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        @Thunk SavedState(Parcel in) {
-            super(in);
-            currentPage = in.readInt();
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeInt(currentPage);
-        }
-
-        public static final Parcelable.Creator<SavedState> CREATOR =
-                new Parcelable.Creator<SavedState>() {
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-
-    // Animate the drag view back to the original position
-    private void animateDragViewToOriginalPosition() {
-        if (mDragView != null) {
-            Animator anim = LauncherAnimUtils.ofPropertyValuesHolder(mDragView,
-                    new PropertyListBuilder()
-                            .scale(1)
-                            .translationX(0)
-                            .translationY(0)
-                            .build())
-                    .setDuration(REORDERING_DROP_REPOSITION_DURATION);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    onPostReorderingAnimationCompleted();
-                }
-            });
-            anim.start();
-        }
-    }
-
-    public void onStartReordering() {
-        // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
-        mTouchState = TOUCH_STATE_REORDERING;
-        mIsReordering = true;
-
-        // We must invalidate to trigger a redraw to update the layers such that the drag view
-        // is always drawn on top
-        invalidate();
-    }
-
-    @Thunk void onPostReorderingAnimationCompleted() {
-        // Trigger the callback when reordering has settled
-        --mPostReorderingPreZoomInRemainingAnimationCount;
-        if (mPostReorderingPreZoomInRunnable != null &&
-                mPostReorderingPreZoomInRemainingAnimationCount == 0) {
-            mPostReorderingPreZoomInRunnable.run();
-            mPostReorderingPreZoomInRunnable = null;
-        }
-    }
-
-    public void onEndReordering() {
-        mIsReordering = false;
-    }
-
-    public boolean startReordering(View v) {
-        int dragViewIndex = indexOfChild(v);
-
-        // Do not allow the first page to be moved around
-        if (mTouchState != TOUCH_STATE_REST || dragViewIndex <= 0) return false;
-
-        // Check if we are within the reordering range
-        if (0 <= dragViewIndex && dragViewIndex <= getPageCount() - 1) {
-            // Find the drag view under the pointer
-            mDragView = getChildAt(dragViewIndex);
-            mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
-            mDragViewBaselineLeft = mDragView.getLeft();
-            mReorderingStarted = true;
-
-            snapToPage(getPageNearestToCenterOfScreen());
-            disableFreeScroll();
-            onStartReordering();
-            return true;
-        }
-        return false;
-    }
-
-    boolean isReordering(boolean testTouchState) {
-        boolean state = mIsReordering;
-        if (testTouchState) {
-            state &= (mTouchState == TOUCH_STATE_REORDERING);
-        }
-        return state;
-    }
-    void endReordering() {
-        // For simplicity, we call endReordering sometimes even if reordering was never started.
-        // In that case, we don't want to do anything.
-        if (!mReorderingStarted) return;
-        mReorderingStarted = false;
-
-        mPostReorderingPreZoomInRunnable = new Runnable() {
-            public void run() {
-                // If we haven't flung-to-delete the current child,
-                // then we just animate the drag view back into position
-                onEndReordering();
-
-                enableFreeScroll();
-            }
-        };
-
-        mPostReorderingPreZoomInRemainingAnimationCount =
-                NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
-        // Snap to the current page
-        snapToPage(indexOfChild(mDragView), 0);
-        // Animate the drag view back to the front position
-        animateDragViewToOriginalPosition();
-    }
-
     /* Accessibility */
     @SuppressWarnings("deprecation")
     @Override
@@ -1900,4 +1542,9 @@
     public boolean onHoverEvent(android.view.MotionEvent event) {
         return true;
     }
+
+    protected interface ComputePageScrollsLogic {
+
+        boolean shouldIncludeView(View view);
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index f329f5e..9960953 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -59,7 +59,6 @@
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
-import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
@@ -82,6 +81,7 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -242,7 +242,6 @@
     private Runnable mOnOverlayHiddenCallback;
 
     private boolean mForceDrawAdjacentPages = false;
-    private boolean mPageRearrangeEnabled = false;
 
     // Total over scrollX in the overlay direction.
     private float mOverlayTranslation;
@@ -250,8 +249,6 @@
     // Handles workspace state transitions
     private final WorkspaceStateTransitionAnimation mStateTransitionAnimation;
 
-    private AccessibilityDelegate mPagesAccessibilityDelegate;
-
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -286,6 +283,7 @@
 
         // Attach a scrim
         new WorkspaceAndHotseatScrim(this).attach();
+        setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
     }
 
     @Override
@@ -475,7 +473,6 @@
         }
         CellLayout cl = ((CellLayout) child);
         cl.setOnInterceptTouchListener(this);
-        cl.setClickable(true);
         cl.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         super.onViewAdded(child);
     }
@@ -555,10 +552,6 @@
         // created CellLayout.
         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                         R.layout.workspace_screen, this, false /* attachToRoot */);
-        newScreen.setOnLongClickListener(mLongClickListener);
-        newScreen.setOnClickListener(mLauncher);
-        newScreen.setSoundEffectsEnabled(false);
-
         int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
         int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
@@ -938,7 +931,6 @@
 
         child.setHapticFeedbackEnabled(false);
         child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
-
         if (child instanceof DropTarget) {
             mDragController.addDropTarget((DropTarget) child);
         }
@@ -1378,8 +1370,7 @@
     }
 
     private void updateChildrenLayersEnabled() {
-        boolean enableChildrenLayers =
-                isPageRearrangeEnabled() || mIsSwitchingState || isPageInTransition();
+        boolean enableChildrenLayers = mIsSwitchingState || isPageInTransition();
 
         if (enableChildrenLayers != mChildrenLayersEnabled) {
             mChildrenLayersEnabled = enableChildrenLayers;
@@ -1463,40 +1454,6 @@
         mOutlineProvider = outlineProvider;
     }
 
-    public void onStartReordering() {
-        super.onStartReordering();
-        // Reordering handles its own animations, disable the automatic ones.
-        disableLayoutTransitions();
-    }
-
-    public void onEndReordering() {
-        super.onEndReordering();
-
-        if (mLauncher.isWorkspaceLoading()) {
-            // Invalid and dangerous operation if workspace is loading
-            return;
-        }
-
-        ArrayList<Long> prevScreenOrder = (ArrayList<Long>) mScreenOrder.clone();
-        mScreenOrder.clear();
-        int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            CellLayout cl = ((CellLayout) getChildAt(i));
-            mScreenOrder.add(getIdForScreen(cl));
-        }
-
-        for (int i = 0; i < prevScreenOrder.size(); i++) {
-            if (mScreenOrder.get(i) != prevScreenOrder.get(i)) {
-                mLauncher.getUserEventDispatcher().logOverviewReorder();
-                break;
-            }
-        }
-        LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
-
-        // Re-enable auto layout transitions for page deletion.
-        enableLayoutTransitions();
-    }
-
     public void snapToPageFromOverView(int whichPage) {
         snapToPage(whichPage, OVERVIEW_TRANSITION_MS, Interpolators.ZOOM_IN);
     }
@@ -1556,47 +1513,17 @@
         if (!mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
             int total = getPageCount();
             for (int i = 0; i < total; i++) {
-                updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i), i);
+                updateAccessibilityFlags(accessibilityFlag, (CellLayout) getPageAt(i));
             }
             setImportantForAccessibility(accessibilityFlag);
         }
     }
 
-    private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page, int pageNo) {
-        if (isPageRearrangeEnabled()) {
-            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            page.getShortcutsAndWidgets().setImportantForAccessibility(
-                    IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-            page.setContentDescription(getPageDescription(pageNo));
-
-            // No custom action for the first page.
-            if (!FeatureFlags.QSB_ON_FIRST_SCREEN || pageNo > 0) {
-                if (mPagesAccessibilityDelegate == null) {
-                    mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
-                }
-                page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
-            }
-        } else {
-            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
-            page.setContentDescription(null);
-            page.setAccessibilityDelegate(null);
-        }
-    }
-
-    public void setPageRearrangeEnabled(boolean isEnabled) {
-        if (mPageRearrangeEnabled != isEnabled) {
-            mPageRearrangeEnabled = isEnabled;
-            if (isEnabled) {
-                enableFreeScroll();
-            } else {
-                disableFreeScroll();
-            }
-        }
-    }
-
-    public boolean isPageRearrangeEnabled() {
-        return mPageRearrangeEnabled;
+    private void updateAccessibilityFlags(int accessibilityFlag, CellLayout page) {
+        page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+        page.getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
+        page.setContentDescription(null);
+        page.setAccessibilityDelegate(null);
     }
 
     public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
@@ -1612,10 +1539,6 @@
                 protected void enableAccessibleDrag(boolean enable) {
                     super.enableAccessibleDrag(enable);
                     setEnableForLayout(mLauncher.getHotseat().getLayout(),enable);
-
-                    // We need to allow our individual children to become click handlers in this
-                    // case, so temporarily unset the click handlers.
-                    setOnClickListener(enable ? null : mLauncher);
                 }
             });
         }
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
deleted file mode 100644
index f9eb2ed..0000000
--- a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2015 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.accessibility;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
-
-public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
-
-    private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
-    private static final int MOVE_FORWARD = R.id.action_move_screen_forwards;
-
-    private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
-    private final Workspace mWorkspace;
-
-    public OverviewScreenAccessibilityDelegate(Workspace workspace) {
-        mWorkspace = workspace;
-
-        Context context = mWorkspace.getContext();
-        boolean isRtl = Utilities.isRtl(context.getResources());
-        mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD,
-                context.getText(isRtl ? R.string.action_move_screen_right :
-                    R.string.action_move_screen_left)));
-        mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD,
-                context.getText(isRtl ? R.string.action_move_screen_left :
-                    R.string.action_move_screen_right)));
-    }
-
-    @Override
-    public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        if (host != null) {
-            if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) {
-                int index = mWorkspace.indexOfChild(host);
-                mWorkspace.setCurrentPage(index);
-            } else if (action == MOVE_FORWARD) {
-                movePage(mWorkspace.indexOfChild(host) + 1, host);
-                return true;
-            } else if (action == MOVE_BACKWARD) {
-                movePage(mWorkspace.indexOfChild(host) - 1, host);
-                return true;
-            }
-        }
-
-        return super.performAccessibilityAction(host, action, args);
-    }
-
-    private void movePage(int finalIndex, View view) {
-        mWorkspace.onStartReordering();
-        mWorkspace.removeView(view);
-        mWorkspace.addView(view, finalIndex);
-        mWorkspace.onEndReordering();
-        mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved));
-
-        mWorkspace.updateAccessibilityFlags();
-        view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-    }
-
-    @Override
-    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(host, info);
-
-        int index = mWorkspace.indexOfChild(host);
-        if (index < mWorkspace.getChildCount() - 1) {
-            info.addAction(mActions.get(MOVE_FORWARD));
-        }
-
-        int startIndex = FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0;
-        if (index > startIndex) {
-            info.addAction(mActions.get(MOVE_BACKWARD));
-        }
-    }
-}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 3fe5d7a..39a8df3 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -25,6 +25,7 @@
 import android.os.Process;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
@@ -192,6 +193,19 @@
         return false;
     }
 
+    public String getDescription() {
+        @StringRes int descriptionRes;
+        if (mUsingTabs) {
+            descriptionRes =
+                    mViewPager.getNextPage() == 0
+                            ? R.string.all_apps_button_personal_label
+                            : R.string.all_apps_button_work_label;
+        } else {
+            descriptionRes = R.string.all_apps_button_label;
+        }
+        return getContext().getString(descriptionRes);
+    }
+
     public AllAppsRecyclerView getActiveRecyclerView() {
         if (!mUsingTabs || mViewPager.getNextPage() == 0) {
             return mAH[AdapterHolder.MAIN].recyclerView;
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 3b4450b..b2e35a4 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -17,10 +17,9 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-
 import android.view.MotionEvent;
+
 import com.android.launcher3.PagedView;
-import com.android.launcher3.R;
 
 public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
 
@@ -42,8 +41,8 @@
 
     @Override
     protected String getCurrentPageDescription() {
-        return getResources().getString(
-                getNextPage() == 0 ? R.string.all_apps_personal_tab : R.string.all_apps_work_tab);
+        // Not necessary, tab-bar already has two tabs with their own descriptions.
+        return "";
     }
 
     @Override
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index b3310c7..1bdd554 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -86,7 +85,7 @@
 /**
  * Represents a set of icons chosen by the user or generated by the system.
  */
-public class Folder extends AbstractFloatingView implements DragSource, View.OnClickListener,
+public class Folder extends AbstractFloatingView implements DragSource,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
         View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
     private static final String TAG = "Launcher.Folder";
@@ -208,11 +207,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mContent = (FolderPagedView) findViewById(R.id.folder_content);
+        mContent = findViewById(R.id.folder_content);
         mContent.setFolder(this);
 
-        mPageIndicator = (PageIndicatorDots) findViewById(R.id.folder_page_indicator);
-        mFolderName = (ExtendedEditText) findViewById(R.id.folder_name);
+        mPageIndicator = findViewById(R.id.folder_page_indicator);
+        mFolderName = findViewById(R.id.folder_name);
         mFolderName.setOnBackKeyListener(this);
         mFolderName.setOnFocusChangeListener(this);
 
@@ -253,13 +252,6 @@
         mFooterHeight = mFooter.getMeasuredHeight();
     }
 
-    public void onClick(View v) {
-        Object tag = v.getTag();
-        if (tag instanceof ShortcutInfo) {
-            mLauncher.onClick(v);
-        }
-    }
-
     public boolean onLongClick(View v) {
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return true;
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index a468cb5..fa7565a 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
@@ -237,7 +238,7 @@
                 R.layout.folder_application, null, false);
         textView.applyFromShortcutInfo(item);
         textView.setHapticFeedbackEnabled(false);
-        textView.setOnClickListener(mFolder);
+        textView.setOnClickListener(ItemClickHandler.INSTANCE);
         textView.setOnLongClickListener(mFolder);
         textView.setOnFocusChangeListener(mFocusIndicatorHelper);
         textView.setOnKeyListener(mKeyListener);
diff --git a/src/com/android/launcher3/graphics/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..3b5585b 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -29,11 +29,13 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
 import android.os.Build;
@@ -60,6 +62,8 @@
  */
 public class LauncherIcons implements AutoCloseable {
 
+    private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
+
     public static final Object sPoolSync = new Object();
     private static LauncherIcons sPool;
 
@@ -84,6 +88,9 @@
      */
     public void recycle() {
         synchronized (sPoolSync) {
+            // Clear any temporary state variables
+            mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+
             next = sPool;
             sPool = this;
         }
@@ -105,6 +112,9 @@
     private IconNormalizer mNormalizer;
     private ShadowGenerator mShadowGenerator;
 
+    private Drawable mWrapperIcon;
+    private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+
     // sometimes we store linked lists of these things
     private LauncherIcons next;
 
@@ -172,6 +182,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 +210,9 @@
             } else {
                 result = createIconBitmap(badged, 1f);
             }
+        } else if (isInstantApp) {
+            badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
+            result = bitmap;
         } else {
             result = bitmap;
         }
@@ -208,13 +231,23 @@
                 Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
     }
 
+    /**
+     * Sets the background color used for wrapped adaptive icon
+     */
+    public void setWrapperBackgroundColor(int color) {
+        mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
+    }
+
     private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
             RectF outIconBounds, float[] outScale) {
         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)) {
@@ -223,6 +256,8 @@
                 fsd.setScale(scale);
                 icon = dr;
                 scale = getNormalizer().getScale(icon, outIconBounds, null, null);
+
+                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
             }
         } else {
             scale = getNormalizer().getScale(icon, outIconBounds, null, null);
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index cbdabf3..1fd7078 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -74,6 +74,9 @@
     /** Maps keys to their corresponding current group key */
     private final Map<String, String> mNotificationGroupKeyMap = new HashMap<>();
 
+    /** The last notification key that was dismissed from launcher UI */
+    private String mLastKeyDismissedByLauncher;
+
     private SettingsObserver mNotificationBadgingObserver;
 
     private final Handler.Callback mWorkerCallback = new Handler.Callback() {
@@ -251,13 +254,25 @@
         }
 
         NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
+        String key = sbn.getKey();
         if (notificationGroup != null) {
-            notificationGroup.removeChildKey(sbn.getKey());
+            notificationGroup.removeChildKey(key);
             if (notificationGroup.isEmpty()) {
-                cancelNotification(notificationGroup.getGroupSummaryKey());
+                if (key.equals(mLastKeyDismissedByLauncher)) {
+                    // Only cancel the group notification if launcher dismissed the last child.
+                    cancelNotification(notificationGroup.getGroupSummaryKey());
+                }
                 mNotificationGroupMap.remove(sbn.getGroupKey());
             }
         }
+        if (key.equals(mLastKeyDismissedByLauncher)) {
+            mLastKeyDismissedByLauncher = null;
+        }
+    }
+
+    public void cancelNotificationFromLauncher(String key) {
+        mLastKeyDismissedByLauncher = key;
+        cancelNotification(key);
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 335426c..f1b8ec0 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -206,7 +206,7 @@
         if (notificationListener == null) {
             return;
         }
-        notificationListener.cancelNotification(notificationKey);
+        notificationListener.cancelNotificationFromLauncher(notificationKey);
     }
 
     public void setAllWidgets(ArrayList<WidgetListRowEntry> allWidgets) {
diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java
index d3c0fef..0a2c3e4 100644
--- a/src/com/android/launcher3/states/InternalStateHandler.java
+++ b/src/com/android/launcher3/states/InternalStateHandler.java
@@ -56,8 +56,8 @@
         sScheduler.schedule(this);
     }
 
-    public void clearReference() {
-        sScheduler.clearReference(this);
+    public boolean clearReference() {
+        return sScheduler.clearReference(this);
     }
 
     public static boolean handleCreate(Launcher launcher, Intent intent) {
@@ -125,10 +125,12 @@
             return false;
         }
 
-        public synchronized void clearReference(InternalStateHandler handler) {
+        public synchronized boolean clearReference(InternalStateHandler handler) {
             if (mPendingHandler.get() == handler) {
                 mPendingHandler.clear();
+                return true;
             }
+            return false;
         }
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
new file mode 100644
index 0000000..df11686
--- /dev/null
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.touch;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.ViewConfiguration.getLongPressTimeout;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.OptionsPopupView;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Helper class to handle touch on empty space in workspace and show options popup on long press
+ */
+public class WorkspaceTouchListener implements OnTouchListener, Runnable {
+
+    /**
+     * STATE_PENDING_PARENT_INFORM is the state between longPress performed & the next motionEvent.
+     * This next event is used to send an ACTION_CANCEL to Workspace, to that it clears any
+     * temporary scroll state. After that, the state is set to COMPLETED, and we just eat up all
+     * subsequent motion events.
+     */
+    private static final int STATE_CANCELLED = 0;
+    private static final int STATE_REQUESTED = 1;
+    private static final int STATE_PENDING_PARENT_INFORM = 2;
+    private static final int STATE_COMPLETED = 3;
+
+    private final Rect mTempRect = new Rect();
+    private final Launcher mLauncher;
+    private final Workspace mWorkspace;
+    private final PointF mTouchDownPoint = new PointF();
+
+    private int mLongPressState = STATE_CANCELLED;
+
+    public WorkspaceTouchListener(Launcher launcher, Workspace workspace) {
+        mLauncher = launcher;
+        mWorkspace = workspace;
+    }
+
+    @Override
+    public boolean onTouch(View view, MotionEvent ev) {
+        int action = ev.getActionMasked();
+        if (action == ACTION_DOWN) {
+            // Check if we can handle long press.
+            boolean handleLongPress = AbstractFloatingView.getTopOpenView(mLauncher) == null
+                    && mLauncher.isInState(NORMAL);
+
+            if (handleLongPress) {
+                // Check if the event is not near the edges
+                DeviceProfile dp = mLauncher.getDeviceProfile();
+                DragLayer dl = mLauncher.getDragLayer();
+                Rect insets = dp.getInsets();
+
+                mTempRect.set(insets.left, insets.top, dl.getWidth() - insets.right,
+                        dl.getHeight() - insets.bottom);
+                mTempRect.inset(dp.edgeMarginPx, dp.edgeMarginPx);
+                handleLongPress = mTempRect.contains((int) ev.getX(), (int) ev.getY());
+            }
+
+            cancelLongPress();
+            if (handleLongPress) {
+                mLongPressState = STATE_REQUESTED;
+                mTouchDownPoint.set(ev.getX(), ev.getY());
+                mWorkspace.postDelayed(this, getLongPressTimeout());
+            }
+
+            mWorkspace.onTouchEvent(ev);
+            // Return true to keep receiving touch events
+            return true;
+        }
+
+        if (mLongPressState == STATE_PENDING_PARENT_INFORM) {
+            // Inform the workspace to cancel touch handling
+            ev.setAction(ACTION_CANCEL);
+            mWorkspace.onTouchEvent(ev);
+            ev.setAction(action);
+            mLongPressState = STATE_COMPLETED;
+        }
+
+        if (mLongPressState == STATE_COMPLETED) {
+            // We have handled the touch, so workspace does not need to know anything anymore.
+            return true;
+        } else if (mLongPressState == STATE_REQUESTED) {
+            mWorkspace.onTouchEvent(ev);
+            if (action == ACTION_UP || action == ACTION_CANCEL || mWorkspace.isHandlingTouch()) {
+                cancelLongPress();
+            }
+            return true;
+        } else {
+            // We don't want to handle touch, let workspace handle it as usual.
+            return false;
+        }
+    }
+
+    private void cancelLongPress() {
+        mWorkspace.removeCallbacks(this);
+        mLongPressState = STATE_CANCELLED;
+    }
+
+    @Override
+    public void run() {
+        if (mLongPressState == STATE_REQUESTED) {
+            mLongPressState = STATE_PENDING_PARENT_INFORM;
+            mWorkspace.getParent().requestDisallowInterceptTouchEvent(true);
+
+            mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
+                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
+                    Action.Direction.NONE, ContainerType.WORKSPACE,
+                    mWorkspace.getCurrentPage());
+            OptionsPopupView.show(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
similarity index 81%
rename from quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
rename to src/com/android/launcher3/views/OptionsPopupView.java
index ccdcbed..21b6773 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.views;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -29,6 +29,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.widget.Toast;
@@ -43,12 +44,15 @@
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.ColorScrim;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.widget.WidgetsFullSheet;
 
 /**
  * Popup shown on long pressing an empty space in launcher
  */
-public class OptionsPopupView extends AbstractFloatingView implements OnClickListener {
+public class OptionsPopupView extends AbstractFloatingView
+        implements OnClickListener, OnLongClickListener {
 
     private final float mOutlineRadius;
     private final Launcher mLauncher;
@@ -81,29 +85,49 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        findViewById(R.id.wallpaper_button).setOnClickListener(this);
-        findViewById(R.id.widget_button).setOnClickListener(this);
-        findViewById(R.id.settings_button).setOnClickListener(this);
+        attachListeners(findViewById(R.id.wallpaper_button));
+        attachListeners(findViewById(R.id.widget_button));
+        attachListeners(findViewById(R.id.settings_button));
+    }
+
+    private void attachListeners(View view) {
+        view.setOnClickListener(this);
+        view.setOnLongClickListener(this);
     }
 
     @Override
     public void onClick(View view) {
+        handleViewClick(view, Action.Touch.TAP);
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        return handleViewClick(view, Action.Touch.LONGPRESS);
+    }
+
+    private boolean handleViewClick(View view, int action) {
         if (view.getId() == R.id.wallpaper_button) {
             mLauncher.onClickWallpaperPicker(null);
+            logTap(action, ControlType.WALLPAPER_BUTTON);
             close(true);
+            return true;
         } else if (view.getId() == R.id.widget_button) {
-            if (mLauncher.getPackageManager().isSafeMode()) {
-                Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-            } else {
-                WidgetsFullSheet.show(mLauncher, true /* animated */);
+            logTap(action, ControlType.WIDGETS_BUTTON);
+            if (onWidgetsClicked(mLauncher)) {
                 close(true);
+                return true;
             }
         } else if (view.getId() == R.id.settings_button) {
-            mLauncher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
-                .setPackage(mLauncher.getPackageName())
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+            startSettings(mLauncher);
+            logTap(action, ControlType.SETTINGS_BUTTON);
             close(true);
+            return true;
         }
+        return false;
+    }
+
+    private void logTap(int action, int controlType) {
+        mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
     }
 
     @Override
@@ -267,4 +291,20 @@
         launcher.getDragLayer().addView(view);
         view.animateOpen();
     }
+
+    public static boolean onWidgetsClicked(Launcher launcher) {
+        if (launcher.getPackageManager().isSafeMode()) {
+            Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
+            return false;
+        } else {
+            WidgetsFullSheet.show(launcher, true /* animated */);
+            return true;
+        }
+    }
+
+    public static void startSettings(Launcher launcher) {
+        launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+                .setPackage(launcher.getPackageName())
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
index 88a1e10..d9ce87c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewAccessibilityDelegate.java
@@ -27,13 +27,13 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.views.OptionsPopupView;
 
 /**
  * Accessibility delegate with actions pointing to various Overview entry points.
  */
 public class OverviewAccessibilityDelegate extends AccessibilityDelegate {
 
-    private static final int OVERVIEW = R.string.accessibility_action_overview;
     private static final int WALLPAPERS = R.string.wallpaper_button_text;
     private static final int WIDGETS = R.string.widget_button_text;
     private static final int SETTINGS = R.string.settings_button_text;
@@ -43,7 +43,6 @@
         super.onInitializeAccessibilityNodeInfo(host, info);
 
         Context context = host.getContext();
-        info.addAction(new AccessibilityAction(OVERVIEW, context.getText(OVERVIEW)));
 
         if (Utilities.isWallpaperAllowed(context)) {
             info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
@@ -55,18 +54,13 @@
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
         Launcher launcher = Launcher.getLauncher(host.getContext());
-        OverviewPanel overviewPanel = launcher.findViewById(R.id.overview_panel);
-        if (action == OVERVIEW) {
-            launcher.getStateManager().goToState(LauncherState.OVERVIEW);
-            return true;
-        } else if (action == WALLPAPERS) {
+        if (action == WALLPAPERS) {
             launcher.onClickWallpaperPicker(host);
             return true;
         } else if (action == WIDGETS) {
-            overviewPanel.onClickAddWidgetButton();
-            return true;
+            return OptionsPopupView.onWidgetsClicked(launcher);
         } else if (action == SETTINGS) {
-            overviewPanel.onClickSettingsButton(host);
+            OptionsPopupView.startSettings(launcher);
             return true;
         }
         return super.performAccessibilityAction(host, action, args);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
deleted file mode 100644
index 616e25c..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
+++ /dev/null
@@ -1,194 +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.OVERVIEW;
-import static com.android.launcher3.WorkspaceStateTransitionAnimation.NO_ANIM_PROPERTY_SETTER;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.Toast;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.WorkspaceStateTransitionAnimation.AnimatedPropertySetter;
-import com.android.launcher3.WorkspaceStateTransitionAnimation.PropertySetter;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.widget.WidgetsFullSheet;
-
-public class OverviewPanel extends LinearLayout implements Insettable, View.OnClickListener,
-        View.OnLongClickListener, LauncherStateManager.StateHandler {
-
-    // Out of 100, the percent of space the overview bar should try and take vertically.
-    private static final float OVERVIEW_ICON_ZONE_RATIO = 0.22f;
-
-    private final Launcher mLauncher;
-
-    public OverviewPanel(Context context) {
-        this(context, null);
-    }
-
-    public OverviewPanel(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public OverviewPanel(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        mLauncher = Launcher.getLauncher(context);
-        setAlpha(0);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        int visibleChildCount = 3;
-        // Attach buttons.
-        attachListeners(findViewById(R.id.wallpaper_button));
-        attachListeners(findViewById(R.id.widget_button));
-
-        View settingsButton = findViewById(R.id.settings_button);
-        if (mLauncher.hasSettings()) {
-            attachListeners(settingsButton);
-        } else {
-            settingsButton.setVisibility(GONE);
-            visibleChildCount--;
-        }
-
-        // Init UI
-        Resources res = getResources();
-        int itemWidthPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_item_width);
-        int spacerWidthPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_bar_spacer_width);
-
-        int totalItemWidth = visibleChildCount * itemWidthPx;
-        int maxWidth = totalItemWidth + (visibleChildCount - 1) * spacerWidthPx;
-
-        getLayoutParams().width = Math.min(mLauncher.getDeviceProfile().availableWidthPx, maxWidth);
-        getLayoutParams().height = getButtonBarHeight(mLauncher);
-    }
-
-    private void attachListeners(View view) {
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        ((FrameLayout.LayoutParams) getLayoutParams()).bottomMargin = insets.bottom;
-    }
-
-    @Override
-    public void onClick(View view) {
-        handleViewClick(view, Action.Touch.TAP);
-    }
-
-    @Override
-    public boolean onLongClick(View view) {
-        return handleViewClick(view, Action.Touch.LONGPRESS);
-    }
-
-    private boolean handleViewClick(View view, int action) {
-        if (mLauncher.getWorkspace().isSwitchingState()) {
-            return false;
-        }
-
-        final int controlType;
-        if (view.getId() == R.id.wallpaper_button) {
-            mLauncher.onClickWallpaperPicker(view);
-            controlType = ControlType.WALLPAPER_BUTTON;
-        } else if (view.getId() == R.id.widget_button) {
-            onClickAddWidgetButton();
-            controlType = ControlType.WIDGETS_BUTTON;
-        } else if (view.getId() == R.id.settings_button) {
-            onClickSettingsButton(view);
-            controlType = ControlType.SETTINGS_BUTTON;
-        } else {
-            return false;
-        }
-
-        mLauncher.getUserEventDispatcher().logActionOnControl(action, controlType);
-        return true;
-    }
-
-    /**
-     * Event handler for the (Add) Widgets button that appears after a long press
-     * on the home screen.
-     */
-    public void onClickAddWidgetButton() {
-        if (getContext().getPackageManager().isSafeMode()) {
-            Toast.makeText(mLauncher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
-        } else {
-            WidgetsFullSheet.show(mLauncher, true /* animated */);
-        }
-    }
-
-    /**
-     * Event handler for a click on the settings button that appears after a long press
-     * on the home screen.
-     */
-    public void onClickSettingsButton(View v) {
-        Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
-                .setPackage(getContext().getPackageName());
-        intent.setSourceBounds(mLauncher.getViewBounds(v));
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(intent, mLauncher.getActivityLaunchOptionsAsBundle(v, false));
-    }
-
-    @Override
-    public void setState(LauncherState state) {
-        setState(state, NO_ANIM_PROPERTY_SETTER);
-    }
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState,
-            AnimatorSetBuilder builder, AnimationConfig config) {
-        setState(toState, new AnimatedPropertySetter(config.duration, builder));
-    }
-
-    private void setState(LauncherState state, PropertySetter setter) {
-        float myAlpha = state == OVERVIEW ? 1 : 0;
-        setter.setViewAlpha(this, myAlpha, Interpolators.ACCEL);
-    }
-
-    public static int getButtonBarHeight(Launcher launcher) {
-        int zoneHeight = (int) (OVERVIEW_ICON_ZONE_RATIO *
-                launcher.getDeviceProfile().availableHeightPx);
-        Resources res = launcher.getResources();
-        int overviewModeMinIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_min_icon_zone_height);
-        int overviewModeMaxIconZoneHeightPx =
-                res.getDimensionPixelSize(R.dimen.dynamic_grid_overview_max_icon_zone_height);
-        return Utilities.boundToRange(zoneHeight,
-                overviewModeMinIconZoneHeightPx,
-                overviewModeMaxIconZoneHeightPx);
-    }
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
index 37d0aa2..3dfbc40 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewState.java
@@ -33,56 +33,7 @@
  */
 public class OverviewState extends LauncherState {
 
-    // The percent to shrink the workspace during overview mode
-    private static final float SCALE_FACTOR = 0.7f;
-
-    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_MULTI_PAGE |
-            FLAG_DISABLE_PAGE_CLIPPING | FLAG_PAGE_BACKGROUNDS | FLAG_OVERVIEW_UI;
-
     public OverviewState(int id) {
-        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
-    }
-
-    @Override
-    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
-        DeviceProfile grid = launcher.getDeviceProfile();
-        Workspace ws = launcher.getWorkspace();
-        Rect insets = launcher.getDragLayer().getInsets();
-
-        int overviewButtonBarHeight = OverviewPanel.getButtonBarHeight(launcher);
-        int scaledHeight = (int) (SCALE_FACTOR * ws.getNormalChildHeight());
-        int workspaceTop = insets.top + grid.workspacePadding.top;
-        int workspaceBottom = ws.getHeight() - insets.bottom - grid.workspacePadding.bottom;
-        int overviewTop = insets.top;
-        int overviewBottom = ws.getHeight() - insets.bottom - overviewButtonBarHeight;
-        int workspaceOffsetTopEdge =
-                workspaceTop + ((workspaceBottom - workspaceTop) - scaledHeight) / 2;
-        int overviewOffsetTopEdge = overviewTop + (overviewBottom - overviewTop - scaledHeight) / 2;
-        return new float[] {SCALE_FACTOR, 0, -workspaceOffsetTopEdge + overviewOffsetTopEdge };
-    }
-
-    @Override
-    public float getHoseatAlpha(Launcher launcher) {
-        return 0;
-    }
-
-    @Override
-    public void onStateEnabled(Launcher launcher) {
-        launcher.getWorkspace().setPageRearrangeEnabled(true);
-
-        if (isAccessibilityEnabled(launcher)) {
-            launcher.getOverviewPanel().getChildAt(0).performAccessibilityAction(
-                    AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
-        }
-    }
-
-    @Override
-    public void onStateDisabled(Launcher launcher) {
-        launcher.getWorkspace().setPageRearrangeEnabled(false);
-    }
-
-    @Override
-    public View getFinalFocus(Launcher launcher) {
-        return launcher.getOverviewPanel();
+        super(id, ContainerType.WORKSPACE, OVERVIEW_TRANSITION_MS, FLAG_DISABLE_RESTORE);
     }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
deleted file mode 100644
index a7c8cee..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/PinchToOverviewListener.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2016 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.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.ScaleGestureDetector.OnScaleGestureListener;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.TouchController;
-
-/**
- * Detects pinches and animates the Workspace to/from overview mode.
- */
-public class PinchToOverviewListener extends AnimatorListenerAdapter
-        implements TouchController, OnScaleGestureListener {
-
-    private static final float ACCEPT_THRESHOLD = 0.65f;
-    /**
-     * The velocity threshold at which a pinch will be completed instead of canceled,
-     * even if the first threshold has not been passed. Measured in scale / millisecond
-     */
-    private static final float FLING_VELOCITY = 0.001f;
-
-    private final ScaleGestureDetector mPinchDetector;
-    private Launcher mLauncher;
-    private Workspace mWorkspace = null;
-    private boolean mPinchStarted = false;
-
-    private AnimatorPlaybackController mCurrentAnimation;
-    private float mCurrentScale;
-    private boolean mShouldGoToFinalState;
-
-    private LauncherState mToState;
-
-    public PinchToOverviewListener(Launcher launcher) {
-        mLauncher = launcher;
-        mPinchDetector = new ScaleGestureDetector(mLauncher, this);
-    }
-
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        mPinchDetector.onTouchEvent(ev);
-        return mPinchStarted;
-    }
-
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        return mPinchDetector.onTouchEvent(ev);
-    }
-
-    @Override
-    public boolean onScaleBegin(ScaleGestureDetector detector) {
-        if (isAccessibilityEnabled(mLauncher)) {
-            return false;
-        }
-        if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(OVERVIEW)) {
-            // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
-            return false;
-        }
-        if (mCurrentAnimation != null) {
-            // Don't listen for the pinch gesture if we are already animating from a previous one.
-            return false;
-        }
-        if (mLauncher.isWorkspaceLocked()) {
-            // Don't listen for the pinch gesture if the workspace isn't ready.
-            return false;
-        }
-        if (mWorkspace == null) {
-            mWorkspace = mLauncher.getWorkspace();
-        }
-        if (mWorkspace.isSwitchingState()) {
-            // Don't listen for the pinch gesture while switching state, as it will cause a jump
-            // once the state switching animation is complete.
-            return false;
-        }
-        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-            // Don't listen for the pinch gesture if a floating view is open.
-            return false;
-        }
-
-        if (mLauncher.getDragController().isDragging()) {
-            mLauncher.getDragController().cancelDrag();
-        }
-
-        mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
-        mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
-        mCurrentAnimation.getTarget().addListener(this);
-        mPinchStarted = true;
-        mCurrentScale = 1;
-        mShouldGoToFinalState = false;
-
-        mCurrentAnimation.dispatchOnStart();
-        return true;
-    }
-
-    @Override
-    public void onAnimationEnd(Animator animation) {
-        mCurrentAnimation = null;
-        mPinchStarted = false;
-    }
-
-    @Override
-    public void onScaleEnd(ScaleGestureDetector detector) {
-        if (mShouldGoToFinalState) {
-            mCurrentAnimation.start();
-        } else {
-            mCurrentAnimation.setEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    mLauncher.getStateManager().goToState(
-                            mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
-                }
-            });
-            mCurrentAnimation.reverse();
-        }
-    }
-
-    @Override
-    public boolean onScale(ScaleGestureDetector detector) {
-        mCurrentScale = detector.getScaleFactor() * mCurrentScale;
-
-        // If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
-        // 0 => Animation complete
-        // 1=> Animation started
-        float animationFraction = mToState == OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
-
-        float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
-        if (Math.abs(velocity) >= FLING_VELOCITY) {
-            LauncherState toState = velocity > 0 ? OVERVIEW : NORMAL;
-            mShouldGoToFinalState = toState == mToState;
-        } else {
-            mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
-        }
-
-        // Move the transition animation to that duration.
-        mCurrentAnimation.setPlayFraction(1 - animationFraction);
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index de75ac9..a9694a7 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,9 +16,6 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.graphics.PointF;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.Launcher;
@@ -28,8 +25,7 @@
 public class UiFactory {
 
     public static TouchController[] createTouchControllers(Launcher launcher) {
-        return new TouchController[] {
-                new AllAppsSwipeController(launcher), new PinchToOverviewListener(launcher)};
+        return new TouchController[] {new AllAppsSwipeController(launcher)};
     }
 
     public static AccessibilityDelegate newPageIndicatorAccessibilityDelegate() {
@@ -38,14 +34,9 @@
 
     public static StateHandler[] getStateHandler(Launcher launcher) {
         return new StateHandler[] {
-                (OverviewPanel) launcher.getOverviewPanel(),
                 launcher.getAllAppsController(), launcher.getWorkspace() };
     }
 
-    public static void onWorkspaceLongPress(Launcher launcher, PointF touchPoint) {
-        launcher.getStateManager().goToState(OVERVIEW);
-    }
-
     public static void resetOverview(Launcher launcher) { }
 
     public static void onLauncherStateOrFocusChanged(Launcher launcher) { }