Merge "Import translations. DO NOT MERGE" into ub-launcher3-master
diff --git a/go/quickstep/res/layout/fallback_recents_activity.xml b/go/quickstep/res/layout/fallback_recents_activity.xml
new file mode 100644
index 0000000..653f463
--- /dev/null
+++ b/go/quickstep/res/layout/fallback_recents_activity.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+     Copyright (C) 2019 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.fallback.GoRecentsActivityRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/drag_layer"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <include
+        android:id="@+id/overview_panel"
+        layout="@layout/overview_panel"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:outlineProvider="none"
+        android:theme="@style/HomeScreenElementTheme" />
+</com.android.quickstep.fallback.GoRecentsActivityRootView>
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
index ac559ba..8f31e9f 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -44,18 +44,6 @@
 public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher>{
 
     @Override
-    public LayoutListener createLayoutListener(Launcher activity) {
-        // Go does not have draggable task snapshots.
-        return null;
-    }
-
-
-    @Override
-    public void executeOnWindowAvailable(Launcher activity, Runnable action) {
-        // Go does not support live tiles.
-    }
-
-    @Override
     public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
         LauncherState startState = activity.getStateManager().getRestState();
         activity.getStateManager().goToState(startState, activityVisible);
@@ -148,23 +136,11 @@
     }
 
     @Override
-    public boolean supportsLongSwipe(Launcher activity) {
-        // Go does not support long swipe from the app.
-        return false;
-    }
-
-    @Override
     public AlphaProperty getAlphaProperty(Launcher activity) {
         return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
     }
 
     @Override
-    public LongSwipeHelper getLongSwipeController(Launcher activity, int runningTaskId) {
-        // Go does not support long swipe from the app.
-        return null;
-    }
-
-    @Override
     public int getContainerType() {
         final Launcher launcher = getVisibleLauncher();
         return launcher != null ? launcher.getStateManager().getState().containerType
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
new file mode 100644
index 0000000..a186aaa
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 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.app.ActivityOptions;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.fallback.GoRecentsActivityRootView;
+import com.android.quickstep.views.IconRecentsView;
+
+/**
+ * A recents activity that displays recent tasks with an icon and small snapshot.
+ */
+public final class RecentsActivity extends BaseRecentsActivity {
+
+    private GoRecentsActivityRootView mRecentsRootView;
+    private IconRecentsView mIconRecentsView;
+
+    @Override
+    protected void initViews() {
+        setContentView(R.layout.fallback_recents_activity);
+        mRecentsRootView = findViewById(R.id.drag_layer);
+        mIconRecentsView = findViewById(R.id.overview_panel);
+    }
+
+    @Override
+    protected void reapplyUi() {
+        //TODO: Implement this depending on how insets will affect the view.
+    }
+
+    @Override
+    public BaseDragLayer getDragLayer() {
+        return mRecentsRootView;
+    }
+
+    @Override
+    public View getRootView() {
+        return mRecentsRootView;
+    }
+
+    @Override
+    public <T extends View> T getOverviewPanel() {
+        return (T) mIconRecentsView;
+    }
+
+    @Override
+    public ActivityOptions getActivityLaunchOptions(View v) {
+        //TODO: Hook into recents launch animation
+        return null;
+    }
+
+    @Override
+    protected void onStart() {
+        // Set the alpha to 1 before calling super, as it may get set back to 0 due to
+        // onActivityStart callback.
+        mIconRecentsView.setAlpha(0);
+        super.onStart();
+    }
+}
diff --git a/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
new file mode 100644
index 0000000..d748e89
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/fallback/GoRecentsActivityRootView.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.fallback;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.RecentsActivity;
+
+/**
+ * Minimal implementation of {@link BaseDragLayer} for Go's fallback recents activity.
+ */
+public final class GoRecentsActivityRootView extends BaseDragLayer<RecentsActivity> {
+    public GoRecentsActivityRootView(Context context, AttributeSet attrs) {
+        super(context, attrs, 1 /* alphaChannelCount */);
+    }
+}
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 16d2b67..5ec699c 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_hint_background_light.xml b/quickstep/recents_ui_overrides/res/drawable/chip_hint_background_light.xml
new file mode 100644
index 0000000..7b4da83
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/chip_hint_background_light.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <stroke
+        android:color="@color/chip_hint_foreground_color"
+        android:width="@dimen/chip_hint_border_width"/>
+    <corners android:radius="@dimen/chip_hint_corner_radius"/>
+    <padding
+        android:left="@dimen/chip_hint_outer_padding"
+        android:top="@dimen/chip_hint_outer_padding"
+        android:right="@dimen/chip_hint_outer_padding"
+        android:bottom="@dimen/chip_hint_outer_padding"/>
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/layout/fallback_recents_activity.xml b/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
similarity index 100%
rename from quickstep/res/layout/fallback_recents_activity.xml
rename to quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/hint.xml b/quickstep/recents_ui_overrides/res/layout/hint.xml
new file mode 100644
index 0000000..7e2d6af
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/hint.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 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.hints.HintView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/chip_hint_height"
+    android:layout_gravity="center_horizontal|bottom"
+    android:paddingStart="@dimen/chip_hint_start_padding"
+    android:paddingEnd="@dimen/chip_hint_end_padding"
+    android:background="@drawable/chip_hint_background_light"
+    android:gravity="center"
+    android:layout_marginHorizontal="@dimen/chip_hint_horizontal_margin"
+    android:orientation="horizontal"
+    android:elevation="@dimen/chip_hint_elevation"
+    android:layoutDirection="ltr">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/chip_icon_size"
+        android:layout_height="@dimen/chip_icon_size"
+        android:visibility="gone"
+        android:scaleType="fitCenter"
+        android:adjustViewBounds="true"
+        android:contentDescription="@null"/>
+
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/chip_text_height"
+        android:paddingTop="@dimen/chip_text_top_padding"
+        android:paddingStart="@dimen/chip_text_start_padding"
+        android:fontFamily="google-sans-medium"
+        android:textAlignment="textStart"
+        android:singleLine="true"
+        android:textColor="@color/chip_hint_foreground_color"
+        android:textSize="@dimen/chip_text_size"
+        android:ellipsize="none"
+        android:includeFontPadding="true"/>
+
+
+</com.android.quickstep.hints.HintView>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/layout/hint_container.xml b/quickstep/recents_ui_overrides/res/layout/hint_container.xml
new file mode 100644
index 0000000..336f63e
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/hint_container.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 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.hints.HintsContainer
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="center_horizontal"
+    android:orientation="horizontal"/>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
new file mode 100644
index 0000000..1e8d0cc
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="chip_hint_foreground_color">#fff</color>
+</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
new file mode 100644
index 0000000..b654d5c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="chip_hint_border_width">1dp</dimen>
+    <dimen name="chip_hint_corner_radius">20dp</dimen>
+    <dimen name="chip_hint_outer_padding">20dp</dimen>
+    <dimen name="chip_hint_start_padding">10dp</dimen>
+    <dimen name="chip_hint_end_padding">12dp</dimen>
+    <dimen name="chip_hint_horizontal_margin">20dp</dimen>
+    <dimen name="chip_hint_elevation">2dp</dimen>
+    <dimen name="chip_icon_size">16dp</dimen>
+    <dimen name="chip_text_height">26dp</dimen>
+    <dimen name="chip_text_top_padding">4dp</dimen>
+    <dimen name="chip_text_start_padding">10dp</dimen>
+    <dimen name="chip_text_size">14sp</dimen>
+</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
index de6f7a7..3602508 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
 import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -76,18 +75,13 @@
     public void onStateEnabled(Launcher launcher) {
         RecentsView rv = launcher.getOverviewPanel();
         rv.setOverviewStateEnabled(true);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            AbstractFloatingView.closeAllOpenViews(launcher);
-        } else {
-            AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
-        }
+        AbstractFloatingView.closeAllOpenViews(launcher);
     }
 
     @Override
     public void onStateDisabled(Launcher launcher) {
         RecentsView rv = launcher.getOverviewPanel();
         rv.setOverviewStateEnabled(false);
-        RecentsModel.INSTANCE.get(launcher).resetAssistCache();
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 1a61be6..0b3bd6c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -23,6 +23,8 @@
 import android.os.Build;
 import android.util.FloatProperty;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -30,8 +32,6 @@
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 
-import androidx.annotation.NonNull;
-
 /**
  * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
  * the basic view properties, this class also manages changes in the task visuals.
@@ -50,6 +50,9 @@
         if (state.overviewUi) {
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
+            mRecentsView.setHintVisibility(1);
+        } else {
+            mRecentsView.setHintVisibility(0);
         }
     }
 
@@ -60,6 +63,7 @@
 
         if (!toState.overviewUi) {
             builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
+            mRecentsView.setHintVisibility(0);
         }
 
         if (toState.overviewUi) {
@@ -71,6 +75,7 @@
             updateAnim.setDuration(config.duration);
             builder.play(updateAnim);
             mRecentsView.updateEmptyMessage();
+            builder.addOnFinishRunnable(() -> mRecentsView.setHintVisibility(1));
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 930cdc5..ffd3b4b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -43,7 +43,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.TestProtocol;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.SpringObjectAnimator;
@@ -55,7 +54,6 @@
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.views.LauncherLayoutListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -73,16 +71,6 @@
 public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
 
     @Override
-    public LayoutListener createLayoutListener(Launcher activity) {
-        return LauncherLayoutListener.resetAndGet(activity);
-    }
-
-    @Override
-    public void executeOnWindowAvailable(Launcher activity, Runnable action) {
-        activity.getWorkspace().runOnOverlayHidden(action);
-    }
-
-    @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
         LayoutUtils.calculateLauncherTaskSize(context, dp, outRect);
         if (dp.isVerticalBarLayout()) {
@@ -385,19 +373,6 @@
     }
 
     @Override
-    public boolean supportsLongSwipe(Launcher activity) {
-        return !activity.getDeviceProfile().isVerticalBarLayout();
-    }
-
-    @Override
-    public LongSwipeHelper getLongSwipeController(Launcher activity, int runningTaskId) {
-        if (activity.getDeviceProfile().isVerticalBarLayout()) {
-            return null;
-        }
-        return new LongSwipeHelper(activity, runningTaskId);
-    }
-
-    @Override
     public AlphaProperty getAlphaProperty(Launcher activity) {
         return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
similarity index 63%
rename from quickstep/src/com/android/quickstep/RecentsActivity.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index b76a1ab..8d7039e 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -15,12 +15,11 @@
  */
 package com.android.quickstep;
 
-import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
-import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl
+        .STATUS_BAR_TRANSITION_DURATION;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl
+        .STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -29,23 +28,16 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
-import android.content.Intent;
 import android.content.res.Configuration;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.view.View;
 
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.uioverrides.UiFactory;
-import com.android.launcher3.util.SystemUiController;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
@@ -56,46 +48,22 @@
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
 /**
- * A simple activity to show the recently launched tasks
+ * A recents activity that shows the recently launched tasks as swipable task cards.
+ * See {@link com.android.quickstep.views.RecentsView}.
  */
-public class RecentsActivity extends BaseDraggingActivity {
+public final class RecentsActivity extends BaseRecentsActivity {
 
     private Handler mUiHandler = new Handler(Looper.getMainLooper());
     private RecentsRootView mRecentsRootView;
     private FallbackRecentsView mFallbackRecentsView;
 
-    private Configuration mOldConfig;
-
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mOldConfig = new Configuration(getResources().getConfiguration());
-        initDeviceProfile();
-
+    protected void initViews() {
         setContentView(R.layout.fallback_recents_activity);
         mRecentsRootView = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
-
         mRecentsRootView.setup();
-
-        getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
-                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
-        RecentsActivityTracker.onRecentsActivityCreate(this);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        int diff = newConfig.diff(mOldConfig);
-        if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
-            onHandleConfigChanged();
-        }
-        mOldConfig.setTo(newConfig);
-        super.onConfigurationChanged(newConfig);
     }
 
     @Override
@@ -110,16 +78,10 @@
         }
     }
 
