blob: 91f2cc69031d89e37e948a268945807e832a1ca4 [file] [log] [blame]
/*
* Copyright 2020 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.car.rotary;
import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.car.ui.FocusArea;
import com.android.car.ui.FocusParkingView;
import java.util.List;
/**
* Utility methods for {@link AccessibilityNodeInfo} and {@link AccessibilityWindowInfo}.
* <p>
* Because {@link AccessibilityNodeInfo}s must be recycled, it's important to be consistent about
* who is responsible for recycling them. For simplicity, it's best to avoid having multiple objects
* refer to the same instance of {@link AccessibilityNodeInfo}. Instead, each object should keep its
* own copy which it's responsible for. Methods that return an {@link AccessibilityNodeInfo}
* generally pass ownership to the caller. Such methods should never return a reference to one of
* their parameters or the caller will recycle it twice.
*/
class Utils {
private static final String FOCUS_AREA_CLASS_NAME = FocusArea.class.getName();
private static final String FOCUS_PARKING_VIEW_CLASS_NAME = FocusParkingView.class.getName();
private Utils() {
}
/** Recycles a node. */
static void recycleNode(@Nullable AccessibilityNodeInfo node) {
if (node != null) {
node.recycle();
}
}
/** Recycles a list of nodes. */
static void recycleNodes(@Nullable List<AccessibilityNodeInfo> nodes) {
if (nodes != null) {
for (AccessibilityNodeInfo node : nodes) {
recycleNode(node);
}
}
}
/**
* Updates the given {@code node} in case the view represented by it is no longer in the view
* tree. If it's still in the view tree, returns the {@code node}. Otherwise recycles the
* {@code node} and returns null.
*/
static AccessibilityNodeInfo refreshNode(@Nullable AccessibilityNodeInfo node) {
if (node == null) {
return null;
}
boolean succeeded = node.refresh();
if (succeeded) {
return node;
}
L.w("This node is no longer in the view tree: " + node);
node.recycle();
return null;
}
/** Returns whether the given {@code node} can be focused by a rotary controller. */
static boolean canTakeFocus(@NonNull AccessibilityNodeInfo node) {
return node.isVisibleToUser() && node.isFocusable() && node.isEnabled()
&& !isFocusParkingView(node);
}
/** Returns whether the given {@code node} or its descendants can take focus. */
static boolean canHaveFocus(@NonNull AccessibilityNodeInfo node) {
if (canTakeFocus(node)) {
return true;
}
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo childNode = node.getChild(i);
if (childNode != null) {
boolean result = canHaveFocus(childNode);
childNode.recycle();
if (result) {
return true;
}
}
}
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();
return className != null && FOCUS_PARKING_VIEW_CLASS_NAME.contentEquals(className);
}
/** Returns whether the given {@code node} represents a {@link FocusArea}. */
static boolean isFocusArea(@NonNull AccessibilityNodeInfo node) {
CharSequence className = node.getClassName();
return className != null && FOCUS_AREA_CLASS_NAME.contentEquals(className);
}
/**
* Returns whether the given node represents a view which can be scrolled using the rotary
* controller, as indicated by its content description.
*/
static boolean isScrollableContainer(@NonNull AccessibilityNodeInfo node) {
CharSequence contentDescription = node.getContentDescription();
return contentDescription != null
&& (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
|| ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
}
/**
* Returns whether the given node represents a view which can be scrolled horizontally using the
* rotary controller, as indicated by its content description.
*/
static boolean isHorizontallyScrollableContainer(@NonNull AccessibilityNodeInfo node) {
CharSequence contentDescription = node.getContentDescription();
return contentDescription != null
&& (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription));
}
/** Returns whether {@code descendant} is a descendant of {@code ancestor}. */
static boolean isDescendant(@NonNull AccessibilityNodeInfo ancestor,
@NonNull AccessibilityNodeInfo descendant) {
AccessibilityNodeInfo parent = descendant.getParent();
if (parent == null) {
return false;
}
boolean result = parent.equals(ancestor) || isDescendant(ancestor, parent);
recycleNode(parent);
return result;
}
/** Recycles a window. */
static void recycleWindow(@Nullable AccessibilityWindowInfo window) {
if (window != null) {
window.recycle();
}
}
/** Recycles a list of windows. */
static void recycleWindows(@Nullable List<AccessibilityWindowInfo> windows) {
if (windows != null) {
for (AccessibilityWindowInfo window : windows) {
recycleWindow(window);
}
}
}
}