Merge "Implement e2e test for desktop windowing" into main
diff --git a/Android.bp b/Android.bp
index e358005..ba04bb3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,6 +42,24 @@
     ],
 }
 
+// Main Launcher source for compose, excluding the build config
+filegroup {
+    name: "launcher-compose-enabled-src",
+    srcs: [
+        "compose/facade/enabled/*.kt",
+        "compose/facade/core/*.kt",
+        "compose/features/**/*.kt",
+    ],
+}
+
+filegroup {
+    name: "launcher-compose-disabled-src",
+    srcs: [
+        "compose/facade/core/*.kt",
+        "compose/facade/disabled/*.kt",
+    ],
+}
+
 // Source code for quickstep build, on top of launcher-src
 filegroup {
     name: "launcher-quickstep-src",
@@ -51,6 +69,24 @@
     ],
 }
 
+// Source code for quickstep build with compose enabled, on top of launcher-src
+filegroup {
+    name: "launcher-quickstep-compose-enabled-src",
+    srcs: [
+        "quickstep/compose/facade/core/*.kt",
+        "quickstep/compose/facade/enabled/*.kt",
+        "quickstep/compose/features/**/*.kt",
+    ],
+}
+
+filegroup {
+    name: "launcher-quickstep-compose-disabled-src",
+    srcs: [
+        "quickstep/compose/facade/core/*.kt",
+        "quickstep/compose/facade/disabled/*.kt",
+    ],
+}
+
 // Alternate source when quickstep is not included
 filegroup {
     name: "launcher-src_no_quickstep",
@@ -74,6 +110,114 @@
     srcs: ["proguard.flags"],
 }
 