-    private void onHandleConfigChanged() {
-        mUserEventDispatcher = null;
-        initDeviceProfile();
-
-        AbstractFloatingView.closeOpenViews(this, true,
-                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
-        dispatchDeviceProfileChanged();
-
+    @Override
+    protected void onHandleConfigChanged() {
+        super.onHandleConfigChanged();
         mRecentsRootView.setup();
-        reapplyUi();
     }
 
     @Override
@@ -127,15 +89,12 @@
         mRecentsRootView.dispatchInsets();
     }
 
-    private void initDeviceProfile() {
+    @Override
+    protected DeviceProfile createDeviceProfile() {
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
-
-        // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
-        // activity.
-        mDeviceProfile = (mRecentsRootView != null) && isInMultiWindowMode()
+        return (mRecentsRootView != null) && isInMultiWindowMode()
                 ? dp.getMultiWindowProfile(this, mRecentsRootView.getLastKnownSize())
-                : dp.copy(this);
-        onDeviceProfileInitiated();
+                : super.createDeviceProfile();
     }
 
     @Override
@@ -211,55 +170,4 @@
         super.onStart();
         mFallbackRecentsView.resetTaskVisuals();
     }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-
-        // Workaround for b/78520668, explicitly trim memory once UI is hidden
-        onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
-    }
-
-    @Override
-    public void onEnterAnimationComplete() {
-        super.onEnterAnimationComplete();
-        UiFactory.onEnterAnimationComplete(this);
-    }
-
-    @Override
-    public void onTrimMemory(int level) {
-        super.onTrimMemory(level);
-        UiFactory.onTrimMemory(this, level);
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        RecentsActivityTracker.onRecentsActivityNewIntent(this);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        RecentsActivityTracker.onRecentsActivityDestroy(this);
-    }
-
-    @Override
-    public void onBackPressed() {
-        // TODO: Launch the task we came from
-        startHome();
-    }
-
-    public void startHome() {
-        startActivity(new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-    }
-
-    @Override
-    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
-        super.dump(prefix, fd, writer, args);
-        writer.println(prefix + "Misc:");
-        dumpMisc(writer);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
similarity index 100%
rename from quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
similarity index 100%
rename from quickstep/src/com/android/quickstep/fallback/RecentsRootView.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
similarity index 100%
rename from quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index 25e0af2..1eaa8bc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -15,10 +15,8 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
 import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
@@ -46,11 +44,7 @@
 
     @Override
     public void onStateEnabled(Launcher launcher) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            AbstractFloatingView.closeAllOpenViews(launcher);
-        } else {
-            AbstractFloatingView.closeAllOpenViewsExcept(launcher, TYPE_QUICKSTEP_PREVIEW);
-        }
+        AbstractFloatingView.closeAllOpenViews(launcher);
         dispatchWindowStateChanged(launcher);
     }
 
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index bb64c2b..eccef04 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -49,10 +49,6 @@
 @TargetApi(Build.VERSION_CODES.P)
 public interface ActivityControlHelper<T extends BaseDraggingActivity> {
 
-    LayoutListener createLayoutListener(T activity);
-
-    void executeOnWindowAvailable(T activity, Runnable action);
-
     void onTransitionCancelled(T activity, boolean activityVisible);
 
     int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
@@ -89,34 +85,15 @@
         return true;
     }
 
-    boolean supportsLongSwipe(T activity);
-
     AlphaProperty getAlphaProperty(T activity);
 
     /**
-     * Must return a non-null controller is supportsLongSwipe was true.
-     */
-    LongSwipeHelper getLongSwipeController(T activity, int runningTaskId);
-
-    /**
      * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
     int getContainerType();
 
     boolean isInLiveTileMode();
 
-    interface LayoutListener {
-
-        void open();
-
-        void setHandler(WindowTransformSwipeHandler handler);
-
-        void finish();
-
-        void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect,
-                float cornerRadius);
-    }
-
     interface ActivityInitListener {
 
         void register();
diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
new file mode 100644
index 0000000..c840132
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 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 android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.UiFactory;
+import com.android.launcher3.util.SystemUiController;
+import com.android.launcher3.util.Themes;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A base fallback recents activity that provides support for device profile changes, activity
+ * lifecycle tracking, and basic input handling from recents.
+ *
+ * This class is only used as a fallback in case the default launcher does not have a recents
+ * implementation.
+ */
+public abstract class BaseRecentsActivity extends BaseDraggingActivity {
+
+    private Configuration mOldConfig;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mOldConfig = new Configuration(getResources().getConfiguration());
+        initDeviceProfile();
+        initViews();
+
+        getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
+                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
+        RecentsActivityTracker.onRecentsActivityCreate(this);
+    }
+
+    /**
+     * Init drag layer and overview panel views.
+     */
+    abstract protected void initViews();
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        int diff = newConfig.diff(mOldConfig);
+        if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
+            onHandleConfigChanged();
+        }
+        mOldConfig.setTo(newConfig);
+        super.onConfigurationChanged(newConfig);
+    }
+
+    /**
+     * Logic for when device configuration changes (rotation, screen size change, multi-window,
+     * etc.)
+     */
+    protected void onHandleConfigChanged() {
+        mUserEventDispatcher = null;
+        initDeviceProfile();
+
+        AbstractFloatingView.closeOpenViews(this, true,
+                AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+        dispatchDeviceProfileChanged();
+
+        reapplyUi();
+    }
+
+    /**
+     * Initialize/update the device profile.
+     */
+    private void initDeviceProfile() {
+        mDeviceProfile = createDeviceProfile();
+        onDeviceProfileInitiated();
+    }
+
+    /**
+     * Generate the device profile to use in this activity.
+     * @return device profile
+     */
+    protected DeviceProfile createDeviceProfile() {
+        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
+
+        // In case we are reusing IDP, create a copy so that we don't conflict with Launcher
+        // activity.
+        return dp.copy(this);
+    }
+
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+
+        // Workaround for b/78520668, explicitly trim memory once UI is hidden
+        onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
+    }
+
+    @Override
+    public void onEnterAnimationComplete() {
+        super.onEnterAnimationComplete();
+        UiFactory.onEnterAnimationComplete(this);
+    }
+
+    @Override
+    public void onTrimMemory(int level) {
+        super.onTrimMemory(level);
+        UiFactory.onTrimMemory(this, level);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        RecentsActivityTracker.onRecentsActivityNewIntent(this);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        RecentsActivityTracker.onRecentsActivityDestroy(this);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // TODO: Launch the task we came from
+        startHome();
+    }
+
+    public void startHome() {
+        startActivity(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    @Override
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        super.dump(prefix, fd, writer, args);
+        writer.println(prefix + "Misc:");
+        dumpMisc(writer);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index b655d1d..a84d230 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -57,11 +57,6 @@
     }
 
     @Override
-    public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) {
-        action.run();
-    }
-
-    @Override
     public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
         // TODO:
     }
@@ -152,26 +147,6 @@
     }
 
     @Override
-    public LayoutListener createLayoutListener(RecentsActivity activity) {
-        // We do not change anything as part of layout changes in fallback activity. Return a
-        // default layout listener.
-        return new LayoutListener() {
-            @Override
-            public void open() { }
-
-            @Override
-            public void setHandler(WindowTransformSwipeHandler handler) { }
-
-            @Override
-            public void finish() { }
-
-            @Override
-            public void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect,
-                    float cornerRadius) { }
-        };
-    }
-
-    @Override
     public ActivityInitListener createActivityInitListener(
             BiPredicate<RecentsActivity, Boolean> onInitListener) {
         return new RecentsActivityTracker(onInitListener);
@@ -211,16 +186,6 @@
     }
 
     @Override
-    public boolean supportsLongSwipe(RecentsActivity activity) {
-        return false;
-    }
-
-    @Override
-    public LongSwipeHelper getLongSwipeController(RecentsActivity activity, int runningTaskId) {
-        return null;
-    }
-
-    @Override
     public AlphaProperty getAlphaProperty(RecentsActivity activity) {
         return activity.getDragLayer().getAlphaProperty(0);
     }
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
deleted file mode 100644
index ef56cb0..0000000
--- a/quickstep/src/com/android/quickstep/LongSwipeHelper.java
+++ /dev/null
@@ -1,181 +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.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_OVERSHOOT_DURATION;
-
-import android.animation.ValueAnimator;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.Interpolators.OvershootParams;
-import com.android.launcher3.uioverrides.PortraitStatesTouchController;
-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.FlingBlockCheck;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Utility class to handle long swipe from an app.
- * This assumes the presence of Launcher activity as long swipe is not supported on the
- * fallback activity.
- */
-public class LongSwipeHelper {
-
-    private static final float SWIPE_DURATION_MULTIPLIER =
-            Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS));
-
-    private final Launcher mLauncher;
-    private final int mRunningTaskId;
-
-    private float mMaxSwipeDistance = 1;
-    private AnimatorPlaybackController mAnimator;
-    private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
-
-    LongSwipeHelper(Launcher launcher, int runningTaskId) {
-        mLauncher = launcher;
-        mRunningTaskId = runningTaskId;
-        init();
-    }
-
-    private void init() {
-        mFlingBlockCheck.blockFling();
-
-        // Init animations
-        AllAppsTransitionController controller = mLauncher.getAllAppsController();
-        // TODO: Scale it down so that we can reach all-apps in screen space
-        mMaxSwipeDistance = Math.max(1, controller.getProgress() * controller.getShiftRange());
-
-        AnimatorSetBuilder builder = PortraitStatesTouchController.getOverviewToAllAppsAnimation();
-        mAnimator = mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, builder,
-                Math.round(2 * mMaxSwipeDistance), null, LauncherStateManager.ANIM_ALL);
-        mAnimator.dispatchOnStart();
-    }
-
-    public void onMove(float displacement) {
-        mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
-        mFlingBlockCheck.onEvent();
-    }
-
-    public void destroy() {
-        // TODO: We can probably also show the task view
-
-        mLauncher.getStateManager().goToState(OVERVIEW, false);
-    }
-
-    public void end(float velocity, boolean isFling, Runnable callback) {
-        float velocityPxPerMs = velocity / 1000;
-        long duration = MAX_SWIPE_DURATION;
-        Interpolator interpolator = DEACCEL;
-
-        final float currentFraction = mAnimator.getProgressFraction();
-        final boolean toAllApps;
-        float endProgress;
-
-        boolean blockedFling = isFling && mFlingBlockCheck.isBlocked();
-        if (blockedFling) {
-            isFling = false;
-        }
-
-        if (!isFling) {
-            toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS;
-            endProgress = toAllApps ? 1 : 0;
-
-            long expectedDuration = Math.abs(Math.round((endProgress - currentFraction)
-                    * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
-            duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
-
-            if (blockedFling && !toAllApps && !QUICKSTEP_SPRINGS.get()) {
-                Interpolators.OvershootParams overshoot = new OvershootParams(currentFraction,
-                        currentFraction, endProgress, velocityPxPerMs, (int) mMaxSwipeDistance);
-                duration = (overshoot.duration + duration);
-                duration = Utilities.boundToRange(duration, MIN_OVERSHOOT_DURATION,
-                        MAX_SWIPE_DURATION);
-                interpolator = overshoot.interpolator;
-                endProgress = overshoot.end;
-            }
-        } else {
-            toAllApps = velocity < 0;
-            endProgress = toAllApps ? 1 : 0;
-
-            float minFlingVelocity = mLauncher.getResources()
-                    .getDimension(R.dimen.quickstep_fling_min_velocity);
-            if (Math.abs(velocity) > minFlingVelocity && mMaxSwipeDistance > 0) {
-                float distanceToTravel = (endProgress - currentFraction) * mMaxSwipeDistance;
-
-                // we want the page's snap velocity to approximately match the velocity at
-                // which the user flings, so we scale the duration by a value near to the
-                // derivative of the scroll interpolator at zero, ie. 2.
-                long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs));
-                duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
-            }
-        }
-
-        final boolean finalIsFling = isFling;
-        mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
-
-        ValueAnimator animator = mAnimator.getAnimationPlayer();
-        animator.setDuration(duration).setInterpolator(interpolator);
-        animator.setFloatValues(currentFraction, endProgress);
-
-        if (QUICKSTEP_SPRINGS.get()) {
-            mAnimator.dispatchOnStartWithVelocity(endProgress, velocityPxPerMs);
-        }
-        animator.start();
-    }
-
-    private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) {
-        RecentsView rv = mLauncher.getOverviewPanel();
-        if (!toAllApps) {
-            rv.setIgnoreResetTask(mRunningTaskId);
-        }
-
-        mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
-        if (!toAllApps) {
-            DiscoveryBounce.showForOverviewIfNeeded(mLauncher);
-            rv.animateUpRunningTaskIconScale();
-            rv.setSwipeDownShouldLaunchApp(true);
-        }
-
-        mLauncher.getUserEventDispatcher().logStateChangeAction(
-                isFling ? Touch.FLING : Touch.SWIPE, Direction.UP,
-                ContainerType.NAVBAR, ContainerType.APP,
-                toAllApps ? ContainerType.ALLAPPS : ContainerType.TASKSWITCHER,
-                0);
-
-        callback.run();
-
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && toAllApps) {
-            rv.finishRecentsAnimation(true, null);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index ba66293..9fceab4 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -44,6 +44,11 @@
      * Adds the provided state flags to the global state and executes any callbacks as a result.
      */
     public void setState(int stateFlag) {
+        if (DEBUG_STATES) {
+            Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
+                    + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
+        }
+
         int oldState = mState;
         mState = mState | stateFlag;
 
@@ -68,6 +73,11 @@
      * as a result.
      */
     public void clearState(int stateFlag) {
+        if (DEBUG_STATES) {
+            Log.d(TAG, "[" + System.identityHashCode(this) + "] Removing "
+                    + convertToFlagNames(stateFlag) + " from " + convertToFlagNames(mState));
+        }
+
         int oldState = mState;
         mState = mState & ~stateFlag;
         notifyStateChangeHandlers(oldState);
@@ -109,24 +119,14 @@
         return (mState & stateMask) == stateMask;
     }
 
-    private void debugNewState(int stateFlag) {
-        if (!DEBUG_STATES) {
-            return;
-        }
-
-        int state = getState();
-        StringJoiner currentStateStr = new StringJoiner(", ", "[", "]");
-        String stateFlagStr = "Unknown-" + stateFlag;
+    private String convertToFlagNames(int flags) {
+        StringJoiner joiner = new StringJoiner(", ", "[", " (" + flags + ")]");
         for (int i = 0; i < mStateNames.length; i++) {
-            if ((state & (i << i)) != 0) {
-                currentStateStr.add(mStateNames[i]);
-            }
-            if (stateFlag == (1 << i)) {
-                stateFlagStr = mStateNames[i] + " (" + stateFlag + ")";
+            if ((flags & (1 << i)) != 0) {
+                joiner.add(mStateNames[i]);
             }
         }
-        Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding " + stateFlagStr + " to "
-                + currentStateStr);
+        return joiner.toString();
     }
 
 }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index a335827..77f900f 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -24,6 +24,7 @@
 
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
+import static com.android.quickstep.TouchInteractionService.EDGE_NAV_BAR;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -35,7 +36,6 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
-import android.os.Bundle;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -43,15 +43,17 @@
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
+import androidx.annotation.UiThread;
+
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
+import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.AssistDataReceiver;
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat;
@@ -59,9 +61,6 @@
 
 import java.util.function.Consumer;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
 /**
  * Touch consumer for handling events originating from an activity other than Launcher
  */
@@ -71,6 +70,7 @@
     public static final String DOWN_EVT = "OtherActivityTouchConsumer.DOWN";
     private static final String UP_EVT = "OtherActivityTouchConsumer.UP";
 
+    private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
     private final RecentsModel mRecentsModel;
     private final Intent mHomeIntent;
@@ -143,6 +143,19 @@
         if (mVelocityTracker == null) {
             return;
         }
+
+        // Proxy events to recents view
+        if (!isNavBarOnLeft() && !isNavBarOnRight()) {
+            if (mPassedDragSlop && mInteractionHandler != null
+                    && !mRecentsViewDispatcher.hasConsumer()) {
+                mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher());
+            }
+            int edgeFlags = ev.getEdgeFlags();
+            ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
+            mRecentsViewDispatcher.dispatchEvent(ev);
+            ev.setEdgeFlags(edgeFlags);
+        }
+
         mVelocityTracker.addMovement(ev);
         if (ev.getActionMasked() == ACTION_POINTER_UP) {
             mVelocityTracker.clear();
@@ -219,7 +232,7 @@
 
                 if (mPassedDragSlop && mInteractionHandler != null) {
                     // Move
-                    dispatchMotion(ev, displacement - mStartDisplacement, null);
+                    mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
 
                     if (FeatureFlags.SWIPE_HOME.get()) {
                         boolean isLandscape = isNavBarOnLeft() || isNavBarOnRight();
@@ -244,14 +257,6 @@
         }
     }
 
-    private void dispatchMotion(MotionEvent ev, float displacement, @Nullable Float velocityX) {
-        mInteractionHandler.updateDisplacement(displacement);
-        boolean isLandscape = isNavBarOnLeft() || isNavBarOnRight();
-        if (!isLandscape) {
-            mInteractionHandler.dispatchMotionEventToRecentsView(ev, velocityX);
-        }
-    }
-
     private void notifyGestureStarted() {
         if (mInteractionHandler == null) {
             return;
@@ -288,32 +293,16 @@
         mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
         handler.initWhenReady();
 
-        TraceHelper.beginSection("RecentsController");
-
         if (listenerSet != null) {
             listenerSet.addListener(handler);
             mSwipeSharedState.applyActiveRecentsAnimationState(handler);
         } else {
-            AssistDataReceiver assistDataReceiver = !mTaskOverlayFactory.needAssist() ? null :
-                    new AssistDataReceiver() {
-                        @Override
-                        public void onHandleAssistData(Bundle bundle) {
-                            if (mInteractionHandler == null) {
-                                // Interaction is probably complete
-                                mRecentsModel.preloadAssistData(mRunningTask.id, bundle);
-                            } else if (handler == mInteractionHandler) {
-                                handler.onAssistDataReceived(bundle);
-                            }
-                        }
-                    };
-
             RecentsAnimationListenerSet newListenerSet =
                     mSwipeSharedState.newRecentsAnimationListenerSet();
             newListenerSet.addListener(handler);
             BackgroundExecutor.get().submit(
                     () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                            mHomeIntent, assistDataReceiver, newListenerSet,
-                            null, null));
+                            mHomeIntent, null, newListenerSet, null, null));
         }
     }
 
