Only stabilize tasks during quick switch session

- A stabilization session starts when going to a new task via a quick switch
- A stabilization session ends after 5 seconds on the same task or when completing
  a gesture to go to home or overview
- We track the system task order and copy it for the duration of the stabilization session
- After the session ends, we switch back to the system task order
- Remove ENABLE_TASK_STABILIZATION flag (and also QUICK_SWITCH)

Bug: 111926330
Change-Id: I2c18dff2d20ad02b73a4935ac534e9fc9f5f49c0
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/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index e61c00a..57694d5 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.
  */
@@ -90,6 +92,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/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/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index aeb648d..e8c47e6 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -200,11 +200,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;
     }
 
@@ -926,6 +930,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) {
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");