Fix rotary IME in Dialog window

1. FocusParkingView is used to initialize the focus. If there is no
FocusParkingView in the window, use another method (focusing the
first focusable view) to initialize the focus.
2. Don't allow to nudge out of the overlay window (e.g., a Dialog)
unless the source node is editable and the target window is an IME
window (e.g., nudging from the EditText in the Dialog to the IME
is allowed).

Fixes: 173150961
Test: atest CarRotaryControllerRoboTests
Test: manually tested EditText in Settings and Dialog in Launcher
Change-Id: I2b2073d10ec28160d3fddd1d77e99ad836d96034
diff --git a/src/com/android/car/rotary/Navigator.java b/src/com/android/car/rotary/Navigator.java
index f1c10e9..2920abb 100644
--- a/src/com/android/car/rotary/Navigator.java
+++ b/src/com/android/car/rotary/Navigator.java
@@ -17,6 +17,8 @@
 
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD;
+import static android.view.accessibility.AccessibilityWindowInfo.TYPE_APPLICATION;
+import static android.view.accessibility.AccessibilityWindowInfo.TYPE_INPUT_METHOD;
 
 import android.graphics.Rect;
 import android.view.View;
@@ -53,10 +55,15 @@
     @View.FocusRealDirection
     private int mHunNudgeDirection;
 