@@ -331,8 +320,7 @@
                     : isNavBarOnLeft() ? -velocityX
                             : mVelocityTracker.getYVelocity(mActivePointerId);
 
-            dispatchMotion(ev, getDisplacement(ev) - mStartDisplacement, velocityX);
-
+            mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
             mInteractionHandler.onGestureEnded(velocity, velocityX);
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 2c3f77f..befeee0 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -16,17 +16,36 @@
 package com.android.quickstep;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserManager;
+import android.util.Log;
 
+import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 
 @SuppressWarnings("unused")
 public class QuickstepProcessInitializer extends MainProcessInitializer {
 
+    private static final String TAG = "QuickstepProcessInitializer";
+
     public QuickstepProcessInitializer(Context context) { }
 
     @Override
     protected void init(Context context) {
+        // Workaround for b/120550382, an external app can cause the launcher process to start for
+        // a work profile user which we do not support. Disable the application immediately when we
+        // detect this to be the case.
+        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        if (um.isManagedProfile()) {
+            PackageManager pm = context.getPackageManager();
+            pm.setApplicationEnabledSetting(context.getPackageName(),
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0 /* flags */);
+            Log.w(TAG, "Disabling " + BuildConfig.APPLICATION_ID
+                    + ", unable to run in a managed profile");
+            return;
+        }
+
         super.init(context);
 
         // Elevate GPU priority for Quickstep and Remote animations.
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index cedd952..00b3e90 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -73,6 +73,14 @@
         });
     }
 
+    public void startStabilizationSession() {
+        mStabilizer.startStabilizationSession();
+    }
+
+    public void endStabilizationSession() {
+        mStabilizer.endStabilizationSession();
+    }
+
     /**
      * Asynchronously fetches the list of recent tasks, reusing cached list if available.
      *
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index fb6090e..0822e61 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -30,17 +30,18 @@
 import java.util.function.BiPredicate;
 
 /**
- * Utility class to track create/destroy for RecentsActivity
+ * Utility class to track create/destroy for some {@link BaseRecentsActivity}.
  */
 @TargetApi(Build.VERSION_CODES.P)
