Refactoring OverviewCommandHelper (1/3)

- Updates pendingCommands to be a ConcurrentLinkedDeque and make the list safe across multiple threads.
- Introduces CommandStatus to clear only IDLE and COMPLETED commands.
- Adds CommandType enum to prevent adding an invalid command value into the queue and having an unexpected behavior.
- Log messages improved

Bug: 352046797
Bug: 351122926
Flag: EXEMPT bugfix.
Test: Manual.
Change-Id: I80705dca0be579e62cb9e2bd923808dd33c4d633
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 43960a1..6b1173a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
-import static com.android.quickstep.OverviewCommandHelper.TYPE_HIDE;
 
 import android.content.Intent;
 import android.graphics.drawable.BitmapDrawable;
@@ -40,6 +39,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.OverviewCommandHelper.CommandType;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.TISBindHelper;
 import com.android.quickstep.views.RecentsView;
@@ -394,7 +394,7 @@
         if (overviewCommandHelper == null) {
             return;
         }
-        overviewCommandHelper.addCommand(TYPE_HIDE);
+        overviewCommandHelper.addCommand(CommandType.HIDE);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index f6b9e4e..56153a9 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -33,6 +33,8 @@
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.*
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.RunnableList
+import com.android.quickstep.OverviewCommandHelper.CommandInfo.CommandStatus
+import com.android.quickstep.OverviewCommandHelper.CommandType.*
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.views.RecentsView
 import com.android.quickstep.views.RecentsViewContainer
@@ -40,6 +42,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper
 import java.io.PrintWriter
