blob: 75bc6c346f6ddd23ec11bd83aded20b22faeb0d6 [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 android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.graphics.Rect;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.annotation.NonNull;
import com.android.car.rotary.Navigator.FindRotateTargetResult;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class NavigatorTest {
private Rect mHunWindowBounds;
private NodeBuilder mNodeBuilder;
private Navigator mNavigator;
@Before
public void setUp() {
mHunWindowBounds = new Rect(50, 10, 950, 200);
mNodeBuilder = new NodeBuilder(new ArrayList<>());
mNavigator = new Navigator(
/* focusHistoryCacheType= */ RotaryCache.CACHE_TYPE_NEVER_EXPIRE,
/* focusHistoryCacheSize= */ 10,
/* focusHistoryExpirationTimeMs= */ 0,
/* focusAreaHistoryCacheType= */ RotaryCache.CACHE_TYPE_NEVER_EXPIRE,
/* focusAreaHistoryCacheSize= */ 5,
/* focusAreaHistoryExpirationTimeMs= */ 0,
/* focusWindowCacheType= */ RotaryCache.CACHE_TYPE_NEVER_EXPIRE,
/* focusWindowCacheSize= */ 5,
/* focusWindowExpirationTimeMs= */ 0,
mHunWindowBounds.left,
mHunWindowBounds.right,
/* showHunOnBottom= */ false);
mNavigator.setNodeCopier(MockNodeCopierProvider.get());
}
@Test
public void testSetRootNodeForWindow() {
AccessibilityWindowInfo window = new WindowBuilder().build();
AccessibilityNodeInfo root = mNodeBuilder.build();
setRootNodeForWindow(root, window);
assertThat(window.getRoot()).isSameAs(root);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* |
* focusArea
* / | \
* / | \
* button1 button2 button3
* </pre>
*/
@Test
public void testFindRotateTarget() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo focusArea = mNodeBuilder.setParent(root).setFocusArea().build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(focusArea).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(focusArea).build();
AccessibilityNodeInfo button3 = mNodeBuilder.setParent(focusArea).build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(button3);
when(button3.focusSearch(direction)).thenReturn(null);
// Rotate once, the focus should move from button1 to button2.
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isSameAs(button2);
assertThat(target.advancedCount).isEqualTo(1);
// Rotate twice, the focus should move from button1 to button3.
target = mNavigator.findRotateTarget(button1, direction, 2);
assertThat(target.node).isSameAs(button3);
assertThat(target.advancedCount).isEqualTo(2);
// Rotate 3 times and exceed the boundary, the focus should stay at the boundary.
target = mNavigator.findRotateTarget(button1, direction, 3);
assertThat(target.node).isSameAs(button3);
assertThat(target.advancedCount).isEqualTo(2);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / \
* / \
* focusParkingView focusArea
* / \
* / \
* button1 button2
* </pre>
*/
@Test
public void testFindRotateTargetNoWrapAround() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder.setParent(root).setFpv().build();
AccessibilityNodeInfo focusArea = mNodeBuilder.setParent(root).setFocusArea().build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(focusArea).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(focusArea).build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(focusParkingView);
when(focusParkingView.focusSearch(direction)).thenReturn(button1);
// 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() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder.setParent(root).setFpv().build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(root).build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(focusParkingView);
when(focusParkingView.focusSearch(direction)).thenReturn(button1);
// 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 invisible button2
* </pre>
*/
@Test
public void testFindRotateTargetDoesNotSkipInvisibleNode() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo invisible = mNodeBuilder
.setParent(root)
.setVisibleToUser(false)
.setBoundsInScreen(new Rect(0, 0, 0, 0))
.build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(root).build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(invisible);
when(invisible.focusSearch(direction)).thenReturn(button2);
// Rotate from button1, it shouldn't skip the invisible view.
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isSameAs(invisible);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / | \
* / | \
* / | \
* button1 empty button2
* </pre>
*/
@Test
public void testFindRotateTargetSkipNodeThatCannotPerformFocus() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo empty = mNodeBuilder
.setParent(root)
.setBoundsInParent(new Rect(0, 0, 0, 0))
.build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(root).build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(empty);
when(empty.focusSearch(direction)).thenReturn(button2);
// Rotate from button1, it should skip the empty view.
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isSameAs(button2);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / | \
* / | \
* / | \
* button1 scrollable button2
* recyclerView
* |
* non-focusable
* </pre>
*/
@Test
public void testFindRotateTargetReturnScrollableContainer() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo recyclerView = mNodeBuilder
.setParent(root)
.setScrollableContainer()
.setScrollable(true)
.build();
AccessibilityNodeInfo nonFocusable = mNodeBuilder
.setFocusable(false)
.setParent(recyclerView)
.build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(recyclerView);
when(recyclerView.focusSearch(direction)).thenReturn(button2);
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isSameAs(recyclerView);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / | \
* / | \
* / | \
* / | \
* button1 non-scrollable button2
* recyclerView
* </pre>
*/
@Test
public void testFindRotateTargetSkipScrollableContainer() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo recyclerView = mNodeBuilder
.setParent(root)
.setScrollableContainer()
.build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(recyclerView);
when(recyclerView.focusSearch(direction)).thenReturn(button2);
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isSameAs(button2);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* root
* / \
* / \
* focusParkingView scrollable
* recyclerView
* / \
* / \
* focusable1 focusable2
* </pre>
*/
@Test
public void testFindRotateTargetSkipScrollableContainer2() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder.setParent(root).setFpv().build();
AccessibilityNodeInfo recyclerView = mNodeBuilder
.setParent(root)
.setScrollableContainer()
.setScrollable(true)
.build();
AccessibilityNodeInfo focusable1 = mNodeBuilder.setParent(recyclerView).build();
AccessibilityNodeInfo focusable2 = mNodeBuilder.setParent(recyclerView).build();
int direction = View.FOCUS_BACKWARD;
when(focusable2.focusSearch(direction)).thenReturn(focusable1);
when(focusable1.focusSearch(direction)).thenReturn(recyclerView);
when(recyclerView.focusSearch(direction)).thenReturn(focusParkingView);
FindRotateTargetResult target = mNavigator.findRotateTarget(focusable2, direction, 2);
assertThat(target.node).isSameAs(focusable1);
assertThat(target.advancedCount).isEqualTo(1);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following node tree:
* <pre>
* node
* </pre>
*/
@Test
public void testFindRotateTargetWithOneNode() {
AccessibilityNodeInfo node = mNodeBuilder.build();
int direction = View.FOCUS_BACKWARD;
when(node.focusSearch(direction)).thenReturn(node);
FindRotateTargetResult target = mNavigator.findRotateTarget(node, direction, 1);
assertThat(target).isNull();
}
/**
* Tests {@link Navigator#findRotateTarget} in the following layout:
* <pre>
* ============ focus area ============
* = =
* = ***** scrollable container **** =
* = * * =
* = * ........ button 1 ........ * =
* = * . . * =
* = * .......................... * =
* = * * =
* = * ........ button 2 ........ * =
* = * . . * =
* = * .......................... * =
* = * * =
* = ******************************* =
* = =
* ============ focus area ============
*
* ........ button 3 ........
* . .
* ..........................
* </pre>
* where {@code button 3} is not a descendant of the scrollable container.
*/
@Test
public void testFindRotateTargetInScrollableContainer() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo focusArea = mNodeBuilder
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
AccessibilityNodeInfo scrollableContainer = mNodeBuilder
.setParent(focusArea)
.setScrollableContainer()
.setActions(ACTION_SCROLL_FORWARD)
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(scrollableContainer)
.setBoundsInScreen(new Rect(0, 0, 100, 50))
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(scrollableContainer)
.setBoundsInScreen(new Rect(0, 50, 100, 100))
.build();
AccessibilityNodeInfo button3 = mNodeBuilder
.setParent(root)
.setBoundsInScreen(new Rect(0, 100, 100, 150))
.build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(button3);
when(button3.focusSearch(direction)).thenReturn(null);
// Rotate once, the focus should move from button1 to button2.
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isSameAs(button2);
assertThat(target.advancedCount).isEqualTo(1);
// Rotate twice, the focus should move from button1 to button2 since button3 is not a
// descendant of the scrollable container.
target = mNavigator.findRotateTarget(button1, direction, 2);
assertThat(target.node).isSameAs(button2);
assertThat(target.advancedCount).isEqualTo(1);
// Rotate three times should do the same.
target = mNavigator.findRotateTarget(button1, direction, 3);
assertThat(target.node).isSameAs(button2);
assertThat(target.advancedCount).isEqualTo(1);
}
/**
* Tests {@link Navigator#findRotateTarget} in the following layout:
* <pre>
* ============ focus area ============
* = =
* = ***** scrollable container **** =
* = * * =
* = * ........ button 1 ........ * =
* = * . . * =
* = * .......................... * =
* = * * =
* = * ........ button 2 ........ * =
* = * . . * =
* = * .......................... * =
* = * * =
* = ******************************* =
* = =
* ============ focus area ============
*
* ........ button 3 ........
* . .
* ..........................
* </pre>
* where {@code button 3} is off the screen.
*/
@Test
public void testFindRotateTargetInScrollableContainer2() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo focusArea = mNodeBuilder
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
AccessibilityNodeInfo scrollableContainer = mNodeBuilder
.setParent(focusArea)
.setScrollableContainer()
.setActions(ACTION_SCROLL_FORWARD)
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(scrollableContainer)
.setBoundsInScreen(new Rect(0, 0, 100, 50))
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(scrollableContainer)
.setBoundsInScreen(new Rect(0, 50, 100, 100))
.build();
AccessibilityNodeInfo button3 = mNodeBuilder
.setParent(root)
.setBoundsInScreen(new Rect(0, 0, 0, 0))
.build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(button3);
when(button3.focusSearch(direction)).thenReturn(null);
// Rotate once, the focus should move from button1 to button2.
FindRotateTargetResult target = mNavigator.findRotateTarget(button1, direction, 1);
assertThat(target.node).isSameAs(button2);
assertThat(target.advancedCount).isEqualTo(1);
// Rotate twice, the focus should move from button1 to button2 since button3 is off the
// screen.
target = mNavigator.findRotateTarget(button1, direction, 2);
assertThat(target.node).isSameAs(button2);
assertThat(target.advancedCount).isEqualTo(1);
// Rotate three times should do the same.
target = mNavigator.findRotateTarget(button1, direction, 3);
assertThat(target.node).isSameAs(button2);
assertThat(target.advancedCount).isEqualTo(1);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* ****************leftWindow************** **************rightWindow****************
* * * * *
* * ========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 testFindNudgeTarget() {
// There are 2 windows. This is the left window.
Rect leftWindowBounds = new Rect(0, 0, 400, 1200);
AccessibilityWindowInfo leftWindow = new WindowBuilder()
.setBoundsInScreen(leftWindowBounds)
.build();
// We must specify window and boundsInScreen for each node when finding nudge target.
AccessibilityNodeInfo leftRoot = mNodeBuilder
.setWindow(leftWindow)
.setBoundsInScreen(leftWindowBounds)
.build();
setRootNodeForWindow(leftRoot, leftWindow);
// Left window has 3 vertically aligned focus areas.
AccessibilityNodeInfo topLeft = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
AccessibilityNodeInfo middleLeft = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 400, 400, 800))
.build();
AccessibilityNodeInfo bottomLeft = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 800, 400, 1200))
.build();
// Each focus area but middleLeft has 2 horizontally aligned views that can take focus.
AccessibilityNodeInfo topLeft1 = mNodeBuilder
.setWindow(leftWindow)
.setParent(topLeft)
.setBoundsInScreen(new Rect(0, 0, 200, 400))
.build();
AccessibilityNodeInfo topLeft2 = mNodeBuilder
.setWindow(leftWindow)
.setParent(topLeft)
.setBoundsInScreen(new Rect(200, 0, 400, 400))
.build();
AccessibilityNodeInfo bottomLeft1 = mNodeBuilder
.setWindow(leftWindow)
.setParent(bottomLeft)
.setBoundsInScreen(new Rect(0, 800, 200, 1200))
.build();
AccessibilityNodeInfo bottomLeft2 = mNodeBuilder
.setWindow(leftWindow)
.setParent(bottomLeft)
.setBoundsInScreen(new Rect(200, 800, 400, 1200))
.build();
// middleLeft focus area has 2 disabled views, so that it will be skipped when nudging.
AccessibilityNodeInfo middleLeft1 = mNodeBuilder
.setWindow(leftWindow)
.setParent(bottomLeft)
.setEnabled(false)
.setBoundsInScreen(new Rect(0, 400, 200, 800))
.build();
AccessibilityNodeInfo middleLeft2 = mNodeBuilder
.setWindow(leftWindow)
.setParent(bottomLeft)
.setEnabled(false)
.setBoundsInScreen(new Rect(200, 400, 400, 800))
.build();
// This is the right window.
Rect rightWindowBounds = new Rect(400, 0, 800, 1200);
AccessibilityWindowInfo rightWindow = new WindowBuilder()
.setBoundsInScreen(rightWindowBounds)
.build();
AccessibilityNodeInfo rightRoot = mNodeBuilder
.setWindow(rightWindow)
.setBoundsInScreen(rightWindowBounds)
.build();
setRootNodeForWindow(rightRoot, rightWindow);
// Right window has 1 focus area.
AccessibilityNodeInfo topRight = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
// The focus area has 1 view that can take focus.
AccessibilityNodeInfo topRight1 = mNodeBuilder
.setWindow(rightWindow)
.setParent(topRight)
.setBoundsInScreen(new Rect(400, 0, 600, 400))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(leftWindow);
windows.add(rightWindow);
// Nudge within the same window.
AccessibilityNodeInfo target =
mNavigator.findNudgeTarget(windows, topLeft1, View.FOCUS_DOWN);
assertThat(target).isSameAs(bottomLeft1);
// Reach to the boundary.
target = mNavigator.findNudgeTarget(windows, topLeft1, View.FOCUS_UP);
assertThat(target).isNull();
// Nudge to a different window.
target = mNavigator.findNudgeTarget(windows, topRight1, View.FOCUS_LEFT);
assertThat(target).isSameAs(topLeft2);
// When nudging back, the focus should return to the previously focused node within the
// previous focus area, rather than the geometrically close node or focus area.
// Firstly, we need to save the focused node.
mNavigator.saveFocusedNode(bottomLeft1);
// Then nudge to right.
target = mNavigator.findNudgeTarget(windows, bottomLeft1, View.FOCUS_RIGHT);
assertThat(target).isSameAs(topRight1);
// Then nudge back.
target = mNavigator.findNudgeTarget(windows, topRight1, View.FOCUS_LEFT);
assertThat(target).isSameAs(bottomLeft1);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* ****************leftWindow************** **************rightWindow***************
* * * * *
* * ===left focus area=== parking1 * * parking2 ===right focus area=== *
* * = = * * = = *
* * = ............. = * * = ............. = *
* * = . . = * * = . . = *
* * = . left . = * * = . right . = *
* * = . . = * * = . . = *
* * = ............. = * * = ............. = *
* * = = * * = = *
* * ===================== * * ====================== *
* * * * *
* **************************************** *****************************************
* </pre>
*/
@Test
public void testFindNudgeTargetWithFocusParkingView() {
// There are 2 windows. This is the left window.
Rect leftWindowBounds = new Rect(0, 0, 400, 400);
AccessibilityWindowInfo leftWindow = new WindowBuilder()
.setBoundsInScreen(leftWindowBounds)
.build();
AccessibilityNodeInfo leftRoot = mNodeBuilder
.setWindow(leftWindow)
.setBoundsInScreen(leftWindowBounds)
.build();
setRootNodeForWindow(leftRoot, leftWindow);
// Left focus area and its view inside.
AccessibilityNodeInfo leftFocusArea = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 300, 400))
.build();
AccessibilityNodeInfo left = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftFocusArea)
.setBoundsInScreen(new Rect(0, 0, 300, 400))
.build();
// Left focus parking view.
AccessibilityNodeInfo parking1 = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftFocusArea)
.setFpv()
.setBoundsInScreen(new Rect(350, 0, 351, 1))
.build();
// Right window.
Rect rightWindowBounds = new Rect(400, 0, 800, 400);
AccessibilityWindowInfo rightWindow = new WindowBuilder()
.setBoundsInScreen(rightWindowBounds)
.build();
AccessibilityNodeInfo rightRoot = mNodeBuilder
.setWindow(rightWindow)
.setBoundsInScreen(rightWindowBounds)
.build();
setRootNodeForWindow(rightRoot, rightWindow);
// Right focus area and its view inside.
AccessibilityNodeInfo rightFocusArea = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(500, 0, 800, 400))
.build();
AccessibilityNodeInfo right = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightFocusArea)
.setBoundsInScreen(new Rect(500, 0, 800, 400))
.build();
// Right focus parking view.
AccessibilityNodeInfo parking2 = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightFocusArea)
.setFpv()
.setBoundsInScreen(new Rect(450, 0, 451, 1))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(leftWindow);
windows.add(rightWindow);
// Nudge from left window to right window.
AccessibilityNodeInfo target = mNavigator.findNudgeTarget(windows, left, View.FOCUS_RIGHT);
assertThat(target).isSameAs(right);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* ****************mainWindow**************
* * *
* * ==============top============= *
* * = = *
* * = ........... ........... = *
* * = . . . . = *
* * = . topLeft . . topRight. = *
* * = . . . . = *
* * = ........... ........... = *
* * = = *
* * ============================== *
* * *
* * ============bottom============ *
* * = = *
* * = ........... ........... = *
* * = . . . . = *
* * = . bottom . . bottom . = *
* * = . Left . . Right . = *
* * = ........... ........... = *
* * = = *
* * ============================== *
* * *
* ****************************************
* </pre>
* with the HUN overlapping the top of the main window:
* <pre>
* *************hunWindow************
* * .............. .............. *
* * . hunLeft . . hunRight . *
* * .............. .............. *
* **********************************
* </pre>
*/
@Test
public void testFindHunNudgeTarget() {
// There are two windows. This is the HUN window.
AccessibilityWindowInfo hunWindow = new WindowBuilder()
.setBoundsInScreen(mHunWindowBounds)
.setType(AccessibilityWindowInfo.TYPE_SYSTEM)
.build();
// We must specify window and boundsInScreen for each node when finding nudge target.
AccessibilityNodeInfo hunRoot = mNodeBuilder
.setWindow(hunWindow)
.setBoundsInScreen(mHunWindowBounds)
.setFocusable(false)
.build();
setRootNodeForWindow(hunRoot, hunWindow);
// HUN window has two views that can take focus (directly in the root).
AccessibilityNodeInfo hunLeft = mNodeBuilder
.setWindow(hunWindow)
.setParent(hunRoot)
.setBoundsInScreen(new Rect(mHunWindowBounds.left, mHunWindowBounds.top,
mHunWindowBounds.centerX(), mHunWindowBounds.bottom))
.build();
AccessibilityNodeInfo hunRight = mNodeBuilder
.setWindow(hunWindow)
.setParent(hunRoot)
.setBoundsInScreen(new Rect(mHunWindowBounds.centerX(), mHunWindowBounds.top,
mHunWindowBounds.right, mHunWindowBounds.bottom))
.build();
// This is the main window.
Rect mainWindowBounds = new Rect(0, 0, 1000, 1000);
AccessibilityWindowInfo mainWindow = new WindowBuilder()
.setBoundsInScreen(mainWindowBounds)
.build();
AccessibilityNodeInfo mainRoot = mNodeBuilder
.setWindow(mainWindow)
.setBoundsInScreen(mainWindowBounds)
.setFocusable(false)
.build();
setRootNodeForWindow(mainRoot, mainWindow);
// Main window has two focus areas.
AccessibilityNodeInfo topFocusArea = mNodeBuilder
.setWindow(mainWindow)
.setParent(mainRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 1000, 500))
.build();
AccessibilityNodeInfo bottomFocusArea = mNodeBuilder
.setWindow(mainWindow)
.setParent(mainRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 500, 1000, 1000))
.build();
// The top focus area has two views that can take focus.
AccessibilityNodeInfo topLeft = mNodeBuilder
.setWindow(mainWindow)
.setParent(topFocusArea)
.setBoundsInScreen(new Rect(0, 0, 500, 500))
.build();
AccessibilityNodeInfo topRight = mNodeBuilder
.setWindow(mainWindow)
.setParent(topFocusArea)
.setBoundsInScreen(new Rect(500, 0, 1000, 500))
.build();
// The bottom focus area has two views that can take focus.
AccessibilityNodeInfo bottomLeft = mNodeBuilder
.setWindow(mainWindow)
.setParent(bottomFocusArea)
.setBoundsInScreen(new Rect(0, 500, 500, 1000))
.build();
AccessibilityNodeInfo bottomRight = mNodeBuilder
.setWindow(mainWindow)
.setParent(bottomFocusArea)
.setBoundsInScreen(new Rect(500, 500, 1000, 1000))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(hunWindow);
windows.add(mainWindow);
// Nudging up from the top left or right view should go to the HUN's left button. The
// source and target overlap so geometric targeting fails. We should fall back to using the
// first focusable view in the HUN.
AccessibilityNodeInfo target = mNavigator.findNudgeTarget(windows, topLeft, View.FOCUS_UP);
assertThat(target).isSameAs(hunLeft);
target = mNavigator.findNudgeTarget(windows, topRight, View.FOCUS_UP);
assertThat(target).isSameAs(hunLeft);
// Nudging up from the bottom left or right view should go to the corresponding button in
// the HUN, skipping over the top focus area. Geometric targeting should work.
target = mNavigator.findNudgeTarget(windows, bottomLeft, View.FOCUS_UP);
assertThat(target).isSameAs(hunLeft);
target = mNavigator.findNudgeTarget(windows, bottomRight, View.FOCUS_UP);
assertThat(target).isSameAs(hunRight);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* In the same window
*
* ======focus area 1===========
* = *view1* =
* =============================
*
* ========focus area 2=========
* = *view2* =
* =============================
*
* =====source focus area=====
* = * source view * =
* ===========================
* </pre>
*/
@Test
public void testNudgeToFocusAreaWithNoCandidates() {
Rect windowBounds = new Rect(0, 0, 600, 500);
AccessibilityWindowInfo window = new WindowBuilder()
.setBoundsInScreen(windowBounds)
.build();
AccessibilityNodeInfo root = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(windowBounds)
.build();
setRootNodeForWindow(root, window);
// Currently focused view.
AccessibilityNodeInfo sourceFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 400, 400, 500))
.build();
AccessibilityNodeInfo sourceView = mNodeBuilder
.setWindow(window)
.setParent(sourceFocusArea)
.setBoundsInScreen(new Rect(0, 400, 400, 500))
.build();
// focusArea1 is a better candidate than focusArea2 for a nudge to right, but its descendant
// view is not a candidate.
AccessibilityNodeInfo focusArea1 = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(200, 0, 600, 100))
.build();
AccessibilityNodeInfo view1 = mNodeBuilder
.setWindow(window)
.setParent(focusArea1)
.setBoundsInScreen(new Rect(599, 0, 600, 100))
.build();
// focusArea2 is a worse candidate than focusArea1 for a nudge to right, but its descendant
// view is a candidate.
AccessibilityNodeInfo focusArea2 = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(200, 200, 600, 300))
.build();
AccessibilityNodeInfo view2 = mNodeBuilder
.setWindow(window)
.setParent(focusArea2)
.setBoundsInScreen(new Rect(200, 200, 201, 300))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window);
// Nudge from sourceView to right, and it should go to view1.
AccessibilityNodeInfo target
= mNavigator.findNudgeTarget(windows, sourceView, View.FOCUS_RIGHT);
assertThat(target).isSameAs(view1);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* In the same window
*
* =======app bar focus area=========
* = *tab* =
* ==================================
* =====browse list focus area ======
* = =
* = =
* = *list item 1* =
* = =
* = =
* = ===control bar focus area=== =
* = = *control bar button* = =
* = ============================ =
* = =
* = *list item 2* =
* ==================================
* </pre>
*/
@Test
public void testNudgeToOverlappedFocusArea() {
Rect windowBounds = new Rect(0, 0, 100, 100);
AccessibilityWindowInfo window = new WindowBuilder()
.setBoundsInScreen(windowBounds)
.build();
AccessibilityNodeInfo root = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(windowBounds)
.build();
setRootNodeForWindow(root, window);
AccessibilityNodeInfo appBarFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 100, 10))
.build();
AccessibilityNodeInfo tab = mNodeBuilder
.setWindow(window)
.setParent(appBarFocusArea)
.setBoundsInScreen(new Rect(0, 0, 100, 10))
.build();
AccessibilityNodeInfo browseListFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 10, 100, 100))
.build();
AccessibilityNodeInfo listItem1 = mNodeBuilder
.setWindow(window)
.setParent(browseListFocusArea)
.setBoundsInScreen(new Rect(0, 40, 100, 50))
.build();
AccessibilityNodeInfo listItem2 = mNodeBuilder
.setWindow(window)
.setParent(browseListFocusArea)
.setBoundsInScreen(new Rect(0, 90, 100, 100))
.build();
AccessibilityNodeInfo ControlBarFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 80, 100, 90))
.build();
AccessibilityNodeInfo controlButton = mNodeBuilder
.setWindow(window)
.setParent(ControlBarFocusArea)
.setBoundsInScreen(new Rect(0, 80, 100, 90))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window);
// Nudge up from controlButton, it should go to listItem1.
AccessibilityNodeInfo target
= mNavigator.findNudgeTarget(windows, controlButton, View.FOCUS_UP);
assertThat(target).isSameAs(listItem1);
// Nudge up from listItem1, it should go to tab.
target = mNavigator.findNudgeTarget(windows, listItem1, View.FOCUS_UP);
assertThat(target).isSameAs(tab);
// Disable cache.
mNavigator = new Navigator(
/* focusHistoryCacheType= */ RotaryCache.CACHE_TYPE_DISABLED,
/* focusHistoryCacheSize= */ 10,
/* focusHistoryExpirationTimeMs= */ 0,
/* focusAreaHistoryCacheType= */ RotaryCache.CACHE_TYPE_DISABLED,
/* focusAreaHistoryCacheSize= */ 5,
/* focusAreaHistoryExpirationTimeMs= */ 0,
/* focusWindowCacheType= */ RotaryCache.CACHE_TYPE_DISABLED,
/* focusWindowCacheSize= */ 5,
/* focusWindowExpirationTimeMs= */ 0,
mHunWindowBounds.left,
mHunWindowBounds.right,
/* showHunOnBottom= */ false);
mNavigator.setNodeCopier(MockNodeCopierProvider.get());
// Nudge down from listItem1, it should go to controlButton.
target = mNavigator.findNudgeTarget(windows, listItem1, View.FOCUS_DOWN);
assertThat(target).isSameAs(controlButton);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* In the same window
*
* =====source focus area=====
* = * source view * =
* ===========================
*
* ======target focus area====
* = ---non-focusable---- =
* = - - =
* = - *target view* - =
* = - - =
* = ---view container--- =
* ===========================
* </pre>
*/
@Test
public void testNudgeToFocusAreaWithIndirectChild() {
Rect windowBounds = new Rect(0, 0, 100, 200);
AccessibilityWindowInfo window = new WindowBuilder()
.setBoundsInScreen(windowBounds)
.build();
AccessibilityNodeInfo root = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(windowBounds)
.build();
setRootNodeForWindow(root, window);
// Currently focused view.
AccessibilityNodeInfo sourceFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
AccessibilityNodeInfo sourceView = mNodeBuilder
.setWindow(window)
.setParent(sourceFocusArea)
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
// Target view.
AccessibilityNodeInfo targetFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 100, 100, 200))
.build();
// viewContainer is non-focusable.
AccessibilityNodeInfo viewContainer = mNodeBuilder
.setWindow(window)
.setParent(targetFocusArea)
.setFocusable(false)
.setBoundsInScreen(new Rect(0, 100, 100, 200))
.build();
AccessibilityNodeInfo targetView = mNodeBuilder
.setWindow(window)
.setParent(viewContainer)
.setBoundsInScreen(new Rect(0, 100, 100, 200))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window);
// Nudge down from sourceView, and it should go to targetView.
AccessibilityNodeInfo target
= mNavigator.findNudgeTarget(windows, sourceView, View.FOCUS_DOWN);
assertThat(target).isSameAs(targetView);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* In the same window
*
* =====source focus area=====
* = * source view * =
* ===========================
*
* ======target focus area====
* = -----focusable------ =
* = - - =
* = - *target view* - =
* = - - =
* = ---view container--- =
* ===========================
* </pre>
*/
@Test
public void testNudgeToFocusAreaWithNestedFocusableChild() {
Rect windowBounds = new Rect(0, 0, 100, 200);
AccessibilityWindowInfo window = new WindowBuilder()
.setBoundsInScreen(windowBounds)
.build();
AccessibilityNodeInfo root = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(windowBounds)
.build();
setRootNodeForWindow(root, window);
// Currently focused view.
AccessibilityNodeInfo sourceFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
AccessibilityNodeInfo sourceView = mNodeBuilder
.setWindow(window)
.setParent(sourceFocusArea)
.setBoundsInScreen(new Rect(0, 0, 100, 100))
.build();
// Target view.
AccessibilityNodeInfo targetFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 100, 100, 200))
.build();
// viewContainer is focusable.
AccessibilityNodeInfo viewContainer = mNodeBuilder
.setWindow(window)
.setParent(targetFocusArea)
.setBoundsInScreen(new Rect(0, 100, 100, 200))
.build();
AccessibilityNodeInfo targetView = mNodeBuilder
.setWindow(window)
.setParent(viewContainer)
.setBoundsInScreen(new Rect(0, 100, 100, 200))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window);
// Nudge down from sourceView, and it should go to viewContainer.
AccessibilityNodeInfo target
= mNavigator.findNudgeTarget(windows, sourceView, View.FOCUS_DOWN);
assertThat(target).isSameAs(viewContainer);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* ********* app window **********
* * *
* * === target focus area === *
* * = = *
* * = [view1] = *
* * = = *
* * = [target view] = *
* * = = *
* * = [view3] = *
* * = = *
* * ========================= *
* * *
* *******************************
*
* ********* IME window **********
* * *
* * === source focus area === *
* * = = *
* * = [source view] = *
* * = = *
* * ========================= *
* * *
* *******************************
* </pre>
*/
@Test
public void testNudgeOutOfIme() {
List<AccessibilityWindowInfo> windows = new ArrayList<>();
int appWindowId = 0x42;
Rect appWindowBounds = new Rect(0, 0, 400, 300);
AccessibilityWindowInfo appWindow = new WindowBuilder()
.setId(appWindowId)
.setBoundsInScreen(appWindowBounds)
.setType(AccessibilityWindowInfo.TYPE_APPLICATION)
.build();
windows.add(appWindow);
AccessibilityNodeInfo appRoot = mNodeBuilder
.setWindow(appWindow)
.setWindowId(appWindowId)
.setBoundsInScreen(appWindowBounds)
.build();
setRootNodeForWindow(appRoot, appWindow);
AccessibilityNodeInfo targetFocusArea = mNodeBuilder
.setWindow(appWindow)
.setWindowId(appWindowId)
.setParent(appRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 400, 300))
.build();
AccessibilityNodeInfo view1 = mNodeBuilder
.setWindow(appWindow)
.setWindowId(appWindowId)
.setParent(targetFocusArea)
.setBoundsInScreen(new Rect(0, 0, 400, 100))
.build();
AccessibilityNodeInfo targetView = mNodeBuilder
.setWindow(appWindow)
.setWindowId(appWindowId)
.setParent(targetFocusArea)
.setBoundsInScreen(new Rect(0, 100, 400, 200))
.build();
AccessibilityNodeInfo view3 = mNodeBuilder
.setWindow(appWindow)
.setWindowId(appWindowId)
.setParent(targetFocusArea)
.setBoundsInScreen(new Rect(0, 200, 400, 300))
.build();
int imeWindowId = 0x39;
Rect imeWindowBounds = new Rect(0, 300, 400, 400);
AccessibilityWindowInfo imeWindow = new WindowBuilder()
.setId(imeWindowId)
.setBoundsInScreen(imeWindowBounds)
.setType(AccessibilityWindowInfo.TYPE_INPUT_METHOD)
.build();
windows.add(imeWindow);
AccessibilityNodeInfo imeRoot = mNodeBuilder
.setWindow(imeWindow)
.setWindowId(imeWindowId)
.setBoundsInScreen(imeWindowBounds)
.build();
setRootNodeForWindow(imeRoot, imeWindow);
AccessibilityNodeInfo sourceFocusArea = mNodeBuilder
.setWindow(imeWindow)
.setWindowId(imeWindowId)
.setParent(imeRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 300, 400, 400))
.build();
AccessibilityNodeInfo sourceView = mNodeBuilder
.setWindow(imeWindow)
.setWindowId(imeWindowId)
.setParent(sourceFocusArea)
.setBoundsInScreen(new Rect(0, 300, 400, 400))
.build();
// Nudge up from sourceView with the targetView already focused, and it should go to
// targetView. This is what happens when the user nudges up from the IME to go back to the
// EditText they were editing.
AccessibilityNodeInfo target
= mNavigator.findNudgeTarget(windows, sourceView, View.FOCUS_UP, targetView);
assertThat(target).isSameAs(targetView);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* **********leftWindow********* ****************rightWindow*****************
* * * * *
* * ===left focus area=== * * ==========right focus area======== *
* * = = * * = = *
* * = = * * = .........scrollable......... = *
* * = = * * = . . = *
* * = left = * * = . non-focusable . = *
* * = = * * = . . = *
* * = = * * = .......recyclerView......... = *
* * = = * * = = *
* * ===================== * * ================================== *
* * * * *
* ***************************** ********************************************
* </pre>
*/
@Test
public void testFindNudgeTargetReturnScrollableContainer() {
// There are 2 windows. This is the left window.
Rect leftWindowBounds = new Rect(0, 0, 400, 400);
AccessibilityWindowInfo leftWindow = new WindowBuilder()
.setBoundsInScreen(leftWindowBounds)
.build();
AccessibilityNodeInfo leftRoot = mNodeBuilder
.setWindow(leftWindow)
.setBoundsInScreen(leftWindowBounds)
.build();
setRootNodeForWindow(leftRoot, leftWindow);
// Left focus area and its view inside.
AccessibilityNodeInfo leftFocusArea = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
AccessibilityNodeInfo left = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftFocusArea)
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
// Right window.
Rect rightWindowBounds = new Rect(400, 0, 800, 400);
AccessibilityWindowInfo rightWindow = new WindowBuilder()
.setBoundsInScreen(rightWindowBounds)
.build();
AccessibilityNodeInfo rightRoot = mNodeBuilder
.setWindow(rightWindow)
.setBoundsInScreen(rightWindowBounds)
.build();
setRootNodeForWindow(rightRoot, rightWindow);
// Right focus area and its view inside.
AccessibilityNodeInfo rightFocusArea = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
AccessibilityNodeInfo recyclerView = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightFocusArea)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.setScrollableContainer()
.setScrollable(true)
.build();
AccessibilityNodeInfo nonFocusable = mNodeBuilder
.setWindow(rightWindow)
.setParent(recyclerView)
.setFocusable(false)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(leftWindow);
windows.add(rightWindow);
// Nudge from left window to right window.
AccessibilityNodeInfo target = mNavigator.findNudgeTarget(windows, left, View.FOCUS_RIGHT);
assertThat(target).isSameAs(recyclerView);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* **********leftWindow********* ****************rightWindow*****************
* * * * *
* * ===left focus area=== * * ==========right focus area======== *
* * = = * * = = *
* * = = * * = .........scrollable......... = *
* * = = * * = . . = *
* * = left = * * = . non-focusable . = *
* * = = * * = . . = *
* * = = * * = . focusable(off screen) . = *
* * = = * * = . . = *
* * = = * * = .......recyclerView......... = *
* * = = * * = = *
* * ===================== * * ================================== *
* * * * *
* ***************************** ********************************************
* </pre>
*/
@Test
public void testFindNudgeTargetReturnScrollableContainer2() {
// There are 2 windows. This is the left window.
Rect leftWindowBounds = new Rect(0, 0, 400, 400);
AccessibilityWindowInfo leftWindow = new WindowBuilder()
.setBoundsInScreen(leftWindowBounds)
.build();
AccessibilityNodeInfo leftRoot = mNodeBuilder
.setWindow(leftWindow)
.setBoundsInScreen(leftWindowBounds)
.build();
setRootNodeForWindow(leftRoot, leftWindow);
// Left focus area and its view inside.
AccessibilityNodeInfo leftFocusArea = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
AccessibilityNodeInfo left = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftFocusArea)
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
// Right window.
Rect rightWindowBounds = new Rect(400, 0, 800, 400);
AccessibilityWindowInfo rightWindow = new WindowBuilder()
.setBoundsInScreen(rightWindowBounds)
.build();
AccessibilityNodeInfo rightRoot = mNodeBuilder
.setWindow(rightWindow)
.setBoundsInScreen(rightWindowBounds)
.build();
setRootNodeForWindow(rightRoot, rightWindow);
// Right focus area and its view inside.
AccessibilityNodeInfo rightFocusArea = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
AccessibilityNodeInfo recyclerView = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightFocusArea)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.setScrollableContainer()
.setScrollable(true)
.build();
AccessibilityNodeInfo nonFocusable = mNodeBuilder
.setWindow(rightWindow)
.setParent(recyclerView)
.setFocusable(false)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
AccessibilityNodeInfo focusable = mNodeBuilder
.setWindow(rightWindow)
.setParent(recyclerView)
.setBoundsInScreen(new Rect(0, 0, 0, 0))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(leftWindow);
windows.add(rightWindow);
// Nudge from left window to right window.
AccessibilityNodeInfo target = mNavigator.findNudgeTarget(windows, left, View.FOCUS_RIGHT);
assertThat(target).isSameAs(recyclerView);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* **********leftWindow********* ****************rightWindow*****************
* * * * *
* * ===left focus area=== * * ==========right focus area======== *
* * = = * * = = *
* * = = * * = .......non-scrollable....... = *
* * = = * * = . . = *
* * = left = * * = . non-focusable . = *
* * = = * * = . . = *
* * = = * * = .......recyclerView......... = *
* * = = * * = = *
* * ===================== * * ================================== *
* * * * *
* ***************************** ********************************************
* </pre>
*/
@Test
public void testFindNudgeTargetSkipScrollableContainer() {
// There are 2 windows. This is the left window.
Rect leftWindowBounds = new Rect(0, 0, 400, 400);
AccessibilityWindowInfo leftWindow = new WindowBuilder()
.setBoundsInScreen(leftWindowBounds)
.build();
AccessibilityNodeInfo leftRoot = mNodeBuilder
.setWindow(leftWindow)
.setBoundsInScreen(leftWindowBounds)
.build();
setRootNodeForWindow(leftRoot, leftWindow);
// Left focus area and its view inside.
AccessibilityNodeInfo leftFocusArea = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
AccessibilityNodeInfo left = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftFocusArea)
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
// Right window.
Rect rightWindowBounds = new Rect(400, 0, 800, 400);
AccessibilityWindowInfo rightWindow = new WindowBuilder()
.setBoundsInScreen(rightWindowBounds)
.build();
AccessibilityNodeInfo rightRoot = mNodeBuilder
.setWindow(rightWindow)
.setBoundsInScreen(rightWindowBounds)
.build();
setRootNodeForWindow(rightRoot, rightWindow);
// Right focus area and its view inside.
AccessibilityNodeInfo rightFocusArea = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
AccessibilityNodeInfo recyclerView = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightFocusArea)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.setScrollableContainer()
.build();
AccessibilityNodeInfo nonFocusable = mNodeBuilder
.setWindow(rightWindow)
.setParent(recyclerView)
.setFocusable(false)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(leftWindow);
windows.add(rightWindow);
// Nudge from left window to right window.
AccessibilityNodeInfo target = mNavigator.findNudgeTarget(windows, left, View.FOCUS_RIGHT);
assertThat(target).isSameAs(null);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* **********leftWindow********* ****************rightWindow*****************
* * * * *
* * ===left focus area=== * * ==========right focus area======== *
* * = = * * = = *
* * = = * * = .........scrollable......... = *
* * = = * * = . . = *
* * = left = * * = . focusable . = *
* * = = * * = . . = *
* * = = * * = .......recyclerView......... = *
* * = = * * = = *
* * ===================== * * ================================== *
* * * * *
* ***************************** ********************************************
* </pre>
*/
@Test
public void testFindNudgeTargetSkipScrollableContainer2() {
// There are 2 windows. This is the left window.
Rect leftWindowBounds = new Rect(0, 0, 400, 400);
AccessibilityWindowInfo leftWindow = new WindowBuilder()
.setBoundsInScreen(leftWindowBounds)
.build();
AccessibilityNodeInfo leftRoot = mNodeBuilder
.setWindow(leftWindow)
.setBoundsInScreen(leftWindowBounds)
.build();
setRootNodeForWindow(leftRoot, leftWindow);
// Left focus area and its view inside.
AccessibilityNodeInfo leftFocusArea = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
AccessibilityNodeInfo left = mNodeBuilder
.setWindow(leftWindow)
.setParent(leftFocusArea)
.setBoundsInScreen(new Rect(0, 0, 400, 400))
.build();
// Right window.
Rect rightWindowBounds = new Rect(400, 0, 800, 400);
AccessibilityWindowInfo rightWindow = new WindowBuilder()
.setBoundsInScreen(rightWindowBounds)
.build();
AccessibilityNodeInfo rightRoot = mNodeBuilder
.setWindow(rightWindow)
.setBoundsInScreen(rightWindowBounds)
.build();
setRootNodeForWindow(rightRoot, rightWindow);
// Right focus area and its view inside.
AccessibilityNodeInfo rightFocusArea = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightRoot)
.setFocusArea()
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
AccessibilityNodeInfo recyclerView = mNodeBuilder
.setWindow(rightWindow)
.setParent(rightFocusArea)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.setScrollableContainer()
.setScrollable(true)
.build();
AccessibilityNodeInfo focusable = mNodeBuilder
.setWindow(rightWindow)
.setParent(recyclerView)
.setBoundsInScreen(new Rect(400, 0, 800, 400))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(leftWindow);
windows.add(rightWindow);
// Nudge from left window to right window.
AccessibilityNodeInfo target = mNavigator.findNudgeTarget(windows, left, View.FOCUS_RIGHT);
assertThat(target).isSameAs(focusable);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* In the same window
*
* =====contact list focus area====== ========app bar focus area========
* = ********contact list********** = <--> = *tab* =
* = * * = ==================================
* = * * =
* = * * =
* = * non-focusable item1 * =
* = * * =
* = * non-focusable item2 * =
* = * * =
* = ********recyclerView********** =
* ==================================
*
* ========nav bar focus area========
* = *button* =
* ==================================
* </pre>
* Where app bar focus area overlaps with contact list focus area.
*/
@Test
public void testFindNudgeTargetReturnContactList() {
Rect windowBounds = new Rect(0, 0, 100, 100);
AccessibilityWindowInfo window = new WindowBuilder()
.setBoundsInScreen(windowBounds)
.build();
AccessibilityNodeInfo root = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(windowBounds)
.build();
setRootNodeForWindow(root, window);
AccessibilityNodeInfo contactListFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 100, 80))
.build();
AccessibilityNodeInfo contactList = mNodeBuilder
.setWindow(window)
.setParent(contactListFocusArea)
.setBoundsInScreen(new Rect(0, 0, 100, 80))
.setScrollableContainer()
.setScrollable(true)
.build();
AccessibilityNodeInfo item1 = mNodeBuilder
.setWindow(window)
.setParent(contactList)
.setFocusable(false)
.setBoundsInScreen(new Rect(0, 40, 100, 50))
.build();
AccessibilityNodeInfo item2 = mNodeBuilder
.setWindow(window)
.setParent(contactList)
.setFocusable(false)
.setBoundsInScreen(new Rect(0, 50, 100, 60))
.build();
AccessibilityNodeInfo appBarFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 0, 100, 20))
.build();
AccessibilityNodeInfo tab = mNodeBuilder
.setWindow(window)
.setParent(appBarFocusArea)
.setBoundsInScreen(new Rect(40, 0, 50, 20))
.build();
AccessibilityNodeInfo navBarFocusArea = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 80, 100, 100))
.build();
AccessibilityNodeInfo button = mNodeBuilder
.setWindow(window)
.setParent(navBarFocusArea)
.setBoundsInScreen(new Rect(40, 80, 50, 100))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window);
// Nudge down from tab, it should go to contact list.
AccessibilityNodeInfo target
= mNavigator.findNudgeTarget(windows, tab, View.FOCUS_DOWN);
assertThat(target).isSameAs(contactList);
}
/**
* Tests {@link Navigator#findNudgeTarget} in the following layout:
* <pre>
* In the same window
*
* ==========focus area 1============
* = .........top offset......... =
* = . *view1* . =
* = .......bottom offset........ =
* = =
* = ========focus area 2======== =
* = = *view2* = =
* = ============================ =
* = =
* = =
* = =
* = =
* ==================================
*
* ===========focus area 3===========
* = *view3* =
* ==================================
* </pre>
*/
@Test
public void testFindNudgeTargetWithFocusAreaBoundsOffset() {
Rect windowBounds = new Rect(0, 0, 100, 100);
AccessibilityWindowInfo window = new WindowBuilder()
.setBoundsInScreen(windowBounds)
.build();
AccessibilityNodeInfo root = mNodeBuilder
.setWindow(window)
.setBoundsInScreen(windowBounds)
.build();
setRootNodeForWindow(root, window);
AccessibilityNodeInfo focusArea1 = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setFocusAreaBoundsOffset(0, 0, 0, 70)
.setBoundsInScreen(new Rect(0, 0, 100, 80))
.build();
AccessibilityNodeInfo view1 = mNodeBuilder
.setWindow(window)
.setParent(focusArea1)
.setBoundsInScreen(new Rect(0, 0, 100, 10))
.build();
AccessibilityNodeInfo focusArea2 = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 10, 100, 20))
.build();
AccessibilityNodeInfo view2 = mNodeBuilder
.setWindow(window)
.setParent(focusArea2)
.setBoundsInScreen(new Rect(0, 10, 100, 20))
.build();
AccessibilityNodeInfo focusArea3 = mNodeBuilder
.setWindow(window)
.setParent(root)
.setFocusArea()
.setBoundsInScreen(new Rect(0, 90, 100, 100))
.build();
AccessibilityNodeInfo view3 = mNodeBuilder
.setWindow(window)
.setParent(focusArea3)
.setBoundsInScreen(new Rect(0, 90, 100, 100))
.build();
List<AccessibilityWindowInfo> windows = new ArrayList<>();
windows.add(window);
// Nudge up from view3, it should go to view2.
AccessibilityNodeInfo target
= mNavigator.findNudgeTarget(windows, view3, View.FOCUS_UP);
assertThat(target).isSameAs(view2);
}
/**
* Tests {@link Navigator#findFirstFocusDescendant} in the following node tree:
* <pre>
* root
* / \
* / \
* focusArea1 focusArea2
* / \ / \
* / \ / \
* button1 button2 button3 button4
* </pre>
*/
@Test
public void testFindFirstFocusDescendant() {
AccessibilityNodeInfo root = mNodeBuilder.setFocusable(false).build();
AccessibilityNodeInfo focusArea1 = mNodeBuilder.setParent(root).setFocusArea().build();
AccessibilityNodeInfo focusArea2 = mNodeBuilder.setParent(root).setFocusArea().build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(focusArea1).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(focusArea1).build();
AccessibilityNodeInfo button3 = mNodeBuilder.setParent(focusArea2).build();
AccessibilityNodeInfo button4 = mNodeBuilder.setParent(focusArea2).build();
int direction = View.FOCUS_FORWARD;
// Search forward from the focus area.
when(focusArea1.focusSearch(direction)).thenReturn(button2);
AccessibilityNodeInfo target = mNavigator.findFirstFocusDescendant(root);
assertThat(target).isSameAs(button2);
// Fall back to tree traversal.
when(focusArea1.focusSearch(direction)).thenReturn(null);
target = mNavigator.findFirstFocusDescendant(root);
assertThat(target).isSameAs(button1);
}
/**
* Tests {@link Navigator#findFirstFocusDescendant} in the following node tree:
* <pre>
* root
* / \
* / \
* focusParkingView focusArea
* / \
* / \
* button1 button2
* </pre>
*/
@Test
public void testFindFirstFocusDescendantWithFocusParkingView() {
AccessibilityNodeInfo root = mNodeBuilder.setFocusable(false).build();
AccessibilityNodeInfo focusParkingView = mNodeBuilder.setParent(root).setFpv().build();
AccessibilityNodeInfo focusArea = mNodeBuilder.setParent(root).setFocusArea().build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(focusArea).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(focusArea).build();
int direction = View.FOCUS_FORWARD;
// Search forward from the focus area.
when(focusArea.focusSearch(direction)).thenReturn(button2);
AccessibilityNodeInfo target = mNavigator.findFirstFocusDescendant(root);
assertThat(target).isSameAs(button2);
// Fall back to tree traversal.
when(focusArea.focusSearch(direction)).thenReturn(null);
target = mNavigator.findFirstFocusDescendant(root);
assertThat(target).isSameAs(button1);
}
/**
* Tests {@link Navigator#findScrollableContainer} in the following node tree:
* <pre>
* root
* |
* |
* focusArea
* / \
* / \
* scrolling button2
* container
* |
* |
* container
* |
* |
* button1
* </pre>
*/
@Test
public void testFindScrollableContainer() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo focusArea = mNodeBuilder.setParent(root).setFocusArea().build();
AccessibilityNodeInfo scrollableContainer = mNodeBuilder
.setParent(focusArea)
.setScrollableContainer()
.build();
AccessibilityNodeInfo container = mNodeBuilder.setParent(scrollableContainer).build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(container).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(focusArea).build();
AccessibilityNodeInfo target = mNavigator.findScrollableContainer(button1);
assertThat(target).isSameAs(scrollableContainer);
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() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo container1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(container1).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(container1).build();
AccessibilityNodeInfo container2 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button3 = mNodeBuilder.setParent(container2).build();
AccessibilityNodeInfo button4 = mNodeBuilder.setParent(container2).build();
int direction = View.FOCUS_BACKWARD;
when(button4.focusSearch(direction)).thenReturn(button3);
when(button3.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(button1);
when(button1.focusSearch(direction)).thenReturn(null);
AccessibilityNodeInfo target = mNavigator.findFocusableDescendantInDirection(
container2, button4, View.FOCUS_BACKWARD);
assertThat(target).isSameAs(button3);
target = mNavigator.findFocusableDescendantInDirection(container2, button3,
View.FOCUS_BACKWARD);
assertThat(target).isNull();
target = mNavigator.findFocusableDescendantInDirection(container1, button2,
View.FOCUS_BACKWARD);
assertThat(target).isSameAs(button1);
target = mNavigator.findFocusableDescendantInDirection(container1, button1,
View.FOCUS_BACKWARD);
assertThat(target).isNull();
}
/**
* 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() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo container1 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(container1).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(container1).build();
AccessibilityNodeInfo container2 = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button3 = mNodeBuilder.setParent(container2).build();
AccessibilityNodeInfo button4 = mNodeBuilder.setParent(container2).build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(button3);
when(button3.focusSearch(direction)).thenReturn(button4);
when(button4.focusSearch(direction)).thenReturn(null);
AccessibilityNodeInfo target = mNavigator.findFocusableDescendantInDirection(
container1, button1, View.FOCUS_FORWARD);
assertThat(target).isSameAs(button2);
target = mNavigator.findFocusableDescendantInDirection(container1, button2,
View.FOCUS_FORWARD);
assertThat(target).isNull();
target = mNavigator.findFocusableDescendantInDirection(container2, button3,
View.FOCUS_FORWARD);
assertThat(target).isSameAs(button4);
target = mNavigator.findFocusableDescendantInDirection(container2, button4,
View.FOCUS_FORWARD);
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() {
AccessibilityNodeInfo root = mNodeBuilder.build();
AccessibilityNodeInfo container = mNodeBuilder.setParent(root).build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(container).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(container).build();
AccessibilityNodeInfo button3 = mNodeBuilder.setParent(container)
.setBoundsInScreen(new Rect(5, 10, 5, 10)).build();
AccessibilityNodeInfo button4 = mNodeBuilder.setParent(container)
.setBoundsInScreen(new Rect(20, 40, 20, 40)).build();
int direction = View.FOCUS_FORWARD;
when(button1.focusSearch(direction)).thenReturn(button2);
when(button2.focusSearch(direction)).thenReturn(button3);
when(button3.focusSearch(direction)).thenReturn(button4);
when(button4.focusSearch(direction)).thenReturn(button1);
AccessibilityNodeInfo target = mNavigator.findFocusableDescendantInDirection(container,
button1, View.FOCUS_FORWARD);
assertThat(target).isSameAs(button2);
target = mNavigator.findFocusableDescendantInDirection(container, button2,
View.FOCUS_FORWARD);
assertThat(target).isSameAs(button1);
target = mNavigator.findFocusableDescendantInDirection(container, button3,
View.FOCUS_FORWARD);
assertThat(target).isSameAs(button1);
target = mNavigator.findFocusableDescendantInDirection(container, button4,
View.FOCUS_FORWARD);
assertThat(target).isSameAs(button1);
}
/**
* 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() {
AccessibilityNodeInfo root = mNodeBuilder.setFocusable(false).build();
AccessibilityNodeInfo container1 = mNodeBuilder
.setParent(root)
.setFocusable(false)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder
.setParent(container1)
.setEnabled(false)
.build();
AccessibilityNodeInfo button2 = mNodeBuilder
.setParent(container1)
.setEnabled(false)
.build();
AccessibilityNodeInfo container2 = mNodeBuilder
.setParent(root)
.setFocusable(false)
.build();
AccessibilityNodeInfo button3 = mNodeBuilder.setParent(container2).build();
AccessibilityNodeInfo button4 = mNodeBuilder.setParent(container2).build();
AccessibilityNodeInfo target = mNavigator.findFirstFocusableDescendant(root);
assertThat(target).isSameAs(button3);
}
/**
* 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() {
AccessibilityNodeInfo root = mNodeBuilder.setFocusable(false).build();
AccessibilityNodeInfo container1 = mNodeBuilder
.setParent(root)
.setFocusable(false)
.build();
AccessibilityNodeInfo button1 = mNodeBuilder.setParent(container1).build();
AccessibilityNodeInfo button2 = mNodeBuilder.setParent(container1).build();
AccessibilityNodeInfo container2 = mNodeBuilder
.setParent(root)
.setFocusable(false)
.build();
AccessibilityNodeInfo button3 = mNodeBuilder
.setParent(container2)
.setEnabled(false)
.build();
AccessibilityNodeInfo button4 = mNodeBuilder
.setParent(container2)
.setEnabled(false)
.build();
AccessibilityNodeInfo target = mNavigator.findLastFocusableDescendant(root);
assertThat(target).isSameAs(button2);
}
/** Sets the {@code root} node in the {@code window}'s hierarchy. */
private void setRootNodeForWindow(@NonNull AccessibilityNodeInfo root,
@NonNull AccessibilityWindowInfo window) {
when(window.getRoot()).thenReturn(root);
}
}