Revert "Merge branch 'ub-launcher3-master' into pi-dev"

Original CL loses the commit history. I believe this is due to doing the merge on master and cherry picking to pi-dev. Tested locally that reverting this results in no conflicts when doing the merge properly on pi-dev.

This reverts commit 3f7df53dda43d05c7a40ed8c4114b63189936ffe.

Bug: 74794600
Test: manual test
Change-Id: I58f3bb1bd5ce789be380bac9716efd2627a90f92
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index d531a46..02b4379 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -36,13 +36,8 @@
         android:restoreAnyVersion="true"
         android:supportsRtl="true" >
 
-        <service
-            android:name="com.android.quickstep.TouchInteractionService"
-            android:permission="android.permission.STATUS_BAR_SERVICE" >
-            <intent-filter>
-                <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
-            </intent-filter>
-        </service>
+        <service android:name="com.android.quickstep.TouchInteractionService"
+            android:exported="true" />
 
         <!-- STOPSHIP: Change exported to false once all the integration is complete.
         It is set to true so that the activity can be started from command line -->
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 9006831..d5859a7 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
deleted file mode 100644
index c416844..0000000
--- a/quickstep/res/layout/fallback_recents_activity.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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/longpress_options_menu.xml b/quickstep/res/layout/longpress_options_menu.xml
new file mode 100644
index 0000000..9cf0fcf
--- /dev/null
+++ b/quickstep/res/layout/longpress_options_menu.xml
@@ -0,0 +1,97 @@
+<?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.launcher3.uioverrides.OptionsPopupView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="96dp"
+    android:background="?attr/popupColorPrimary"
+    android:elevation="@dimen/deep_shortcuts_elevation"
+    android:orientation="horizontal"
+    launcher:layout_ignoreInsets="true">
+
+    <FrameLayout
+        android:id="@+id/wallpaper_button"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/selectableItemBackground">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:drawablePadding="4dp"
+            android:drawableTint="?android:attr/textColorPrimary"
+            android:drawableTop="@drawable/ic_wallpaper"
+            android:fontFamily="sans-serif-condensed"
+            android:gravity="center"
+            android:paddingLeft="16dp"
+            android:paddingRight="16dp"
+            android:text="@string/wallpaper_button_text"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="12sp"/>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/widget_button"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/selectableItemBackground">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:drawablePadding="4dp"
+            android:drawableTint="?android:attr/textColorPrimary"
+            android:drawableTop="@drawable/ic_widget"
+            android:fontFamily="sans-serif-condensed"
+            android:gravity="center"
+            android:paddingLeft="16dp"
+            android:paddingRight="16dp"
+            android:text="@string/widget_button_text"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="12sp"/>
+
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/settings_button"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/selectableItemBackground">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:drawablePadding="4dp"
+            android:drawableTint="?android:attr/textColorPrimary"
+            android:drawableTop="@drawable/ic_setting"
+            android:fontFamily="sans-serif-condensed"
+            android:gravity="center"
+            android:paddingLeft="16dp"
+            android:paddingRight="16dp"
+            android:text="@string/settings_button_text"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="12sp"/>
+
+    </FrameLayout>
+
+</com.android.launcher3.uioverrides.OptionsPopupView>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 54a90cf..9f4f8a1 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.views.LauncherRecentsView
+<com.android.quickstep.RecentsView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:theme="@style/HomeScreenElementTheme"
     android:layout_width="match_parent"
@@ -24,4 +24,8 @@
     android:alpha="0.0"
     android:visibility="invisible" >
 
-</com.android.quickstep.views.LauncherRecentsView>
\ No newline at end of file
+    <com.android.launcher3.uioverrides.WorkspaceCard
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</com.android.quickstep.RecentsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 0ac2b11..91b6aa3 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.views.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.quickstep.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.views.TaskThumbnailView
+    <com.android.quickstep.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.views.TaskView>
\ No newline at end of file
+</com.android.quickstep.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index b846665..6e3fb4f 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.views.TaskMenuView
+<com.android.quickstep.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.views.TaskMenuView>
\ No newline at end of file
+</com.android.quickstep.TaskMenuView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 8497191..0956048 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,21 +16,24 @@
 
 <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>
     <dimen name="task_corner_radius">2dp</dimen>
     <dimen name="task_fade_length">20dp</dimen>
     <dimen name="recents_page_spacing">10dp</dimen>
-    <dimen name="recents_page_fade_length">100dp</dimen>
 
-    <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
-             loading full resolution screenshots. -->
-    <dimen name="recents_fast_fling_velocity">600dp</dimen>
 
     <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
 
+    <dimen name="workspace_overview_offset_x">-24dp</dimen>
+
+    <!-- TODO: This can be calculated using other resource values -->
+    <dimen name="all_apps_search_box_full_height">90dp</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 2bd9f8f..ba99d81 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -16,7 +16,5 @@
 
 <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 ba0cbfa..0fe29e3 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,8 +16,7 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
@@ -32,13 +31,11 @@
 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;
 import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
 import android.util.Log;
@@ -50,18 +47,14 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.InsettableFrameLayout.LayoutParams;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.DrawableFactory;
-import com.android.launcher3.shortcuts.DeepShortcutTextView;
-import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.quickstep.RecentsAnimationInterpolator;
 import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
-import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -81,16 +74,12 @@
 
     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 LAUNCHER_RESUME_START_DELAY = 150;
     private static final int CLOSING_TRANSITION_DURATION_MS = 350;
 
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
@@ -162,7 +151,6 @@
     @Override
     public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
         if (hasControlRemoteAppTransitionPermission()) {
-            TaskView taskView = findTaskViewToLaunch(launcher, v);
             try {
                 RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
                     @Override
@@ -172,8 +160,8 @@
                         // processed before the next frame.
                         postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
                             final boolean removeTrackingView;
-                            LauncherTransitionAnimator animator = composeRecentsLaunchAnimator(
-                                    taskView == null ? v : taskView, targets);
+                            LauncherTransitionAnimator animator =
+                                    composeRecentsLaunchAnimator(v, targets);
                             if (animator != null) {
                                 // We are animating the task view directly, do not remove it after
                                 removeTrackingView = false;
@@ -213,10 +201,8 @@
                     }
                 };
 
-                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));
+                return ActivityOptionsCompat.makeRemoteAnimation(
+                        new RemoteAnimationAdapterCompat(runner, 500, 380));
             } catch (NoClassDefFoundError e) {
                 // Gracefully fall back to default launch options if the user's platform doesn't
                 // have the latest changes.
@@ -226,65 +212,19 @@
     }
 
     /**
-     * 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) {
+        if (!mLauncher.isInState(LauncherState.OVERVIEW)) {
             return null;
         }
 
         // Resolve the opening task id
         int openingTaskId = -1;
         for (RemoteAnimationTargetCompat target : targets) {
-            if (target.mode == MODE_OPENING) {
+            if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
                 openingTaskId = target.taskId;
                 break;
             }
@@ -297,40 +237,15 @@
 
         // 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;
         }
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
-        Animator launcherAnim;
-        AnimatorListenerAdapter windowAnimEndListener;
-        if (launcherClosing) {
-            launcherAnim = getRecentsLauncherAnimator(recentsView, taskView);
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    // Make sure recents gets fixed up by resetting task alphas and scales, etc.
-                    mLauncher.getStateManager().reapplyState();
-                }
-            };
-        } else {
-            AnimatorPlaybackController controller =
-                    mLauncher.getStateManager()
-                            .createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
-            controller.dispatchOnStart();
-            launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
-            windowAnimEndListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    mLauncher.getStateManager().goToState(NORMAL, false);
-                }
-            };
-        }
-
-        Animator windowAnim = getRecentsWindowAnimator(taskView, skipLauncherChanges, targets);
-        windowAnim.addListener(windowAnimEndListener);
-        return new LauncherTransitionAnimator(launcherAnim, windowAnim, skipLauncherChanges);
+        return new LauncherTransitionAnimator(getRecentsLauncherAnimator(recentsView, taskView),
+                getRecentsWindowAnimator(taskView, targets));
     }
 
     /**
@@ -345,16 +260,15 @@
         int launchedTaskIndex = recentsView.indexOfChild(v);
         int centerTaskIndex = recentsView.getCurrentPage();
         boolean launchingCenterTask = launchedTaskIndex == centerTaskIndex;
-        boolean isRtl = recentsView.isRtl();
         if (launchingCenterTask) {
-            if (launchedTaskIndex - 1 >= 0) {
+            if (launchedTaskIndex - 1 >= recentsView.getFirstTaskIndex()) {
                 TaskView adjacentPage1 = (TaskView) recentsView.getPageAt(launchedTaskIndex - 1);
                 ObjectAnimator adjacentTask1ScaleAndTranslate =
                         LauncherAnimUtils.ofPropertyValuesHolder(adjacentPage1,
                                 new PropertyListBuilder()
                                         .scale(adjacentPage1.getScaleX() * mRecentsScale)
                                         .translationY(mRecentsTransY)
-                                        .translationX(isRtl ? mRecentsTransX : -mRecentsTransX)
+                                        .translationX(mIsRtl ? mRecentsTransX : -mRecentsTransX)
                                         .build());
                 launcherAnimator.play(adjacentTask1ScaleAndTranslate);
             }
@@ -365,11 +279,11 @@
                                 new PropertyListBuilder()
                                         .scale(adjacentTask2.getScaleX() * mRecentsScale)
                                         .translationY(mRecentsTransY)
-                                        .translationX(isRtl ? -mRecentsTransX : mRecentsTransX)
+                                        .translationX(mIsRtl ? -mRecentsTransX : mRecentsTransX)
                                         .build());
                 launcherAnimator.play(adjacentTask2ScaleAndTranslate);
             }
-        } else {
+        } else if (centerTaskIndex >= recentsView.getFirstTaskIndex()) {
             // 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());
@@ -377,11 +291,11 @@
                     LauncherAnimUtils.ofPropertyValuesHolder(centerTask,
                             new PropertyListBuilder()
                                     .scale(v.getScaleX())
-                                    .translationX(isRtl ? -translationX : translationX)
+                                    .translationX(mIsRtl ? -translationX : translationX)
                                     .build());
             launcherAnimator.play(centerTaskParallaxToRight);
             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - launchedTaskIndex);
-            if (otherAdjacentTaskIndex >= 0
+            if (otherAdjacentTaskIndex >= recentsView.getFirstTaskIndex()
                     && otherAdjacentTaskIndex < recentsView.getPageCount()) {
                 TaskView otherAdjacentTask = (TaskView) recentsView.getPageAt(
                         otherAdjacentTaskIndex);
@@ -389,7 +303,7 @@
                         LauncherAnimUtils.ofPropertyValuesHolder(otherAdjacentTask,
                                 new PropertyListBuilder()
                                         .translationX(otherAdjacentTask.getTranslationX()
-                                                + (isRtl ? -translationX : translationX))
+                                                + (mIsRtl ? -translationX : translationX))
                                         .build());
                 launcherAnimator.play(otherAdjacentTaskParallaxToRight);
             }
@@ -400,7 +314,7 @@
         launcherAnimator.play(allAppsSlideOut);
 
         Workspace workspace = mLauncher.getWorkspace();
-        float[] workspaceScaleAndTranslation = NORMAL
+        float[] workspaceScaleAndTranslation = LauncherState.NORMAL
                 .getWorkspaceScaleAndTranslation(mLauncher);
         Animator recenterWorkspace = LauncherAnimUtils.ofPropertyValuesHolder(
                 workspace, new PropertyListBuilder()
@@ -410,6 +324,9 @@
         launcherAnimator.play(recenterWorkspace);
         CellLayout currentWorkspacePage = (CellLayout) workspace.getPageAt(
                 workspace.getCurrentPage());
+        Animator hideWorkspaceScrim = ObjectAnimator.ofInt(
+                currentWorkspacePage.getScrimBackground(), DRAWABLE_ALPHA, 0);
+        launcherAnimator.play(hideWorkspaceScrim);
 
         launcherAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
         launcherAnimator.setDuration(RECENTS_LAUNCH_DURATION);
@@ -420,7 +337,7 @@
      * @return Animator that controls the window of the opening targets for the recents launch
      * animation.
      */