+// Opt-in configuration for Launcher3 code depending on Jetpack Compose.
+soong_config_module_type {
+    name: "launcher_compose_java_defaults",
+    module_type: "java_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["release_enable_compose_in_launcher"],
+    properties: [
+        "srcs",
+        "static_libs",
+    ],
+}
+
+// Opt-in configuration for Launcher Quickstep code depending on Jetpack Compose.
+soong_config_bool_variable {
+    name: "release_enable_compose_in_launcher",
+}
+
+soong_config_module_type {
+    name: "quickstep_compose_java_defaults",
+    module_type: "java_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["release_enable_compose_in_launcher"],
+    properties: [
+        "srcs",
+        "static_libs",
+    ],
+}
+
+soong_config_module_type {
+    name: "launcher_compose_tests_java_defaults",
+    module_type: "java_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["release_enable_compose_in_launcher"],
+    properties: [
+        "static_libs",
+    ],
+}
+
+launcher_compose_java_defaults {
+    name: "launcher_compose_defaults",
+    soong_config_variables: {
+        release_enable_compose_in_launcher: {
+            srcs: [
+                ":launcher-compose-enabled-src"
+            ],
+
+            // Compose dependencies
+            static_libs: [
+                "androidx.compose.runtime_runtime",
+                "androidx.compose.material3_material3",
+            ],
+
+            // By default, Compose is disabled and we compile the ComposeFacade
+            // in compose/launcher3/facade/disabled/.
+            conditions_default: {
+                srcs: [
+                    ":launcher-compose-disabled-src"
+                ],
+                static_libs: [],
+            },
+        },
+    },
+}
+
+quickstep_compose_java_defaults {
+    name: "quickstep_compose_defaults",
+    soong_config_variables: {
+        release_enable_compose_in_launcher: {
+            srcs: [
+                ":launcher-quickstep-compose-enabled-src"
+            ],
+
+            // Compose dependencies
+            static_libs: [
+                "androidx.compose.runtime_runtime",
+                "androidx.compose.material3_material3",
+            ],
+
+            // By default, Compose is disabled and we compile the ComposeFacade
+            // in compose/quickstep/facade/disabled/.
+            conditions_default: {
+                srcs: [
+                    ":launcher-quickstep-compose-disabled-src"
+                ],
+                static_libs: [],
+            },
+        },
+    },
+}
+
+launcher_compose_tests_java_defaults {
+    name: "launcher_compose_tests_defaults",
+    soong_config_variables: {
+        release_enable_compose_in_launcher: {
+            // Compose dependencies
+            static_libs: [
+                "androidx.compose.runtime_runtime",
+                "androidx.compose.ui_ui-test-junit4",
+                "androidx.compose.ui_ui-test-manifest",
+            ],
+
+            conditions_default: {
+                static_libs: [],
+            },
+        },
+    },
+}
+
 android_library {
     name: "launcher-aosp-tapl",
     libs: [
@@ -147,6 +291,9 @@
 // Library with all the dependencies for building Launcher3
 android_library {
     name: "Launcher3ResLib",
+    defaults: [
+        "launcher_compose_defaults",
+    ],
     srcs: [],
     resource_dirs: ["res"],
     static_libs: [
@@ -173,6 +320,7 @@
         "kotlinx_coroutines",
         "com_android_launcher3_flags_lib",
         "com_android_wm_shell_flags_lib",
+
     ],
     manifest: "AndroidManifest-common.xml",
     sdk_version: "current",
@@ -250,6 +398,10 @@
 // Library with all the source code and dependencies for building Launcher Go
 android_library {
     name: "Launcher3GoLib",
+    defaults: [
+        "launcher_compose_defaults",
+        "quickstep_compose_defaults",
+    ],
     srcs: [
         ":launcher-src",
         ":launcher-quickstep-src",
@@ -281,6 +433,10 @@
 // Library with all the source code and dependencies for building Quickstep
 android_library {
     name: "Launcher3QuickStepLib",
+    defaults: [
+        "launcher_compose_defaults",
+        "quickstep_compose_defaults"
+    ],
     srcs: [
         ":launcher-src",
         ":launcher-quickstep-src",
@@ -308,7 +464,6 @@
 // Build rule for Quickstep app.
 android_app {
     name: "Launcher3QuickStep",
-
     static_libs: ["Launcher3QuickStepLib"],
     optimize: {
         proguard_flags_files: [":launcher-proguard-rules"],
@@ -349,7 +504,6 @@
 // eventually be merged into a single target
 android_app {
     name: "Launcher3Go",
-
     static_libs: ["Launcher3GoLib"],
     resource_dirs: [],
 
@@ -386,7 +540,6 @@
 }
 android_app {
     name: "Launcher3QuickStepGo",
-
     static_libs: ["Launcher3GoLib"],
     resource_dirs: [],
 
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index f9327fe..11740ee 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -21,3 +21,13 @@
     description: "Enables rewritten version of TaskThumbnailViews in Overview"
     bug: "331753115"
 }
+
+flag {
+    name: "enable_hover_of_child_elements_in_taskview"
+    namespace: "launcher_overview"
+    description: "Enables child elements to receive hover events in TaskView and respond visually to those hover events."
+    bug: "342594235"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/compose/facade/core/BaseComposeFacade.kt b/compose/facade/core/BaseComposeFacade.kt
new file mode 100644
index 0000000..bc7ba47
--- /dev/null
+++ b/compose/facade/core/BaseComposeFacade.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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.launcher3.compose.core
+
+import android.content.Context
+import android.view.View
+
+interface BaseComposeFacade {
+    fun isComposeAvailable(): Boolean
+
+    fun initComposeView(appContext: Context): View
+}
diff --git a/compose/facade/disabled/ComposeFacade.kt b/compose/facade/disabled/ComposeFacade.kt
new file mode 100644
index 0000000..c1cbfff
--- /dev/null
+++ b/compose/facade/disabled/ComposeFacade.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.launcher3.compose
+
+import android.content.Context
+import android.view.View
+import com.android.launcher3.compose.core.BaseComposeFacade
+
+object ComposeFacade : BaseComposeFacade {
+    override fun isComposeAvailable(): Boolean = false
+
+    override fun initComposeView(appContext: Context): View {
+        error(
+            "Compose is not available. Make sure to check isComposeAvailable() before calling any" +
+                " other function on ComposeFacade."
+        )
+    }
+}
diff --git a/compose/facade/enabled/ComposeFacade.kt b/compose/facade/enabled/ComposeFacade.kt
new file mode 100644
index 0000000..d98a979f
--- /dev/null
+++ b/compose/facade/enabled/ComposeFacade.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.launcher3.compose
+
+import android.content.Context
+import android.view.View
+import androidx.compose.ui.platform.ComposeView
+import com.android.launcher3.compose.core.BaseComposeFacade
+
+object ComposeFacade : BaseComposeFacade {
+    override fun isComposeAvailable(): Boolean = true
+
+    override fun initComposeView(appContext: Context): View = ComposeView(appContext)
+}
diff --git a/quickstep/compose/facade/core/QuickstepComposeFeatures.kt b/quickstep/compose/facade/core/QuickstepComposeFeatures.kt
new file mode 100644
index 0000000..ca9e5c9
--- /dev/null
+++ b/quickstep/compose/facade/core/QuickstepComposeFeatures.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.compose.core
+
+interface QuickstepComposeFeatures
diff --git a/quickstep/compose/facade/disabled/QuickstepComposeFacade.kt b/quickstep/compose/facade/disabled/QuickstepComposeFacade.kt
new file mode 100644
index 0000000..0a4345a
--- /dev/null
+++ b/quickstep/compose/facade/disabled/QuickstepComposeFacade.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.compose
+
+import android.content.Context
+import com.android.launcher3.compose.ComposeFacade
+import com.android.launcher3.compose.core.BaseComposeFacade
+import com.android.quickstep.compose.core.QuickstepComposeFeatures
+
+object QuickstepComposeFacade : BaseComposeFacade, QuickstepComposeFeatures {
+
+    override fun isComposeAvailable() = ComposeFacade.isComposeAvailable()
+
+    override fun initComposeView(appContext: Context) = ComposeFacade.initComposeView(appContext)
+}
diff --git a/quickstep/compose/facade/enabled/QuickstepComposeFacade.kt b/quickstep/compose/facade/enabled/QuickstepComposeFacade.kt
new file mode 100644
index 0000000..97cd300
--- /dev/null
+++ b/quickstep/compose/facade/enabled/QuickstepComposeFacade.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.compose
+
+import android.content.Context
+import com.android.launcher3.compose.ComposeFacade
+import com.android.launcher3.compose.core.BaseComposeFacade
+import com.android.quickstep.compose.core.QuickstepComposeFeatures
+
+object QuickstepComposeFacade : BaseComposeFacade, QuickstepComposeFeatures {
+    override fun isComposeAvailable() = ComposeFacade.isComposeAvailable()
+
+    override fun initComposeView(appContext: Context) = ComposeFacade.initComposeView(appContext)
+}
diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml
index 797ea45..a11974c 100644
--- a/quickstep/res/layout/split_instructions_view.xml
+++ b/quickstep/res/layout/split_instructions_view.xml
@@ -41,5 +41,6 @@
         android:textColor="?androidprv:attr/textColorOnAccent"
         android:layout_marginStart="@dimen/split_instructions_start_margin_cancel"
         android:text="@string/toast_split_select_app_cancel"
+        android:textStyle="bold"
         android:visibility="gone"/>
 </com.android.quickstep.views.SplitInstructionsView>
\ No newline at end of file
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 8bcbb33..037a0f6 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -237,7 +237,7 @@
     <!-- Label for toast with instructions for split screen selection mode. [CHAR_LIMIT=50] -->
     <string name="toast_split_select_app">Tap another app to use split screen</string>
     <string name="toast_contextual_split_select_app">Choose another app to use split screen</string>
-    <string name="toast_split_select_app_cancel"><b>Cancel</b></string>
+    <string name="toast_split_select_app_cancel">Cancel</string>
     <string name="toast_split_select_cont_desc">Exit split screen selection</string>
     <!-- Label for toast when app selected for split isn't supported. [CHAR_LIMIT=50] -->
     <string name="toast_split_app_unsupported">Choose another app to use split screen</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index ea2adcf..7338485 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -264,11 +264,19 @@
         boolean isThreeButtonNav = mContext.isThreeButtonNav();
         DeviceProfile deviceProfile = mContext.getDeviceProfile();
         Resources resources = mContext.getResources();
-        Point p = !mContext.isUserSetupComplete()
-                ? new Point(0, mControllers.taskbarActivityContext.getSetupWindowSize())
-                : DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
-                        mContext.isPhoneMode());
-        mNavButtonsView.getLayoutParams().height = p.y;
+
+        int setupSize = mControllers.taskbarActivityContext.getSetupWindowSize();
+        Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
+                mContext.isPhoneMode());
+        ViewGroup.LayoutParams navButtonsViewLayoutParams = mNavButtonsView.getLayoutParams();
+        navButtonsViewLayoutParams.width = p.x;
+        if (!mContext.isUserSetupComplete()) {
+            // Setup mode in phone mode uses gesture nav.
+            navButtonsViewLayoutParams.height = setupSize;
+        } else {
+            navButtonsViewLayoutParams.height = p.y;
+        }
+        mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
 
         mIsImeRenderingNavButtons =
                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2cb37ff..9d394a8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1136,6 +1136,9 @@
      * window.
      */
     public void setTaskbarWindowFocusable(boolean focusable) {
+        if (isPhoneMode()) {
+            return;
+        }
         if (focusable) {
             mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
         } else {
@@ -1148,7 +1151,7 @@
      * Applies forcibly show flag to taskbar window iff transient taskbar is unstashed.
      */
     public void applyForciblyShownFlagWhileTransientTaskbarUnstashed(boolean shouldForceShow) {
-        if (!DisplayController.isTransientTaskbar(this)) {
+        if (!DisplayController.isTransientTaskbar(this) || isPhoneMode()) {
             return;
         }
         if (shouldForceShow) {
@@ -1691,7 +1694,7 @@
      * @param exclude {@code true} then the magnification region computation will omit the window.
      */
     public void excludeFromMagnificationRegion(boolean exclude) {
-        if (mIsExcludeFromMagnificationRegion == exclude) {
+        if (mIsExcludeFromMagnificationRegion == exclude || isPhoneMode()) {
             return;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 20ab32e..e6b3acd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -470,7 +470,8 @@
                 // We're changing state to home, should close open popups e.g. Taskbar AllApps
                 handleOpenFloatingViews = true;
             }
-            if (mLauncherState == LauncherState.OVERVIEW) {
+            if (mLauncherState == LauncherState.OVERVIEW
+                    && !mControllers.taskbarActivityContext.isPhoneMode()) {
                 // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
                 mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 32ca9f2..4d0cad2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -418,6 +418,7 @@
         LayoutParams lp = (LayoutParams) getLayoutParams();
         lp.gravity = Gravity.BOTTOM | (onLeft ? Gravity.LEFT : Gravity.RIGHT);
         setLayoutParams(lp); // triggers a relayout
+        updateBubbleAccessibilityStates();
     }
 
     /**
@@ -887,6 +888,33 @@
         updateNotificationDotsIfCollapsed();
     }
 
+    /**
+     * Return child views in the order which they are shown on the screen.
+     * <p>
+     * Child views (bubbles) are always ordered based on recency. The most recent bubble is at index
+     * 0.
+     * For example if the child views are (1)(2)(3) then (1) is the most recent bubble and at index
+     * 0.<br>
+     *
+     * How bubbles show up on the screen depends on the bubble bar location. If the bar is on the
+     * left, the most recent bubble is shown on the right. The bubbles from the example above would
+     * be shown as: (3)(2)(1).<br>
+     *
+     * If bubble bar is on the right, then the most recent bubble is on the left. Bubbles from the
+     * example above would be shown as: (1)(2)(3).
+     */
+    private List<View> getChildViewsInOnScreenOrder() {
+        List<View> childViews = new ArrayList<>(getChildCount());
+        for (int i = 0; i < getChildCount(); i++) {
+            childViews.add(getChildAt(i));
+        }
+        if (mBubbleBarLocation.isOnLeft(isLayoutRtl())) {
+            // Visually child views are shown in reverse order when bar is on the left
+            return childViews.reversed();
+        }
+        return childViews;
+    }
+
     private void updateNotificationDotsIfCollapsed() {
         if (isExpanded()) {
             return;
@@ -1331,21 +1359,39 @@
     }
 
     private void updateBubbleAccessibilityStates() {
-        final int childA11y;
         if (mIsBarExpanded) {
             // Bar is expanded, focus on the bubbles
             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-            childA11y = View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+
+            // Set up a11y navigation order. Get list of child views in the order they are shown
+            // on screen. And use that to set up navigation so that swiping left focuses the view
+            // on the left and swiping right focuses view on the right.
+            View prevChild = null;
+            for (View childView : getChildViewsInOnScreenOrder()) {
+                childView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+                childView.setFocusable(true);
+                final View finalPrevChild = prevChild;
+                // Always need to set a new delegate to clear out any previous.
+                childView.setAccessibilityDelegate(new AccessibilityDelegate() {
+                    @Override
+                    public void onInitializeAccessibilityNodeInfo(View host,
+                            AccessibilityNodeInfo info) {
+                        super.onInitializeAccessibilityNodeInfo(host, info);
+                        if (finalPrevChild != null) {
+                            info.setTraversalAfter(finalPrevChild);
+                        }
+                    }
+                });
+                prevChild = childView;
+            }
         } else {
             // Bar is collapsed, only focus on the bar
             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-            childA11y = View.IMPORTANT_FOR_ACCESSIBILITY_NO;
-        }
-        for (int i = 0; i < getChildCount(); i++) {
-            getChildAt(i).setImportantForAccessibility(childA11y);
-            // Only allowing focusing on bubbles when bar is expanded. Otherwise, in talkback mode,
-            // bubbles can be navigates to in collapsed mode.
-            getChildAt(i).setFocusable(mIsBarExpanded);
+            for (int i = 0; i < getChildCount(); i++) {
+                View childView = getChildAt(i);
+                childView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+                childView.setFocusable(false);
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3c9bd0f..4dc04e7 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -989,7 +989,6 @@
                 dp = dp.copy(mContext);
             }
             dp.updateInsets(targets.homeContentInsets);
-            dp.updateIsSeascape(mContext);
             initTransitionEndpoints(dp);
             orientationState.setMultiWindowMode(dp.isMultiWindowMode);
         }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index f89888a..54653fa 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -782,7 +782,6 @@
                 (RelativeLayout.LayoutParams) mFakeHotseatView.getLayoutParams();
         if (!mTutorialFragment.isLargeScreen()) {
             DeviceProfile dp = mTutorialFragment.getDeviceProfile();
-            dp.updateIsSeascape(mContext);
 
             hotseatLayoutParams.addRule(dp.isLandscape
                     ? (dp.isSeascape()
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index fa9a282..5a6c278 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -135,6 +135,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.PagedView;
@@ -553,6 +554,7 @@
     // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
     private float mGridProgress = 0;
     private float mTaskThumbnailSplashAlpha = 0;
+    private boolean mBorderEnabled = false;
     private boolean mShowAsGridLastOnLayout = false;
     private final IntSet mTopRowIdSet = new IntSet();
     private int mClearAllShortTotalWidthTranslation = 0;
@@ -1525,6 +1527,7 @@
      * Enable or disable showing border on hover and focus change on task views
      */
     public void setTaskBorderEnabled(boolean enabled) {
+        mBorderEnabled = enabled;
         int taskCount = getTaskViewCount();
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = requireTaskViewAt(i);
@@ -2044,6 +2047,7 @@
                 taskView.setFullscreenProgress(mFullscreenProgress);
                 taskView.setModalness(mTaskModalness);
                 taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha);
+                taskView.setBorderEnabled(mBorderEnabled);
             }
         }
         // resetTaskVisuals is called at the end of dismiss animation which could update
@@ -4917,6 +4921,9 @@
                 mSplitHiddenTaskView.updateSnapshotRadius();
             });
         } else if (isInitiatingSplitFromTaskView) {
+            if (Flags.enableHoverOfChildElementsInTaskview()) {
+                mSplitHiddenTaskView.setBorderEnabled(false);
+            }
             // Splitting from Overview for fullscreen task
             createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
                     true /* dismissingForSplitSelection*/);
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index e10d38c..158ae33 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -42,7 +42,11 @@
     companion object {
         const val TAG = "TaskMenuViewWithArrow"
 
-        fun showForTask(taskContainer: TaskContainer, alignedOptionIndex: Int = 0): Boolean {
+        fun showForTask(
+            taskContainer: TaskContainer,
+            alignedOptionIndex: Int = 0,
+            onClosedCallback: Runnable? = null
+        ): Boolean {
             val container: RecentsViewContainer =
                 RecentsViewContainer.containerFromContext(taskContainer.taskView.context)
             val taskMenuViewWithArrow =
@@ -52,7 +56,11 @@
                     false
                 ) as TaskMenuViewWithArrow<*>
 
-            return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignedOptionIndex)
+            return taskMenuViewWithArrow.populateAndShowForTask(
+                taskContainer,
+                alignedOptionIndex,
+                onClosedCallback
+            )
         }
     }
 
@@ -98,6 +106,7 @@
     private var iconView: IconView? = null
     private var scrim: View? = null
     private val scrimAlpha = 0.8f
+    private var onClosedCallback: Runnable? = null
 
     override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
 
@@ -141,7 +150,8 @@
 
     private fun populateAndShowForTask(
         taskContainer: TaskContainer,
-        alignedOptionIndex: Int
+        alignedOptionIndex: Int,
+        onClosedCallback: Runnable?
     ): Boolean {
         if (isAttachedToWindow) {
             return false
@@ -150,6 +160,7 @@
         taskView = taskContainer.taskView
         this.taskContainer = taskContainer
         this.alignedOptionIndex = alignedOptionIndex
+        this.onClosedCallback = onClosedCallback
         if (!populateMenu()) return false
         addScrim()
         show()
@@ -252,6 +263,7 @@
         super.closeComplete()
         popupContainer.removeView(scrim)
         popupContainer.removeView(iconView)
+        onClosedCallback?.run()
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 99574a6..176a538 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -48,6 +48,7 @@
 import com.android.launcher3.Flags.enableCursorHoverStates
 import com.android.launcher3.Flags.enableFocusOutline
 import com.android.launcher3.Flags.enableGridOnlyOverview
+import com.android.launcher3.Flags.enableHoverOfChildElementsInTaskview
 import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.R
@@ -413,6 +414,26 @@
             focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
         }
 
+    /**
+     * Used to cache the hover border state so we don't repeatedly call the border animator with
+     * every hover event when the user hasn't crossed the threshold of the [thumbnailBounds].
+     */
+    private var hoverBorderVisible = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            Log.d(
+                TAG,
+                "${taskIds.contentToString()} - setting border animator visibility to: $field"
+            )
+            hoverBorderAnimator?.setBorderVisibility(visible = field, animated = true)
+        }
+
+    // Used to cache thumbnail bounds to avoid recalculating on every hover move.
+    private var thumbnailBounds = Rect()
+
     private var focusTransitionProgress = 1f
         set(value) {
             field = value
@@ -511,20 +532,28 @@
     override fun onHoverEvent(event: MotionEvent): Boolean {
         if (borderEnabled) {
             when (event.action) {
-                MotionEvent.ACTION_HOVER_ENTER ->
-                    hoverBorderAnimator?.setBorderVisibility(visible = true, animated = true)
-                MotionEvent.ACTION_HOVER_EXIT ->
-                    hoverBorderAnimator?.setBorderVisibility(visible = false, animated = true)
+                MotionEvent.ACTION_HOVER_ENTER -> {
+                    hoverBorderVisible =
+                        if (enableHoverOfChildElementsInTaskview()) {
+                            getThumbnailBounds(thumbnailBounds)
+                            event.isWithinThumbnailBounds()
+                        } else {
+                            true
+                        }
+                }
+                MotionEvent.ACTION_HOVER_MOVE ->
+                    if (enableHoverOfChildElementsInTaskview())
+                        hoverBorderVisible = event.isWithinThumbnailBounds()
+                MotionEvent.ACTION_HOVER_EXIT -> hoverBorderVisible = false
                 else -> {}
             }
         }
         return super.onHoverEvent(event)
     }
 
-    // avoid triggering hover event on child elements which would cause HOVER_EXIT for this
-    // task view
-    override fun onInterceptHoverEvent(event: MotionEvent) =
-        if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(event)
+    override fun onInterceptHoverEvent(event: MotionEvent): Boolean =
+        if (enableHoverOfChildElementsInTaskview()) super.onInterceptHoverEvent(event)
+        else if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(event)
 
     override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
         val recentsView = recentsView ?: return false
@@ -567,6 +596,9 @@
                 it.right = width
                 it.bottom = height
             }
+        if (enableHoverOfChildElementsInTaskview()) {
+            getThumbnailBounds(thumbnailBounds)
+        }
     }
 
     override fun onRecycle() {
@@ -579,6 +611,7 @@
         setOverlayEnabled(false)
         onTaskListVisibilityChanged(false)
         borderEnabled = false
+        hoverBorderVisible = false
         taskViewId = UNBOUND_TASK_VIEW_ID
         taskContainers.forEach { it.destroy() }
     }
@@ -1238,10 +1271,17 @@
 
     private fun showTaskMenuWithContainer(menuContainer: TaskContainer): Boolean {
         val recentsView = recentsView ?: return false
+        if (enableHoverOfChildElementsInTaskview()) {
+            // Disable hover on all TaskView's whilst menu is showing.
+            recentsView.setTaskBorderEnabled(false)
+        }
         return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
             menuContainer.iconView.revealAnim(/* isRevealing= */ true)
             TaskMenuView.showForTask(menuContainer) {
                 menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+                if (enableHoverOfChildElementsInTaskview()) {
+                    recentsView.setTaskBorderEnabled(true)
+                }
             }
         } else if (container.deviceProfile.isTablet) {
             val alignedOptionIndex =
@@ -1261,9 +1301,17 @@
                 } else {
                     0
                 }
-            TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex)
+            TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex) {
+                if (enableHoverOfChildElementsInTaskview()) {
+                    recentsView.setTaskBorderEnabled(true)
+                }
+            }
         } else {
-            TaskMenuView.showForTask(menuContainer)
+            TaskMenuView.showForTask(menuContainer) {
+                if (enableHoverOfChildElementsInTaskview()) {
+                    recentsView.setTaskBorderEnabled(true)
+                }
+            }
         }
     }
 
@@ -1596,6 +1644,10 @@
         override fun close() {}
     }
 
+    private fun MotionEvent.isWithinThumbnailBounds(): Boolean {
+        return thumbnailBounds.contains(x.toInt(), y.toInt())
+    }
+
     companion object {
         private const val TAG = "TaskView"
         const val FLAG_UPDATE_ICON = 1
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
index e583f63..961d4dc 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarEduTooltipControllerTest.kt
@@ -14,11 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.taskbar
+package com.android.launcher3.taskbar.test
 
+import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.Utilities
+import com.android.launcher3.taskbar.TOOLTIP_STEP_FEATURES
+import com.android.launcher3.taskbar.TOOLTIP_STEP_NONE
+import com.android.launcher3.taskbar.TOOLTIP_STEP_PINNING
+import com.android.launcher3.taskbar.TOOLTIP_STEP_SWIPE
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarEduTooltipController
 import com.android.launcher3.taskbar.rules.TaskbarModeRule
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.THREE_BUTTONS
@@ -35,12 +42,14 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+@Ignore
 class TaskbarEduTooltipControllerTest {
 
     private val context =
@@ -48,25 +57,25 @@
             InstrumentationRegistry.getInstrumentation().targetContext
         )
 
-    @get:Rule
+    @get:Rule(order = 0)
     val tooltipStepPreferenceRule =
         TaskbarPreferenceRule(
             context,
             OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.prefItem,
         )
 
-    @get:Rule
+    @get:Rule(order = 1)
     val searchEduPreferenceRule =
         TaskbarPreferenceRule(
             context,
             OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN,
         )
 
-    @get:Rule val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
+    @get:Rule(order = 2) val taskbarPinningPreferenceRule = TaskbarPinningPreferenceRule(context)
 
-    @get:Rule val taskbarModeRule = TaskbarModeRule(context)
+    @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
 
-    @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+    @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
 
     @InjectController lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
 
@@ -77,6 +86,7 @@
 
     @Before
     fun setUp() {
+        Log.e("Taskbar", "TaskbarEduTooltipControllerTest test started")
         Utilities.disableRunningInTestHarnessForTests()
     }
 
@@ -85,6 +95,7 @@
         if (wasInTestHarness) {
             Utilities.enableRunningInTestHarnessForTests()
         }
+        Log.e("Taskbar", "TaskbarEduTooltipControllerTest test completed")
     }
 
     @Test
diff --git a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
index 1886ce6..a8f39af 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
@@ -31,6 +31,10 @@
 
     static final int STRESS_REPEAT_COUNT = 10;
 
+    private enum TestCase {
+        TO_HOME, TO_OVERVIEW,
+    }
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -41,28 +45,55 @@
     }
 
     @Test
-    @NavigationModeSwitch
+    @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON)
     public void testStressPressHome() {
-        for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
-            // Destroy Launcher activity.
-            closeLauncherActivity();
-
-            // The test action.
-            mLauncher.goHome();
-        }
+        runTest(TestCase.TO_HOME);
     }
 
     @Test
-    @NavigationModeSwitch
+    @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.ZERO_BUTTON)
+    public void testStressSwipeHome() {
+        runTest(TestCase.TO_HOME);
+    }
+
+    @Test
+    @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON)
+    public void testStressPressOverview() {
+        runTest(TestCase.TO_OVERVIEW);
+    }
+
+    @Test
+    @NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.ZERO_BUTTON)
     public void testStressSwipeToOverview() {
+        runTest(TestCase.TO_OVERVIEW);
+    }
+
+    private void runTest(TestCase testCase) {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
             closeLauncherActivity();
 
             // The test action.
-            mLauncher.getLaunchedAppState().switchToOverview();
+            switch (testCase) {
+                case TO_OVERVIEW:
+                    mLauncher.getLaunchedAppState().switchToOverview();
+                    break;
+                case TO_HOME:
+                    mLauncher.goHome();
+                    break;
+                default:
+                    throw new IllegalStateException("Cannot run test case: " + testCase);
+            }
         }
-        closeLauncherActivity();
-        mLauncher.goHome();
+        switch (testCase) {
+            case TO_OVERVIEW:
+                closeLauncherActivity();
+                mLauncher.goHome();
+                break;
+            case TO_HOME:
+            default:
+                // No-Op
+                break;
+        }
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
index 512557b..dc1da69 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
@@ -88,18 +88,6 @@
     }
 
     @Test
-    public void showBorderOnHoverEvent() {
-        mTaskView.setBorderEnabled(/* enabled= */ true);
-        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
-        mTaskView.onHoverEvent(MotionEvent.obtain(event));
-        verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
-                true);
-        mTaskView.onFocusChanged(true, 0, new Rect());
-        verify(mFocusAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
-                true);
-    }
-
-    @Test
     public void showBorderOnBorderEnabled() {
         presetBorderStatus(/* enabled= */ false);
         mTaskView.setBorderEnabled(/* enabled= */ true);
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 8585b66..177b28c 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -140,15 +140,11 @@
     }
 
     protected void onDeviceProfileInitiated() {
-        if (mDeviceProfile.isVerticalBarLayout()) {
-            mDeviceProfile.updateIsSeascape(this);
-        }
     }
 
     @Override
     public void onDisplayInfoChanged(Context context, Info info, int flags) {
         if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
-            mDeviceProfile.updateIsSeascape(this);
             reapplyUi();
         }
     }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 1a5f4b9..5cbf6fb 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -65,7 +65,6 @@
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
 import com.android.launcher3.responsive.ResponsiveSpecsProvider;
 import com.android.launcher3.util.CellContentDimensions;
