Update getBoundsInScreen() method

Previously for a scrollable container, the union of its children's
bounds is used as its bounds when searching for nudge target. This
CL applies similar logic to the rotary container.
This CL also removes unused method hasFocus().

Bug: 171737334
Test: atest CarRotaryControllerRoboTests
Change-Id: I88fee7ef7971a3cfcdd3879c6722bed0b1e5da9e
diff --git a/src/com/android/car/rotary/Navigator.java b/src/com/android/car/rotary/Navigator.java
index f6a7e08..9c8c841 100644
--- a/src/com/android/car/rotary/Navigator.java
+++ b/src/com/android/car/rotary/Navigator.java
@@ -162,8 +162,7 @@
                 //    we want to focus on container and scroll it, we won't skip the container.
                 if (!Utils.canPerformFocus(nextCandidate)
                         || (Utils.isScrollableContainer(nextCandidate)
-                            && (!nextCandidate.isScrollable()
-                                || Utils.descendantCanTakeFocus(nextCandidate)))) {
+                            && !Utils.canScrollableContainerTakeFocus(nextCandidate))) {
                     Utils.recycleNode(candidate);
                     Utils.recycleNode(candidateFocusArea);
                     candidate = nextCandidate;
diff --git a/src/com/android/car/rotary/Utils.java b/src/com/android/car/rotary/Utils.java
index 293937d..39e3308 100644
--- a/src/com/android/car/rotary/Utils.java
+++ b/src/com/android/car/rotary/Utils.java
@@ -16,6 +16,7 @@
 
 package com.android.car.rotary;
 
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_CONTAINER;
 import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_BOTTOM_BOUND_OFFSET;
 import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_LEFT_BOUND_OFFSET;
 import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET;
@@ -122,10 +123,7 @@
      * <ul>
      *     <li>To be a focus candidate, a node must be able to perform focus action.
      *     <li>A {@link FocusParkingView} is not a focus candidate.
-     *     <li>A scrollable container is not a focus candidate unless it should scroll (i.e.,
-     *         is scrollable and has no focusable descendants on screen). We skip a container
-     *         because we want to focus on its element directly. We don't skip a container because
-     *         we want to focus on it, thus we can scroll it when the rotary controller is rotated.
+     *     <li>A scrollable container is a focus candidate if it meets certain conditions.
      *     <li>To be a focus candidate, a node must be on the screen. Usually the node off the
      *         screen (its bounds in screen is empty) is ignored by RotaryService, but there are
      *         exceptions, e.g. nodes in a WebView.
@@ -134,8 +132,7 @@
     static boolean canTakeFocus(@NonNull AccessibilityNodeInfo node) {
         boolean result = canPerformFocus(node)
                 && !isFocusParkingView(node)
-                && (!isScrollableContainer(node)
-                        || (node.isScrollable() && !descendantCanTakeFocus(node)));
+                && (!isScrollableContainer(node) || canScrollableContainerTakeFocus(node));
         if (result) {
             Rect bounds = getBoundsInScreen(node);
             if (!bounds.isEmpty()) {
@@ -146,6 +143,20 @@
         return false;
     }
 
+    /**
+     * Returns whether the given {@code scrollableContainer} can be focused by the rotary
+     * controller.
+     * <p>
+     * A scrollable container can take focus if it should scroll (i.e., is scrollable and has no
+     * focusable descendants on screen). A container is skipped so that its element can take focus.
+     * A container is not skipped so that it can be focused and scrolled when the rotary controller
+     * is rotated.
+     */
+    static boolean canScrollableContainerTakeFocus(
+            @NonNull AccessibilityNodeInfo scrollableContainer) {
+        return scrollableContainer.isScrollable() && !descendantCanTakeFocus(scrollableContainer);
+    }
+
     /** Returns whether the given {@code node} or its descendants can take focus. */
     static boolean canHaveFocus(@NonNull AccessibilityNodeInfo node) {
         return canTakeFocus(node) || descendantCanTakeFocus(node);
@@ -166,27 +177,6 @@
         return false;
     }
 
-    /**
-     * Returns whether the given {@code node} has focus (i.e. the node or one of its descendants is
-     * focused).
-     */
-    static boolean hasFocus(@NonNull AccessibilityNodeInfo node) {
-        if (node.isFocused()) {
-            return true;
-        }
-        for (int i = 0; i < node.getChildCount(); i++) {
-            AccessibilityNodeInfo childNode = node.getChild(i);
-            if (childNode != null) {
-                boolean result = hasFocus(childNode);
-                childNode.recycle();
-                if (result) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /** Returns whether the given {@code node} represents a {@link FocusParkingView}. */
     static boolean isFocusParkingView(@NonNull AccessibilityNodeInfo node) {
         CharSequence className = node.getClassName();
@@ -215,6 +205,19 @@
     }
 
     /**
+     * Returns whether the given node represents a rotary container, as indicated by its content
+     * description. This includes containers that can be scrolled using the rotary controller as
+     * well as other containers."
+     */
+    static boolean isRotaryContainer(@NonNull AccessibilityNodeInfo node) {
+        CharSequence contentDescription = node.getContentDescription();
+        return contentDescription != null
+                && (ROTARY_CONTAINER.contentEquals(contentDescription)
+                || ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
+                || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
+    }
+
+    /**
      * Returns whether the given node represents a view which can be scrolled using the rotary
      * controller, as indicated by its content description.
      */
@@ -292,8 +295,8 @@
             bounds.right -= bundle.getInt(FOCUS_AREA_RIGHT_BOUND_OFFSET);
             bounds.top += bundle.getInt(FOCUS_AREA_TOP_BOUND_OFFSET);
             bounds.bottom -= bundle.getInt(FOCUS_AREA_BOTTOM_BOUND_OFFSET);
-        } else if (Utils.isScrollableContainer(node)) {
-            // For a scrollable container, the bounds used for finding the nudge target are the
+        } else if (Utils.isRotaryContainer(node)) {
+            // For a rotary container, the bounds used for finding the nudge target are the
             // minimum bounds containing its children.
             bounds.setEmpty();
             Rect childBounds = new Rect();