-    private ValueAnimator getRecentsWindowAnimator(TaskView v, MutableBoolean skipLauncherChanges,
+    private ValueAnimator getRecentsWindowAnimator(TaskView v,
             RemoteAnimationTargetCompat[] targets) {
         Rect taskViewBounds = new Rect();
         mDragLayer.getDescendantRectRelativeToSelf(v, taskViewBounds);
@@ -456,15 +373,13 @@
                 final float percent = animation.getAnimatedFraction();
                 TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
 
-                if (!skipLauncherChanges.value) {
-                    v.setScaleX(tw.taskScale);
-                    v.setScaleY(tw.taskScale);
-                    v.setTranslationX(tw.taskX);
-                    v.setTranslationY(tw.taskY);
-                    // Defer fading out the view until after the app window gets faded in
-                    v.setAlpha(getValue(1f, 0f, 75, 75,
-                            appAnimator.getDuration() * percent, Interpolators.LINEAR));
-                }
+                v.setScaleX(tw.taskScale);
+                v.setScaleY(tw.taskScale);
+                v.setTranslationX(tw.taskX);
+                v.setTranslationY(tw.taskY);
+                // Defer fading out the view until after the app window gets faded in
+                v.setAlpha(getValue(1f, 0f, 75, 75,
+                        appAnimator.getDuration() * percent, Interpolators.LINEAR));
 
                 matrix.setScale(tw.winScale, tw.winScale);
                 matrix.postTranslate(tw.winX, tw.winY);
@@ -486,10 +401,7 @@
                         matrix.postTranslate(target.position.x, target.position.y);
                         t.setMatrix(target.leash, matrix);
                         t.setWindowCrop(target.leash, crop);
-
-                        if (!skipLauncherChanges.value) {
-                            t.deferTransactionUntil(target.leash, surface, frameNumber);
-                        }
+                        t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
                     }
                     if (isFirstFrame) {
                         t.show(target.leash);
@@ -501,6 +413,13 @@
                 isFirstFrame = false;
             }
         });
+        appAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+                mLauncher.getStateManager().reapplyState();
+            }
+        });
         return appAnimator;
     }
 
@@ -578,40 +497,26 @@
      * @return Animator that controls the icon used to launch the target.
      */
     private AnimatorSet getIconAnimator(View v) {
-        final boolean isBubbleTextView = v instanceof BubbleTextView;
+        boolean isBubbleTextView = v instanceof BubbleTextView;
         mFloatingView = new View(mLauncher);
         if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) {
             // Create a copy of the app icon
-            mFloatingView.setBackground(
-                    DrawableFactory.get(mLauncher).newIcon((ItemInfoWithIcon) v.getTag()));
+            ItemInfoWithIcon info = (ItemInfoWithIcon) v.getTag();
+            FastBitmapDrawable d = DrawableFactory.get(mLauncher).newIcon(info);
+            d.setIsDisabled(info.isDisabled());
+            mFloatingView.setBackground(d);
         }
 
         // Position the floating view exactly on top of the original
         Rect rect = new Rect();
-        final 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(), rect);
-        } else {
-            mDragLayer.getDescendantRectRelativeToSelf(v, rect);
-        }
-        final int viewLocationStart = mIsRtl
+        mDragLayer.getDescendantRectRelativeToSelf(v, rect);
+        int viewLocationStart = mIsRtl
                 ? mDeviceProfile.widthPx - rect.right
                 : rect.left;
-        final int viewLocationTop = rect.top;
+        int viewLocationTop = rect.top;
 
-        float startScale = 1f;
-        if (isBubbleTextView && !isDeepShortcutTextView) {
-            BubbleTextView btv = (BubbleTextView) v;
-            btv.getIconBounds(rect);
-            Drawable dr = btv.getIcon();
-            if (dr instanceof FastBitmapDrawable) {
-                startScale = ((FastBitmapDrawable) dr).getAnimatedScale();
-            }
-        } else {
-            rect.set(0, 0, rect.width(), rect.height());
+        if (isBubbleTextView) {
+            ((BubbleTextView) v).getIconBounds(rect);
         }
         LayoutParams lp = new LayoutParams(rect.width(), rect.height());
         lp.ignoreInsets = true;
@@ -639,8 +544,8 @@
 
         // Adjust the duration to change the "curve" of the app icon to the center.
         boolean isBelowCenterY = lp.topMargin < centerY;
-        x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
-        y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
+        x.setDuration(isBelowCenterY ? 500 : 233);
+        y.setDuration(isBelowCenterY ? 233 : 500);
         x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
         y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
         appIconAnimatorSet.play(x);
@@ -651,10 +556,14 @@
         float maxScaleX = mDeviceProfile.widthPx / (float) rect.width();
         float maxScaleY = mDeviceProfile.heightPx / (float) rect.height();
         float scale = Math.max(maxScaleX, maxScaleY);
-        ObjectAnimator scaleAnim = ObjectAnimator
-                .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
-        scaleAnim.setDuration(APP_LAUNCH_DURATION).setInterpolator(Interpolators.EXAGGERATED_EASE);
-        appIconAnimatorSet.play(scaleAnim);
+        ObjectAnimator sX = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_X, 1f, scale);
+        ObjectAnimator sY = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_Y, 1f, scale);
+        sX.setDuration(500);
+        sY.setDuration(500);
+        sX.setInterpolator(Interpolators.EXAGGERATED_EASE);
+        sY.setInterpolator(Interpolators.EXAGGERATED_EASE);
+        appIconAnimatorSet.play(sX);
+        appIconAnimatorSet.play(sY);
 
         // Fade out the app icon.
         ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
@@ -671,13 +580,7 @@
      */
     private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
         Rect bounds = new Rect();
