blob: 1c11450a6dae37a18de496a52578625bd36a6247 [file] [log] [blame]
/*
* Copyright (C) 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.google.common.truth.Truth.assertThat;
import android.app.Activity;
import android.app.UiAutomation;
import android.content.Intent;
import android.graphics.Rect;
import android.view.Display;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.Button;
import androidx.annotation.LayoutRes;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import com.android.car.rotary.Navigator.FindRotateTargetResult;
import com.android.car.rotary.ui.TestRecyclerViewAdapter;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class NavigatorTest {
private final static String HOST_APP_PACKAGE_NAME = "host.app.package.name";
private final static String CLIENT_APP_PACKAGE_NAME = "client.app.package.name";
private static UiAutomation sUiAutomoation;
private final List<AccessibilityNodeInfo> mNodes = new ArrayList<>();
private ActivityTestRule<NavigatorTestActivity> mActivityRule;
private Intent mIntent;
private Rect mDisplayBounds;
private Rect mHunWindowBounds;
private Navigator mNavigator;
private AccessibilityNodeInfo mWindowRoot;
private NodeBuilder mNodeBuilder;
@BeforeClass
public static void oneTimeSetup() {
sUiAutomoation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
}
@Before
public void setUp() {
mActivityRule = new ActivityTestRule<>(NavigatorTestActivity.class);
mIntent = new Intent();
mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
mDisplayBounds = new Rect(0, 0, 1080, 920);
mHunWindowBounds = new Rect(50, 10, 950, 200);
// The values of displayWidth and displayHeight don't affect the test, so just use 0.
mNavigator = new Navigator(/* displayWidth= */ mDisplayBounds.right,
/* displayHeight= */ mDisplayBounds.bottom,
mHunWindowBounds.left, mHunWindowBounds.right, /* showHunOnBottom= */ false);
mNavigator.setNodeCopier(MockNodeCopierProvider.get());
mNodeBuilder = new NodeBuilder(new ArrayList<>());
}
@After
public void tearDown() {
mActivityRule.finishActivity();
Utils.recycleNode(mWindowRoot);
Utils.recycleNodes(mNodes);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* |
* focusArea
* / | \
* / | \
* button1 button2 button3
* </pre>
*/
@Test
public void testFindRotateTarget() {
initActivity(R.layout.navigator_find_rotate_target_test_activity);
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo button2 = createNode("button2");
AccessibilityNodeInfo button3 = createNode("button3");
int direction = View.FOCUS_FORWARD;
// Rotate once, the focus should move from button1 to button2.
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isEqualTo(button2);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
// Rotate twice, the focus should move from button1 to button3.
target = mNavigator.findRotateTarget(button1, direction, 2);
assertThat(target.node).isEqualTo(button3);
assertThat(target.advancedCount).isEqualTo(2);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / \
* / \
* focusParkingView focusArea
* / \
* / \
* button1 button2
* </pre>
*/
@Test
public void testFindRotateTargetNoWrapAround() {
initActivity(R.layout.navigator_find_rotate_target_no_wrap_test_1_activity);
AccessibilityNodeInfo button2 = createNode("button2");
int direction = View.FOCUS_FORWARD;
// Rotate at the end of focus area, no wrap-around should happen.
FindRotateTargetResult target = mNavigator.findRotateTarget(button2, direction, 1);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / \
* / \
* focusArea genericFocusParkingView
* / \
* / \
* button1 button2
* </pre>
*/
@Test
public void testFindRotateTargetNoWrapAroundWithGenericFpv() {
initActivity(R.layout.navigator_find_rotate_target_no_wrap_test_1_generic_fpv_activity);
AccessibilityNodeInfo button2 = createNode("button2");
int direction = View.FOCUS_FORWARD;
// Rotate at the end of focus area, no wrap-around should happen.
FindRotateTargetResult target = mNavigator.findRotateTarget(button2, direction, 1);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / | \
* / | \
* / | \
* focusParkingView button1 button2
* </pre>
*/
@Test
public void testFindRotateTargetNoWrapAround2() {
initActivity(R.layout.navigator_find_rotate_target_no_wrap_test_2_activity);
AccessibilityNodeInfo button2 = createNode("button2");
int direction = View.FOCUS_FORWARD;
// Rotate at the end of focus area, no wrap-around should happen.
FindRotateTargetResult target = mNavigator.findRotateTarget(button2, direction, 1);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / | \
* / | \
* / | \
* button1 button2 genericFocusParkingView
* </pre>
*/
@Test
public void testFindRotateTargetNoWrapAround2WithGenericFpv() {
initActivity(R.layout.navigator_find_rotate_target_no_wrap_test_2_generic_fpv_activity);
AccessibilityNodeInfo button2 = createNode("button2");
int direction = View.FOCUS_FORWARD;
// Rotate at the end of focus area, no wrap-around should happen.
FindRotateTargetResult target = mNavigator.findRotateTarget(button2, direction, 1);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* ============ focus area ============
* = =
* = ***** recycler view **** =
* = * * =
* = * ........ text 1 ........ * =
* = * . visible . * =
* = * .......................... * =
* = * * =
* = * ........ text 2 ........ * =
* = * . visible . * =
* = * .......................... * =
* = * * =
* = * ........ text 3 ........ * =
* = * . offscreen ....... * =
* = * .......................... * =
* = * * =
* = ******************************* =
* = =
* ============ focus area ============
* </pre>
*/
@Test
public void testFindRotateTargetDoesNotSkipOffscreenNode() {
initActivity(
R.layout.navigator_find_rotate_target_does_not_skip_offscreen_node_test_activity);
Activity activity = mActivityRule.getActivity();
RecyclerView recyclerView = activity.findViewById(R.id.scrollable);
recyclerView.post(() -> {
TestRecyclerViewAdapter adapter = new TestRecyclerViewAdapter(activity, 3);
adapter.setItemsFocusable(true);
recyclerView.setAdapter(adapter);
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
AccessibilityNodeInfo text1 = createNodeByText("Test Item 1");
AccessibilityNodeInfo text2 = createNodeByText("Test Item 2");
int direction = View.FOCUS_FORWARD;
FindRotateTargetResult target = mNavigator.findRotateTarget(text1, direction, 1);
assertThat(target.node).isEqualTo(text2);
Utils.recycleNode(target.node);
AccessibilityNodeInfo text3 = createNodeByText("Test Item 3");
assertThat(text3).isNull();
target = mNavigator.findRotateTarget(text2, direction, 1);
// Need to query for text3 after the rotation, so that it is visible on the screen for the
// instrumentation to pick it up.
text3 = createNodeByText("Test Item 3");
assertThat(target.node).isEqualTo(text3);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* |
* focusArea
* / | \
* / | \
* button1 button2 button3
* </pre>
* where {@code button2} is not focusable.
*/
@Test
public void testFindRotateTargetSkipNodeThatCannotPerformFocus() {
initActivity(R.layout.navigator_find_rotate_target_test_activity);
Activity activity = mActivityRule.getActivity();
View rootView = activity.findViewById(R.id.root);
rootView.post(() -> {
Button button2 = activity.findViewById(R.id.button2);
button2.setFocusable(false);
});
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo button3 = createNode("button3");
int direction = View.FOCUS_FORWARD;
// Rotate from button1, it should skip the empty view.
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isEqualTo(button3);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / | \
* / | \
* / | \
* button1 scrollable button2
* recyclerView
* |
* non-focusable
* </pre>
*/
@Test
public void testFindRotateTargetReturnScrollableContainer() {
initActivity(R.layout.navigator_find_rotate_target_scrollable_container_test_activity);
Activity activity = mActivityRule.getActivity();
RecyclerView recyclerView = activity.findViewById(R.id.scrollable);
recyclerView.post(() -> {
TestRecyclerViewAdapter adapter = new TestRecyclerViewAdapter(activity, 1);
recyclerView.setAdapter(adapter);
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
AccessibilityNodeInfo windowRoot = sUiAutomoation.getRootInActiveWindow();
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo scrollable = createNode("scrollable");
int direction = View.FOCUS_FORWARD;
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isEqualTo(scrollable);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / | \
* / | \
* / | \
* / | \
* button1 non-scrollable button2
* recyclerView
* </pre>
*/
@Test
public void testFindRotateTargetSkipScrollableContainer() {
initActivity(
R.layout.navigator_find_rotate_target_skip_scrollable_container_test_1_activity);
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo button2 = createNode("button2");
int direction = View.FOCUS_FORWARD;
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isEqualTo(button2);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / \
* / \
* focusParkingView scrollable
* container
* / \
* / \
* focusable1 focusable2
* </pre>
*/
@Test
public void testFindRotateTargetSkipScrollableContainer2() {
initActivity(
R.layout.navigator_find_rotate_target_skip_scrollable_container_test_2_activity);
Activity activity = mActivityRule.getActivity();
RecyclerView recyclerView = activity.findViewById(R.id.scrollable);
recyclerView.post(() -> {
TestRecyclerViewAdapter adapter = new TestRecyclerViewAdapter(activity, 2);
adapter.setItemsFocusable(true);
recyclerView.setAdapter(adapter);
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
AccessibilityNodeInfo focusable1 = createNodeByText("Test Item 1");
AccessibilityNodeInfo focusable2 = createNodeByText("Test Item 2");
int direction = View.FOCUS_BACKWARD;
FindRotateTargetResult target = mNavigator.findRotateTarget(focusable2, direction, 2);
assertThat(target.node).isEqualTo(focusable1);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* node
* </pre>
*/
@Test
public void testFindRotateTargetWithOneNode() {
initActivity(
R.layout.navigator_find_rotate_target_one_node_test_activity);
AccessibilityNodeInfo node = createNode("node");
int direction = View.FOCUS_BACKWARD;
FindRotateTargetResult target = mNavigator.findRotateTarget(node, direction, 1);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findRotateTarget} in the following layout:
* <pre>
* ============ focus area 1 ==========
* = =
* = ***** scrollable container **** =
* = * * =
* = * ........ text 1 ........ * =
* = * . . * =
* = * .......................... * =
* = * * =
* = * ........ text 2 ........ * =
* = * . . * =
* = * .......................... * =
* = * * =
* = ******************************* =
* = =
* ============ focus area 1 ==========
* ============ focus area 2 ==========
* = ........ text 3 ........ =
* = . . =
* = .......................... =
* ============ focus area 2 ==========
* </pre>
*/
@Test
public void testFindRotateTargetInScrollableContainer1() {
initActivity(
R.layout.navigator_find_rotate_target_in_scrollable_container_test_1_activity);
Activity activity = mActivityRule.getActivity();
RecyclerView recyclerView = activity.findViewById(R.id.scrollable);
recyclerView.post(() -> {
TestRecyclerViewAdapter adapter = new TestRecyclerViewAdapter(activity, 2);
adapter.setItemsFocusable(true);
recyclerView.setAdapter(adapter);
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
AccessibilityNodeInfo text1 = createNodeByText("Test Item 1");
AccessibilityNodeInfo text2 = createNodeByText("Test Item 2");
int direction = View.FOCUS_FORWARD;
// Rotate once, the focus should move from text1 to text2.
FindRotateTargetResult target = mNavigator.findRotateTarget(text1, direction, 1);
assertThat(target.node).isEqualTo(text2);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
// Rotate twice, the focus should move from text1 to text2 since text3 is not a
// descendant of the scrollable container.
target = mNavigator.findRotateTarget(text1, direction, 2);
assertThat(target.node).isEqualTo(text2);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
// Rotate three times should do the same.
target = mNavigator.findRotateTarget(text1, direction, 3);
assertThat(target.node).isEqualTo(text2);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following layout:
* <pre>
* ============ focus area ============
* = =
* = ***** scrollable container **** =
* = * * =
* = * ........ text 1 ........ * =
* = * . visible . * =
* = * .......................... * =
* = * * =
* = * ........ text 2 ........ * =
* = * . visible . * =
* = * .......................... * =
* = * * =
* = * ........ text 3 ........ * =
* = * . not visible . * =
* = * .......................... * =
* = * * =
* = ******************************* =
* = =
* ============ focus area ============
* </pre>
* where {@code text 3} is off the screen.
*/
@Test
public void testFindRotateTargetInScrollableContainer2() {
initActivity(
R.layout.navigator_find_rotate_target_in_scrollable_container_test_2_activity);
Activity activity = mActivityRule.getActivity();
RecyclerView recyclerView = activity.findViewById(R.id.scrollable);
recyclerView.post(() -> {
TestRecyclerViewAdapter adapter = new TestRecyclerViewAdapter(activity, 3);
adapter.setItemsFocusable(true);
recyclerView.setAdapter(adapter);
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
AccessibilityNodeInfo text1 = createNodeByText("Test Item 1");
AccessibilityNodeInfo text2 = createNodeByText("Test Item 2");
int direction = View.FOCUS_FORWARD;
// Rotate once, the focus should move from text1 to text2.
FindRotateTargetResult target = mNavigator.findRotateTarget(text1, direction, 1);
assertThat(target.node).isEqualTo(text2);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
// Rotate twice, the focus should move from text1 to text2 since text3 is off the
// screen.
target = mNavigator.findRotateTarget(text1, direction, 2);
assertThat(target.node).isEqualTo(text2);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
// Rotate three times should do the same.
target = mNavigator.findRotateTarget(text1, direction, 3);
assertThat(target.node).isEqualTo(text2);
assertThat(target.advancedCount).isEqualTo(1);
Utils.recycleNode(target.node);
}
/**
* Tests {@link Navigator#findScrollableContainer} in the following node tree:
* <pre>
* root
* |
* |
* focusArea
* / \
* / \
* scrolling button2
* container
* |
* |
* container
* |
* |
* button1
* </pre>
*/
@Test
public void testFindScrollableContainer() {
initActivity(R.layout.navigator_find_scrollable_container_test_activity);
AccessibilityNodeInfo scrollableContainer = createNode("scrollableContainer");
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo button2 = createNode("button2");
AccessibilityNodeInfo target = mNavigator.findScrollableContainer(button1);
assertThat(target).isEqualTo(scrollableContainer);
Utils.recycleNodes(target);
target = mNavigator.findScrollableContainer(button2);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findFocusableDescendantInDirection} going
* {@link View#FOCUS_BACKWARD} in the following node tree:
* <pre>
* root
* / \
* / \
* container1 container2
* / \ / \
* / \ / \
* button1 button2 button3 button4
* </pre>
*/
@Test
public void testFindFocusableVisibleDescendantInDirectionBackward() {
initActivity(R.layout.navigator_find_focusable_descendant_test_activity);
AccessibilityNodeInfo container1 = createNode("container1");
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo button2 = createNode("button2");
AccessibilityNodeInfo container2 = createNode("container2");
AccessibilityNodeInfo button3 = createNode("button3");
AccessibilityNodeInfo button4 = createNode("button4");
int direction = View.FOCUS_BACKWARD;
AccessibilityNodeInfo target = mNavigator.findFocusableDescendantInDirection(container2,
button4, direction);
assertThat(target).isEqualTo(button3);
Utils.recycleNode(target);
target = mNavigator.findFocusableDescendantInDirection(container2, button3, direction);
assertThat(target).isNull();
Utils.recycleNode(target);
target = mNavigator.findFocusableDescendantInDirection(container1, button2, direction);
assertThat(target).isEqualTo(button1);
Utils.recycleNode(target);
target = mNavigator.findFocusableDescendantInDirection(container1, button1, direction);
assertThat(target).isNull();
Utils.recycleNode(target);
}
/**
* Tests {@link Navigator#findFocusableDescendantInDirection} going
* {@link View#FOCUS_FORWARD} in the following node tree:
* <pre>
* root
* / \
* / \
* container1 container2
* / \ / \
* / \ / \
* button1 button2 button3 button4
* </pre>
*/
@Test
public void testFindFocusableVisibleDescendantInDirectionForward() {
initActivity(R.layout.navigator_find_focusable_descendant_test_activity);
AccessibilityNodeInfo container1 = createNode("container1");
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo button2 = createNode("button2");
AccessibilityNodeInfo container2 = createNode("container2");
AccessibilityNodeInfo button3 = createNode("button3");
AccessibilityNodeInfo button4 = createNode("button4");
int direction = View.FOCUS_FORWARD;
AccessibilityNodeInfo target = mNavigator.findFocusableDescendantInDirection(container1,
button1, direction);
assertThat(target).isEqualTo(button2);
Utils.recycleNode(target);
target = mNavigator.findFocusableDescendantInDirection(container1, button2, direction);
assertThat(target).isNull();
target = mNavigator.findFocusableDescendantInDirection(container2, button3, direction);
assertThat(target).isEqualTo(button4);
Utils.recycleNode(target);
target = mNavigator.findFocusableDescendantInDirection(container2, button4, direction);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findNextFocusableDescendant} in the following node tree:
* <pre>
* root
* |
* |
* container
* / / \ \
* / / \ \
* button1 button2 button3 button4
* </pre>
* where {@code button3} and {@code button4} have empty bounds.
*/
@Test
public void testFindNextFocusableDescendantWithEmptyBounds() {
initActivity(R.layout.navigator_find_focusable_descendant_empty_bounds_test_activity);
AccessibilityNodeInfo container = createNode("container");
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo button2 = createNode("button2");
AccessibilityNodeInfo button3 = createNode("button3");
AccessibilityNodeInfo button4 = createNode("button4");
int direction = View.FOCUS_FORWARD;
AccessibilityNodeInfo target =
mNavigator.findFocusableDescendantInDirection(container, button1, direction);
assertThat(target).isEqualTo(button2);
Utils.recycleNode(target);
target = mNavigator.findFocusableDescendantInDirection(container, button2, direction);
assertThat(target).isEqualTo(button1);
Utils.recycleNode(target);
target = mNavigator.findFocusableDescendantInDirection(container, button3, direction);
assertThat(target).isEqualTo(button1);
Utils.recycleNode(target);
// Wrap around since there is no Focus Parking View present.
target = mNavigator.findFocusableDescendantInDirection(container, button4, direction);
assertThat(target).isEqualTo(button1);
Utils.recycleNode(target);
}
/**
* Tests {@link Navigator#findFirstFocusableDescendant} in the following node tree:
* <pre>
* root
* / \
* / \
* container1 container2
* / \ / \
* / \ / \
* button1 button2 button3 button4
* </pre>
* where {@code button1} and {@code button2} are disabled.
*/
@Test
public void testFindFirstFocusableDescendant() {
initActivity(R.layout.navigator_find_focusable_descendant_test_activity);
Activity activity = mActivityRule.getActivity();
View rootView = activity.findViewById(R.id.root);
rootView.post(() -> {
Button button1View = activity.findViewById(R.id.button1);
button1View.setEnabled(false);
Button button2View = activity.findViewById(R.id.button2);
button2View.setEnabled(false);
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// When searching for the target node, even though the states of the views are correct
// (i.e., button1View and button2View have been disabled), the states of the nodes might
// not be up to date (i.e., button1 and button2 haven't been disabled yet) because they're
// fetched from the node pool. So new nodes are created here to ensure the nodes are up to
// date with the most recent state changes of the views they represent.
AccessibilityNodeInfo button1 = createNode("button1");
assertThat(button1.isEnabled()).isFalse();
AccessibilityNodeInfo button2 = createNode("button2");
assertThat(button2.isEnabled()).isFalse();
AccessibilityNodeInfo root = createNode("root");
AccessibilityNodeInfo button3 = createNode("button3");
AccessibilityNodeInfo target = mNavigator.findFirstFocusableDescendant(root);
assertThat(target).isEqualTo(button3);
Utils.recycleNode(target);
}
/**
* Tests {@link Navigator#findLastFocusableDescendant} in the following node tree:
* <pre>
* root
* / \
* / \
* container1 container2
* / \ / \
* / \ / \
* button1 button2 button3 button4
* </pre>
* where {@code button3} and {@code button4} are disabled.
*/
@Test
public void testFindLastFocusableDescendant() {
initActivity(R.layout.navigator_find_focusable_descendant_test_activity);
Activity activity = mActivityRule.getActivity();
View rootView = activity.findViewById(R.id.root);
rootView.post(() -> {
Button button3View = activity.findViewById(R.id.button3);
button3View.setEnabled(false);
Button button4View = activity.findViewById(R.id.button4);
button4View.setEnabled(false);
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// When searching for the target node, even though the states of the views are correct
// (i.e., button3View and button4View have been disabled), the states of the nodes might
// not be up to date (i.e., button3 and button4 haven't been disabled yet) because they're
// fetched from the node pool. So new nodes are created here to ensure the nodes are up to
// date with the most recent state changes of the views they represent.
AccessibilityNodeInfo button3 = createNode("button3");
assertThat(button3.isEnabled()).isFalse();
AccessibilityNodeInfo button4 = createNode("button4");
assertThat(button4.isEnabled()).isFalse();
AccessibilityNodeInfo root = createNode("root");
AccessibilityNodeInfo button2 = createNode("button2");
AccessibilityNodeInfo target = mNavigator.findLastFocusableDescendant(root);
assertThat(target).isEqualTo(button2);
Utils.recycleNode(target);
}
/**
* Tests {@link Navigator#findNudgeTargetFocusArea} in the following layout:
* <pre>
*
* =====focusArea1==============
* = =========focusArea2==== =
* = = *view* = =
* = ======================= =
* = =
* = =
* = *scrollableContainer* =
* = =
* =============================
* </pre>
* Where scrollableContainer has the same size as focusArea1. The top offset of focusArea1
* equals the height of focusArea2.
*/
@Test
public void test_findNudgeTargetFocusArea_fromScrollableContainer() {
initActivity(R.layout.navigator_find_nudge_target_focus_area_1_test_activity);
Activity activity = mActivityRule.getActivity();
RecyclerView scrollable = activity.findViewById(R.id.scrollable_container);
scrollable.post(() -> {
TestRecyclerViewAdapter adapter = new TestRecyclerViewAdapter(activity, 20);
scrollable.setAdapter(adapter);
scrollable.requestFocus();
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertThat(scrollable.isFocused()).isEqualTo(true);
AccessibilityNodeInfo currentFocusArea = createNode("focus_area1");
// Only an AccessibilityService with the permission to retrieve the active window content
// can create an AccessibilityWindowInfo. So the AccessibilityWindowInfo and the associated
// AccessibilityNodeInfos have to be mocked.
AccessibilityWindowInfo window = new WindowBuilder()
.setRoot(mWindowRoot)
.setBoundsInScreen(mWindowRoot.getBoundsInScreen())
.build();
AccessibilityNodeInfo sourceNode = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(mWindowRoot.getBoundsInScreen())
.setParent(currentFocusArea)
.setRotaryContainer()
.build();
// Though there are 20 children in the layout, only one child is mocked. This is fine as
// long as the bounds are correct so that it can occupy the entire container.
AccessibilityNodeInfo childNode = mNodeBuilder
.setBoundsInScreen(mWindowRoot.getBoundsInScreen())
.setParent(sourceNode)
.build();
List<AccessibilityWindowInfo> windows = Collections.singletonList(window);
int direction = View.FOCUS_UP;
AccessibilityNodeInfo targetFocusArea = createNode("focus_area2");
AccessibilityNodeInfo result = mNavigator.findNudgeTargetFocusArea(
windows, sourceNode, currentFocusArea, direction);
assertThat(result).isEqualTo(targetFocusArea);
// Note: only real nodes (and windows) need to be recycled.
Utils.recycleNode(result);
}
/**
* Tests {@link Navigator#findNudgeTargetFocusArea} in the following layout:
* <pre>
* ========topLeft focus area======== ========topRight focus area========
* = = = =
* = ............. ............. = = ............. =
* = . . . . = = . . =
* = . topLeft1 . . topLeft2 . = = . topRight1 . =
* = . . . . = = . . =
* = ............. ............. = = ............. =
* = = = =
* ================================== ===================================
*
* =======middleLeft focus area======
* = =
* = ............. ............. =
* = . . . . =
* = .middleLeft1. .middleLeft2. =
* = . disabled . . disabled . =
* = ............. ............. =
* = =
* ==================================
*
* =======bottomLeft focus area======
* = =
* = ............. ............. =
* = . . . . =
* = .bottomLeft1. .bottomLeft2. =
* = . . . . =
* = ............. ............. =
* = =
* ==================================
* </pre>
*/
@Test
public void testFindNudgeTargetFocusArea2() {
initActivity(R.layout.navigator_find_nudge_target_focus_area_2_test_activity);
// The only way to create a AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo window = new WindowBuilder()
.setRoot(mWindowRoot)
.setBoundsInScreen(mWindowRoot.getBoundsInScreen())
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window);
// Nudge down from topLeft1.
AccessibilityNodeInfo topLeftFocusArea = createNode("top_left");
AccessibilityNodeInfo bottomLeftFocusArea = createNode("bottom_left");
AccessibilityNodeInfo topLeft1 = createNode("top_left1");
AccessibilityNodeInfo mockTopLeft1 = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(topLeft1.getBoundsInScreen())
.setParent(topLeftFocusArea)
.build();
AccessibilityNodeInfo target1 = mNavigator.findNudgeTargetFocusArea(
windows, mockTopLeft1, topLeftFocusArea, View.FOCUS_DOWN);
assertThat(target1).isEqualTo(bottomLeftFocusArea);
// Reach to the boundary.
AccessibilityNodeInfo bottomLeft1 = createNode("bottom_left1");
AccessibilityNodeInfo mockBottomLeft1 = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(bottomLeft1.getBoundsInScreen())
.setParent(bottomLeftFocusArea)
.build();
AccessibilityNodeInfo target2 = mNavigator.findNudgeTargetFocusArea(
windows, mockBottomLeft1, bottomLeftFocusArea, View.FOCUS_DOWN);
assertThat(target2).isNull();
// Nudge to the right.
AccessibilityNodeInfo topRightFocusArea = createNode("top_right");
AccessibilityNodeInfo target3 = mNavigator.findNudgeTargetFocusArea(
windows, mockBottomLeft1, bottomLeftFocusArea, View.FOCUS_RIGHT);
assertThat(target3).isEqualTo(topRightFocusArea);
Utils.recycleNodes(target1, target2, target3);
}
/**
* Tests {@link Navigator#findFocusParkingView} in the following node tree:
* <pre>
* root
* / \
* / \
* parent button
* |
* |
* focusParkingView
* </pre>
*/
@Test
public void testFindFocusParkingView() {
initActivity(R.layout.navigator_find_focus_parking_view_test_activity);
// The only way to create a AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo window = new WindowBuilder().setRoot(mWindowRoot).build();
AccessibilityNodeInfo button = mNodeBuilder.setWindow(window).build();
AccessibilityNodeInfo fpv = createNode("focusParkingView");
AccessibilityNodeInfo result = mNavigator.findFocusParkingView(button);
assertThat(result).isEqualTo(fpv);
Utils.recycleNode(result);
}
@Test
public void testfindHunWindow() {
// The only way to create a AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo hunWindow = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_SYSTEM)
.setBoundsInScreen(mHunWindowBounds)
.build();
AccessibilityWindowInfo window2 = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_APPLICATION)
.setBoundsInScreen(mHunWindowBounds)
.build();
AccessibilityWindowInfo window3 = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_SYSTEM)
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window2);
windows.add(window3);
windows.add(hunWindow);
AccessibilityWindowInfo result = mNavigator.findHunWindow(windows);
assertThat(result).isEqualTo(hunWindow);
}
@Test
public void testIsHunWindow() {
// The only way to create an AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo window = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_SYSTEM)
.setBoundsInScreen(mHunWindowBounds)
.build();
boolean isHunWindow = mNavigator.isHunWindow(window);
assertThat(isHunWindow).isTrue();
}
@Test
public void testIsMainApplicationWindow_returnsTrue() {
// The only way to create an AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo window = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_APPLICATION)
.setBoundsInScreen(mDisplayBounds)
.setDisplayId(Display.DEFAULT_DISPLAY)
.build();
boolean isMainApplicationWindow = mNavigator.isMainApplicationWindow(window);
assertThat(isMainApplicationWindow).isTrue();
}
@Test
public void testIsMainApplicationWindow_wrongDisplay_returnsFalse() {
// The only way to create an AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo window = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_APPLICATION)
.setBoundsInScreen(mDisplayBounds)
.setDisplayId(1)
.build();
boolean isMainApplicationWindow = mNavigator.isMainApplicationWindow(window);
assertThat(isMainApplicationWindow).isFalse();
}
@Test
public void testIsMainApplicationWindow_wrongType_returnsFalse() {
// The only way to create an AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo window = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_SYSTEM)
.setBoundsInScreen(mDisplayBounds)
.setDisplayId(Display.DEFAULT_DISPLAY)
.build();
boolean isMainApplicationWindow = mNavigator.isMainApplicationWindow(window);
assertThat(isMainApplicationWindow).isFalse();
}
@Test
public void testIsMainApplicationWindow_wrongBounds_returnsFalse() {
// The only way to create an AccessibilityWindowInfo in the test is via mock.
AccessibilityWindowInfo window = new WindowBuilder()
.setType(AccessibilityWindowInfo.TYPE_APPLICATION)
.setBoundsInScreen(mHunWindowBounds)
.setDisplayId(Display.DEFAULT_DISPLAY)
.build();
boolean isMainApplicationWindow = mNavigator.isMainApplicationWindow(window);
assertThat(isMainApplicationWindow).isFalse();
}
/**
* Tests {@link Navigator#getAncestorFocusArea} in the following node tree:
* <pre>
* root
* / \
* / \
* focusArea button3
* / \
* / \
* parent button2
* |
* |
* button1
* </pre>
*/
@Test
public void testGetAncestorFocusArea() {
initActivity(R.layout.navigator_get_ancestor_focus_area_test_activity);
AccessibilityNodeInfo focusArea = createNode("focusArea");
AccessibilityNodeInfo button1 = createNode("button1");
AccessibilityNodeInfo result1 = mNavigator.getAncestorFocusArea(button1);
assertThat(result1).isEqualTo(focusArea);
AccessibilityNodeInfo button2 = createNode("button2");
AccessibilityNodeInfo result2 = mNavigator.getAncestorFocusArea(button2);
assertThat(result2).isEqualTo(focusArea);
AccessibilityNodeInfo button3 = createNode("button3");
AccessibilityNodeInfo result3 = mNavigator.getAncestorFocusArea(button3);
assertThat(result3).isEqualTo(mWindowRoot);
Utils.recycleNodes(result1, result2, result3);
}
/**
* Tests {@link Navigator#getRoot} in the following node tree:
* <pre>
* clientAppRoot
* / \
* / \
* button1 surfaceView
* |
* hostAppRoot
* / \
* / \
* focusParkingView button2
* </pre>
*/
@Test
public void testGetRoot_returnHostAppRoot() {
mNavigator.addClientApp(CLIENT_APP_PACKAGE_NAME);
mNavigator.mSurfaceViewHelper.mHostApp = HOST_APP_PACKAGE_NAME;
AccessibilityNodeInfo clientAppRoot = mNodeBuilder
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo surfaceView = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.setClassName(Utils.SURFACE_VIEW_CLASS_NAME)
.build();
AccessibilityNodeInfo hostAppRoot = mNodeBuilder
.setParent(surfaceView)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.setFpv()
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
assertThat(mNavigator.getRoot(button2)).isEqualTo(hostAppRoot);
}
/**
* Tests {@link Navigator#getRoot} in the following node tree:
* <pre>
* root
* / \
* / \
* button1 viewGroup1
* |
* viewGroup2
* / \
* / \
* focusParkingView button2
* </pre>
*/
@Test
public void testGetRoot_returnNodeRoot() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo viewGroup1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo viewGroup2 = mNodeBuilder.setParent(viewGroup1).build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder
.setParent(viewGroup2)
.setFpv()
.build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(viewGroup2).build();
assertThat(mNavigator.getRoot(button2)).isEqualTo(root);
}
/**
* Tests {@link Navigator#getRoot} in the window:
* <pre>
* windowRoot
* ...
* button
* </pre>
* where {@code windowRoot} is the root node of the window containing {@code button}.
*/
@Test
public void testGetRoot_returnWindowRoot() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityWindowInfo window = new WindowBuilder().setRoot(root).build();
AccessibilityNodeInfo button = mNodeBuilder.setWindow(window).build();
assertThat(mNavigator.getRoot(button)).isEqualTo(root);
}
/**
* Tests {@link Navigator#findFocusParkingViewInRoot} in the following node tree:
* <pre>
* clientAppRoot
* / \
* / \
* button1 surfaceView
* |
* hostAppRoot
* / \
* / \
* focusParkingView button2
* </pre>
*/
@Test
public void testFindFocusParkingViewInRoot() {
mNavigator.addClientApp(CLIENT_APP_PACKAGE_NAME);
mNavigator.mSurfaceViewHelper.mHostApp = HOST_APP_PACKAGE_NAME;
AccessibilityNodeInfo clientAppRoot = mNodeBuilder
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo surfaceView = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.setClassName(Utils.SURFACE_VIEW_CLASS_NAME)
.build();
AccessibilityNodeInfo hostAppRoot = mNodeBuilder
.setParent(surfaceView)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.setFpv()
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
assertThat(mNavigator.findFocusParkingViewInRoot(clientAppRoot))
.isEqualTo(focusParkingView);
}
/**
* Tests {@link Navigator#findSurfaceViewInRoot} in the following node tree:
* <pre>
* clientAppRoot
* / \
* / \
* button1 surfaceView
* |
* hostAppRoot
* / \
* / \
* focusParkingView button2
* </pre>
*/
@Test
public void testFindSurfaceViewInRoot() {
mNavigator.addClientApp(CLIENT_APP_PACKAGE_NAME);
mNavigator.mSurfaceViewHelper.mHostApp = HOST_APP_PACKAGE_NAME;
AccessibilityNodeInfo clientAppRoot = mNodeBuilder
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo surfaceView = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.setClassName(Utils.SURFACE_VIEW_CLASS_NAME)
.build();
AccessibilityNodeInfo hostAppRoot = mNodeBuilder
.setParent(surfaceView)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.setFpv()
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
assertThat(mNavigator.findSurfaceViewInRoot(clientAppRoot)).isEqualTo(surfaceView);
}
/**
* Tests {@link Navigator#getDescendantHostRoot} in the following node tree:
* <pre>
* clientAppRoot
* / \
* / \
* button1 surfaceView
* |
* hostAppRoot
* / \
* / \
* focusParkingView button2
* </pre>
*/
@Test
public void testGetDescendantHostRoot() {
mNavigator.addClientApp(CLIENT_APP_PACKAGE_NAME);
mNavigator.mSurfaceViewHelper.mHostApp = HOST_APP_PACKAGE_NAME;
AccessibilityNodeInfo clientAppRoot = mNodeBuilder
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo surfaceView = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.setClassName(Utils.SURFACE_VIEW_CLASS_NAME)
.build();
AccessibilityNodeInfo hostAppRoot = mNodeBuilder
.setParent(surfaceView)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.setFpv()
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
assertThat(mNavigator.getDescendantHostRoot(clientAppRoot)).isEqualTo(hostAppRoot);
}
/**
* Tests {@link Navigator#findFocusAreas} in the following node tree:
* <pre>
* clientAppRoot
* / \
* / \
* button1 surfaceView
* |
* hostAppRoot
* / \
* / \
* focusParkingView button2
* </pre>
*/
@Test
public void testFindFocusAreas_inHostApp() {
mNavigator.addClientApp(CLIENT_APP_PACKAGE_NAME);
mNavigator.mSurfaceViewHelper.mHostApp = HOST_APP_PACKAGE_NAME;
AccessibilityNodeInfo clientAppRoot = mNodeBuilder
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo surfaceView = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.setClassName(Utils.SURFACE_VIEW_CLASS_NAME)
.build();
AccessibilityNodeInfo hostAppRoot = mNodeBuilder
.setParent(surfaceView)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.setFpv()
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
AccessibilityWindowInfo window = new WindowBuilder().setRoot(clientAppRoot).build();
assertThat(mNavigator.findFocusAreas(window)).containsExactly(hostAppRoot);
}
/**
* Tests {@link Navigator#findFocusedNodeInRoot} in the following node tree:
* <pre>
* clientAppRoot
* / \
* / \
* button1 surfaceView(focused)
* |
* hostAppRoot
* / \
* / \
* focusParkingView button2(focused)
* </pre>
*/
@Test
public void testFindFocusedNodeInRoot_inHostApp() {
mNavigator.addClientApp(CLIENT_APP_PACKAGE_NAME);
mNavigator.mSurfaceViewHelper.mHostApp = HOST_APP_PACKAGE_NAME;
AccessibilityNodeInfo clientAppRoot = mNodeBuilder
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(clientAppRoot)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo surfaceView = mNodeBuilder
.setParent(clientAppRoot)
.setFocused(true)
.setPackageName(CLIENT_APP_PACKAGE_NAME)
.setClassName(Utils.SURFACE_VIEW_CLASS_NAME)
.build();
AccessibilityNodeInfo hostAppRoot = mNodeBuilder
.setParent(surfaceView)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder
.setParent(hostAppRoot)
.setPackageName(HOST_APP_PACKAGE_NAME)
.setFpv()
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(hostAppRoot)
.setFocused(true)
.setPackageName(HOST_APP_PACKAGE_NAME)
.build();
assertThat(mNavigator.findFocusedNodeInRoot(clientAppRoot)).isEqualTo(button2);
}
/**
* Starts the test activity with the given layout and initializes the root
* {@link AccessibilityNodeInfo}.
*/
private void initActivity(@LayoutRes int layoutResId) {
mIntent.putExtra(NavigatorTestActivity.KEY_LAYOUT_ID, layoutResId);
mActivityRule.launchActivity(mIntent);
mWindowRoot = sUiAutomoation.getRootInActiveWindow();
}
/**
* Returns the {@link AccessibilityNodeInfo} related to the provided viewId. Returns null if no
* such node exists. Callers should ensure {@link #initActivity} has already been called. Caller
* shouldn't recycle the result because it will be recycled in {@link #tearDown}.
*/
private AccessibilityNodeInfo createNode(String viewId) {
String fullViewId = "com.android.car.rotary.tests.unit:id/" + viewId;
List<AccessibilityNodeInfo> nodes =
mWindowRoot.findAccessibilityNodeInfosByViewId(fullViewId);
if (nodes.isEmpty()) {
L.e("Failed to create node by View ID " + viewId);
return null;
}
mNodes.addAll(nodes);
return nodes.get(0);
}
/**
* Returns the {@link AccessibilityNodeInfo} of the view containing the provided text. Returns
* null if no such node exists. Callers should ensure {@link #initActivity} has already
* been called and also recycle the result.
*/
private AccessibilityNodeInfo createNodeByText(String text) {
List<AccessibilityNodeInfo> nodes = mWindowRoot.findAccessibilityNodeInfosByText(text);
if (nodes.isEmpty()) {
L.e("Failed to create node by text '" + text + "'");
return null;
}
return nodes.get(0);
}
}