-import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.IconSizeSteps;
 import com.android.launcher3.util.ResourceHelper;
@@ -298,9 +297,6 @@
     // the widgetView, such that the actual view size is same as the widget size.
     public final Rect widgetPadding = new Rect();
 
-    // When true, nav bar is on the left side of the screen.
-    private boolean mIsSeascape;
-
     // Notification dots
     public final DotRenderer mDotRendererWorkSpace;
     public final DotRenderer mDotRendererAllApps;
@@ -2007,25 +2003,8 @@
         return isLandscape && transposeLayoutWithOrientation;
     }
 
-    /**
-     * Updates orientation information and returns true if it has changed from the previous value.
-     */
-    public boolean updateIsSeascape(Context context) {
-        if (isVerticalBarLayout()) {
-            boolean isSeascape = DisplayController.INSTANCE.get(context)
-                    .getInfo().rotation == Surface.ROTATION_270;
-            if (mIsSeascape != isSeascape) {
-                mIsSeascape = isSeascape;
-                // Hotseat changing sides requires updating workspace left/right paddings
-                updateWorkspacePadding();
-                return true;
-            }
-        }
-        return false;
-    }
-
     public boolean isSeascape() {
-        return isVerticalBarLayout() && mIsSeascape;
+        return rotationHint == Surface.ROTATION_270 && isVerticalBarLayout();
     }
 
     public boolean shouldFadeAdjacentWorkspaceScreens() {
diff --git a/tests/Android.bp b/tests/Android.bp
index e51242f..c99f656 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -71,6 +71,9 @@
 // Library with all the dependencies for building quickstep
 android_library {
     name: "Launcher3TestLib",
+    defaults: [
+        "launcher_compose_tests_defaults",
+    ],
     srcs: [],
     asset_dirs: ["assets"],
     resource_dirs: ["res"],
@@ -112,6 +115,9 @@
 
 android_test {
     name: "Launcher3Tests",
+    defaults: [
+        "launcher_compose_tests_defaults",
+    ],
     srcs: [
         ":launcher-tests-src",
         ":launcher-non-quickstep-tests-src",