-        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) {
+        if (v instanceof BubbleTextView) {
             ((BubbleTextView) v).getIconBounds(bounds);
         } else {
             mDragLayer.getDescendantRectRelativeToSelf(v, bounds);
@@ -688,7 +591,7 @@
         Matrix matrix = new Matrix();
 
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
-        appAnimator.setDuration(APP_LAUNCH_DURATION);
+        appAnimator.setDuration(500);
         appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             boolean isFirstFrame = true;
 
@@ -810,7 +713,7 @@
                 postAtFrontOfQueueAsynchronously(handler, () -> {
                     if ((Utilities.getPrefs(mLauncher)
                             .getBoolean("pref_use_screenshot_for_swipe_up", false)
-                            && mLauncher.getStateManager().getState().overviewUi)
+                            && mLauncher.isInState(LauncherState.OVERVIEW))
                             || !launcherIsATargetWithMode(targets, MODE_OPENING)) {
                         // We use a separate transition for Overview mode. And we can skip the
                         // animation in cases where Launcher is not in the set of opening targets.
@@ -847,7 +750,7 @@
         Matrix matrix = new Matrix();
         float height = mLauncher.getDeviceProfile().heightPx;
         float width = mLauncher.getDeviceProfile().widthPx;
-        float endX = (mLauncher.<RecentsView>getOverviewPanel().isRtl() ? -width : width) * 1.16f;
+        float endX = (Utilities.isRtl(mLauncher.getResources()) ? -width : width) * 1.16f;
 
         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
         closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
diff --git a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
index ab9234b..aec2869 100644
--- a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
+++ b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
@@ -27,20 +27,11 @@
  */
 public class LauncherTransitionAnimator {
 
-    private final MutableBoolean mLauncherAnimCancelState;
-
     private AnimatorSet mAnimatorSet;
     private Animator mLauncherAnimator;
     private Animator mWindowAnimator;
 
     LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator) {
-        this(launcherAnimator, windowAnimator, new MutableBoolean(false));
-    }
-
-
-    LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator,
-            MutableBoolean launcherAnimCancelState) {
-        mLauncherAnimCancelState = launcherAnimCancelState;
         if (launcherAnimator != null) {
             mLauncherAnimator = launcherAnimator;
         }
@@ -59,7 +50,6 @@
 
     public void cancel() {
         mAnimatorSet.cancel();
-        mLauncherAnimCancelState.value = true;
     }
 
     public boolean isRunning() {
@@ -68,7 +58,6 @@
 
     public void finishLauncherAnimation() {
         if (mLauncherAnimator != null) {
-            mLauncherAnimCancelState.value = true;
             mLauncherAnimator.end();
         }
     }
diff --git a/quickstep/src/com/android/launcher3/MutableBoolean.java b/quickstep/src/com/android/launcher3/MutableBoolean.java
deleted file mode 100644
index 7538217..0000000
--- a/quickstep/src/com/android/launcher3/MutableBoolean.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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;
-
-public class MutableBoolean {
-    public boolean value;
-
-    public MutableBoolean(boolean value) {
-        this.value = value;
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 31261d9..426fe35 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -33,8 +32,7 @@
  */
 public class AllAppsState extends LauncherState {
 
-    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY
-            | FLAG_SHOW_SCRIM | FLAG_ALL_APPS_SCRIM;
+    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
 
     private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
         @Override
@@ -59,8 +57,7 @@
 
     @Override
     public String getDescription(Launcher launcher) {
-        AllAppsContainerView appsView = launcher.getAppsView();
-        return appsView.getDescription();
+        return launcher.getString(R.string.all_apps_button_label);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index 97ac3e6..541c6bb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -31,12 +31,13 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.VerticalSwipeController;
-import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.RecentsView;
 
 class EventLogTags {
     private EventLogTags() {
@@ -142,6 +143,11 @@
     }
 
     @Override
+    protected void initSprings() {
+        mSpringHandlers = new SpringAnimationHandler[0];
+    }
+
+    @Override
     protected float getShiftRange() {
         return getShiftRange(mLauncher);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
deleted file mode 100644
index 9541d0d..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.uioverrides;
-
-import com.android.launcher3.Launcher;
-import com.android.quickstep.QuickScrubController;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Extension of overview state used for QuickScrub
- */
-public class FastOverviewState extends OverviewState {
-
-    private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
-            | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI;
-
-    private static final boolean DEBUG_DIFFERENT_UI = false;
-
-    public FastOverviewState(int id) {
-        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
-    public float getHoseatAlpha(Launcher launcher) {
-        if (DEBUG_DIFFERENT_UI) {
-            return 0;
-        }
-        return super.getHoseatAlpha(launcher);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java b/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java
new file mode 100644
index 0000000..2d5eb5a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/IgnoreTouchesInQuickScrub.java
@@ -0,0 +1,41 @@
+/*
+ * 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.uioverrides;
+
+import android.view.MotionEvent;
+
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.TouchInteractionService;
+
+/**
+ * Consumes touches when quick scrub is enabled.
+ */
+public class IgnoreTouchesInQuickScrub implements TouchController {
+
+    public IgnoreTouchesInQuickScrub() {
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return true;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        return TouchInteractionService.isQuickScrubEnabled();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java b/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
new file mode 100644
index 0000000..c089d06
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/OptionsPopupView.java
@@ -0,0 +1,276 @@
+/*
+ * 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.uioverrides;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Outline;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.widget.Toast;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.RevealOutlineAnimation;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.graphics.GradientView;
+import com.android.launcher3.widget.WidgetsFullSheet;
+
+/**
+ * Popup shown on long pressing an empty space in launcher
+ */
+public class OptionsPopupView extends AbstractFloatingView implements OnClickListener {
+
+    private final float mOutlineRadius;
+    private final Launcher mLauncher;
+    private final PointF mTouchPoint = new PointF();
+
+    private final GradientView mGradientView;
+
+    protected Animator mOpenCloseAnimator;
+
+    public OptionsPopupView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
+        setClipToOutline(true);
+        setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
+            }
+        });
+
+        mLauncher = Launcher.getLauncher(context);
+
+        mGradientView = (GradientView) mLauncher.getLayoutInflater().inflate(
+                R.layout.widgets_bottom_sheet_scrim, mLauncher.getDragLayer(), false);
+        mGradientView.setProgress(1, false);
+        mGradientView.setAlpha(0);
+    }
+
+    @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);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view.getId() == R.id.wallpaper_button) {
+            mLauncher.onClickWallpaperPicker(null);
+            close(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 */);
+                close(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));
+            close(true);
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() != MotionEvent.ACTION_DOWN) {
+            return false;
+        }
+        if (mLauncher.getDragLayer().isEventOverView(this, ev)) {
+            return false;
+        }
+        close(true);
+        return true;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (animate) {
+            animateClose();
+        } else {
+            closeComplete();
+        }
+    }
+
+    protected void animateClose() {
+        if (!mIsOpen) {
+            return;
+        }
+        mIsOpen = false;
+
+        final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
+        closeAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration));
+
+        // Rectangular reveal (reversed).
+        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, true);
+        closeAnim.play(revealAnim);
+
+        Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
+        fadeOut.setInterpolator(Interpolators.DEACCEL);
+        closeAnim.play(fadeOut);
+
+        Animator gradientAlpha = ObjectAnimator.ofFloat(mGradientView, ALPHA, 0);
+        gradientAlpha.setInterpolator(Interpolators.DEACCEL);
+        closeAnim.play(gradientAlpha);
+
+        closeAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenCloseAnimator = null;
+                closeComplete();
+            }
+        });
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel();
+        }
+        mOpenCloseAnimator = closeAnim;
+        closeAnim.start();
+    }
+
+    /**
+     * Closes the popup without animation.
+     */
+    private void closeComplete() {
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel();
+            mOpenCloseAnimator = null;
+        }
+        mIsOpen = false;
+        mLauncher.getDragLayer().removeView(this);
+        mLauncher.getDragLayer().removeView(mGradientView);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // TODO:
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_OPTIONS_POPUP) != 0;
+    }
+
+    private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+        Rect startRect = new Rect();
+        startRect.offset((int) (mTouchPoint.x - lp.x), (int) (mTouchPoint.y - lp.y));
+
+        Rect endRect = new Rect(0, 0, lp.width, lp.height);
+        if (getOutlineProvider() instanceof RevealOutlineAnimation) {
+            ((RevealOutlineAnimation) getOutlineProvider()).getOutline(endRect);
+        }
+
+        return new RoundedRectRevealOutlineProvider
+                (mOutlineRadius, mOutlineRadius, startRect, endRect);
+    }
+
+    private void animateOpen() {
+        mIsOpen = true;
+        final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
+        openAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration));
+
+        final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, false);
+        openAnim.play(revealAnim);
+
+        Animator gradientAlpha = ObjectAnimator.ofFloat(mGradientView, ALPHA, 1);
+        gradientAlpha.setInterpolator(Interpolators.ACCEL);
+        openAnim.play(gradientAlpha);
+
+        mOpenCloseAnimator = openAnim;
+
+        openAnim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mOpenCloseAnimator = null;
+            }
+        });
+        openAnim.start();
+    }
+
+    public static void show(Launcher launcher, float x, float y) {
+        DragLayer dl = launcher.getDragLayer();
+        OptionsPopupView view = (OptionsPopupView) launcher.getLayoutInflater()
+                .inflate(R.layout.longpress_options_menu, dl, false);
+        DragLayer.LayoutParams lp = (DragLayer.LayoutParams) view.getLayoutParams();
+
+        int maxWidth = dl.getWidth();
+        int maxHeight = dl.getHeight();
+        if (x <= 0 || y <= 0 || x >= maxWidth || y >= maxHeight) {
+            x = maxWidth / 2;
+            y = maxHeight / 2;
+        }
+        view.mTouchPoint.set(x, y);
+
+        int height = lp.height;
+
+        // Find a good width;
+        int childCount = view.getChildCount();
+        int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+        int widthSpec = MeasureSpec.makeMeasureSpec(maxWidth / childCount, MeasureSpec.AT_MOST);
+        int maxChildWidth = 0;
+
+        for (int i = 0; i < childCount; i ++) {
+            View child = ((ViewGroup) view.getChildAt(i)).getChildAt(0);
+            child.measure(widthSpec, heightSpec);
+            maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth());
+        }
+        Rect insets = dl.getInsets();
+        int margin = (int) (2 * view.getElevation());
+
+        int width = Math.min(maxWidth - insets.left - insets.right - 2 * margin,
+                maxChildWidth * childCount);
+        lp.width = width;
+
+        // Position is towards the finger
+        lp.customPosition = true;
+        lp.x = Utilities.boundToRange((int) (x - width / 2), insets.left + margin,
+                maxWidth - insets.right - width - margin);
+        lp.y = Utilities.boundToRange((int) (y - height / 2), insets.top + margin,
+                maxHeight - insets.bottom - height - margin);
+
+        launcher.getDragLayer().addView(view.mGradientView);
+        launcher.getDragLayer().addView(view);
+        view.animateOpen();
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index abb4ecf..9ba2308 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -16,17 +16,20 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 
+import android.content.Context;
 import android.graphics.Rect;
 import android.view.View;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.RecentsView;
 
 /**
  * Definition for overview state
@@ -34,26 +37,27 @@
 public class OverviewState extends LauncherState {
 
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
-            | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI;
+            | FLAG_DISABLE_RESTORE;
 
     public OverviewState(int id) {
-        this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
-    }
-
-    protected OverviewState(int id, int transitionDuration, int stateFlags) {
-        super(id, ContainerType.TASKSWITCHER, transitionDuration, stateFlags);
+        super(id, ContainerType.TASKSWITCHER, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
     }
 
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
         Rect pageRect = new Rect();
-        RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, pageRect);
+        RecentsView.getScaledDownPageRect(launcher.getDeviceProfile(), launcher, pageRect);
+        RecentsView rv = launcher.getOverviewPanel();
 
         if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
-        return getScaleAndTranslationForPageRect(launcher, pageRect);
+        float overlap = 0;
+        if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
+            overlap = launcher.getResources().getDimension(R.dimen.workspace_overview_offset_x);
+        }
+        return getScaleAndTranslationForPageRect(launcher, overlap, pageRect);
     }
 
     @Override
@@ -69,8 +73,8 @@
     }
 
     @Override
-    public void onStateTransitionEnd(Launcher launcher) {
-        launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
+    public float getVerticalProgress(Launcher launcher) {
+        return getVerticalProgress(launcher.getDeviceProfile(), launcher);
     }
 
     @Override
@@ -79,25 +83,51 @@
     }
 
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
-        return new PageAlphaProvider(DEACCEL_2) {
+        final int centerPage = launcher.getWorkspace().getNextPage();
+        return new PageAlphaProvider(ACCEL_2) {
             @Override
             public float getPageAlpha(int pageIndex) {
-                return 0;
+                return  pageIndex != centerPage ? 0 : 1f;
             }
         };
     }
 
-    public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
+    public static float[] getScaleAndTranslationForPageRect(Launcher launcher, float offsetX,
+            Rect pageRect) {
         Workspace ws = launcher.getWorkspace();
         float childWidth = ws.getNormalChildWidth();
+        float childHeight = ws.getNormalChildHeight();
 
-        float scale = pageRect.width() / childWidth;
+        float scale = pageRect.height() / childHeight;
         Rect insets = launcher.getDragLayer().getInsets();
 
         float halfHeight = ws.getExpectedHeight() / 2;
         float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
         float translationY = pageRect.top - childTop;
 
-        return new float[] {scale, 0, translationY};
+        // 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 (Utilities.isRtl(launcher.getResources())) {
+            translationX -= offsetX / scale;
+        } else {
+            translationX += offsetX / scale;
+        }
+
+        return new float[] {scale, translationX, translationY};
+    }
+
+    public static float getVerticalProgress(DeviceProfile grid, Context context) {
+        if (!grid.isVerticalBarLayout()) {
+            return 1f;
+        }
+
+        float total = grid.heightPx;
+        float searchHeight = total - grid.availableHeightPx +
+                context.getResources().getDimension(R.dimen.all_apps_search_box_full_height);
+        return 1 - (searchHeight / total);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
index c8b54ad..468a561 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -15,14 +15,10 @@
  */
 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;
