Merge "Removing support for capturing assist structure on swipe up" 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/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/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/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/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/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 30b0c02..0c20bd1 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -292,8 +292,6 @@
         mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
         handler.initWhenReady();
 
-        TraceHelper.beginSection("RecentsController");
-
         if (listenerSet != null) {
             listenerSet.addListener(handler);
             mSwipeSharedState.applyActiveRecentsAnimationState(handler);
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 02deef1..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.
  */
@@ -86,6 +88,14 @@
         return mThumbnailCache;
     }
 
+    public void startStabilizationSession() {
+        mTaskList.startStabilizationSession();
+    }
+
+    public void endStabilizationSession() {
+        mTaskList.endStabilizationSession();
+    }
+
     /**
      * Fetches the list of recent tasks.
      *
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/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 84d70d9..8d90813 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -98,7 +98,6 @@
 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;
@@ -190,11 +189,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;
     }
 
@@ -337,6 +340,9 @@
                 | STATE_SCALED_CONTROLLER_LAST_TASK,
                 this::notifyTransitionCancelled);
 
+        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
                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
@@ -585,7 +591,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) {
@@ -600,12 +606,8 @@
                     .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()) {
@@ -684,7 +686,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);
 
@@ -693,7 +695,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();
@@ -705,8 +707,6 @@
         mShiftAtGestureStart = mCurrentShift.value;
         setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
-        mRecentsAnimationWrapper.hideCurrentInputMethod();
-        mRecentsAnimationWrapper.enableInputConsumer();
     }
 
     /**
@@ -888,6 +888,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) {
@@ -1097,7 +1108,7 @@
             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) {
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/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0c1867d..5465a0c 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;
@@ -1590,7 +1590,7 @@
             return;
         }
 
-        RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
             // Update the screenshot of the task
             ThumbnailData taskSnapshot = controller.screenshotTask(mRunningTaskId);
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/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index b921d29..61467e0 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");