Add keyboard navigation for App Chip and Chip Menu

Bug: 400410772
Flag: com.android.launcher3.enable_overview_icon_menu
Test: Manual using Keyboard. Access the app chip via TAB.
Change-Id: I75f80b5c6f366ecfc27596519a721cc6ace18d45
diff --git a/quickstep/res/layout/icon_app_chip_view.xml b/quickstep/res/layout/icon_app_chip_view.xml
index de05d59..09fb509 100644
--- a/quickstep/res/layout/icon_app_chip_view.xml
+++ b/quickstep/res/layout/icon_app_chip_view.xml
@@ -22,7 +22,8 @@
     android:layout_width="@dimen/task_thumbnail_icon_menu_expanded_width"
     android:layout_height="@dimen/task_thumbnail_icon_menu_expanded_height"
     android:clipToOutline="true"
-    android:focusable="false"
+    android:focusable="true"
+    android:focusableInTouchMode="false"
     android:importantForAccessibility="no"
     android:autoMirrored="true"
     android:elevation="@dimen/task_thumbnail_icon_menu_elevation"
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index c20aa11..7683a15 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -122,6 +122,9 @@
             field = max(value, minMaxWidth)
         }
 
+    var isExpanded: Boolean = false
+        private set
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         iconView = findViewById(R.id.icon_view)
@@ -356,6 +359,7 @@
                 ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, -1f),
             )
             animator!!.setDuration(MENU_BACKGROUND_REVEAL_DURATION.toLong())
+            isExpanded = true
         } else {
             // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
             val expandedTextClipAnim =
@@ -390,6 +394,7 @@
                 ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, 1f),
             )
             animator!!.setDuration(MENU_BACKGROUND_HIDE_DURATION.toLong())
+            isExpanded = false
         }
 
         if (!animated) animator!!.duration = 0
@@ -416,6 +421,17 @@
         }
     }
 
+    override fun focusSearch(direction: Int): View? {
+        if (mParent == null) return null
+        return when (direction) {
+            FOCUS_RIGHT,
+            FOCUS_DOWN -> mParent.focusSearch(this, View.FOCUS_FORWARD)
+            FOCUS_UP,
+            FOCUS_LEFT -> mParent.focusSearch(this, View.FOCUS_BACKWARD)
+            else -> super.focusSearch(direction)
+        }
+    }
+
     override fun asView(): View = this
 
     private companion object {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 07eae21..6067550 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4780,6 +4780,11 @@
         if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) {
             return super.dispatchKeyEvent(event);
         }
+
+        if (mUtils.shouldInterceptKeyEvent(event)) {
+            return super.dispatchKeyEvent(event);
+        }
+
         switch (event.getKeyCode()) {
             case KeyEvent.KEYCODE_TAB:
                 return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index 29601ef..24b7fa7 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -18,8 +18,11 @@
 
 import android.graphics.Rect
 import android.util.FloatProperty
+import android.view.KeyEvent
 import android.view.View
 import androidx.core.view.children
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.Flags
 import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
 import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
 import com.android.launcher3.statehandlers.DesktopVisibilityController
@@ -354,6 +357,18 @@
         }
     }
 
+    fun shouldInterceptKeyEvent(event: KeyEvent): Boolean {
+        if (Flags.enableOverviewIconMenu()) {
+            val floatingView: AbstractFloatingView? = AbstractFloatingView.getTopOpenViewWithType(
+                recentsView.mContainer as RecentsViewContainer,
+                AbstractFloatingView.TYPE_TASK_MENU
+            )
+            val isMenuOpen = floatingView?.isOpen
+            return isMenuOpen == true || event.keyCode == KeyEvent.KEYCODE_TAB
+        }
+        return false
+    }
+
     var deskExplodeProgress: Float = 0f
         set(value) {
             field = value
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
index 6bc0666..696f934 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -26,6 +26,7 @@
 import android.graphics.drawable.shapes.RectShape
 import android.util.AttributeSet
 import android.view.Gravity
+import android.view.KeyEvent
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewOutlineProvider
@@ -465,6 +466,24 @@
         animatorBuilder.with(menuTranslationXAnim)
     }
 
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (enableOverviewIconMenu()) {
+            if (event.action != KeyEvent.ACTION_DOWN) return super.dispatchKeyEvent(event)
+
+            val isFirstMenuOptionFocused = optionLayout.indexOfChild(optionLayout.focusedChild) == 0
+            val isLastMenuOptionFocused =
+                optionLayout.indexOfChild(optionLayout.focusedChild) == optionLayout.childCount - 1
+            if (
+                (isLastMenuOptionFocused && event.keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
+                || (isFirstMenuOptionFocused && event.keyCode == KeyEvent.KEYCODE_DPAD_UP)
+            ) {
+                iconView.requestFocus()
+                return true
+            }
+        }
+        return super.dispatchKeyEvent(event)
+    }
+
     companion object {
         private val REVEAL_OPEN_DURATION = if (enableOverviewIconMenu()) 417L else 150L
         private val REVEAL_CLOSE_DURATION = if (enableOverviewIconMenu()) 333L else 100L
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index cdf5f81..8d95b13 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -43,6 +43,7 @@
 import androidx.core.view.updateLayoutParams
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.traceSection
+import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.Flags.enableCursorHoverStates
 import com.android.launcher3.Flags.enableDesktopExplodedView
 import com.android.launcher3.Flags.enableGridOnlyOverview
@@ -1482,6 +1483,19 @@
         return showTaskMenuWithContainer(menuContainer)
     }
 
+    private fun closeTaskMenu(): Boolean {
+        val floatingView: AbstractFloatingView? = AbstractFloatingView.getTopOpenViewWithType(
+            container,
+            AbstractFloatingView.TYPE_TASK_MENU
+        )
+        if (floatingView?.isOpen == true) {
+            floatingView.close(true)
+            return true
+        } else {
+            return false
+        }
+    }
+
     private fun showTaskMenuWithContainer(menuContainer: TaskContainer): Boolean {
         val recentsView = recentsView ?: return false
         if (enableHoverOfChildElementsInTaskview()) {
@@ -1489,12 +1503,16 @@
             recentsView.setTaskBorderEnabled(false)
         }
         return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
-            menuContainer.iconView.revealAnim(/* isRevealing= */ true)
-            TaskMenuView.showForTask(menuContainer) {
-                val isAnimated = !recentsView.isSplitSelectionActive
-                menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
-                if (enableHoverOfChildElementsInTaskview()) {
-                    recentsView.setTaskBorderEnabled(true)
+            if (menuContainer.iconView.isExpanded) {
+                closeTaskMenu()
+            } else {
+                menuContainer.iconView.revealAnim(/* isRevealing= */ true)
+                TaskMenuView.showForTask(menuContainer) {
+                    val isAnimated = !recentsView.isSplitSelectionActive
+                    menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
+                    if (enableHoverOfChildElementsInTaskview()) {
+                        recentsView.setTaskBorderEnabled(true)
+                    }
                 }
             }
         } else if (container.deviceProfile.isTablet) {