@@ -36,13 +32,19 @@
 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.PendingAnimation;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 
 /**
  * Touch controller for swipe interaction in Overview state
@@ -63,7 +65,6 @@
     private final RecentsView mRecentsView;
     private final int[] mTempCords = new int[2];
 
-    private PendingAnimation mPendingAnimation;
     private AnimatorPlaybackController mCurrentAnimation;
     private boolean mCurrentAnimationIsGoingUp;
 
@@ -131,21 +132,28 @@
                 mTaskBeingDragged = null;
                 mSwipeDownEnabled = true;
 
-                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;
+                int currentPage = mRecentsView.getCurrentPage();
+                if (currentPage == 0) {
+                    // User is on home tile
                     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;
+                    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;
+                    }
                 }
             }
 
@@ -177,11 +185,6 @@
         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);
@@ -198,11 +201,19 @@
             }
         } else {
             if (goingUp) {
-                mPendingAnimation = mRecentsView
-                        .createTaskDismissAnimation(mTaskBeingDragged, maxDuration);
-                mCurrentAnimation = AnimatorPlaybackController
-                        .wrap(mPendingAnimation.anim, maxDuration);
-                mEndDisplacement = -mTaskBeingDragged.getHeight();
+                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();
             } else {
                 AnimatorSet anim = new AnimatorSet();
                 // TODO: Setup a zoom animation
@@ -288,17 +299,15 @@
     }
 
     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) {
+            if (mCurrentAnimationIsGoingUp) {
+                mRecentsView.onTaskDismissed(mTaskBeingDragged);
+            } else {
                 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 b7f79b3..7d371e9 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -28,71 +28,84 @@
 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.views.RecentsView;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
 
 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 mIsRecentsSlidingInOrOut;
+    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) {
-        setVisibility(state.overviewUi);
-        setTransitionProgress(state.overviewUi ? 1 : 0);
-        if (state.overviewUi) {
-            mRecentsView.resetTaskVisuals();
+        mWorkspaceCard.setWorkspaceScrollingEnabled(state == OVERVIEW);
+        setVisibility(state == OVERVIEW);
+        setTransitionProgress(state == OVERVIEW ? 1 : 0);
+        if (state == OVERVIEW) {
+            for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) {
+                ((TaskView) mRecentsView.getPageAt(i)).resetVisualProperties();
+            }
+            mRecentsView.updateCurveProperties();
         }
     }
 
     @Override
     public void setStateWithAnimation(final LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        LauncherState fromState = mLauncher.getStateManager().getState();
-        mIsRecentsSlidingInOrOut = fromState == NORMAL && toState.overviewUi
-                || fromState.overviewUi && toState == NORMAL;
+        boolean settingEnabled = Utilities.getPrefs(mLauncher)
+            .getBoolean("pref_scroll_to_first_task", false);
+        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();
-        if (fromState.overviewUi && toState == NORMAL && currPage != 0 && !config.userControlled) {
+        if (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);
-            // Let the snapping animation play for a bit before we translate off screen.
-            builder.setStartDelay(snapDuration / 4);
+            builder.setStartDelay(snapDuration);
         }
 
         ObjectAnimator progressAnim =
-                mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
+                mTransitionProgress.animateToValue(toState == OVERVIEW ? 1 : 0);
         progressAnim.setDuration(config.duration);
         progressAnim.setInterpolator(Interpolators.LINEAR);
         progressAnim.addListener(new AnimationSuccessListener() {
 
             @Override
             public void onAnimationSuccess(Animator animator) {
+                mWorkspaceCard.setWorkspaceScrollingEnabled(toState == OVERVIEW);
                 mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen());
             }
         });
         builder.play(progressAnim);
 
-        ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
+        ObjectAnimator visibilityAnim = animateVisibility(toState == OVERVIEW);
         visibilityAnim.setDuration(config.duration);
         visibilityAnim.setInterpolator(Interpolators.LINEAR);
         builder.play(visibilityAnim);
@@ -131,14 +144,11 @@
 
     private void onTransitionProgress() {
         applyProgress();
-        if (mIsRecentsSlidingInOrOut) {
-            float interpolatedProgress = ACCEL.getInterpolation(mTransitionProgress.value);
-            // Slide in from the side as we swipe.
-            int translation = mRecentsView.getWidth();
-            if (mRecentsView.isRtl()) {
-                translation = -translation;
-            }
-            mRecentsView.setTranslationX(translation * (1 - interpolatedProgress));
+        if (mIsRecentsScrollingToFirstTask) {
+            int scrollForFirstTask = mRecentsView.getScrollForPage(mRecentsView.getFirstTaskIndex());
+            int scrollForPage0 = mRecentsView.getScrollForPage(0);
+            mRecentsView.setScrollX((int) (mTransitionProgress.value * scrollForFirstTask
+                    + (1 - mTransitionProgress.value) * scrollForPage0));
         }
     }
 
@@ -148,9 +158,5 @@
 
     private void applyProgress() {
         mRecentsView.setAlpha(mTransitionProgress.value * mVisibilityMultiplier.value);
-        if (mIsRecentsSlidingInOrOut) {
-            // While animating into recents, update the visible task data as needed
-            mRecentsView.loadVisibleTaskData();
-        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
index c8d75dc..2695054 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
 import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
 
 import android.animation.Animator;
@@ -26,6 +27,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.support.animation.SpringAnimation;
 import android.util.Log;
 import android.view.MotionEvent;
 
@@ -36,9 +38,11 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -48,6 +52,8 @@
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TouchInteractionService;
 
+import java.util.ArrayList;
+
 /**
  * Handles vertical touch gesture on the DragLayer
  */
@@ -106,6 +112,8 @@
     // Ratio of transition process [0, 1] to drag displacement (px)
     private float mProgressMultiplier;
 
+    private SpringAnimationHandler[] mSpringHandlers;
+
     public TwoStepSwipeController(Launcher l) {
         mLauncher = l;
         mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
@@ -148,6 +156,29 @@
         }
     }
 
+    private void initSprings() {
+        AllAppsContainerView appsView = mLauncher.getAppsView();
+
+        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+        if (handler == null) {
+            mSpringHandlers = new SpringAnimationHandler[0];
+            return;
+        }
+
+        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+        handlers.add(handler);
+
+        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+        if (searchSpring != null) {
+            SpringAnimationHandler searchHandler =
+                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+            searchHandler.add(searchSpring, true /* setDefaultValues */);
+            handlers.add(searchHandler);
+        }
+
+        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+    }
+
     @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -183,6 +214,10 @@
 
             mDetector.setDetectableScrollConditions(
                     directionsToDetectScroll, ignoreSlopWhenSettling);
+
+            if (mSpringHandlers == null) {
+                initSprings();
+            }
         }
 
         if (mNoIntercept) {
@@ -195,6 +230,9 @@
 
     @Override
     public boolean onControllerTouchEvent(MotionEvent ev) {
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.addMovement(ev);
+        }
         return mDetector.onTouchEvent(ev);
     }
 