-public class RecentsActivityTracker implements ActivityInitListener {
+public class RecentsActivityTracker<T extends BaseRecentsActivity> implements ActivityInitListener {
 
-    private static WeakReference<RecentsActivity> sCurrentActivity = new WeakReference<>(null);
+    private static WeakReference<BaseRecentsActivity> sCurrentActivity =
+            new WeakReference<>(null);
     private static final Scheduler sScheduler = new Scheduler();
 
-    private final BiPredicate<RecentsActivity, Boolean> mOnInitListener;
+    private final BiPredicate<T, Boolean> mOnInitListener;
 
-    public RecentsActivityTracker(BiPredicate<RecentsActivity, Boolean> onInitListener) {
+    public RecentsActivityTracker(BiPredicate<T, Boolean> onInitListener) {
         mOnInitListener = onInitListener;
     }
 
@@ -54,12 +55,12 @@
         sScheduler.clearReference(this);
     }
 
-    private boolean init(RecentsActivity activity, boolean visible) {
+    private boolean init(T activity, boolean visible) {
         return mOnInitListener.test(activity, visible);
     }
 
-    public static RecentsActivity getCurrentActivity() {
-        return sCurrentActivity.get();
+    public static <T extends BaseRecentsActivity> T getCurrentActivity() {
+        return (T) sCurrentActivity.get();
     }
 
     @Override
@@ -71,17 +72,17 @@
         context.startActivity(intent, options);
     }
 
-    public static void onRecentsActivityCreate(RecentsActivity activity) {
+    public static void onRecentsActivityCreate(BaseRecentsActivity activity) {
         sCurrentActivity = new WeakReference<>(activity);
         sScheduler.initIfPending(activity, false);
     }
 
 
-    public static void onRecentsActivityNewIntent(RecentsActivity activity) {
+    public static void onRecentsActivityNewIntent(BaseRecentsActivity activity) {
         sScheduler.initIfPending(activity, activity.isStarted());
     }
 
-    public static void onRecentsActivityDestroy(RecentsActivity activity) {
+    public static void onRecentsActivityDestroy(BaseRecentsActivity activity) {
         if (sCurrentActivity.get() == activity) {
             sCurrentActivity.clear();
         }
@@ -103,13 +104,14 @@
 
         @Override
         public void run() {
-            RecentsActivity activity = sCurrentActivity.get();
+            BaseRecentsActivity activity = sCurrentActivity.get();
             if (activity != null) {
                 initIfPending(activity, activity.isStarted());
             }
         }
 
-        public synchronized boolean initIfPending(RecentsActivity activity, boolean alreadyOnHome) {
+        public synchronized boolean initIfPending(BaseRecentsActivity activity,
+                boolean alreadyOnHome) {
             RecentsActivityTracker tracker = mPendingTracker.get();
             if (tracker != null) {
                 if (!tracker.init(activity, alreadyOnHome)) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index c2d4d80..5e7c1a1 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -21,17 +21,11 @@
 
 import android.view.MotionEvent;
 
-import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.UiThreadHelper;
-import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 
 import java.util.ArrayList;
-import java.util.concurrent.ExecutorService;
 import java.util.function.Supplier;
 
 import androidx.annotation.UiThread;
@@ -45,17 +39,10 @@
     // than the state callbacks as these run on the current worker thread.
     private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
 
-    public RemoteAnimationTargetSet targetSet;
+    public SwipeAnimationTargetSet targetSet;
 
-    private RecentsAnimationControllerCompat mController;
-    private boolean mInputConsumerEnabled = false;
-    private boolean mBehindSystemBars = true;
-    private boolean mSplitScreenMinimized = false;
+    private boolean mWindowThresholdCrossed = false;
 
-    private final ExecutorService mExecutorService =
-            new LooperExecutor(UiThreadHelper.getBackgroundLooper());
-
-    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
     private final InputConsumerController mInputConsumer;
     private final Supplier<TouchConsumer> mTouchProxySupplier;
 
@@ -71,19 +58,14 @@
     }
 
     @UiThread
-    public synchronized void setController(
-            RecentsAnimationControllerCompat controller, RemoteAnimationTargetSet targetSet) {
+    public synchronized void setController(SwipeAnimationTargetSet targetSet) {
         Preconditions.assertUIThread();
-        TraceHelper.partitionSection("RecentsController", "Set controller " + controller);
-        this.mController = controller;
         this.targetSet = targetSet;
 
-        if (controller == null) {
+        if (targetSet == null) {
             return;
         }
-        if (mInputConsumerEnabled) {
-            enableInputConsumer();
-        }
+        targetSet.setWindowThresholdCrossed(mWindowThresholdCrossed);
 
         if (!mCallbacks.isEmpty()) {
             for (Runnable action : new ArrayList<>(mCallbacks)) {
@@ -105,13 +87,12 @@
      * @param onFinishComplete A callback that runs on the main thread after the animation
      *                         controller has finished on the background thread.
      */
+    @UiThread
     public void finish(boolean toRecents, Runnable onFinishComplete) {
+        Preconditions.assertUIThread();
         if (!toRecents) {
-            mExecutorService.submit(() -> finishBg(false, onFinishComplete));
-            return;
-        }
-
-        mMainThreadExecutor.execute(() -> {
+            finishAndClear(false, onFinishComplete);
+        } else {
             if (mTouchInProgress) {
                 mFinishPending = true;
                 // Execute the callback
@@ -119,45 +100,39 @@
                     onFinishComplete.run();
                 }
             } else {
-                mExecutorService.submit(() -> finishBg(true, onFinishComplete));
+                finishAndClear(true, onFinishComplete);
             }
-        });
+        }
     }
 
-    protected void finishBg(boolean toRecents, Runnable onFinishComplete) {
-        RecentsAnimationControllerCompat controller = mController;
-        mController = null;
-        TraceHelper.endSection("RecentsController", "Finish " + controller
-                + ", toRecents=" + toRecents);
+    private void finishAndClear(boolean toRecents, Runnable onFinishComplete) {
+        SwipeAnimationTargetSet controller = targetSet;
+        targetSet = null;
         if (controller != null) {
-            controller.setInputConsumerEnabled(false);
-            controller.finish(toRecents);
-
-            if (onFinishComplete != null) {
-                mMainThreadExecutor.execute(onFinishComplete);
-            }
+            controller.finishController(toRecents, onFinishComplete);
         }
     }
 
     public void enableInputConsumer() {
-        mInputConsumerEnabled = true;
-        if (mInputConsumerEnabled) {
-            mExecutorService.submit(() -> {
-                RecentsAnimationControllerCompat controller = mController;
-                TraceHelper.partitionSection("RecentsController",
-                        "Enabling consumer on " + controller);
-                if (controller != null) {
-                    controller.setInputConsumerEnabled(true);
-                }
-            });
+        if (targetSet != null) {
+            targetSet.enableInputConsumer();
+        }
+    }
+
+    /**
+     * Indicates that the gesture has crossed the window boundary threshold and system UI can be
+     * update the represent the window behind
+     */
+    public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
+        if (mWindowThresholdCrossed != windowThresholdCrossed) {
+            mWindowThresholdCrossed = windowThresholdCrossed;
+            if (targetSet != null) {
+                targetSet.setWindowThresholdCrossed(windowThresholdCrossed);
+            }
         }
     }
 
     public void enableTouchProxy() {
-        mMainThreadExecutor.execute(this::enableTouchProxyUi);
-    }
-
-    private void enableTouchProxyUi() {
         mInputConsumer.setTouchListener(this::onInputConsumerTouch);
     }
 
@@ -171,7 +146,7 @@
             mTouchInProgress = false;
             if (mFinishPending) {
                 mFinishPending = false;
-                mExecutorService.submit(() -> finishBg(true, null));
+                finishAndClear(true /* toRecents */, null);
             }
         }
         if (mTouchConsumer != null) {
@@ -181,54 +156,7 @@
         return true;
     }
 
-    public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) {
-        if (mBehindSystemBars == behindSystemBars) {
-            return;
-        }
-        mBehindSystemBars = behindSystemBars;
-        mExecutorService.submit(() -> {
-            RecentsAnimationControllerCompat controller = mController;
-            TraceHelper.partitionSection("RecentsController",
-                    "Setting behind system bars on " + controller);
-            if (controller != null) {
-                controller.setAnimationTargetsBehindSystemBars(behindSystemBars);
-            }
-        });
-    }
-
-    /**
-     * NOTE: As a workaround for conflicting animations (Launcher animating the task leash, and
-     * SystemUI resizing the docked stack, which resizes the task), we currently only set the
-     * minimized mode, and not the inverse.
-     * TODO: Synchronize the minimize animation with the launcher animation
-     */
-    public void setSplitScreenMinimizedForTransaction(boolean minimized) {
-        if (mSplitScreenMinimized || !minimized) {
-            return;
-        }
-        mSplitScreenMinimized = minimized;
-        mExecutorService.submit(() -> {
-            RecentsAnimationControllerCompat controller = mController;
-            TraceHelper.partitionSection("RecentsController",
-                    "Setting minimize dock on " + controller);
-            if (controller != null) {
-                controller.setSplitScreenMinimized(minimized);
-            }
-        });
-    }
-
-    public void hideCurrentInputMethod() {
-        mExecutorService.submit(() -> {
-            RecentsAnimationControllerCompat controller = mController;
-            TraceHelper.partitionSection("RecentsController",
-                    "Hiding currentinput method on " + controller);
-            if (controller != null) {
-                controller.hideCurrentInputMethod();
-            }
-        });
-    }
-
-    public RecentsAnimationControllerCompat getController() {
-        return mController;
+    public SwipeAnimationTargetSet getController() {
+        return targetSet;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index e61c00a..f3e1545 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,8 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
-
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.ComponentCallbacks2;
@@ -28,6 +26,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
+
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
@@ -35,11 +34,14 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
+
 import java.util.ArrayList;
 import java.util.function.Consumer;
 
 import androidx.annotation.WorkerThread;
 
+import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+
 /**
  * Singleton class to load and manage recents model.
  */
@@ -52,14 +54,10 @@
     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
             new MainThreadInitializedObject<>(c -> new RecentsModel(c));
 
-    private final SparseArray<Bundle> mCachedAssistData = new SparseArray<>(1);
-    private final ArrayList<AssistDataListener> mAssistDataListeners = new ArrayList<>();
-
     private final Context mContext;
     private final MainThreadExecutor mMainThreadExecutor;
 
     private ISystemUiProxy mSystemUiProxy;
-    private boolean mClearAssistCacheOnStackChange = true;
 
     private final RecentTasksList mTaskList;
     private final TaskIconCache mIconCache;
@@ -90,6 +88,14 @@
         return mThumbnailCache;
     }
 
+    public void startStabilizationSession() {
+        mTaskList.startStabilizationSession();
+    }
+
+    public void endStabilizationSession() {
+        mTaskList.endStabilizationSession();
+    }
+
     /**
      * Fetches the list of recent tasks.
      *
@@ -162,16 +168,6 @@
         });
     }
 
-    @Override
-    public void onTaskStackChanged() {
-        Preconditions.assertUIThread();
-        if (mClearAssistCacheOnStackChange) {
-            mCachedAssistData.clear();
-        } else {
-            mClearAssistCacheOnStackChange = true;
-        }
-    }
-
     public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
         mSystemUiProxy = systemUiProxy;
     }
@@ -243,44 +239,4 @@
                             + ": ", e);
         }
     }
-
-    public void resetAssistCache() {
-        mCachedAssistData.clear();
-    }
-
-    @WorkerThread
-    public void preloadAssistData(int taskId, Bundle data) {
-        mMainThreadExecutor.execute(() -> {
-            mCachedAssistData.put(taskId, data);
-            // We expect a stack change callback after the assist data is set. So ignore the
-            // very next stack change callback.
-            mClearAssistCacheOnStackChange = false;
-
-            int count = mAssistDataListeners.size();
-            for (int i = 0; i < count; i++) {
-                mAssistDataListeners.get(i).onAssistDataReceived(taskId);
-            }
-        });
-    }
-
-    public Bundle getAssistData(int taskId) {
-        Preconditions.assertUIThread();
-        return mCachedAssistData.get(taskId);
-    }
-
-    public void addAssistDataListener(AssistDataListener listener) {
-        mAssistDataListeners.add(listener);
-    }
-
-    public void removeAssistDataListener(AssistDataListener listener) {
-        mAssistDataListeners.remove(listener);
-    }
-
-    /**
-     * Callback for receiving assist data
-     */
-    public interface AssistDataListener {
-
-        void onAssistDataReceived(int taskId);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/SwipeSharedState.java b/quickstep/src/com/android/quickstep/SwipeSharedState.java
index 15914ba..7c6638a 100644
--- a/quickstep/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/src/com/android/quickstep/SwipeSharedState.java
@@ -15,6 +15,9 @@
  */
 package com.android.quickstep;
 
+import android.util.Log;
+
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
@@ -25,23 +28,35 @@
  */
 public class SwipeSharedState implements SwipeAnimationListener {
 
+    private final OverviewComponentObserver mOverviewComponentObserver;
+
     private RecentsAnimationListenerSet mRecentsAnimationListener;
     private SwipeAnimationTargetSet mLastAnimationTarget;
+
     private boolean mLastAnimationCancelled = false;
+    private boolean mLastAnimationRunning = false;
 
     public boolean canGestureBeContinued;
     public boolean goingToLauncher;
 
+    public SwipeSharedState(OverviewComponentObserver overviewComponentObserver) {
+        mOverviewComponentObserver = overviewComponentObserver;
+    }
+
     @Override
     public final void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
         mLastAnimationTarget = targetSet;
+
         mLastAnimationCancelled = false;
+        mLastAnimationRunning = true;
     }
 
     @Override
     public final void onRecentsAnimationCanceled() {
         mLastAnimationTarget = null;
+
         mLastAnimationCancelled = true;
+        mLastAnimationRunning = false;
     }
 
     private void clearListenerState() {
@@ -51,12 +66,31 @@
         mRecentsAnimationListener = null;
         mLastAnimationTarget = null;
         mLastAnimationCancelled = false;
+        mLastAnimationRunning = false;
+    }
+
+    private void onSwipeAnimationFinished(SwipeAnimationTargetSet targetSet) {
+        if (mLastAnimationTarget == targetSet) {
+            mLastAnimationRunning = false;
+        }
     }
 
     public RecentsAnimationListenerSet newRecentsAnimationListenerSet() {
         Preconditions.assertUIThread();
+
+        if (mLastAnimationRunning) {
+            String msg = "New animation started before completing old animation";
+            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                throw new IllegalArgumentException(msg);
+            } else {
+                Log.e("SwipeSharedState", msg, new Exception());
+            }
+        }
+
         clearListenerState();
-        mRecentsAnimationListener = new RecentsAnimationListenerSet();
+        mRecentsAnimationListener = new RecentsAnimationListenerSet(mOverviewComponentObserver
+                .getActivityControlHelper().shouldMinimizeSplitScreen(),
+                this::onSwipeAnimationFinished);
         mRecentsAnimationListener.addListener(this);
         return mRecentsAnimationListener;
     }
diff --git a/quickstep/src/com/android/quickstep/TaskListStabilizer.java b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
index 3eb26b4..4c63f81 100644
--- a/quickstep/src/com/android/quickstep/TaskListStabilizer.java
+++ b/quickstep/src/com/android/quickstep/TaskListStabilizer.java
@@ -15,13 +15,10 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASK_STABILIZER;
-
 import android.app.ActivityManager.RecentTaskInfo;
 import android.content.ComponentName;
 import android.os.Process;
 import android.os.SystemClock;
-import android.util.Log;
 
 import com.android.launcher3.util.IntArray;
 import com.android.systemui.shared.recents.model.Task;
@@ -33,98 +30,77 @@
 import java.util.Collections;
 import java.util.List;
 
+/**
+ * Keeps the task list stable during quick switch gestures. So if you swipe right to switch from app
+ * A to B, you can then swipe right again to get to app C or left to get back to A.
+ */
 public class TaskListStabilizer {
 
     private static final long TASK_CACHE_TIMEOUT_MS = 5000;
 
-    private static final int INVALID_TASK_ID = -1;
-
     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
 
         @Override
         public void onTaskCreated(int taskId, ComponentName componentName) {
-            onTaskCreatedInternal(taskId);
-        }
-
-        @Override
-        public void onTaskMovedToFront(int taskId) {
-            onTaskMovedToFrontInternal(taskId);
+            endStabilizationSession();
         }
 
         @Override
         public void onTaskRemoved(int taskId) {
-            onTaskRemovedInternal(taskId);
+            endStabilizationSession();
         }
     };
 
-    // Task ids ordered based on recency, 0th index is the latest task
-    private final IntArray mOrderedTaskIds;
+    // Task ids ordered based on recency, 0th index is the least recent task
+    private final IntArray mSystemOrder;
+    private final IntArray mStabilizedOrder;
 
     // Wrapper objects used for sorting tasks
     private final ArrayList<TaskWrapper> mTaskWrappers = new ArrayList<>();
 
-    // Information about recent task re-order which has not been applied yet
-    private int mScheduledMoveTaskId = INVALID_TASK_ID;
-    private long mScheduledMoveTime = 0;
+    private boolean mInStabilizationSession;
+    private long mSessionStartTime;
 
     public TaskListStabilizer() {
-        if (ENABLE_TASK_STABILIZER.get()) {
-            // Initialize the task ids map
-            List<RecentTaskInfo> rawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
-                    Integer.MAX_VALUE, Process.myUserHandle().getIdentifier());
-            mOrderedTaskIds = new IntArray(rawTasks.size());
-            for (RecentTaskInfo info : rawTasks) {
-                mOrderedTaskIds.add(new TaskKey(info).id);
-            }
-
-            ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
-        } else {
-            mOrderedTaskIds = null;
+        // Initialize the task ids map
+        List<RecentTaskInfo> rawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
+                Integer.MAX_VALUE, Process.myUserHandle().getIdentifier());
+        mSystemOrder = new IntArray(rawTasks.size());
+        for (RecentTaskInfo info : rawTasks) {
+            mSystemOrder.add(new TaskKey(info).id);
         }
+        // We will lazily copy the task id's from mSystemOrder when a stabilization session starts.
+        mStabilizedOrder = new IntArray(rawTasks.size());
+
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
     }
 
-    private synchronized void onTaskCreatedInternal(int taskId) {
-        applyScheduledMoveUnchecked();
-        mOrderedTaskIds.add(taskId);
-    }
-
-    private synchronized void onTaskRemovedInternal(int taskId) {
-        applyScheduledMoveUnchecked();
-        mOrderedTaskIds.removeValue(taskId);
-    }
-
-    private void applyScheduledMoveUnchecked() {
-        if (mScheduledMoveTaskId != INVALID_TASK_ID) {
-            // Mode the scheduled task to front
-            mOrderedTaskIds.removeValue(mScheduledMoveTaskId);
-            mOrderedTaskIds.add(mScheduledMoveTaskId);
-            mScheduledMoveTaskId = INVALID_TASK_ID;
+    public synchronized void startStabilizationSession() {
+        if (!mInStabilizationSession) {
+            mStabilizedOrder.clear();
+            mStabilizedOrder.addAll(mSystemOrder);
         }
+        mInStabilizationSession = true;
+        mSessionStartTime = SystemClock.uptimeMillis();
     }
 
-    /**
-     * Checks if the scheduled move has timed out and moves the task to front accordingly.
-     */
-    private void applyScheduledMoveIfTime() {
-        if (mScheduledMoveTaskId != INVALID_TASK_ID
-                && (SystemClock.uptimeMillis() - mScheduledMoveTime) > TASK_CACHE_TIMEOUT_MS) {
-            applyScheduledMoveUnchecked();
-        }
+    public synchronized void endStabilizationSession() {
+        mInStabilizationSession = false;
     }
 
-    private synchronized void onTaskMovedToFrontInternal(int taskId) {
-        applyScheduledMoveIfTime();
-        mScheduledMoveTaskId = taskId;
-        mScheduledMoveTime = SystemClock.uptimeMillis();
-    }
-
-
     public synchronized ArrayList<Task> reorder(ArrayList<Task> tasks) {
-        if (!ENABLE_TASK_STABILIZER.get()) {
-            return tasks;
+        mSystemOrder.clear();
+        for (Task task : tasks) {
+            mSystemOrder.add(task.key.id);
         }
 
-        applyScheduledMoveIfTime();
+        if ((SystemClock.uptimeMillis() - mSessionStartTime) > TASK_CACHE_TIMEOUT_MS) {
+            endStabilizationSession();
+        }
+
+        if (!mInStabilizationSession) {
+            return tasks;
+        }
 
         // Ensure that we have enough wrappers
         int taskCount = tasks.size();
@@ -139,7 +115,7 @@
         for (int i = 0; i < taskCount; i++){
             TaskWrapper wrapper = listToSort.get(i);
             wrapper.task = tasks.get(i);
-            wrapper.index = mOrderedTaskIds.indexOf(wrapper.task.key.id);
+            wrapper.index = mStabilizedOrder.indexOf(wrapper.task.key.id);
 
             // Ensure that missing tasks are put in the front, in the order they appear in the
             // original list
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index cc49d46..d979c99 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -31,8 +31,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.AnyThread;
-
 /**
  * Factory class to create and add an overlays on the TaskView
  */
@@ -51,11 +49,6 @@
             new MainThreadInitializedObject<>(c -> Overrides.getObject(TaskOverlayFactory.class,
                     c, R.string.task_overlay_factory_class));
 
-    @AnyThread
-    public boolean needAssist() {
-        return false;
-    }
-
     public TaskOverlay createOverlay(View thumbnailView) {
         return new TaskOverlay();
     }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 74ac1c6..0ccd141 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -37,6 +37,8 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -55,6 +57,9 @@
 public class TouchInteractionService extends Service {
 
     public static final MainThreadExecutor MAIN_THREAD_EXECUTOR = new MainThreadExecutor();
+    public static final LooperExecutor BACKGROUND_EXECUTOR =
+            new LooperExecutor(UiThreadHelper.getBackgroundLooper());
+
     public static final TouchInteractionLog TOUCH_INTERACTION_LOG = new TouchInteractionLog();
 
     public static final int EDGE_NAV_BAR = 1 << 8;
@@ -174,7 +179,7 @@
         mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.INSTANCE.get(this);
-        mSwipeSharedState = new SwipeSharedState();
+        mSwipeSharedState = new SwipeSharedState(mOverviewComponentObserver);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mInputConsumer.registerInputConsumer();
 
@@ -249,10 +254,8 @@
                     mOverviewComponentObserver.getActivityControlHelper();
             boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
             return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
-                    mOverviewComponentObserver.getOverviewIntent(),
-                    mOverviewComponentObserver.getActivityControlHelper(),
-                    shouldDefer, mOverviewCallbacks,
-                    mTaskOverlayFactory, mInputConsumer,
+                    mOverviewComponentObserver.getOverviewIntent(), activityControl,
+                    shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
                     this::onConsumerInactive, mSwipeSharedState);
         }
     }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index aeb648d..f578149 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -53,14 +53,16 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
 
@@ -73,7 +75,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -87,31 +88,30 @@
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
-import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.quickstep.views.LiveTileOverlay;
 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.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.WindowCallbacksCompat;
 
 import java.util.function.BiFunction;
+import java.util.function.Consumer;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
-        implements SwipeAnimationListener {
+        implements SwipeAnimationListener, OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[19] : null;
@@ -164,21 +164,11 @@
             getFlagForIndex(16, "STATE_START_NEW_TASK");
     private static final int STATE_CURRENT_TASK_FINISHED =
             getFlagForIndex(17, "STATE_CURRENT_TASK_FINISHED");
-    private static final int STATE_ASSIST_DATA_RECEIVED =
-            getFlagForIndex(18, "STATE_ASSIST_DATA_RECEIVED");
 
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
             | STATE_LAUNCHER_STARTED;
 
-    private static final int LONG_SWIPE_ENTER_STATE =
-            STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_STARTED
-                    | STATE_APP_CONTROLLER_RECEIVED;
-
-    private static final int LONG_SWIPE_START_STATE =
-            STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_STARTED
-                    | STATE_APP_CONTROLLER_RECEIVED | STATE_SCREENSHOT_CAPTURED;
-
     // For debugging, keep in sync with above states
 
     enum GestureEndTarget {
@@ -200,11 +190,15 @@
             this.containerType = containerType;
         }
 
-        // 0 is app, 1 is overview
+        /** 0 is app, 1 is overview */
         public final float endShift;
+        /** The state to apply when we reach this final target */
         public final int endState;
+        /** Whether the target is in the launcher activity */
         public final boolean isLauncher;
+        /** Whether the user can start a new gesture while this one is finishing */
         public final boolean canBeContinued;
+        /** Used to log where the user ended up after the gesture ends */
         public final int containerType;
     }
 
@@ -252,10 +246,10 @@
     private AnimatorPlaybackController mLauncherTransitionController;
 
     private T mActivity;
-    private LayoutListener mLayoutListener;
     private RecentsView mRecentsView;
     private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
     private AnimationFactory mAnimationFactory = (t) -> { };
+    private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
 
     private boolean mWasLauncherAlreadyVisible;
 
@@ -268,12 +262,6 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
-    private boolean mLongSwipeMode = false;
-    private float mLongSwipeDisplacement = 0;
-    private LongSwipeHelper mLongSwipeController;
-
-    private Bundle mAssistData;
-
     WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
             long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
@@ -345,11 +333,6 @@
                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
                         | STATE_GESTURE_STARTED,
                 this::setupLauncherUiAfterSwipeUpAnimation);
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
-                        | STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_SCALED_CONTROLLER_RECENTS
-                        | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
-                        | STATE_GESTURE_STARTED | STATE_ASSIST_DATA_RECEIVED,
-                this::preloadAssistData);
 
         mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
         mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
@@ -358,8 +341,8 @@
                 | STATE_SCALED_CONTROLLER_LAST_TASK,
                 this::notifyTransitionCancelled);
 
-        mStateCallback.addCallback(LONG_SWIPE_ENTER_STATE, this::checkLongSwipeCanEnter);
-        mStateCallback.addCallback(LONG_SWIPE_START_STATE, this::checkLongSwipeCanStart);
+        mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
+                mRecentsAnimationWrapper::enableInputConsumer);
 
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
@@ -410,7 +393,6 @@
             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
             initStateCallbacks();
             mStateCallback.setState(oldState);
-            mLayoutListener.setHandler(null);
         }
         mWasLauncherAlreadyVisible = alreadyOnHome;
         mActivity = activity;
@@ -423,18 +405,18 @@
         }
 
         mRecentsView = activity.getOverviewPanel();
-        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, (applier) -> {
-            mSyncTransactionApplier = applier;
-        });
+        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView,
+                applier ->  mSyncTransactionApplier = applier );
         mRecentsView.setEnableFreeScroll(false);
+
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-            if (!mLongSwipeMode && mGestureEndTarget != HOME) {
+            if (mGestureEndTarget != HOME) {
                 updateFinalShift();
             }
         });
         mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
         mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
-        mLayoutListener = mActivityControlHelper.createLayoutListener(mActivity);
+        mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
 
         mStateCallback.setState(STATE_LAUNCHER_PRESENT);
         if (alreadyOnHome) {
@@ -480,7 +462,7 @@
         }
 
         setupRecentsViewUi();
-        mLayoutListener.open();
+        activity.getRootView().setOnApplyWindowInsetsListener(this);
         mStateCallback.setState(STATE_LAUNCHER_STARTED);
     }
 
@@ -522,7 +504,6 @@
     }
 
     private void initializeLauncherAnimationController() {
-        mLayoutListener.setHandler(this);
         buildAnimationController();
 
         if (LatencyTrackerCompat.isEnabled(mContext)) {
@@ -542,24 +523,8 @@
         return TaskView.getCurveScaleForInterpolation(interpolation);
     }
 
-    @UiThread
-    public void dispatchMotionEventToRecentsView(MotionEvent event, @Nullable Float velocityX) {
-        if (mRecentsView == null) {
-            return;
-        }
-        // Pass the motion events to RecentsView to allow scrolling during swipe up.
-        if (!mDispatchedDownEvent) {
-            // The first event we dispatch should be ACTION_DOWN.
-            mDispatchedDownEvent = true;
-            MotionEvent downEvent = MotionEvent.obtain(event);
-            downEvent.setAction(MotionEvent.ACTION_DOWN);
-            int flags = downEvent.getEdgeFlags();
-            downEvent.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
-            mRecentsView.simulateTouchEvent(downEvent, velocityX);
-            downEvent.recycle();
-        }
-
-        mRecentsView.simulateTouchEvent(event, velocityX);
+    public Consumer<MotionEvent> getRecentsViewDispatcher() {
+        return mRecentsView != null ? mRecentsView::dispatchTouchEvent : null;
     }
 
     @UiThread
@@ -568,16 +533,7 @@
         displacement = -displacement;
         if (displacement > mTransitionDragLength && mTransitionDragLength > 0) {
             mCurrentShift.updateValue(1);
-
-            if (!mLongSwipeMode && !FeatureFlags.SWIPE_HOME.get()) {
-                onLongSwipeEnabled();
-            }
-            mLongSwipeDisplacement = displacement - mTransitionDragLength;
-            onLongSwipeDisplacementUpdated();
         } else {
-            if (mLongSwipeMode) {
-                onLongSwipeDisabled();
-            }
             float translation = Math.max(displacement, 0);
             float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
             mCurrentShift.updateValue(shift);
@@ -598,14 +554,18 @@
         }
     }
 
-    /**
-     * Called by {@link #mLayoutListener} when launcher layout changes
-     */
-    public void buildAnimationController() {
+    private void buildAnimationController() {
         initTransitionEndpoints(mActivity.getDeviceProfile());
         mAnimationFactory.createActivityController(mTransitionDragLength);
     }
 
+    @Override
+    public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
+        WindowInsets result = view.onApplyWindowInsets(windowInsets);
+        buildAnimationController();
+        return result;
+    }
+
     private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
         mLauncherTransitionController = anim;
         mLauncherTransitionController.dispatchOnStart();
@@ -616,7 +576,7 @@
     private void updateFinalShift() {
         float shift = mCurrentShift.value;
 
-        RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
             float offsetX = 0;
             if (mRecentsView != null) {
@@ -631,19 +591,13 @@
                     .setSyncTransactionApplier(mSyncTransactionApplier);
             mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
                     mTransformParams);
-
-            boolean passedThreshold = shift > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
-            mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
-            if (mActivityControlHelper.shouldMinimizeSplitScreen()) {
-                mRecentsAnimationWrapper.setSplitScreenMinimizedForTransaction(passedThreshold);
-            }
+            mRecentsAnimationWrapper.setWindowThresholdCrossed(
+                    shift > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD);
         }
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationWrapper.getController() != null && mLayoutListener != null) {
-                mLayoutListener.open();
-                mLayoutListener.update(mCurrentShift.value > 1, mLongSwipeMode,
-                        mClipAnimationHelper.getCurrentRectWithInsets(),
+            if (mRecentsAnimationWrapper.getController() != null) {
+                mLiveTileOverlay.update(mClipAnimationHelper.getCurrentRectWithInsets(),
                         mClipAnimationHelper.getCurrentCornerRadius());
             }
         }
@@ -717,7 +671,7 @@
         mClipAnimationHelper.prepareAnimation(false /* isOpening */);
         initTransitionEndpoints(dp);
 
-        mRecentsAnimationWrapper.setController(targetSet.controller, targetSet);
+        mRecentsAnimationWrapper.setController(targetSet);
         TOUCH_INTERACTION_LOG.startRecentsAnimationCallback(targetSet.apps.length);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
@@ -726,7 +680,7 @@
 
     @Override
     public void onRecentsAnimationCanceled() {
-        mRecentsAnimationWrapper.setController(null, null);
+        mRecentsAnimationWrapper.setController(null);
         mActivityInitListener.unregister();
         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
         TOUCH_INTERACTION_LOG.cancelRecentsAnimation();
@@ -738,8 +692,6 @@
         mShiftAtGestureStart = mCurrentShift.value;
         setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
-        mRecentsAnimationWrapper.hideCurrentInputMethod();
-        mRecentsAnimationWrapper.enableInputConsumer();
     }
 
     /**
@@ -763,12 +715,7 @@
         setStateOnUiThread(STATE_GESTURE_COMPLETED);
 
         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
-
-        if (mLongSwipeMode) {
-            onLongSwipeGestureFinish(endVelocity, isFling, velocityX);
-        } else {
-            handleNormalGestureEnd(endVelocity, isFling, velocityX);
-        }
+        handleNormalGestureEnd(endVelocity, isFling, velocityX);
     }
 
     @UiThread
@@ -926,6 +873,17 @@
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, float velocityPxPerMs) {
         mGestureEndTarget = target;
+
+        if (mGestureEndTarget.canBeContinued) {
+            // Because we might continue this gesture, e.g. for consecutive quick switch, we need to
+            // stabilize the task list so that tasks don't rearrange in the middle of the gesture.
+            RecentsModel.INSTANCE.get(mContext).startStabilizationSession();
+        } else if (mGestureEndTarget.isLauncher) {
+            // Otherwise, if we're going to home or overview,
+            // we reset the tasks to a consistent start state.
+            RecentsModel.INSTANCE.get(mContext).endStabilizationSession();
+        }
+
         HomeAnimationFactory homeAnimFactory;
         Animator windowAnim;
         if (mGestureEndTarget == HOME) {
@@ -1106,12 +1064,16 @@
 
     private void invalidateHandlerWithLauncher() {
         mLauncherTransitionController = null;
-        mLayoutListener.finish();
         mActivityControlHelper.getAlphaProperty(mActivity).setValue(1);
 
         mRecentsView.setEnableFreeScroll(true);
         mRecentsView.setRunningTaskIconScaledDown(false);
         mRecentsView.setOnScrollChangeListener(null);
+        mRecentsView.setRunningTaskHidden(false);
+        mRecentsView.setEnableDrawingLiveTile(true);
+
+        mActivity.getRootView().setOnApplyWindowInsetsListener(null);
+        mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
     }
 
     private void notifyTransitionCancelled() {
@@ -1126,20 +1088,12 @@
         mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
     }
 
-    public void layoutListenerClosed() {
-        mRecentsView.setRunningTaskHidden(false);
-        if (mWasLauncherAlreadyVisible && mLauncherTransitionController != null) {
-            mLauncherTransitionController.setPlayFraction(1);
-        }
-        mRecentsView.setEnableDrawingLiveTile(true);
-    }
-
     private void switchToScreenshot() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else {
             boolean finishTransitionPosted = false;
-            RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+            SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
             if (controller != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
@@ -1223,94 +1177,12 @@
         mGestureEndCallback = gestureEndCallback;
     }
 
-    // Handling long swipe
-    private void onLongSwipeEnabled() {
-        mLongSwipeMode = true;
-        checkLongSwipeCanEnter();
-        checkLongSwipeCanStart();
-    }
-
-    private void onLongSwipeDisabled() {
-        mLongSwipeMode = false;
-        mStateCallback.clearState(STATE_SCREENSHOT_VIEW_SHOWN);
-
-        if (mLongSwipeController != null) {
-            mLongSwipeController.destroy();
-            setTargetAlphaProvider((t, a1) -> a1);
-
-            // Rebuild animations
-            buildAnimationController();
-        }
-    }
-
-    private void onLongSwipeDisplacementUpdated() {
-        if (!mLongSwipeMode || mLongSwipeController == null) {
-            return;
-        }
-
-        mLongSwipeController.onMove(mLongSwipeDisplacement);
-    }
-
-    private void checkLongSwipeCanEnter() {
-        if (!mLongSwipeMode || !mStateCallback.hasStates(LONG_SWIPE_ENTER_STATE)
-                || !mActivityControlHelper.supportsLongSwipe(mActivity)) {
-            return;
-        }
-
-        // We are entering long swipe mode, make sure the screen shot is captured.
-        mStateCallback.setState(STATE_CAPTURE_SCREENSHOT | STATE_SCREENSHOT_VIEW_SHOWN);
-
-    }
-
-    private void checkLongSwipeCanStart() {
-        if (!mLongSwipeMode || !mStateCallback.hasStates(LONG_SWIPE_START_STATE)
-                || !mActivityControlHelper.supportsLongSwipe(mActivity)) {
-            return;
-        }
-
-        RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
-        if (targetSet == null) {
-            // This can happen when cancelAnimation comes on the background thread, while we are
-            // processing the long swipe on the UI thread.
-            return;
-        }
-
-        mLongSwipeController = mActivityControlHelper.getLongSwipeController(
-                mActivity, mRunningTaskId);
-        onLongSwipeDisplacementUpdated();
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
-        }
-    }
-
-    private void onLongSwipeGestureFinish(float velocity, boolean isFling, float velocityX) {
-        if (!mLongSwipeMode || mLongSwipeController == null) {
-            mLongSwipeMode = false;
-            handleNormalGestureEnd(velocity, isFling, velocityX);
-            return;
-        }
-        mLongSwipeMode = false;
-        finishCurrentTransitionToRecents();
-        mLongSwipeController.end(velocity, isFling,
-                () -> setStateOnUiThread(STATE_HANDLER_INVALIDATED));
-
-    }
-
     private void setTargetAlphaProvider(
             BiFunction<RemoteAnimationTargetCompat, Float, Float> provider) {
         mClipAnimationHelper.setTaskAlphaCallback(provider);
         updateFinalShift();
     }
 
-    public void onAssistDataReceived(Bundle assistData) {
-        mAssistData = assistData;
-        setStateOnUiThread(STATE_ASSIST_DATA_RECEIVED);
-    }
-
-    private void preloadAssistData() {
-        RecentsModel.INSTANCE.get(mContext).preloadAssistData(mRunningTaskId, mAssistData);
-    }
-
     public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
         if (!(app.isNotInRecents
                 || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
diff --git a/quickstep/src/com/android/quickstep/hints/HintUtil.java b/quickstep/src/com/android/quickstep/hints/HintUtil.java
new file mode 100644
index 0000000..f2d40ec
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/hints/HintUtil.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 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.hints;
+
+import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+
+public final class HintUtil {
+
+    public static final String ID_KEY = "id";
+    public static final String ICON_KEY = "icon";
+    public static final String TEXT_KEY = "text";
+    public static final String TAP_ACTION_KEY = "tap_action";
+
+    private HintUtil() {}
+
+    public static Bundle makeHint(String id, Icon icon, CharSequence text) {
+        Bundle hint = new Bundle();
+        hint.putString(ID_KEY, id);
+        hint.putParcelable(ICON_KEY, icon);
+        hint.putCharSequence(TEXT_KEY, text);
+        return hint;
+    }
+
+    public static Bundle makeHint(Icon icon, CharSequence text, PendingIntent tapAction) {
+        Bundle hint = new Bundle();
+        hint.putParcelable(ICON_KEY, icon);
+        hint.putCharSequence(TEXT_KEY, text);
+        hint.putParcelable(TAP_ACTION_KEY, tapAction);
+        return hint;
+    }
+
+    public static String getId(Bundle hint) {
+        String id = hint.getString(ID_KEY);
+        if (id == null) {
+            throw new IllegalArgumentException("Hint does not contain an ID");
+        }
+        return id;
+    }
+
+    public static Icon getIcon(Bundle hint) {
+        Icon icon = hint.getParcelable(ICON_KEY);
+        if (icon == null) {
+            throw new IllegalArgumentException("Hint does not contain an icon");
+        }
+        return icon;
+    }
+
+    public static CharSequence getText(Bundle hint) {
+        CharSequence text = hint.getCharSequence(TEXT_KEY);
+        if (text == null) {
+            throw new IllegalArgumentException("Hint does not contain text");
+        }
+        return text;
+    }
+
+    public static PendingIntent getTapAction(Bundle hint) {
+        PendingIntent tapAction = hint.getParcelable(TAP_ACTION_KEY);
+        if (tapAction == null) {
+            throw new IllegalArgumentException("Hint does not contain a tap action");
+        }
+        return tapAction;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/hints/HintView.java b/quickstep/src/com/android/quickstep/hints/HintView.java
new file mode 100644
index 0000000..5399cc4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/hints/HintView.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.hints;
+
+import static com.android.quickstep.hints.HintUtil.getIcon;
+import static com.android.quickstep.hints.HintUtil.getText;
+
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+public class HintView extends LinearLayout {
+    private ImageView mIconView;
+    private TextView mLabelView;
+
+    public HintView(Context context) {
+        super(context);
+    }
+
+    public HintView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public HintView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public HintView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public void setHint(Bundle hint) {
+        mLabelView.setText(getText(hint));
+
+        Icon icon = getIcon(hint);
+        if (icon == null) {
+            mIconView.setVisibility(GONE);
+        } else {
+            mIconView.setImageIcon(icon);
+            mIconView.setVisibility(VISIBLE);
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconView = findViewById(R.id.icon);
+        mLabelView = findViewById(R.id.label);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/hints/HintsContainer.java b/quickstep/src/com/android/quickstep/hints/HintsContainer.java
new file mode 100644
index 0000000..22b1217
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/hints/HintsContainer.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2019 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.hints;
+
+import static com.android.quickstep.hints.UiHintListenerConstants.HINTS_KEY;
+import static com.android.quickstep.hints.UiHintListenerConstants.ON_HINTS_RETURNED_CODE;
+import static com.android.quickstep.hints.UiInterfaceConstants.REQUEST_HINTS_CODE;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+
+import java.util.ArrayList;
+
+public class HintsContainer extends LinearLayout {
+
+    private static final String TAG = "HintsView";
+
+    public static final FloatProperty<HintsContainer> HINT_VISIBILITY =
+            new FloatProperty<HintsContainer>("hint_visibility") {
+                @Override
+                public void setValue(HintsContainer hintsContainer, float v) {
+                    hintsContainer.setHintVisibility(v);
+                }
+
+                @Override
+                public Float get(HintsContainer hintsContainer) {
+                    return hintsContainer.mHintVisibility;
+                }
+            };
+
+    private static Intent mServiceIntent =
+            new Intent("com.android.systemui.action.UI_PULL_INTERFACE")
+                    .setClassName(
+                            "com.android.systemui.navbarhint",
+                            "com.android.systemui.navbarhint.service.HintService");
+
+    @Nullable
+    private Messenger mHintServiceInterface;
+    private UiHintListener mUiHintListener;
+    private boolean mBound = false;
+    private float mHintVisibility;
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+            mHintServiceInterface = new Messenger(iBinder);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            mHintServiceInterface = null;
+            attemptBinding();
+        }
+
+        @Override
+        public void onBindingDied(ComponentName componentName) {
+            mHintServiceInterface = null;
+            attemptBinding();
+        }
+    };
+
+    public HintsContainer(Context context) {
+        super(context);
+    }
+
+    public HintsContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public HintsContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mUiHintListener == null) {
+            mUiHintListener = new UiHintListener(this);
+        }
+        if (!mBound) {
+            attemptBinding();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if (mBound) {
+            getContext().unbindService(mServiceConnection);
+            mBound = false;
+        }
+        super.onDetachedFromWindow();
+    }
+
+    public void setHintVisibility(float v) {
+        if (v == 1) {
+            getHints();
+            setVisibility(VISIBLE);
+        } else {
+            setVisibility(GONE);
+        }
+        mHintVisibility = v;
+    }
+
+    private void attemptBinding() {
+        if (mBound) {
+            getContext().unbindService(mServiceConnection);
+            mBound = false;
+        }
+        boolean success = getContext().bindService(mServiceIntent,
+                mServiceConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+        if (success) {
+            mBound = true;
+        } else {
+            Log.w(TAG, "Binding to hint supplier failed");
+        }
+    }
+    
+    private void sendOnHintTap(Bundle hint) {
+        if (mHintServiceInterface != null) {
+            Message msg = Message.obtain(null, UiInterfaceConstants.ON_HINT_TAP_CODE);
+            Bundle data = new Bundle();
+            data.putString(UiInterfaceConstants.HINT_ID_KEY, HintUtil.getId(hint));
+            data.putInt(UiInterfaceConstants.WIDTH_PX_KEY, getWidth());
+            data.putInt(UiInterfaceConstants.HEIGHT_PX_KEY, getHeight());
+            data.putInt(UiInterfaceConstants.HINT_SPACE_WIDTH_PX_KEY, 0);
+            data.putInt(UiInterfaceConstants.HINT_SPACE_HEIGHT_PX_KEY, 0);
+            msg.setData(data);
+            try {
+                mHintServiceInterface.send(msg);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send hint tap", e);
+            }
+        }
+    }
+
+    private void getHints() {
+        if (mHintServiceInterface != null) {
+            try {
+                Message m = Message.obtain(null, REQUEST_HINTS_CODE);
+                m.replyTo = new Messenger(mUiHintListener);
+                mHintServiceInterface.send(m);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send message", e);
+            }
+        }
+    }
+
+    private static class UiHintListener extends Handler {
+        private HintsContainer mView;
+
+        UiHintListener(HintsContainer v) {
+            mView = v;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ON_HINTS_RETURNED_CODE:
+                    handleHints(msg);
+                    break;
+                default:
+                    Log.e(TAG, "UiPullInterface got unrecognized code: " + msg.what);
+                    break;
+            }
+        }
+
+        private void handleHints(Message msg) {
+            Bundle bundle = msg.getData();
+            ArrayList<Bundle> hints = bundle.getParcelableArrayList(HINTS_KEY);
+
+            if (hints != null) {
+                mView.removeAllViews();
+
+                for (Bundle hint : hints) {
+                    HintView h = (HintView) LayoutInflater.from(mView.getContext()).inflate(
+                            R.layout.hint, mView, false);
+                    h.setHint(hint);
+                    h.setOnClickListener((v) -> mView.sendOnHintTap(hint));
+                    mView.addView(h);
+                }
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/hints/UiHintListenerConstants.java b/quickstep/src/com/android/quickstep/hints/UiHintListenerConstants.java
new file mode 100644
index 0000000..420033d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/hints/UiHintListenerConstants.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 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.hints;
+
+public final class UiHintListenerConstants {
+
+    private UiHintListenerConstants() {}
+
+    // Operations
+    public static final int ON_HINTS_RETURNED_CODE = 5;
+
+    // Keys
+    public static final String SESSION_ID_KEY = "session_id";
+    public static final String HINTS_KEY = "hints";
+}
diff --git a/quickstep/src/com/android/quickstep/hints/UiInterfaceConstants.java b/quickstep/src/com/android/quickstep/hints/UiInterfaceConstants.java
new file mode 100644
index 0000000..0140613
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/hints/UiInterfaceConstants.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.hints;
+
+public final class UiInterfaceConstants {
+
+    private UiInterfaceConstants() {}
+
+    // Operations
+    public static final int ON_HINT_TAP_CODE = 4;
+
+    public static final int REQUEST_HINTS_CODE = 7;
+
+    // Keys
+    public static final String SESSION_ID_KEY = "session_id";
+    public static final String HINT_ID_KEY = "hint_id";
+    public static final String WIDTH_PX_KEY = "width_px";
+    public static final String HEIGHT_PX_KEY = "height_px";
+    public static final String HINT_SPACE_WIDTH_PX_KEY = "hint_space_width_px";
+    public static final String HINT_SPACE_HEIGHT_PX_KEY = "hint_space_height_px";
+}
diff --git a/quickstep/src/com/android/quickstep/util/BinderTracker.java b/quickstep/src/com/android/quickstep/util/BinderTracker.java
new file mode 100644
index 0000000..32d0d53
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/BinderTracker.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.launcher3.config.FeatureFlags;
+
+/**
+ * Utility class to test and check binder calls during development.
+ */
+public class BinderTracker {
+
+    private static final String TAG = "BinderTracker";
+
+    public static void start() {
+        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+            Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
+            return;
+        }
+
+        Binder.setProxyTransactListener(new Tracker());
+    }
+
+    public static void stop() {
+        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+            Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
+            return;
+        }
+        Binder.setProxyTransactListener(null);
+    }
+
+    private static class Tracker implements Binder.ProxyTransactListener {
+
+        @Override
+        public Object onTransactStarted(IBinder iBinder, int code) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                Log.e(TAG, "Binder call on ui thread", new Exception());
+            }
+            return null;
+        }
+
+        @Override
+        public void onTransactEnded(Object session) { }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java b/quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java
new file mode 100644
index 0000000..194c7d4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import static com.android.systemui.shared.system.InputChannelCompat.mergeMotionEvent;
+
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to dispatch touch events to a different class. It stores the events locally
+ * until a valid dispatcher is available.
+ */
+public class CachedEventDispatcher {
+
+    private Consumer<MotionEvent> mConsumer;
+
+    private ArrayList<MotionEvent> mCache;
+    private MotionEvent mLastEvent;
+
+    public void dispatchEvent(MotionEvent event) {
+        if (mConsumer != null) {
+            mConsumer.accept(event);
+        } else {
+            if (mLastEvent == null || !mergeMotionEvent(event, mLastEvent)) {
+                // Queue event.
+                if (mCache == null) {
+                    mCache = new ArrayList<>();
+                }
+                mLastEvent = MotionEvent.obtain(event);
+                mCache.add(mLastEvent);
+            }
+        }
+    }
+
+    public void setConsumer(Consumer<MotionEvent> consumer) {
+        if (consumer == null) {
+            return;
+        }
+        mConsumer = consumer;
+        int cacheCount = mCache == null ? 0 : mCache.size();
+        for (int i = 0; i < cacheCount; i++) {
+            MotionEvent ev = mCache.get(i);
+            mConsumer.accept(ev);
+            ev.recycle();
+        }
+        mCache = null;
+        mLastEvent = null;
+    }
+
+    public boolean hasConsumer() {
+        return mConsumer != null;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index ed585c1..a06209a 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -43,6 +43,8 @@
         float extraSpace;
         if (dp.isVerticalBarLayout()) {
             extraSpace = 0;
+        } else if (FeatureFlags.ENABLE_HINTS_IN_OVERVIEW.get()){
+            extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx + dp.chipHintHeightPx;
         } else {
             extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
         }
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 686e74d..62f2183 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -28,6 +28,7 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.Set;
+import java.util.function.Consumer;
 
 import androidx.annotation.UiThread;
 
@@ -38,6 +39,14 @@
 public class RecentsAnimationListenerSet implements RecentsAnimationListener {
 
     private final Set<SwipeAnimationListener> mListeners = new ArraySet<>();
+    private final boolean mShouldMinimizeSplitScreen;
+    private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
+
+    public RecentsAnimationListenerSet(boolean shouldMinimizeSplitScreen,
+            Consumer<SwipeAnimationTargetSet> onFinishListener) {
+        mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+        mOnFinishListener = onFinishListener;
+    }
 
     @UiThread
     public void addListener(SwipeAnimationListener listener) {
@@ -56,7 +65,8 @@
             RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
             Rect minimizedHomeBounds) {
         SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
-                homeContentInsets, minimizedHomeBounds);
+                homeContentInsets, minimizedHomeBounds, mShouldMinimizeSplitScreen,
+                mOnFinishListener);
         Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
             for (SwipeAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationStart(targetSet);
diff --git a/quickstep/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index 4f02acf..b682481 100644
--- a/quickstep/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -15,32 +15,79 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
+import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.graphics.Rect;
 
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
+import java.util.function.Consumer;
+
 /**
  * Extension of {@link RemoteAnimationTargetSet} with additional information about swipe
  * up animation
  */
 public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet {
 
+    private final boolean mShouldMinimizeSplitScreen;
+    private final Consumer<SwipeAnimationTargetSet> mOnFinishListener;
+
+
     public final RecentsAnimationControllerCompat controller;
     public final Rect homeContentInsets;
     public final Rect minimizedHomeBounds;
 
     public SwipeAnimationTargetSet(RecentsAnimationControllerCompat controller,
             RemoteAnimationTargetCompat[] targets, Rect homeContentInsets,
-            Rect minimizedHomeBounds) {
+            Rect minimizedHomeBounds, boolean shouldMinimizeSplitScreen,
+            Consumer<SwipeAnimationTargetSet> onFinishListener) {
         super(targets, MODE_CLOSING);
         this.controller = controller;
         this.homeContentInsets = homeContentInsets;
         this.minimizedHomeBounds = minimizedHomeBounds;
+        this.mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+        this.mOnFinishListener = onFinishListener;
     }
 
+    public void finishController(boolean toRecents, Runnable callback) {
+        mOnFinishListener.accept(this);
+        BACKGROUND_EXECUTOR.execute(() -> {
+            controller.setInputConsumerEnabled(false);
+            controller.finish(toRecents);
+
+            if (callback != null) {
+                MAIN_THREAD_EXECUTOR.execute(callback);
+            }
+        });
+    }
+
+    public void enableInputConsumer() {
+        BACKGROUND_EXECUTOR.submit(() -> {
+            controller.hideCurrentInputMethod();
+            controller.setInputConsumerEnabled(true);
+        });
+    }
+
+    public void setWindowThresholdCrossed(boolean thresholdCrossed) {
+        BACKGROUND_EXECUTOR.execute(() -> {
+            controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed);
+            if (mShouldMinimizeSplitScreen && thresholdCrossed) {
+                // NOTE: As a workaround for conflicting animations (Launcher animating the task
+                // leash, and SystemUI resizing the docked stack, which resizes the task), we
+                // currently only set the minimized mode, and not the inverse.
+                // TODO: Synchronize the minimize animation with the launcher animation
+                controller.setSplitScreenMinimized(thresholdCrossed);
+            }
+        });
+    }
+
+    public ThumbnailData screenshotTask(int taskId) {
+        return controller != null ? controller.screenshotTask(taskId) : null;
+    }
 
     public interface SwipeAnimationListener {
 
@@ -48,4 +95,9 @@
 
         void onRecentsAnimationCanceled();
     }
+
+    public interface SwipeAnimationFinishListener {
+
+        void onSwipeAnimationFinished(SwipeAnimationTargetSet targetSet);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
deleted file mode 100644
index a8205cd..0000000
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ /dev/null
@@ -1,158 +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 android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.view.MotionEvent;
-import android.widget.FrameLayout;
-
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.quickstep.ActivityControlHelper.LayoutListener;
-import com.android.quickstep.WindowTransformSwipeHandler;
-
-/**
- * Floating view which shows the task snapshot allowing it to be dragged and placed.
- */
-public class LauncherLayoutListener extends AbstractFloatingView
-        implements Insettable, LayoutListener {
-
-    public static LauncherLayoutListener resetAndGet(Launcher launcher) {
-        LauncherRecentsView lrv = launcher.getOverviewPanel();
-        LauncherLayoutListener listener = lrv.mLauncherLayoutListener;
-        if (listener.isOpen()) {
-            listener.close(false);
-        }
-        listener.setHandler(null);
-        return listener;
-    }
-
-    private final Launcher mLauncher;
-    private final Paint mPaint = new Paint();
-    private WindowTransformSwipeHandler mHandler;
-    private RectF mCurrentRect;
-    private float mCornerRadius;
-
-    private boolean mWillNotDraw;
-
-    /**
-     * package private
-     */
-    LauncherLayoutListener(Launcher launcher) {
-        super(launcher, null);
-        mLauncher = launcher;
-        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
-        setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
-        mWillNotDraw = willNotDraw();
-        super.setWillNotDraw(false);
-    }
-
-    @Override
-    public void update(boolean shouldFinish, boolean isLongSwipe, RectF currentRect,
-                  float cornerRadius) {
-        if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (shouldFinish) {
-                finish();
-            }
-            return;
-        }
-
-        mCurrentRect = currentRect;
-        mCornerRadius = cornerRadius;
-
-        setWillNotDraw(mCurrentRect == null || isLongSwipe);
-        invalidate();
-    }
-
-    @Override
-    public void setWillNotDraw(boolean willNotDraw) {
-        // Prevent super call as that causes additional relayout.
-        mWillNotDraw = willNotDraw;
-    }
-
-    @Override
-    public void setHandler(WindowTransformSwipeHandler handler) {
-        mHandler = handler;
-    }
-
-    @Override
-    public void setInsets(Rect insets) {
-        if (mHandler != null) {
-            mHandler.buildAnimationController();
-        }
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        if (mIsOpen) {
-            mIsOpen = false;
-            // We don't support animate.
-            mLauncher.getDragLayer().removeView(this);
-
-            if (mHandler != null) {
-                mHandler.layoutListenerClosed();
-            }
-        }
-    }
-
-    @Override
-    public void open() {
-        if (!mIsOpen) {
-            mLauncher.getDragLayer().addView(this);
-            mIsOpen = true;
-        }
-    }
-
-    @Override
-    public void logActionCommand(int command) {
-        // We should probably log the weather
-    }
-
-    @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_QUICKSTEP_PREVIEW) != 0;
-    }
-
-    @Override
-    public void finish() {
-        close(false);
-        setHandler(null);
-        mLauncher.getRotationHelper().setStateHandlerRequest(REQUEST_NONE);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (!mWillNotDraw) {
-            canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 1ee5110..88fe2ee 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,13 +15,13 @@
  */
 package com.android.quickstep.views;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 
 import android.animation.AnimatorSet;
@@ -36,15 +36,15 @@
 import android.view.View;
 import android.view.ViewDebug;
 
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.hints.HintsContainer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
 import com.android.quickstep.util.LayoutUtils;
@@ -77,7 +77,7 @@
     private float mTranslationYFactor;
 
     private final TransformParams mTransformParams = new TransformParams();
-    final LauncherLayoutListener mLauncherLayoutListener;
+    private HintsContainer mHintsContainer;
 
     public LauncherRecentsView(Context context) {
         this(context, null);
@@ -90,7 +90,6 @@
     public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setContentAlpha(0);
-        mLauncherLayoutListener = new LauncherLayoutListener(BaseActivity.fromContext(context));
     }
 
     @Override
@@ -109,6 +108,13 @@
         setTranslationYFactor(mTranslationYFactor);
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mHintsContainer = mActivity.findViewById(R.id.hints);
+        mHintsContainer.setPadding(0, 0, 0, mActivity.getDeviceProfile().chipHintBottomMarginPx);
+    }
+
     public void setTranslationYFactor(float translationFactor) {
         mTranslationYFactor = translationFactor;
         setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
@@ -124,6 +130,12 @@
         return translationYFactor * (getPaddingBottom() - getPaddingTop());
     }
 
+    public void setHintVisibility(float v) {
+        if (mHintsContainer != null && ENABLE_HINTS_IN_OVERVIEW.get()) {
+            mHintsContainer.setHintVisibility(v);
+        }
+    }
+
     @Override
     public void draw(Canvas canvas) {
         maybeDrawEmptyMessage(canvas);
@@ -175,6 +187,37 @@
     }
 
     @Override
+    public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
+        PendingAnimation anim = super.createTaskLauncherAnimation(tv, duration);
+
+        if (ENABLE_HINTS_IN_OVERVIEW.get()) {
+            anim.anim.play(ObjectAnimator.ofFloat(
+                    mHintsContainer, HintsContainer.HINT_VISIBILITY, 0));
+        }
+
+        return anim;
+    }
+
+    @Override
+    public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
+            boolean shouldRemoveTask, long duration) {
+        PendingAnimation anim = super.createTaskDismissAnimation(taskView, animateTaskView,
+                shouldRemoveTask, duration);
+
+        if (ENABLE_HINTS_IN_OVERVIEW.get()) {
+            anim.anim.play(ObjectAnimator.ofFloat(
+                    mHintsContainer, HintsContainer.HINT_VISIBILITY, 0));
+            anim.addEndListener(onEndListener -> {
+                if (!onEndListener.isSuccess) {
+                    mHintsContainer.setHintVisibility(1);
+                }
+            });
+        }
+
+        return anim;
+    }
+
+    @Override
     protected void getTaskSize(DeviceProfile dp, Rect outRect) {
         LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
     }
@@ -205,12 +248,8 @@
 
     @Override
     public void redrawLiveTile(boolean mightNeedToRefill) {
-        AbstractFloatingView layoutListener = AbstractFloatingView.getTopOpenViewWithType(
-                mActivity, TYPE_QUICKSTEP_PREVIEW);
-        if (layoutListener != null && layoutListener.isOpen()) {
-            return;
-        }
-        if (mRecentsAnimationWrapper == null || mClipAnimationHelper == null) {
+        if (!mEnableDrawingLiveTile || mRecentsAnimationWrapper == null
+                || mClipAnimationHelper == null) {
             return;
         }
         TaskView taskView = getRunningTaskView();
diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
new file mode 100644
index 0000000..ab2b90f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -0,0 +1,62 @@
+package com.android.quickstep.views;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+public class LiveTileOverlay extends Drawable {
+
+    private final Paint mPaint = new Paint();
+
+    private Rect mBoundsRect = new Rect();
+    private RectF mCurrentRect;
+    private float mCornerRadius;
+
+    private boolean mDrawEnabled = true;
+
+    public LiveTileOverlay() {
+        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+    }
+
+    public void update(RectF currentRect, float cornerRadius) {
+        invalidateSelf();
+
+        mCurrentRect = currentRect;
+        mCornerRadius = cornerRadius;
+
+        mCurrentRect.roundOut(mBoundsRect);
+        setBounds(mBoundsRect);
+        invalidateSelf();
+    }
+
+    public void setDrawEnabled(boolean drawEnabled) {
+        if (mDrawEnabled != drawEnabled) {
+            mDrawEnabled = drawEnabled;
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mCurrentRect != null && mDrawEnabled) {
+            canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+        }
+    }
+
+    @Override
+    public void setAlpha(int i) { }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) { }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0c1867d..8faf95d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -93,13 +93,13 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.SwipeAnimationTargetSet;
 import com.android.quickstep.util.TaskViewDrawable;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowCallbacksCompat;
@@ -166,8 +166,6 @@
 
     private final ViewPool<TaskView> mTaskViewPool;
 
-    @Nullable Float mSimulatedVelocityX = null;
-
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
      */
@@ -1590,7 +1588,7 @@
             return;
         }
 
-        RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
             // Update the screenshot of the task
             ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
@@ -1626,18 +1624,4 @@
             }
         }
     }
-
-    public void simulateTouchEvent(MotionEvent event, @Nullable Float velocityX) {
-        mSimulatedVelocityX = velocityX;
-        dispatchTouchEvent(event);
-        mSimulatedVelocityX = null;
-    }
-
-    @Override
-    protected int computeXVelocity() {
-        if (mSimulatedVelocityX != null) {
-            return mSimulatedVelocityX.intValue();
-        }
-        return super.computeXVelocity();
-    }
 }
diff --git a/res/layout/hint_container.xml b/res/layout/hint_container.xml
new file mode 100644
index 0000000..75aa913
--- /dev/null
+++ b/res/layout/hint_container.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 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.
+-->
+<merge/>
\ No newline at end of file
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index 87078b9..91c3705 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -43,6 +43,11 @@
             layout="@layout/overview_panel"
             android:visibility="gone" />
 
+        <include
+            android:id="@+id/hints"
+            layout="@layout/hint_container"
+            android:visibility="gone"/>
+
         <!-- Keep these behind the workspace so that they are not visible when
          we go into AllApps -->
         <com.android.launcher3.pageindicators.WorkspacePageIndicator
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 078ce60..04e4591 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -236,4 +236,8 @@
     <dimen name="snackbar_elevation">3dp</dimen>
     <dimen name="snackbar_min_text_size">12sp</dimen>
     <dimen name="snackbar_max_text_size">14sp</dimen>
+
+<!-- Hints -->
+    <dimen name="chip_hint_height">26dp</dimen>
+    <dimen name="chip_hint_bottom_margin">194dp</dimen>
 </resources>
diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
index 54038d2..656d55c 100644
--- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
+++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java
@@ -26,7 +26,6 @@
     @Test
     public void withFlagOn() {
         assertTrue(FeatureFlags.EXAMPLE_FLAG.get());
-        assertFalse(FeatureFlags.QUICK_SWITCH.get());
         assertFalse(FeatureFlags.STYLE_WALLPAPER.get());
     }
 
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index e75527e..599a353 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -55,7 +55,6 @@
             TYPE_DISCOVERY_BOUNCE,
             TYPE_SNACKBAR,
 
-            TYPE_QUICKSTEP_PREVIEW,
             TYPE_TASK_MENU,
             TYPE_OPTIONS_POPUP
     })