-    Navigator(int hunLeft, int hunRight, boolean showHunOnBottom) {
+    @NonNull
+    private final Rect mAppWindowBounds;
+
+    Navigator(int displayWidth, int displayHeight, int hunLeft, int hunRight,
+            boolean showHunOnBottom) {
         mHunLeft = hunLeft;
         mHunRight = hunRight;
         mHunNudgeDirection = showHunOnBottom ? View.FOCUS_DOWN : View.FOCUS_UP;
+        mAppWindowBounds = new Rect(0, 0, displayWidth, displayHeight);
     }
 
     /**
@@ -282,7 +289,9 @@
 
         // Add candidate focus areas in other windows in the given direction.
         List<AccessibilityWindowInfo> candidateWindows = new ArrayList<>();
-        addWindowsInDirection(windows, currentWindow, candidateWindows, direction);
+        boolean isSourceNodeEditable = sourceNode.isEditable();
+        addWindowsInDirection(windows, currentWindow, candidateWindows, direction,
+                isSourceNodeEditable);
         currentWindow.recycle();
         for (AccessibilityWindowInfo window : candidateWindows) {
             List<AccessibilityNodeInfo> focusAreasInAnotherWindow = findFocusAreas(window);
@@ -335,24 +344,39 @@
 
     /**
      * Adds all the {@code windows} in the given {@code direction} of the given {@code source}
-     * window to the given list.
+     * window to the given list if the {@code source} window is not an overlay. If it's an overlay
+     * and the source node is editable, adds the IME window only. Otherwise does nothing.
      */
     private void addWindowsInDirection(@NonNull List<AccessibilityWindowInfo> windows,
             @NonNull AccessibilityWindowInfo source,
             @NonNull List<AccessibilityWindowInfo> results,
-            int direction) {
+            int direction,
+            boolean isSourceNodeEditable) {
         Rect sourceBounds = new Rect();
         source.getBoundsInScreen(sourceBounds);
+
+        // If the source window is an application window and it's smaller than the display, then
+        // it's an overlay window (such as a Dialog window). Nudging out of the overlay window is
+        // not allowed unless the source node is editable and the target window is an IME window
+        // (e.g., nudging from the EditText in the Dialog to the IME is allowed, while nudging from
+        // the Button in the Dialog to the IME is not allowed).
+        boolean isSourceWindowOverlayWindow =
+                source.getType() == TYPE_APPLICATION && !mAppWindowBounds.equals(sourceBounds);
         Rect destBounds = new Rect();
         for (AccessibilityWindowInfo window : windows) {
-            if (!window.equals(source)) {
-                window.getBoundsInScreen(destBounds);
+            if (window.equals(source)) {
+               continue;
+            }
+            if (isSourceWindowOverlayWindow
+                    && (!isSourceNodeEditable || window.getType() != TYPE_INPUT_METHOD)) {
+                continue;
+            }
 
-                // Even if only part of destBounds is in the given direction of sourceBounds, we
-                // still include it because that part may contain the target focus area.
-                if (FocusFinder.isPartiallyInDirection(sourceBounds, destBounds, direction)) {
-                    results.add(window);
-                }
+            window.getBoundsInScreen(destBounds);
+            // Even if only part of destBounds is in the given direction of sourceBounds, we
+            // still include it because that part may contain the target focus area.
+            if (FocusFinder.isPartiallyInDirection(sourceBounds, destBounds, direction)) {
+                results.add(window);
             }
         }
     }
diff --git a/src/com/android/car/rotary/RotaryService.java b/src/com/android/car/rotary/RotaryService.java
index dd0704d..23cde59 100644
--- a/src/com/android/car/rotary/RotaryService.java
+++ b/src/com/android/car/rotary/RotaryService.java
@@ -60,6 +60,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputManager;
 import android.os.Build;
@@ -363,7 +364,9 @@
                 res.getDimensionPixelSize(R.dimen.notification_headsup_card_margin_horizontal);
         int hunLeft = hunMarginHorizontal;
         WindowManager windowManager = getSystemService(WindowManager.class);
-        int displayWidth = windowManager.getCurrentWindowMetrics().getBounds().width();
+        Rect displayBounds = windowManager.getCurrentWindowMetrics().getBounds();
+        int displayWidth = displayBounds.width();
+        int displayHeight = displayBounds.height();
         int hunRight = displayWidth - hunMarginHorizontal;
         boolean showHunOnBottom = res.getBoolean(R.bool.config_showHeadsUpNotificationOnBottom);
         mHunNudgeDirection = showHunOnBottom ? View.FOCUS_DOWN : View.FOCUS_UP;
@@ -372,7 +375,7 @@
         mIgnoreViewClickedMs = res.getInteger(R.integer.ignore_view_clicked_ms);
         mAfterScrollTimeoutMs = res.getInteger(R.integer.after_scroll_timeout_ms);
 
-        mNavigator = new Navigator(hunLeft, hunRight, showHunOnBottom);
+        mNavigator = new Navigator(displayWidth, displayHeight, hunLeft, hunRight, showHunOnBottom);
 
         mPrefs = createDeviceProtectedStorageContext().getSharedPreferences(SHARED_PREFS,
                 Context.MODE_PRIVATE);
@@ -987,20 +990,15 @@
 
         if (fpv == null) {
             L.e("No FocusParkingView in the window containing " + node);
-            return false;
-        }
-
-        if (Utils.isCarUiFocusParkingView(fpv)) {
-            boolean result = fpv.performAction(ACTION_RESTORE_DEFAULT_FOCUS);
+        } else if (Utils.isCarUiFocusParkingView(fpv)
+                    && fpv.performAction(ACTION_RESTORE_DEFAULT_FOCUS)) {
             fpv.recycle();
-            if (result) {
-                findFocusedNode(node);
-            }
-            return result;
+            findFocusedNode(node);
+            return true;
         }
+        Utils.recycleNode(fpv);
 
-        AccessibilityWindowInfo window = fpv.getWindow();
-        fpv.recycle();
+        AccessibilityWindowInfo window = node.getWindow();
         if (window == null) {
             L.e("No window found for the generic FocusParkingView");
             return false;
diff --git a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
index 278c9f0..ee79412 100644
--- a/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
+++ b/tests/robotests/src/com/android/car/rotary/NavigatorTest.java
@@ -48,8 +48,9 @@
     public void setUp() {
         mHunWindowBounds = new Rect(50, 10, 950, 200);
         mNodeBuilder = new NodeBuilder(new ArrayList<>());
-        mNavigator = new Navigator(mHunWindowBounds.left, mHunWindowBounds.right,
-                /* showHunOnBottom= */ false);
+        // The values of displayWidth and displayHeight don't affect the test, so just use 0.
+        mNavigator = new Navigator(/* displayWidth= */ 0, /* displayHeight= */ 0,
+                mHunWindowBounds.left, mHunWindowBounds.right,/* showHunOnBottom= */ false);
         mNavigator.setNodeCopier(MockNodeCopierProvider.get());
     }