Merge "Ignoring testHideTaskbarPersistsOnRecreate" into main
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 16abdee..5af8d51 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -17,6 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyboard_quick_switch_view"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginTop="@dimen/keyboard_quick_switch_margin_top"
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 4e9e301..42c423c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -46,6 +46,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.quickstep.util.GroupTask;
 
 import java.util.HashMap;
@@ -360,11 +362,8 @@
                                         OPEN_OUTLINE_INTERPOLATOR));
                     }
                 });
-                if (currentFocusIndexOverride == -1) {
-                    initializeScroll(/* index= */ 0, /* shouldTruncateTarget= */ false);
-                } else {
-                    animateFocusMove(-1, currentFocusIndexOverride);
-                }
+                animateFocusMove(-1, currentFocusIndexOverride == -1
+                        ? Math.min(mContent.getChildCount(), 1) : currentFocusIndexOverride);
                 displayedContent.setVisibility(VISIBLE);
                 setVisibility(VISIBLE);
                 requestFocus();
@@ -413,7 +412,8 @@
                     // there are more tasks
                     initializeScroll(
                             firstVisibleTaskIndex,
-                            /* shouldTruncateTarget= */ firstVisibleTaskIndex != toIndex);
+                            /* shouldTruncateTarget= */ firstVisibleTaskIndex != 0
+                                    && firstVisibleTaskIndex != toIndex);
                 } else if (toIndex > fromIndex || toIndex == 0) {
                     // Scrolling to next task view
                     if (mIsRtl) {
@@ -439,6 +439,13 @@
     }
 
     @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        TestLogging.recordKeyEvent(
+                TestProtocol.SEQUENCE_MAIN, "KeyboardQuickSwitchView key event", event);
+        return super.dispatchKeyEvent(event);
+    }
+
+    @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         return (mViewCallbacks != null
                 && mViewCallbacks.onKeyUp(keyCode, event, mIsRtl, mDisplayingRecentTasks))
@@ -454,56 +461,80 @@
             return;
         }
         if (mIsRtl) {
-            scrollRightTo(
-                    task, shouldTruncateTarget, /* smoothScroll= */ false);
-        } else {
             scrollLeftTo(
-                    task, shouldTruncateTarget, /* smoothScroll= */ false);
+                    task,
+                    shouldTruncateTarget,
+                    /* smoothScroll= */ false,
+                    /* waitForLayout= */ true);
+        } else {
+            scrollRightTo(
+                    task,
+                    shouldTruncateTarget,
+                    /* smoothScroll= */ false,
+                    /* waitForLayout= */ true);
         }
     }
 
     private void scrollRightTo(@NonNull View targetTask) {
-        scrollRightTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
+        scrollRightTo(
+                targetTask,
+                /* shouldTruncateTarget= */ false,
+                /* smoothScroll= */ true,
+                /* waitForLayout= */ false);
     }
 
     private void scrollRightTo(
-            @NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
+            @NonNull View targetTask,
+            boolean shouldTruncateTarget,
+            boolean smoothScroll,
+            boolean waitForLayout) {
         if (!mDisplayingRecentTasks) {
             return;
         }
         if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
             return;
         }
-        int scrollTo = targetTask.getLeft() - mSpacing
-                + (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
-        // Scroll so that the focused task is to the left of the list
-        if (smoothScroll) {
-            mScrollView.smoothScrollTo(scrollTo, 0);
-        } else {
-            mScrollView.scrollTo(scrollTo, 0);
-        }
+        runScrollCommand(waitForLayout, () -> {
+            int scrollTo = targetTask.getLeft() - mSpacing
+                    + (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
+            // Scroll so that the focused task is to the left of the list
+            if (smoothScroll) {
+                mScrollView.smoothScrollTo(scrollTo, 0);
+            } else {
+                mScrollView.scrollTo(scrollTo, 0);
+            }
+        });
     }
 
     private void scrollLeftTo(@NonNull View targetTask) {
-        scrollLeftTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
+        scrollLeftTo(
+                targetTask,
+                /* shouldTruncateTarget= */ false,
+                /* smoothScroll= */ true,
+                /* waitForLayout= */ false);
     }
 
     private void scrollLeftTo(
-            @NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
+            @NonNull View targetTask,
+            boolean shouldTruncateTarget,
+            boolean smoothScroll,
+            boolean waitForLayout) {
         if (!mDisplayingRecentTasks) {
             return;
         }
         if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
             return;
         }
-        int scrollTo = targetTask.getRight() + mSpacing - mScrollView.getWidth()
-                - (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
-        // Scroll so that the focused task is to the right of the list
-        if (smoothScroll) {
-            mScrollView.smoothScrollTo(scrollTo, 0);
-        } else {
-            mScrollView.scrollTo(scrollTo, 0);
-        }
+        runScrollCommand(waitForLayout, () -> {
+            int scrollTo = targetTask.getRight() + mSpacing - mScrollView.getWidth()
+                    - (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
+            // Scroll so that the focused task is to the right of the list
+            if (smoothScroll) {
+                mScrollView.smoothScrollTo(scrollTo, 0);
+            } else {
+                mScrollView.scrollTo(scrollTo, 0);
+            }
+        });
     }
 
     private boolean shouldScroll(@NonNull View targetTask, boolean shouldTruncateTarget) {
@@ -514,6 +545,21 @@
         return isTargetTruncated && !shouldTruncateTarget;
     }
 
+    private void runScrollCommand(boolean waitForLayout, @NonNull Runnable scrollCommand) {
+        if (!waitForLayout) {
+            scrollCommand.run();
+            return;
+        }
+        mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        scrollCommand.run();
+                        mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                    }
+                });
+    }
+
     @Nullable
     protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
         return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index a293f74..cbb991d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -24,7 +24,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
 import com.android.quickstep.SystemUiProxy;