@@ -71,25 +70,23 @@
     public static final int TYPE_SNACKBAR = 1 << 7;
 
     // Popups related to quickstep UI
-    public static final int TYPE_QUICKSTEP_PREVIEW = 1 << 8;
-    public static final int TYPE_TASK_MENU = 1 << 9;
-    public static final int TYPE_OPTIONS_POPUP = 1 << 10;
+    public static final int TYPE_TASK_MENU = 1 << 8;
+    public static final int TYPE_OPTIONS_POPUP = 1 << 9;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
-            | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
+            | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
             | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
-            | TYPE_QUICKSTEP_PREVIEW | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
+            | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE;
 
     // Usually we show the back button when a floating view is open. Instead, hide for these types.
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_SNACKBAR;
 
-    public static final int TYPE_ACCESSIBLE = TYPE_ALL
-            & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_QUICKSTEP_PREVIEW;
+    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE;
 
     // These view all have particular operation associated with swipe down interaction.
     public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 70c8aaa..4c6824a 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -91,6 +91,7 @@
             mText = getResources().getString(canRemove(item)
                     ? R.string.remove_drop_target_label
                     : android.R.string.cancel);
+            setContentDescription(mText);
             requestLayout();
         }
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 296c951..7aea870 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -103,6 +103,10 @@
     public int folderChildTextSizePx;
     public int folderChildDrawablePaddingPx;
 