@@ -245,6 +283,10 @@
             mDragPauseDetector.clearDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING);
             updatePauseDetectorRangeFlag();
         }
+
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.skipToEnd();
+        }
     }
 
     private float getShiftRange() {
@@ -287,6 +329,13 @@
             targetState = (progress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
         }
 
+        if (fling && targetState == ALL_APPS) {
+            for (SpringAnimationHandler h : mSpringHandlers) {
+                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+            }
+        }
+
         float endProgress;
 
         if (mDragPauseDetector.isTriggered() && targetState == NORMAL) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index 9051cfb..7bd4366 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,29 +18,37 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 
-import android.view.View;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.PointF;
 import android.view.View.AccessibilityDelegate;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.OverviewInteractionState;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.RecentsView;
+import com.android.systemui.shared.recents.view.RecentsTransition;
 
 public class UiFactory {
 
+    private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
+            "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+
+    public static final boolean USE_HARDWARE_BITMAP = false; // FeatureFlags.IS_DOGFOOD_BUILD;
+
     public static TouchController[] createTouchControllers(Launcher launcher) {
         if (FeatureFlags.ENABLE_TWO_SWIPE_TARGETS) {
             return new TouchController[] {
+                    new IgnoreTouchesInQuickScrub(),
                     new EdgeSwipeController(launcher),
                     new TwoStepSwipeController(launcher),
                     new OverviewSwipeController(launcher)};
         } else {
             return new TouchController[] {
+                    new IgnoreTouchesInQuickScrub(),
                     new TwoStepSwipeController(launcher),
                     new OverviewSwipeController(launcher)};
         }
@@ -56,40 +64,28 @@
                 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)
-                || !launcher.hasWindowFocus();
-        if (!shouldBackButtonBeVisible) {
-            // Show the back button if there is a floating view visible.
-            DragLayer dragLayer = launcher.getDragLayer();
-            for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
-                View child = dragLayer.getChildAt(i);
-                if (child instanceof AbstractFloatingView) {
-                    shouldBackButtonBeVisible = true;
-                    break;
-                }
-            }
+        OverviewInteractionState.setBackButtonVisible(launcher, launcher == null
+                || !launcher.isInState(NORMAL) || !launcher.hasWindowFocus());
+    }
+
+    public static Bitmap createFromRenderer(int width, int height, boolean forceSoftwareRenderer,
+            BitmapRenderer renderer) {
+        if (USE_HARDWARE_BITMAP && !forceSoftwareRenderer) {
+            return RecentsTransition.createHardwareBitmap(width, height, renderer::render);
+        } else {
+            Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            renderer.render(new Canvas(result));
+            return result;
         }
-        OverviewInteractionState.setBackButtonVisible(launcher, shouldBackButtonBeVisible);
     }
 
     public static void resetOverview(Launcher launcher) {
         RecentsView recents = launcher.getOverviewPanel();
         recents.reset();
     }
-
-    public static void onStart(Launcher launcher) {
-        RecentsModel model = RecentsModel.getInstance(launcher);
-        if (model != null) {
-            model.onStart();
-        }
-    }
-
-    public static void onTrimMemory(Launcher launcher, int level) {
-        RecentsModel model = RecentsModel.getInstance(launcher);
-        if (model != null) {
-            model.onTrimMemory(level);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
new file mode 100644
index 0000000..8533502
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
@@ -0,0 +1,127 @@
+/*
+ * 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 84dfa45..214b3f3 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -77,12 +77,6 @@
         }
     }
 
-    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 5871a6d..b3ebd77 100644
--- a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java
@@ -23,7 +23,6 @@
 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
deleted file mode 100644
index b92678a..0000000
--- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 22f6e0c..0000000
--- a/quickstep/src/com/android/quickstep/FallbackRecentsView.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 12757c0..0000000
--- a/quickstep/src/com/android/quickstep/InstantAppResolverImpl.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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 94b6faa..6e92d83 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -53,8 +53,6 @@
             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();
@@ -78,10 +76,10 @@
     public MotionEventQueue(Choreographer choreographer, TouchConsumer consumer) {
         mMainChoreographer = choreographer;
         mConsumer = consumer;
+
         mCurrentChoreographer = mMainChoreographer;
         mCurrentRunnable = mMainFrameCallback;
-
-        setInterimChoreographer(consumer.getIntrimChoreographer(this));
+        setInterimChoreographerLocked(consumer.getIntrimChoreographer(this));
     }
 
     public void setInterimChoreographer(Choreographer choreographer) {
@@ -158,9 +156,6 @@
                         case ACTION_RESET:
                             mConsumer.reset();
                             break;
-                        case ACTION_DEFER_INIT:
-                            mConsumer.deferInit();
-                            break;
                         default:
                             Log.e(TAG, "Invalid virtual event: " + event.getAction());
                     }
@@ -209,14 +204,6 @@
         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 89c9d16..ff7d434 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -17,6 +17,7 @@
 
 
 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;
 
@@ -50,8 +51,6 @@
 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;
@@ -320,14 +319,13 @@
 
     /** 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(mIsGoingToHome
-                        ? STATE_SCALED_SNAPSHOT_RECENTS : STATE_SCALED_SNAPSHOT_APP);
+                mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+                        ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
             }
         });
         anim.start();
diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
index f875bb7..431fb30 100644
--- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
+++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java
@@ -16,20 +16,19 @@
 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;
@@ -41,13 +40,11 @@
 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
@@ -56,7 +53,7 @@
             BitmapInfo info = mDefaultIcons.get(userId);
             if (info == null) {
                 info = getBitmapInfo(Resources.getSystem()
-                        .getDrawable(android.R.drawable.sym_def_app_icon), userId, 0, false);
+                        .getDrawable(android.R.drawable.sym_def_app_icon), userId);
                 mDefaultIcons.put(userId, info);
             }
 
@@ -65,31 +62,23 @@
     }
 
     @Override
-    protected Drawable createBadgedDrawable(Drawable drawable, int userId, TaskDescription desc) {
-        return new FastBitmapDrawable(getBitmapInfo(drawable, userId, desc.getPrimaryColor(),
-                false));
+    protected Drawable createBadgedDrawable(Drawable drawable, int userId) {
+        return new FastBitmapDrawable(getBitmapInfo(drawable, userId));
     }
 
-    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId,
-            int primaryColor, boolean isInstantApp) {
+    private synchronized BitmapInfo getBitmapInfo(Drawable drawable, int userId) {
         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, isInstantApp);
+                Build.VERSION_CODES.O);
     }
 
     @Override
-    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);
+    protected Drawable getBadgedActivityIcon(ActivityInfo activityInfo, int userId) {
+        return createBadgedDrawable(
+                activityInfo.loadUnbadgedIcon(mContext.getPackageManager()), userId);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index c96f6d7..488cd72 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -23,11 +23,9 @@
 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_OVERVIEW;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
 
-import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.content.Context;
@@ -39,9 +37,10 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Build;
+import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.os.Looper;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.Display;
@@ -64,20 +63,50 @@
 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;
 
+class EventLogTags {
+    private EventLogTags() {
+    }  // don't instantiate
+
+    /** 524292 sysui_multi_action (content|4) */
+    public static final int SYSUI_MULTI_ACTION = 524292;
+
+    public static void writeSysuiMultiAction(Object[] content) {
+        android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
+    }
+}
+
+class MetricsLogger {
+    private static MetricsLogger sMetricsLogger;
+
+    private static MetricsLogger getLogger() {
+        if (sMetricsLogger == null) {
+            sMetricsLogger = new MetricsLogger();
+        }
+        return sMetricsLogger;
+    }
+
+    protected void saveLog(Object[] rep) {
+        EventLogTags.writeSysuiMultiAction(rep);
+    }
+
+    public void write(LogMaker content) {
+        if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
+            content.setType(4/*MetricsEvent.TYPE_ACTION*/);
+        }
+        saveLog(content.serialize());
+    }
+}
+
 /**
  * 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;
@@ -86,7 +115,6 @@
     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;
@@ -96,24 +124,26 @@
     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;
+
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
             RecentsModel recentsModel, Intent homeIntent, ISystemUiProxy systemUiProxy,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
-            @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
+            @HitTarget int downHitTarget) {
         super(base);
         mRunningTask = runningTaskInfo;
         mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
-        mVelocityTracker = velocityTracker;
+        mVelocityTracker = VelocityTracker.obtain();
         mISystemUiProxy = systemUiProxy;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadChoreographer = backgroundThreadChoreographer;
-        mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
+        mDownHitTarget = downHitTarget;
     }
 
     @Override
@@ -132,7 +162,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() && !mIsDeferredDownTarget) {
+                if (!isUsingScreenShot() && mDownHitTarget != HIT_TARGET_BACK) {
                     startTouchTrackingForWindowAnimation(ev.getEventTime());
                 }
 
@@ -174,7 +204,7 @@
 
                         if (isUsingScreenShot()) {
                             startTouchTrackingForScreenshotAnimation();
-                        } else if (mIsDeferredDownTarget) {
+                        } else if (mDownHitTarget == HIT_TARGET_BACK) {
                             // If we deferred starting the window animation on touch down, then
                             // start tracking now
                             startTouchTrackingForWindowAnimation(ev.getEventTime());
@@ -282,7 +312,7 @@
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
         // Create the shared handler
         final WindowTransformSwipeHandler handler =
-                new WindowTransformSwipeHandler(mRunningTask, this, touchTimeMs);
+                new WindowTransformSwipeHandler(mRunningTask, this);
 
         // Preload the plan
         mRecentsModel.loadTasks(mRunningTask.id, null);
@@ -320,6 +350,16 @@
                             TraceHelper.endSection("RecentsController", "Finishing no handler");
                             controller.finish(false /* toHome */);
                         }