+import java.util.concurrent.ConcurrentLinkedDeque
 
 /** Helper class to handle various atomic commands for switching between Overview. */
 class OverviewCommandHelper(
@@ -47,7 +50,7 @@
     private val overviewComponentObserver: OverviewComponentObserver,
     private val taskAnimationManager: TaskAnimationManager
 ) {
-    private val pendingCommands = mutableListOf<CommandInfo>()
+    private val commandQueue = ConcurrentLinkedDeque<CommandInfo>()
 
     /**
      * Index of the TaskView that should be focused when launching Overview. Persisted so that we do
@@ -64,22 +67,26 @@
      */
     private var waitForToggleCommandComplete = false
 
+    private val activityInterface: BaseActivityInterface<*, *>
+        get() = overviewComponentObserver.activityInterface
+
+    private val visibleRecentsView: RecentsView<*, *>?
+        get() = activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
+
     /** Called when the command finishes execution. */
-    private fun scheduleNextTask(command: CommandInfo) {
-        if (pendingCommands.isEmpty()) {
-            Log.d(TAG, "no pending commands to schedule")
-            return
-        }
-        if (pendingCommands.first() !== command) {
+    private fun onCommandFinished(command: CommandInfo) {
+        command.status = CommandStatus.COMPLETED
+        if (commandQueue.first() !== command) {
             Log.d(
                 TAG,
                 "next task not scheduled. First pending command type " +
-                    "is ${pendingCommands.first()} - command type is: $command"
+                    "is ${commandQueue.first()} - command type is: $command"
             )
             return
         }
-        Log.d(TAG, "scheduleNextTask called: $command")
-        pendingCommands.removeFirst()
+
+        Log.d(TAG, "command executed successfully! $command")
+        commandQueue.remove(command)
         executeNext()
     }
 
@@ -90,24 +97,21 @@
      */
     @UiThread
     private fun executeNext() {
-        if (pendingCommands.isEmpty()) {
-            Log.d(TAG, "executeNext - pendingCommands is empty")
-            return
-        }
-        val command = pendingCommands.first()
-        val result = executeCommand(command)
-        Log.d(TAG, "executeNext command type: $command, result: $result")
-        if (result) {
-            scheduleNextTask(command)
-        }
-    }
+        val command: CommandInfo =
+            commandQueue.firstOrNull()
+                ?: run {
+                    Log.d(TAG, "no pending commands to be executed.")
+                    return
+                }
 
-    @UiThread
-    private fun addCommand(command: CommandInfo) {
-        val wasEmpty = pendingCommands.isEmpty()
-        pendingCommands.add(command)
-        if (wasEmpty) {
-            executeNext()
+        Log.d(TAG, "executing command: $command")
+        val result = executeCommand(command)
+
+        Log.d(TAG, "command executed: $command with result: $result")
+        if (result) {
+            onCommandFinished(command)
+        } else {
+            Log.d(TAG, "waiting for command callback: $command")
         }
     }
 
@@ -117,29 +121,29 @@
      * dropped.
      */
     @BinderThread
-    fun addCommand(type: Int) {
-        if (pendingCommands.size >= MAX_QUEUE_SIZE) {
-            Log.d(
-                TAG,
-                "the pending command queue is full (${pendingCommands.size}). command not added: $type"
-            )
+    fun addCommand(type: CommandType) {
+        if (commandQueue.size >= MAX_QUEUE_SIZE) {
+            Log.d(TAG, "commands queue is full ($commandQueue). command not added: $type")
             return
         }
-        Log.d(TAG, "adding command type: $type")
+
         val command = CommandInfo(type)
-        Executors.MAIN_EXECUTOR.execute { addCommand(command) }
+        commandQueue.add(command)
+        Log.d(TAG, "command added: $command")
+
+        if (commandQueue.size == 1) {
+            Executors.MAIN_EXECUTOR.execute { executeNext() }
+        }
     }
 
-    @UiThread
+    fun canStartHomeSafely(): Boolean = commandQueue.isEmpty() || commandQueue.first().type == HOME
+
+    /** Clear commands from the queue */
     fun clearPendingCommands() {
-        Log.d(TAG, "clearing pending commands - size: ${pendingCommands.size}")
-        pendingCommands.clear()
+        Log.d(TAG, "clearing pending commands: $commandQueue")
+        commandQueue.clear()
     }
 
-    @UiThread
-    fun canStartHomeSafely(): Boolean =
-        pendingCommands.isEmpty() || pendingCommands.first().type == TYPE_HOME
-
     private fun getNextTask(view: RecentsView<*, *>): TaskView? {
         val runningTaskView = view.runningTaskView
 
@@ -166,7 +170,7 @@
         if (callbackList != null) {
             callbackList.add {
                 Log.d(TAG, "launching task callback: $command")
-                scheduleNextTask(command)
+                onCommandFinished(command)
                 waitForToggleCommandComplete = false
             }
             Log.d(TAG, "launching task - waiting for callback: $command")
@@ -183,21 +187,18 @@
      * is deferred until [.scheduleNextTask] is called
      */
     private fun executeCommand(command: CommandInfo): Boolean {
-        if (waitForToggleCommandComplete && command.type == TYPE_TOGGLE) {
+        command.status = CommandStatus.PROCESSING
+
+        if (waitForToggleCommandComplete && command.type == TOGGLE) {
             Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
             return true
         }
-        val activityInterface: BaseActivityInterface<*, *> =
-            overviewComponentObserver.activityInterface
 
-        val visibleRecentsView: RecentsView<*, *>? =
-            activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
-        val createdRecentsView: RecentsView<*, *>?
-
-        Log.d(TAG, "executeCommand: $command - visibleRecentsView: $visibleRecentsView")
-        if (visibleRecentsView == null) {
+        var recentsView = visibleRecentsView
+        Log.d(TAG, "executeCommand: $command - visibleRecentsView: $recentsView")
+        if (recentsView == null) {
             val activity = activityInterface.getCreatedContainer() as? RecentsViewContainer
-            createdRecentsView = activity?.getOverviewPanel()
+            recentsView = activity?.getOverviewPanel()
             val deviceProfile = activity?.getDeviceProfile()
             val uiController = activityInterface.getTaskbarController()
             val allowQuickSwitch =
@@ -207,22 +208,20 @@
                     (deviceProfile.isTablet || deviceProfile.isTwoPanels)
 
             when (command.type) {
-                TYPE_HIDE -> {
+                HIDE -> {
                     if (!allowQuickSwitch) return true
                     keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
                     if (keyboardTaskFocusIndex == -1) return true
                 }
-                TYPE_KEYBOARD_INPUT ->
+                KEYBOARD_INPUT ->
                     if (allowQuickSwitch) {
                         uiController!!.openQuickSwitchView()
                         return true
                     } else {
                         keyboardTaskFocusIndex = 0
                     }
-                TYPE_HOME -> {
-                    ActiveGestureLog.INSTANCE.addLog(
-                        "OverviewCommandHelper.executeCommand(TYPE_HOME)"
-                    )
+                HOME -> {
+                    ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(HOME)")
                     // Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
                     // we should still call it on main thread because launcher is waiting for
                     // ActivityTaskManager to resume it. Also calling startActivity() on bg thread
@@ -230,38 +229,37 @@
                     touchInteractionService.startActivity(overviewComponentObserver.homeIntent)
                     return true
                 }
-                TYPE_SHOW ->
-                    // When Recents is not currently visible, the command's type is
-                    // TYPE_SHOW
+                SHOW ->
+                    // When Recents is not currently visible, the command's type is SHOW
                     // when overview is triggered via the keyboard overview button or Action+Tab
                     // keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
                     // nav is TYPE_TOGGLE.
                     keyboardTaskFocusIndex = 0
-                else -> {}
+                TOGGLE -> {}
             }
         } else {
-            createdRecentsView = visibleRecentsView
-            when (command.type) {
-                TYPE_SHOW -> return true // already visible
-                TYPE_KEYBOARD_INPUT,
-                TYPE_HIDE -> {
-                    if (visibleRecentsView.isHandlingTouch) return true
-
-                    keyboardTaskFocusIndex = PagedView.INVALID_PAGE
-                    val currentPage = visibleRecentsView.nextPage
-                    val taskView = visibleRecentsView.getTaskViewAt(currentPage)
-                    return launchTask(visibleRecentsView, taskView, command)
+            return when (command.type) {
+                SHOW -> true // already visible
+                KEYBOARD_INPUT,
+                HIDE -> {
+                    if (recentsView.isHandlingTouch) {
+                        true
+                    } else {
+                        keyboardTaskFocusIndex = PagedView.INVALID_PAGE
+                        val currentPage = recentsView.nextPage
+                        val taskView = recentsView.getTaskViewAt(currentPage)
+                        launchTask(recentsView, taskView, command)
+                    }
                 }
-                TYPE_TOGGLE ->
-                    return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), command)
-                TYPE_HOME -> {
-                    visibleRecentsView.startHome()
-                    return true
+                TOGGLE -> launchTask(recentsView, getNextTask(recentsView), command)
+                HOME -> {
+                    recentsView.startHome()
+                    true
                 }
             }
         }
 
-        createdRecentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
+        recentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
         // Handle recents view focus when launching from home
         val animatorListener: Animator.AnimatorListener =
             object : AnimatorListenerAdapter() {
@@ -275,7 +273,7 @@
                     Log.d(TAG, "switching to Overview state - onAnimationEnd: $command")
                     super.onAnimationEnd(animation)
                     onRecentsViewFocusUpdated(command)
-                    scheduleNextTask(command)
+                    onCommandFinished(command)
                 }
             }
         if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
@@ -326,7 +324,7 @@
                     command.removeListener(this)
 
                     activityInterface.getCreatedContainer() ?: return
-                    createdRecentsView?.onRecentsAnimationComplete()
+                    recentsView?.onRecentsAnimationComplete()
                 }
             }
 
@@ -365,17 +363,12 @@
         command.removeListener(handler)
         Trace.endAsyncSection(TRANSITION_NAME, 0)
         onRecentsViewFocusUpdated(command)
-        scheduleNextTask(command)
+        onCommandFinished(command)
     }
 
     private fun updateRecentsViewFocus(command: CommandInfo) {
-        val recentsView: RecentsView<*, *> =
-            overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
-        if (
-            command.type != TYPE_KEYBOARD_INPUT &&
-                command.type != TYPE_HIDE &&
-                command.type != TYPE_SHOW
-        ) {
+        val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
+        if (command.type != KEYBOARD_INPUT && command.type != HIDE && command.type != SHOW) {
             return
         }
 
@@ -394,9 +387,8 @@
     }
 
     private fun onRecentsViewFocusUpdated(command: CommandInfo) {
-        val recentsView: RecentsView<*, *> =
-            overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
-        if (command.type != TYPE_HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
+        val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
+        if (command.type != HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
             return
         }
         recentsView.setKeyboardTaskFocusIndex(PagedView.INVALID_PAGE)
@@ -413,15 +405,13 @@
         return true
     }
 
-    private fun logShowOverviewFrom(commandType: Int) {
-        val activityInterface: BaseActivityInterface<*, *> =
-            overviewComponentObserver.activityInterface
+    private fun logShowOverviewFrom(commandType: CommandType) {
         val container = activityInterface.getCreatedContainer() as? RecentsViewContainer ?: return
         val event =
             when (commandType) {
-                TYPE_SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
-                TYPE_HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
-                TYPE_TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
+                SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
+                HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
+                TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
                 else -> return
             }
         StatsLogManager.newInstance(container.asContext())
@@ -438,16 +428,17 @@
 
     fun dump(pw: PrintWriter) {
         pw.println("OverviewCommandHelper:")
-        pw.println("  pendingCommands=${pendingCommands.size}")
-        if (pendingCommands.isNotEmpty()) {
-            pw.println("    pendingCommandType=${pendingCommands.first().type}")
+        pw.println("  pendingCommands=${commandQueue.size}")
+        if (commandQueue.isNotEmpty()) {
+            pw.println("    pendingCommandType=${commandQueue.first().type}")
         }
-        pw.println("  mKeyboardTaskFocusIndex=$keyboardTaskFocusIndex")
-        pw.println("  mWaitForToggleCommandComplete=$waitForToggleCommandComplete")
+        pw.println("  keyboardTaskFocusIndex=$keyboardTaskFocusIndex")
+        pw.println("  waitForToggleCommandComplete=$waitForToggleCommandComplete")
     }
 
     private data class CommandInfo(
-        val type: Int,
+        val type: CommandType,
+        var status: CommandStatus = CommandStatus.IDLE,
         val createTime: Long = SystemClock.elapsedRealtime(),
         private var animationCallbacks: RecentsAnimationCallbacks? = null
     ) {
@@ -462,23 +453,30 @@
         fun removeListener(listener: RecentsAnimationCallbacks.RecentsAnimationListener?) {
             animationCallbacks?.removeListener(listener)
         }
+
+        enum class CommandStatus {
+            IDLE,
+            PROCESSING,
+            COMPLETED
+        }
+    }
+
+    enum class CommandType {
+        SHOW,
+        KEYBOARD_INPUT,
+        HIDE,
+        TOGGLE, // Navigate to Overview
+        HOME, // Navigate to Home
     }
 
     companion object {
         private const val TAG = "OverviewCommandHelper"
-
-        const val TYPE_SHOW: Int = 1
-        const val TYPE_KEYBOARD_INPUT: Int = 2
-        const val TYPE_HIDE: Int = 3
-        const val TYPE_TOGGLE: Int = 4
-        const val TYPE_HOME: Int = 5
+        private const val TRANSITION_NAME = "Transition:toOverview"
 
         /**
          * Use case for needing a queue is double tapping recents button in 3 button nav. Size of 2
          * should be enough. We'll toss in one more because we're kind hearted.
          */
         private const val MAX_QUEUE_SIZE = 3
-
-        private const val TRANSITION_NAME = "Transition:toOverview"
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 2b5aa71..69d1c35 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -108,6 +108,7 @@
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.OverviewCommandHelper.CommandType;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
@@ -245,7 +246,7 @@
                     return;
                 }
                 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-                tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+                tis.mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
             });
         }
 
@@ -255,10 +256,9 @@
             executeForTouchInteractionService(tis -> {
                 if (triggeredFromAltTab) {
                     TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-                    tis.mOverviewCommandHelper.addCommand(
-                            OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
+                    tis.mOverviewCommandHelper.addCommand(CommandType.KEYBOARD_INPUT);
                 } else {
-                    tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
+                    tis.mOverviewCommandHelper.addCommand(CommandType.SHOW);
                 }
             });
         }
@@ -269,7 +269,7 @@
             executeForTouchInteractionService(tis -> {
                 if (triggeredFromAltTab && !triggeredFromHomeKey) {
                     // onOverviewShownFromAltTab hides the overview and ends at the target app
-                    tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
+                    tis.mOverviewCommandHelper.addCommand(CommandType.HIDE);
                 }
             });
         }
@@ -594,12 +594,12 @@
     private final TaskbarNavButtonCallbacks mNavCallbacks = new TaskbarNavButtonCallbacks() {
         @Override
         public void onNavigateHome() {
-            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+            mOverviewCommandHelper.addCommand(CommandType.HOME);
         }
 
         @Override
         public void onToggleOverview() {
-            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
+            mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
         }
     };
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 17a97fa..9284e13 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -46,6 +46,7 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.OverviewCommandHelper.CommandType;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -200,7 +201,7 @@
                         break;
                     case MotionEvent.ACTION_BUTTON_RELEASE:
                         if (isStashedTaskbarHovered) {
-                            mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
+                            mOverviewCommandHelper.addCommand(CommandType.HOME);
                         }
                         break;
                 }