+    // Hints
+    public int chipHintHeightPx;
+    public int chipHintBottomMarginPx;
+
     // Hotseat
     public int hotseatCellHeightPx;
     // In portrait: size = height, in landscape: size = width
@@ -200,6 +204,9 @@
 
         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
 
+        chipHintHeightPx = res.getDimensionPixelSize(R.dimen.chip_hint_height);
+        chipHintBottomMarginPx = res.getDimensionPixelSize(R.dimen.chip_hint_bottom_margin);
+
         hotseatBarTopPaddingPx =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_top_padding);
         hotseatBarBottomPaddingPx = (isTallDevice ? 0
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index cf16759..f571aa3 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -257,7 +257,6 @@
 
     private RotationHelper mRotationHelper;
 
-
     private final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
 
@@ -756,6 +755,7 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onStop();
         }
+
         getUserEventDispatcher().logActionCommand(Action.Command.STOP,
                 mStateManager.getState().containerType, -1);
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 471d28d..6d85612 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,6 +21,7 @@
 
 import static com.android.launcher3.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
@@ -81,7 +82,8 @@
     /**
      * TODO: Create a separate class for NORMAL state.
      */
-    public static final LauncherState NORMAL = new LauncherState(0, ContainerType.WORKSPACE, 0,
+    public static final LauncherState NORMAL = new LauncherState(NORMAL_STATE_ORDINAL,
+            ContainerType.WORKSPACE, 0,
             FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HIDE_BACK_BUTTON |
             FLAG_HAS_SYS_UI_SCRIM);
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 018ec5f..4b7d8b4 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1134,7 +1134,9 @@
                 final int activePointerId = mActivePointerId;
                 final int pointerIndex = ev.findPointerIndex(activePointerId);
                 final float x = ev.getX(pointerIndex);
-                int velocityX = computeXVelocity();
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
                 final int deltaX = (int) (x - mDownMotionX);
                 final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
                 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
@@ -1238,12 +1240,6 @@
         return true;
     }
 