+
+                        // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
+                        // "Recents" activity for app transition tests.
+                        final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
+                        builder.setPackageName("com.android.systemui");
+                        builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
+                                "com.android.systemui.recents.RecentsActivity");
+                        builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
+                                SystemClock.uptimeMillis() - touchTimeMs);
+                        mMetricsLogger.write(builder);
                     }
 
                     public void onAnimationCanceled() {
@@ -377,7 +417,6 @@
         if (mInteractionHandler != null) {
             final BaseSwipeInteractionHandler handler = mInteractionHandler;
             mInteractionHandler = null;
-            mIsGoingToHome = handler.mIsGoingToHome;
             mMainThreadExecutor.execute(handler::reset);
         }
     }
@@ -432,15 +471,4 @@
            }
         }
     }
-
-    @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
deleted file mode 100644
index 031624a..0000000
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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 4af89bf..3c68281 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,9 +15,7 @@
  */
 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;
@@ -62,7 +60,7 @@
         }
     };
 
-    private static int sFlags = DEBUG_SHOW_OVERVIEW_BUTTON ? FLAG_SHOW_OVERVIEW_BUTTON : 0;
+    private static int sFlags;
 
     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
deleted file mode 100644
index d22ef61..0000000
--- a/quickstep/src/com/android/quickstep/PendingAnimation.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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 a154b29..f28d51c 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -19,14 +19,13 @@
 import android.view.HapticFeedbackConstants;
 
 import com.android.launcher3.Alarm;
-import com.android.launcher3.BaseActivity;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * Responds to quick scrub callbacks to page through and launch recent tasks.
@@ -36,7 +35,8 @@
  */
 public class QuickScrubController implements OnAlarmListener {
 
-    public static final int QUICK_SWITCH_START_DURATION = 210;
+    public static final int QUICK_SWITCH_START_DURATION = 133;
+    public static final int QUICK_SWITCH_SNAP_DURATION = 120;
 
     private static final boolean ENABLE_AUTO_ADVANCE = true;
     private static final int NUM_QUICK_SCRUB_SECTIONS = 3;
@@ -47,17 +47,15 @@
 
     private final Alarm mAutoAdvanceAlarm;
     private final RecentsView mRecentsView;
-    private final BaseActivity mActivity;
+    private final Launcher mLauncher;
 
     private boolean mInQuickScrub;
     private int mQuickScrubSection;
-    private boolean mStartedFromHome;
+    private int mStartPage;
     private boolean mHasAlarmRun;
-    private boolean mQuickSwitched;
-    private boolean mFinishedTransitionToQuickScrub;
 
-    public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
-        mActivity = activity;
+    public QuickScrubController(Launcher launcher, RecentsView recentsView) {
+        mLauncher = launcher;
         mRecentsView = recentsView;
         if (ENABLE_AUTO_ADVANCE) {
             mAutoAdvanceAlarm = new Alarm();
@@ -67,14 +65,10 @@
 
     public void onQuickScrubStart(boolean startingFromHome) {
         mInQuickScrub = true;
-        mStartedFromHome = startingFromHome;
+        mStartPage = startingFromHome ? 0 : mRecentsView.getFirstTaskIndex();
         mQuickScrubSection = 0;
         mHasAlarmRun = false;
-        mQuickSwitched = false;
-        mFinishedTransitionToQuickScrub = false;
-
-        snapToNextTaskIfAvailable();
-        mActivity.getUserEventDispatcher().resetActionDurationMillis();
+        mLauncher.getUserEventDispatcher().resetActionDurationMillis();
     }
 
     public void onQuickScrubEnd() {
@@ -84,7 +78,11 @@
         }
         int page = mRecentsView.getNextPage();
         Runnable launchTaskRunnable = () -> {
-            ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
+            if (page < mRecentsView.getFirstTaskIndex()) {
+                mRecentsView.getPageAt(page).performClick();
+            } else {
+                ((TaskView) mRecentsView.getPageAt(page)).launchTask(true);
+            }
         };
         int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
                 * QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
@@ -95,8 +93,8 @@
             // No page move needed, just launch it
             launchTaskRunnable.run();
         }
-        mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
-                ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
+        mLauncher.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
+                ControlType.QUICK_SCRUB_BUTTON, null, mStartPage == 0 ?
                         ContainerType.WORKSPACE : ContainerType.APP);
     }
 
@@ -104,9 +102,7 @@
         int quickScrubSection = Math.round(progress * NUM_QUICK_SCRUB_SECTIONS);
         if (quickScrubSection != mQuickScrubSection) {
             int pageToGoTo = mRecentsView.getNextPage() + quickScrubSection - mQuickScrubSection;
-            if (mFinishedTransitionToQuickScrub) {
-                goToPageWithHaptic(pageToGoTo);
-            }
+            goToPageWithHaptic(pageToGoTo);
             if (ENABLE_AUTO_ADVANCE) {
                 if (quickScrubSection == NUM_QUICK_SCRUB_SECTIONS || quickScrubSection == 0) {
                     mAutoAdvanceAlarm.setAlarm(mHasAlarmRun
@@ -120,45 +116,36 @@
     }
 
     public void onQuickSwitch() {
-        mQuickSwitched = true;
-        quickSwitchIfReady();
-    }
-
-    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);
+        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);
     }
 
-    public void snapToNextTaskIfAvailable() {
-        if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
-            int toPage = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
-            goToPageWithHaptic(toPage, QUICK_SWITCH_START_DURATION);
+    public void snapToPageForCurrentQuickScrubSection() {
+        if (mInQuickScrub) {
+            goToPageWithHaptic(mRecentsView.getFirstTaskIndex() + mQuickScrubSection);
         }
     }
 
     private void goToPageWithHaptic(int pageToGoTo) {
-        goToPageWithHaptic(pageToGoTo, -1);
-    }
-
-    private void goToPageWithHaptic(int pageToGoTo, int overrideDuration) {
-        pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
+        pageToGoTo = Utilities.boundToRange(pageToGoTo, mStartPage, mRecentsView.getPageCount() - 1);
         if (pageToGoTo != mRecentsView.getNextPage()) {
-            int duration = overrideDuration > -1 ? overrideDuration
-                    : Math.abs(pageToGoTo - mRecentsView.getNextPage())
-                            * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
+            int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
+                    * QUICKSCRUB_SNAP_DURATION_PER_PAGE;
             mRecentsView.snapToPage(pageToGoTo, duration);
             mRecentsView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP,
                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
@@ -171,7 +158,7 @@
         if (mQuickScrubSection == NUM_QUICK_SCRUB_SECTIONS
                 && currPage < mRecentsView.getPageCount() - 1) {
             goToPageWithHaptic(currPage + 1);
-        } else if (mQuickScrubSection == 0 && currPage > 0) {
+        } else if (mQuickScrubSection == 0 && currPage > mStartPage) {
             goToPageWithHaptic(currPage - 1);
         }
         mHasAlarmRun = true;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 598c34d..f92d773 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,29 +15,34 @@
  */
 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.launcher3.BaseActivity;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
+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;
 
 /**
  * A simple activity to show the recently launched tasks
  */
-public class RecentsActivity extends BaseActivity {
+public class RecentsActivity extends ListActivity {
+
+    private ArrayAdapter<Task> mAdapter;
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        // 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));
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
+        plan.preloadPlan(new PreloadOptions(), new RecentsTaskLoader(this, 1, 1, 0), -1,
+                UserHandle.myUserId());
 
-        setContentView(R.layout.fallback_recents_activity);
+        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
+        mAdapter.addAll(plan.getTaskStack().getTasks());
+        setListAdapter(mAdapter);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 392b73f..7fe7751 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -16,8 +16,6 @@
 package com.android.quickstep;
 
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -53,6 +51,7 @@
  */
 @TargetApi(Build.VERSION_CODES.O)
 public class RecentsModel extends TaskStackChangeListener {
+
     // We do not need any synchronization for this variable as its only written on UI thread.
     private static RecentsModel INSTANCE;
 
@@ -84,16 +83,10 @@
     private int mTaskChangeId;
     private ISystemUiProxy mSystemUiProxy;
     private boolean mClearAssistCacheOnStackChange = true;
-    private final boolean mPreloadTasksInBackground;
 
     private RecentsModel(Context context) {
         mContext = context;
 
-        ActivityManager activityManager =
-                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-        mPreloadTasksInBackground = !activityManager.isLowRamDevice();
-        mMainThreadExecutor = new MainThreadExecutor();
-
         Resources res = context.getResources();
         mRecentsTaskLoader = new RecentsTaskLoader(mContext,
                 res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize),
@@ -107,6 +100,8 @@
             }
         };
         mRecentsTaskLoader.startLoader(mContext);
+
+        mMainThreadExecutor = new MainThreadExecutor();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
 
         mTaskChangeId = 1;
@@ -167,31 +162,6 @@
         }
     }
 
-    @Override
-    public void onTaskStackChangedBackground() {
-        int userId = UserHandle.myUserId();
-        if (!mPreloadTasksInBackground || !checkCurrentUserId(userId, false /* debug */)) {
-            // TODO: Only register this for the current user
-            return;
-        }
-
-        // Preload a fixed number of task icons/thumbnails in the background
-        ActivityManager.RunningTaskInfo runningTaskInfo =
-                ActivityManagerWrapper.getInstance().getRunningTask();
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
-        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-        launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
-        launchOpts.numVisibleTasks = 2;
-        launchOpts.numVisibleTaskThumbnails = 2;
-        launchOpts.onlyLoadForCache = true;
-        launchOpts.onlyLoadPausedActivities = true;
-        launchOpts.loadThumbnails = true;
-        PreloadOptions preloadOpts = new PreloadOptions();
-        preloadOpts.loadTitles = false;
-        plan.preloadPlan(preloadOpts, mRecentsTaskLoader, -1, userId);
-        mRecentsTaskLoader.loadTasks(plan, launchOpts);
-    }
-
     public boolean isLoadPlanValid(int resultId) {
         return mTaskChangeId == resultId;
     }
@@ -208,19 +178,6 @@
         return mSystemUiProxy;
     }
 
-    public void onStart() {
-        mRecentsTaskLoader.startLoader(mContext);
-        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.onTrimMemory(level);
-    }
-
     @WorkerThread
     public void preloadAssistData(int taskId, Bundle data) {
         mMainThreadExecutor.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/RecentsRootView.java b/quickstep/src/com/android/quickstep/RecentsRootView.java
deleted file mode 100644
index 3c69dbf..0000000
--- a/quickstep/src/com/android/quickstep/RecentsRootView.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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
new file mode 100644
index 0000000..ec0716c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -0,0 +1,564 @@
+/*
+ * 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.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.view.LayoutInflater;
+import android.view.View;
+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 {
+
+    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 final Launcher mLauncher;
+    private QuickScrubController mQuickScrubController;
+    private final ScrollState mScrollState = new ScrollState();
+    private boolean mOverviewStateEnabled;
+    private boolean mTaskStackListenerRegistered;
+    private LayoutTransition mLayoutTransition;
+    private Runnable mNextPageSwitchRunnable;
+
+    /**
+     * 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;
+
+    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);
+
+        mScrollState.isRtl = 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.
+        mLayoutTransition = new LayoutTransition();
+        mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
+        mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+
+        mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
+        mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
+        setLayoutTransition(mLayoutTransition);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mFirstTaskIndex = getPageCount();
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        updateTaskStackListenerState();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        updateTaskStackListenerState();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        updateTaskStackListenerState();
+    }
+
+    @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);
+            removeView(taskView);
+            loader.unloadTaskData(taskView.getTask());
+        }
+        setLayoutTransition(mLayoutTransition);
+
+        // Rebind and reset all task views
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            final Task task = tasks.get(i);
+            final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1 + mFirstTaskIndex);
+            taskView.bind(task);
+            taskView.resetVisualProperties();
+            loader.loadTaskData(task);
+        }
+        updateCurveProperties();
+        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()) {
+            // Use the same padding on both sides for symmetry.
+            float availableWidth = taskWidth - 2 * Math.max(padding.left, padding.right);
+            float availableHeight = profile.availableHeightPx - padding.top
+                    - sTempStableInsets.top - Math.max(padding.bottom,
+                    profile.heightPx * (1 - OverviewState.getVerticalProgress(profile, context)));
+            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
+    public void computeScroll() {
+        super.computeScroll();
+        updateCurveProperties();
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    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);
+        }
+        if (!needsReload) {
+            needsReload = !mModel.isLoadPlanValid(mLoadPlanId);
+        }
+        if (needsReload) {
+            mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+        }
+        mRunningTaskId = runningTaskId;
+        setCurrentPage(mFirstTaskIndex);
+        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
new file mode 100644
index 0000000..8425fa3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SimpleTaskView.java
@@ -0,0 +1,52 @@
+/*
+ * 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/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/TaskMenuView.java
similarity index 98%
rename from quickstep/src/com/android/quickstep/views/TaskMenuView.java
rename to quickstep/src/com/android/quickstep/TaskMenuView.java
index 94f440d..6bbcb37 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/TaskMenuView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.views;
+package com.android.quickstep;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -40,8 +40,6 @@
 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/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index eebfb91..75d8619 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -16,39 +16,27 @@
 
 package com.android.quickstep;
 
+import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 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;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import java.util.Collections;
-import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -98,10 +86,9 @@
         }
     }
 
-    public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener {
+    public static class SplitScreen extends TaskSystemShortcut {
 
         private Handler mHandler;
-        private TaskView mTaskView;
 
         public SplitScreen() {
             super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -117,55 +104,22 @@
             if (!task.isDockable) {
                 return null;
             }
-            mTaskView = taskView;
             return (v -> {
-                AbstractFloatingView.closeOpenViews(launcher, true,
-                        AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
-
-                if (ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key.id,
-                        ActivityOptionsCompat.makeSplitScreenOptions(true))) {
-                    ISystemUiProxy sysUiProxy = RecentsModel.getInstance(launcher).getSystemUiProxy();
-                    try {
-                        sysUiProxy.onSplitScreenInvoked();
-                    } catch (RemoteException e) {
-                        Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
-                        return;
-                    }
-
-                    final Runnable animStartedListener = () -> {
-                        mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
+                final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(true);
+                final Consumer<Boolean> resultCallback = success -> {
+                    if (success) {
                         launcher.<RecentsView>getOverviewPanel().removeView(taskView);
-                    };
+                    }
+                };
+                ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(
+                        task.key, options, resultCallback, mHandler);
 
-                    final int[] position = new int[2];
-                    taskView.getLocationOnScreen(position);
-                    final int width = (int) (taskView.getWidth() * taskView.getScaleX());
-                    final int height = (int) (taskView.getHeight() * taskView.getScaleY());
-                    final Rect taskBounds = new Rect(position[0], position[1],
-                            position[0] + width, position[1] + height);
-
-                    Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
-                            taskBounds.width(), taskBounds.height(), taskView, 1f, Color.BLACK);
-                    AppTransitionAnimationSpecsFuture future =
-                            new AppTransitionAnimationSpecsFuture(mHandler) {
-                        @Override
-                        public List<AppTransitionAnimationSpecCompat> composeSpecs() {
-                            return Collections.singletonList(new AppTransitionAnimationSpecCompat(
-                                    task.key.id, thumbnail, taskBounds));
-                        }
-                    };
-                    WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
-                            future, animStartedListener, mHandler, true /* scaleUp */);
-                }
+                // TODO improve transition; see:
+                // DockedFirstAnimationFrameEvent
+                // RecentsTransitionHelper#composeDockAnimationSpec
+                // WindowManagerWrapper#overridePendingAppTransitionMultiThumbFuture
             });
         }
