Merge Android 14 QPR2 to AOSP main

Bug: 319669529
Merged-In: I5d00a84ec17b7b0d39efde52fcf610044fbfbb99
Change-Id: I1c09ab77fef36b75ed28166895dd59bfb9256460
diff --git a/src/com/android/car/rotary/Navigator.java b/src/com/android/car/rotary/Navigator.java
index c417958..ccaf357 100644
--- a/src/com/android/car/rotary/Navigator.java
+++ b/src/com/android/car/rotary/Navigator.java
@@ -317,7 +317,7 @@
             @NonNull AccessibilityNodeInfo root) {
         AccessibilityNodeInfo surfaceView = null;
         if (!isClientNode(root)) {
-            AccessibilityNodeInfo focusedNode = root.findFocus(FOCUS_INPUT);
+            AccessibilityNodeInfo focusedNode = Utils.findFocusWithRetry(root);
             if (focusedNode != null && Utils.isSurfaceView(focusedNode)) {
                 // The focused node represents a SurfaceView. In this case the root node is actually
                 // a client node but Navigator doesn't know that because SurfaceViewHelper doesn't
diff --git a/src/com/android/car/rotary/RotaryService.java b/src/com/android/car/rotary/RotaryService.java
index 7b506a1..899f4a3 100644
--- a/src/com/android/car/rotary/RotaryService.java
+++ b/src/com/android/car/rotary/RotaryService.java
@@ -1645,6 +1645,7 @@
         // what FocusArea to nudge to. In this case, we'll find a target FocusArea using geometry.
         AccessibilityNodeInfo targetFocusArea =
                 mNavigator.findNudgeTargetFocusArea(windows, mFocusedNode, mFocusArea, direction);
+        L.d("Found targetFocusArea: " + targetFocusArea);
 
         if (targetFocusArea == null) {
             L.d("Failed to find nearest FocusArea for nudge");
@@ -1975,6 +1976,7 @@
         int direction = clockwise ? View.FOCUS_FORWARD : View.FOCUS_BACKWARD;
         Navigator.FindRotateTargetResult result =
                 mNavigator.findRotateTarget(mFocusedNode, direction, rotationCount);
+        L.d("Found rotation result: " + result);
         if (result != null) {
             if (performFocusAction(result.node)) {
                 remainingRotationCount -= result.advancedCount;
@@ -1983,6 +1985,7 @@
         } else {
             L.w("Failed to find rotate target from " + mFocusedNode);
         }
+        L.d("mFocusedNode: " + mFocusedNode);
 
         // If navigation didn't consume all of rotationCount and the focused node either is a
         // scrollable container or is a descendant of one, scroll it. The former happens when no
@@ -1992,7 +1995,7 @@
         // is only supported in the focused window because injected events always go to the focused
         // window. We don't bother checking whether the scrollable container can currently scroll
         // because there's nothing else to do if it can't.
-        if (remainingRotationCount > 0 && isInFocusedWindow(mFocusedNode)) {
+        if (mFocusedNode != null && remainingRotationCount > 0 && isInFocusedWindow(mFocusedNode)) {
             AccessibilityNodeInfo scrollableContainer =
                     mNavigator.findScrollableContainer(mFocusedNode);
             if (scrollableContainer != null) {
diff --git a/src/com/android/car/rotary/Utils.java b/src/com/android/car/rotary/Utils.java
index 27f7028..c78a40e 100644
--- a/src/com/android/car/rotary/Utils.java
+++ b/src/com/android/car/rotary/Utils.java
@@ -77,6 +77,8 @@
     @VisibleForTesting
     static final String SURFACE_VIEW_CLASS_NAME = SurfaceView.class.getName();
 
+    private static final int FIND_FOCUS_MAX_TRY_COUNT = 3;
+
     private Utils() {
     }
 
@@ -451,4 +453,22 @@
         }
         return false;
     }
+
+    /** Retries several times to find the focused node. See b/301318227. */
+    @Nullable
+    static AccessibilityNodeInfo findFocusWithRetry(@NonNull AccessibilityNodeInfo root) {
+        AccessibilityNodeInfo focusedNode;
+        for (int i = 0; i < FIND_FOCUS_MAX_TRY_COUNT; i++) {
+            focusedNode = root.findFocus(FOCUS_INPUT);
+            L.v("findFocus():" + focusedNode);
+            focusedNode = Utils.refreshNode(focusedNode);
+            if (focusedNode != null && focusedNode.isFocused()) {
+                return focusedNode;
+            }
+            Utils.recycleNode(focusedNode);
+            L.w("Retry findFocus()");
+        }
+        L.e("Failed to find focused node in " + root);
+        return null;
+    }
 }
diff --git a/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java b/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java
index 7730eea..b1a26f7 100644
--- a/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java
+++ b/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java
@@ -1102,6 +1102,8 @@
         mRotaryService.onKeyEvents(validDisplayId, Collections.singletonList(nudgeUpEventActionUp));
 
         // It should initialize the focus.
+        Button appDefaultFocus = activity.findViewById(R.id.app_default_focus);
+        assertThat(appDefaultFocus.isFocused()).isTrue();
         AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus");
         assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode);
     }