@@ -92,27 +92,19 @@
 
     protected void closeQuickSwitchView(boolean animate) {
         if (mCloseAnimation != null) {
-            if (animate) {
-                // Let currently-running animation finish.
-                return;
-            } else {
-                mCloseAnimation.cancel();
+            // Let currently-running animation finish.
+            if (!animate) {
+                mCloseAnimation.end();
             }
+            return;
         }
         if (!animate) {
-            mCloseAnimation = null;
             onCloseComplete();
             return;
         }
         mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation();
 
-        mCloseAnimation.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                mCloseAnimation = null;
-                onCloseComplete();
-            }
-        });
+        mCloseAnimation.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
         mCloseAnimation.start();
     }
 
@@ -160,6 +152,7 @@
     }
 
     private void onCloseComplete() {
+        mCloseAnimation = null;
         mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
         mControllerCallbacks.onCloseComplete();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index eb15cef..881f5c4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -226,24 +226,24 @@
         }
 
 
-        val imeInsetsSize = getInsetsForGravity(taskbarHeightForIme, gravity)
+        // When in gesture nav, report the stashed height to the IME, to allow hiding the
+        // IME navigation bar.
+        val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
+            getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity);
+        } else {
+            getInsetsForGravity(taskbarHeightForIme, gravity)
+        }
         val imeInsetsSizeOverride =
-                if (!ENABLE_HIDE_IME_CAPTION_BAR) {
-                    arrayOf(
-                            InsetsFrameProvider.InsetsSizeOverride(
-                                    TYPE_INPUT_METHOD,
-                                    imeInsetsSize
-                            ),
-                    )
-                } else {
-                    arrayOf()
-                }
+                arrayOf(
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+                )
         // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
         val visInsetsSizeForTappableElement =
                 if (context.isGestureNav) getInsetsForGravity(0, gravity)
                 else getInsetsForGravity(tappableHeight, gravity)
         val insetsSizeOverrideForTappableElement =
-                imeInsetsSizeOverride + arrayOf(
+                arrayOf(
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
                         InsetsFrameProvider.InsetsSizeOverride(
                                 TYPE_VOICE_INTERACTION,
                                 visInsetsSizeForTappableElement
@@ -252,7 +252,7 @@
         if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
                 && provider.type == tappableElement()) {
             provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
-        } else if (provider.type != systemGestures() && imeInsetsSizeOverride.isNotEmpty()) {
+        } else if (provider.type != systemGestures()) {
             // We only override insets at the bottom of the screen
             provider.insetsSizeOverrides = imeInsetsSizeOverride
         }
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 23b5d30..a10b24d 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -177,7 +177,6 @@
 
     // b/143488140
     //@NavigationModeSwitch
-    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/266606727
     @Test
     public void goToOverviewFromHome() {
         mDevice.pressHome();
@@ -225,7 +224,6 @@
 
     // b/143488140
     //@NavigationModeSwitch
-    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/266606727
     @Test
     public void testOverview() {
         startAppFast(getAppPackageName());
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 7b82195..04f2ffa 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -273,13 +273,19 @@
             final WindowInsetsController wic = root.getWindowInsetsController();
             WindowInsets insets = root.getRootWindowInsets();
             boolean isImeShown = insets != null && insets.isVisible(WindowInsets.Type.ime());
-            if (wic != null && isImeShown) {
-                StatsLogManager slm  = getStatsLogManager();
-                slm.keyboardStateManager().setKeyboardState(HIDE);
+            if (wic != null) {
+                // Only hide the keyboard if it is actually showing.
+                if (isImeShown) {
+                    StatsLogManager slm = getStatsLogManager();
+                    slm.keyboardStateManager().setKeyboardState(HIDE);
 
-                // this method cannot be called cross threads
-                wic.hide(WindowInsets.Type.ime());
-                slm.logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED);
+                    // this method cannot be called cross threads
+                    wic.hide(WindowInsets.Type.ime());
+                    slm.logger().log(LAUNCHER_ALLAPPS_KEYBOARD_CLOSED);
+                }
+
+                // If the WindowInsetsController is not null, we end here regardless of whether we
+                // hid the keyboard or not.
                 return;
             }
         }