-
-        @Override
-        public boolean onPreDraw() {
-            mTaskView.getViewTreeObserver().removeOnPreDrawListener(this);
-            WindowManagerWrapper.getInstance().endProlongedAnimations();
-            return true;
-        }
     }
 
     public static class Pin extends TaskSystemShortcut {
@@ -183,11 +137,7 @@
             if (sysUiProxy == null) {
                 return null;
             }
-            if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
-                return null;
-            }
-            if (ActivityManagerWrapper.getInstance().isLockToAppActive()) {
-                // We shouldn't be able to pin while an app is locked.
+            if (!ActivityManagerWrapper.getInstance().isLockToAppEnabled()) {
                 return null;
             }
             return view -> {
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
similarity index 96%
rename from quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 87bb53b..9b9c6f2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.views;
+package com.android.quickstep;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -33,10 +33,9 @@
 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;
@@ -75,7 +74,6 @@
         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() {
@@ -153,8 +151,7 @@
             } else {
                 final Configuration configuration =
                         getContext().getApplicationContext().getResources().getConfiguration();
-                final DeviceProfile profile = BaseActivity.fromContext(getContext())
-                        .getDeviceProfile();
+                final DeviceProfile profile = Launcher.getLauncher(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/views/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
similarity index 76%
rename from quickstep/src/com/android/quickstep/views/TaskView.java
rename to quickstep/src/com/android/quickstep/TaskView.java
index 7a575ad..b407f75 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.views;
+package com.android.quickstep;
+
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
 
 import android.animation.TimeInterpolator;
 import android.app.ActivityOptions;
@@ -30,8 +33,8 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.quickstep.views.RecentsView.PageCallbacks;
-import com.android.quickstep.views.RecentsView.ScrollState;
+import com.android.quickstep.RecentsView.PageCallbacks;
+import com.android.quickstep.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;
@@ -44,15 +47,17 @@
  */
 public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
 
-    /** 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;
+    /** 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)));
 
     /**
      * 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.4f;
+    private static final float MAX_PAGE_SCRIM_ALPHA = 0.8f;
 
     private static final long SCALE_ICON_DURATION = 120;
 
@@ -154,16 +159,38 @@
         setScaleY(1f);
         setTranslationX(0f);
         setTranslationY(0f);
-        setTranslationZ(0);
         setAlpha(1f);
     }
 
     @Override
-    public void onPageScroll(ScrollState scrollState) {
+    public int onPageScroll(ScrollState scrollState) {
         float curveInterpolation =
                 CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
+        float scale = 1 - curveInterpolation * CURVE_FACTOR;
+        setScaleX(scale);
+        setScaleY(scale);
+
+        // Make sure the biggest card (i.e. the one in front) shows on top of the adjacent ones.
+        setTranslationZ(scale);
 
         mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
+
+        float translation =
+                scrollState.distanceFromScreenCenter * curveInterpolation * CURVE_FACTOR;
+
+        if (scrollState.lastScrollType == SCROLL_TYPE_WORKSPACE) {
+            // Make sure that the task cards do not overlap with the workspace card
+            float min = scrollState.halfPageWidth * (1 - scale);
+            if (scrollState.isRtl) {
+                setTranslationX(Math.min(translation, min) - scrollState.prevPageExtraWidth);
+            } else {
+                setTranslationX(Math.max(translation, -min) + scrollState.prevPageExtraWidth);
+            }
+        } else {
+            setTranslationX(translation);
+        }
+        scrollState.prevPageExtraWidth = 0;
+        return SCROLL_TYPE_TASK;
     }
 
     private static final class TaskOutlineProvider extends ViewOutlineProvider {
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 768fbda..f35f6a6 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -21,6 +21,8 @@
 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;
@@ -62,14 +64,4 @@
     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 a522ef1..c166292 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,13 +21,15 @@
 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.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.Service;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.graphics.PointF;
 import android.os.Build;
 import android.os.Handler;
@@ -38,18 +40,15 @@
 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;
@@ -61,8 +60,6 @@
 @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 {
@@ -120,6 +117,7 @@
         @Override
         public void onQuickScrubStart() {
             mEventQueue.onQuickScrubStart();
+            sQuickScrubEnabled = true;
             TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
         }
 
@@ -132,37 +130,35 @@
         public void onQuickScrubEnd() {
             mEventQueue.onQuickScrubEnd();
             TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
+            sQuickScrubEnabled = false;
         }
-
-        @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) -> {};
 
     private static boolean sConnected = false;
+    private static boolean sQuickScrubEnabled = false;
 
     public static boolean isConnected() {
         return sConnected;
     }
 
+    public static boolean isQuickScrubEnabled() {
+        return sQuickScrubEnabled;
+    }
+
     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() {
@@ -170,9 +166,19 @@
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.getInstance(this);
         mMainThreadExecutor = new MainThreadExecutor();
-        mOverviewCommandHelper = new OverviewCommandHelper(this);
+
+        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);
+
         mMainThreadChoreographer = Choreographer.getInstance();
-        mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+        mNoOpEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
+        mEventQueue = mNoOpEventQueue;
 
         sConnected = true;
 
@@ -184,6 +190,7 @@
     @Override
     public void onDestroy() {
         sConnected = false;
+        sQuickScrubEnabled = false;
         super.onDestroy();
     }
 
@@ -194,45 +201,31 @@
     }
 
     private void onBinderPreMotionEvent(@HitTarget int downHitTarget) {
+        mRunningTask = mAM.getRunningTask();
+
         mEventQueue.reset();
-        TouchConsumer oldConsumer = mEventQueue.getConsumer();
-        if (oldConsumer.deferNextEventToMainThread()) {
+
+        if (mRunningTask == null) {
+            mEventQueue = mNoOpEventQueue;
+        } else if (mRunningTask.topActivity.equals(mLauncher)) {
+            mEventQueue = getLauncherEventQueue();
+        } else {
             mEventQueue = new MotionEventQueue(mMainThreadChoreographer,
-                    new DeferredTouchConsumer((v) -> getCurrentTouchConsumer(downHitTarget,
-                            oldConsumer.forceToLauncherConsumer(), v)));
-            mEventQueue.deferInit();
-        } else {
-            mEventQueue = new MotionEventQueue(
-                    mMainThreadChoreographer, getCurrentTouchConsumer(downHitTarget, false, null));
+                    new OtherActivityTouchConsumer(this, mRunningTask, mRecentsModel,
+                    mHomeIntent, mISystemUiProxy, mMainThreadExecutor,
+                    mBackgroundThreadChoreographer, downHitTarget));
         }
     }
 
-    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() {
+    private MotionEventQueue getLauncherEventQueue() {
         Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
         if (launcher == null) {
-            return mNoOpTouchConsumer;
+            return mNoOpEventQueue;
         }
+
         View target = launcher.getDragLayer();
-        return new LauncherTouchConsumer(launcher, target);
+        return new MotionEventQueue(mMainThreadChoreographer,
+                new LauncherTouchConsumer(launcher, target));
     }
 
     private static class LauncherTouchConsumer implements TouchConsumer {
@@ -317,12 +310,13 @@
             if (TouchConsumer.isInteractionQuick(interactionType)) {
                 Runnable action = () -> {
                     Runnable onComplete = null;
-                    if (interactionType == INTERACTION_QUICK_SWITCH) {
+                    if (interactionType == INTERACTION_QUICK_SCRUB) {
+                        mQuickScrubController.onQuickScrubStart(true);
+                    } else if (interactionType == INTERACTION_QUICK_SWITCH) {
                         onComplete = mQuickScrubController::onQuickSwitch;
                     }
-                    LauncherState fromState = mLauncher.getStateManager().getState();
-                    mLauncher.getStateManager().goToState(FAST_OVERVIEW, true, onComplete);
-                    mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
+                    mLauncher.getStateManager().goToState(OVERVIEW, true, 0,
+                            QUICK_SWITCH_START_DURATION, onComplete);
                 };
 
                 if (mLauncher.getWorkspace().runOnOverlayHidden(action)) {
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index af09842..19942c3 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -15,12 +15,9 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
 import static com.android.quickstep.QuickScrubController.QUICK_SWITCH_START_DURATION;
 import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
 import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
@@ -35,17 +32,16 @@
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.metrics.LogMaker;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.support.annotation.UiThread;
 import android.support.annotation.WorkerThread;
 import android.util.Log;
@@ -71,12 +67,9 @@
 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;
@@ -84,40 +77,6 @@
 
 import java.util.StringJoiner;
 
-class EventLogTags {
-    private EventLogTags() {
-    }  // don't instantiate
-
-    /** 524292 sysui_multi_action (content|4) */
-    public static final int SYSUI_MULTI_ACTION = 524292;
-
-    public static void writeSysuiMultiAction(Object[] content) {
-        android.util.EventLog.writeEvent(SYSUI_MULTI_ACTION, content);
-    }
-}
-
-class MetricsLogger {
-    private static MetricsLogger sMetricsLogger;
-
-    private static MetricsLogger getLogger() {
-        if (sMetricsLogger == null) {
-            sMetricsLogger = new MetricsLogger();
-        }
-        return sMetricsLogger;
-    }
-
-    protected void saveLog(Object[] rep) {
-        EventLogTags.writeSysuiMultiAction(rep);
-    }
-
-    public void write(LogMaker content) {
-        if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
-            content.setType(4/*MetricsEvent.TYPE_ACTION*/);
-        }
-        saveLog(content.serialize());
-    }
-}
-
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
@@ -230,17 +189,11 @@
 
     private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
     private Matrix mTmpMatrix = new Matrix();
-    private final long mTouchTimeMs;
-    private long mLauncherFrameDrawnTime;
-    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
-    WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs) {
+    WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context) {
         mContext = context;
         mRunningTaskId = runningTaskInfo.id;
-        mTouchTimeMs = touchTimeMs;
-        // Register the input consumer on the UI thread, to ensure that it runs after any pending
-        // unregister calls
-        mMainExecutor.execute(mInputConsumer::registerInputConsumer);
+        mInputConsumer.registerInputConsumer();
         initStateCallbacks();
     }
 
@@ -370,8 +323,7 @@
 
         // For the duration of the gesture, lock the screen orientation to ensure that we do not
         // rotate mid-quickscrub
-        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
-
+        mLauncher.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
         mRecentsView = mLauncher.getOverviewPanel();
         mQuickScrubController = mRecentsView.getQuickScrubController();
         mLauncherLayoutListener = new LauncherLayoutListener(mLauncher);
@@ -464,26 +416,11 @@
         if (mLauncherDrawnCallback != null) {
             mLauncherDrawnCallback.run();
         }
-        mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
     }
 
     private void initializeLauncherAnimationController() {
         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*/);
-        builder.setPackageName("com.android.systemui");
-        builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
-                "com.android.systemui.recents.RecentsActivity");
-        builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
-                transitionDelay);
-        mMetricsLogger.write(builder);
-        if (LatencyTrackerCompat.isEnabled(mContext)) {
-            LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
-        }
     }
 
     public void updateInteractionType(@InteractionType int interactionType) {
@@ -505,8 +442,6 @@
     }
 
     private void onQuickInteractionStart() {
-        mLauncher.getStateManager().goToState(FAST_OVERVIEW,
-                mWasLauncherAlreadyVisible || mGestureStarted);
         mQuickScrubController.onQuickScrubStart(false);
     }
 
@@ -574,13 +509,8 @@
                 for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
                     if (app.mode == MODE_CLOSING) {
                         transaction.setMatrix(app.leash, mTmpMatrix)
-                                .setWindowCrop(app.leash, mClipRect);
-
-                        if (app.isNotInRecents) {
-                            transaction.setAlpha(app.leash, 1 - shift);
-                        }
-
-                        transaction.show(app.leash);
+                                .setWindowCrop(app.leash, mClipRect)
+                                .show(app.leash);
                     }
                 }
                 transaction.apply();
@@ -595,8 +525,9 @@
                 mLauncherTransitionController.setPlayFraction(shift);
 
                 // Make sure the window follows the first task if it moves, e.g. during quick scrub.
-                View firstTask = mRecentsView.getPageAt(0);
-                int scrollForFirstTask = mRecentsView.getScrollForPage(0);
+                int firstTaskIndex = mRecentsView.getFirstTaskIndex();
+                View firstTask = mRecentsView.getPageAt(firstTaskIndex);
+                int scrollForFirstTask = mRecentsView.getScrollForPage(firstTaskIndex);
                 int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
                 if (offsetFromFirstTask != 0) {
                     synchronized (mTargetRect) {
@@ -727,14 +658,13 @@
 
     /** 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(mIsGoingToHome ?
-                        STATE_SCALED_CONTROLLER_RECENTS : STATE_SCALED_CONTROLLER_APP);
+                setStateOnUiThread((Float.compare(mCurrentShift.value, 0) == 0)
+                        ? STATE_SCALED_CONTROLLER_APP : STATE_SCALED_CONTROLLER_RECENTS);
             }
         });
         anim.start();
@@ -755,7 +685,7 @@
     }
 
     private void invalidateHandler() {
-        mCurrentShift.finishAnimation();
+        mCurrentShift.cancelAnimation();
 
         if (mGestureEndCallback != null) {
             mGestureEndCallback.run();
@@ -771,8 +701,7 @@
         mLauncherLayoutListener.close(false);
 
         // Restore the requested orientation to the user preference after the gesture has ended
-        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
-
+        mLauncher.updateRequestedOrientation();
         mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
deleted file mode 100644
index 7989e84..0000000
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 23e6e5b..0000000
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ /dev/null
@@ -1,588 +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.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);
-    }
-}