-    protected int computeXVelocity() {
-        final VelocityTracker velocityTracker = mVelocityTracker;
-        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-        return (int) velocityTracker.getXVelocity(mActivePointerId);
-    }
-
     protected boolean shouldFlingForVelocity(int velocityX) {
         return Math.abs(velocityX) > mFlingThresholdVelocity;
     }
diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java
index f19b0e0..23df79e 100644
--- a/src/com/android/launcher3/TestProtocol.java
+++ b/src/com/android/launcher3/TestProtocol.java
@@ -25,6 +25,7 @@
     public static final String STATE_FIELD = "state";
     public static final String SWITCHED_TO_STATE_MESSAGE = "TAPL_SWITCHED_TO_STATE";
     public static final String RESPONSE_MESSAGE_POSTFIX = "_RESPONSE";
+    public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
     public static final int ALL_APPS_STATE_ORDINAL = 3;
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index b921d29..882529d 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -88,10 +88,6 @@
     // trying to make them fit the orientation the device is in.
     public static final boolean OVERVIEW_USE_SCREENSHOT_ORIENTATION = true;
 
-    public static final ToggleableGlobalSettingsFlag QUICK_SWITCH
-            = new ToggleableGlobalSettingsFlag("QUICK_SWITCH", false,
-            "Swiping right on the nav bar while in an app switches to the previous app");
-
     public static final ToggleableGlobalSettingsFlag STYLE_WALLPAPER
             = new ToggleableGlobalSettingsFlag("STYLE_WALLPAPER", false,
             "Direct users to the new ThemePicker based WallpaperPicker");
@@ -102,10 +98,6 @@
     public static final TogglableFlag APPLY_CONFIG_AT_RUNTIME = new TogglableFlag(
             "APPLY_CONFIG_AT_RUNTIME", true, "Apply display changes dynamically");
 
-    public static final ToggleableGlobalSettingsFlag ENABLE_TASK_STABILIZER
-            = new ToggleableGlobalSettingsFlag("ENABLE_TASK_STABILIZER", false,
-            "Stable task list across fast task switches");
-
     public static final TogglableFlag QUICKSTEP_SPRINGS = new TogglableFlag("QUICKSTEP_SPRINGS",
             false, "Enable springs for quickstep animations");
 
@@ -116,6 +108,10 @@
             = new ToggleableGlobalSettingsFlag("SWIPE_HOME", false,
             "Swiping up on the nav bar goes home. Swipe and hold goes to recent apps.");
 
+    public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag(
+            "ENABLE_HINTS_IN_OVERVIEW", false,
+            "Show chip hints and gleams on the overview screen");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {