未验证 提交 7d6b2c86 编写于 作者: C chunhtai 提交者: GitHub

Reland "Fixes android voice access delete text, redo, and undo action" (#25289)

* Reland "Fixes android voice access delete text, redo, and undo actions. (#25050)"

This reverts commit 4c6abc1e.

* fix condition
上级 f0476334
......@@ -34,6 +34,8 @@ import io.flutter.util.Predicate;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Bridge between Android's OS accessibility system and Flutter's accessibility system.
......@@ -633,8 +635,7 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
}
// These are non-ops on older devices. Attempting to interact with the text will cause Talkback
// to read the
// contents of the text box instead.
// to read the contents of the text box instead.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (semanticsNode.hasAction(Action.SET_SELECTION)) {
result.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
......@@ -650,6 +651,13 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
}
}
// Set text API isn't available until API 21.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (semanticsNode.hasAction(Action.SET_TEXT)) {
result.addAction(AccessibilityNodeInfo.ACTION_SET_TEXT);
}
}
if (semanticsNode.hasFlag(Flag.IS_BUTTON) || semanticsNode.hasFlag(Flag.IS_LINK)) {
result.setClassName("android.widget.Button");
}
......@@ -1034,6 +1042,12 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
}
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.SET_SELECTION, selection);
// The voice access expects the semantics node to update immediately. We update the
// semantics node based on prediction. If the result is incorrect, it will be updated in
// the next frame.
SemanticsNode node = flutterSemanticsTree.get(virtualViewId);
node.textSelectionBase = selection.get("base");
node.textSelectionExtent = selection.get("extent");
return true;
}
case AccessibilityNodeInfo.ACTION_COPY:
......@@ -1064,7 +1078,7 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false;
}
return performSetText(virtualViewId, arguments);
return performSetText(semanticsNode, virtualViewId, arguments);
}
default:
// might be a custom accessibility accessibilityAction.
......@@ -1094,6 +1108,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
final boolean extendSelection =
arguments.getBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
// The voice access expects the semantics node to update immediately. We update the semantics
// node based on prediction. If the result is incorrect, it will be updated in the next frame.
predictCursorMovement(semanticsNode, granularity, forward, extendSelection);
switch (granularity) {
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
{
......@@ -1121,23 +1138,99 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
return true;
}
break;
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
return true;
}
return false;
}
private void predictCursorMovement(
@NonNull SemanticsNode node, int granularity, boolean forward, boolean extendSelection) {
if (node.textSelectionExtent < 0 || node.textSelectionBase < 0) {
return;
}
switch (granularity) {
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
if (forward && node.textSelectionExtent < node.value.length()) {
node.textSelectionExtent += 1;
} else if (!forward && node.textSelectionExtent > 0) {
node.textSelectionExtent -= 1;
}
break;
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD:
if (forward && node.textSelectionExtent < node.value.length()) {
Pattern pattern = Pattern.compile("\\p{L}(\\b)");
Matcher result = pattern.matcher(node.value.substring(node.textSelectionExtent));
// we discard the first result because we want to find the "next" word
result.find();
if (result.find()) {
node.textSelectionExtent += result.start(1);
} else {
node.textSelectionExtent = node.value.length();
}
} else if (!forward && node.textSelectionExtent > 0) {
// Finds last beginning of the word boundary.
Pattern pattern = Pattern.compile("(?s:.*)(\\b)\\p{L}");
Matcher result = pattern.matcher(node.value.substring(0, node.textSelectionExtent));
if (result.find()) {
node.textSelectionExtent = result.start(1);
}
}
break;
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
if (forward && node.textSelectionExtent < node.value.length()) {
// Finds the next new line.
Pattern pattern = Pattern.compile("(?!^)(\\n)");
Matcher result = pattern.matcher(node.value.substring(node.textSelectionExtent));
if (result.find()) {
node.textSelectionExtent += result.start(1);
} else {
node.textSelectionExtent = node.value.length();
}
} else if (!forward && node.textSelectionExtent > 0) {
// Finds the last new line.
Pattern pattern = Pattern.compile("(?s:.*)(\\n)");
Matcher result = pattern.matcher(node.value.substring(0, node.textSelectionExtent));
if (result.find()) {
node.textSelectionExtent = result.start(1);
} else {
node.textSelectionExtent = 0;
}
}
break;
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
if (forward) {
node.textSelectionExtent = node.value.length();
} else {
node.textSelectionExtent = 0;
}
break;
}
if (!extendSelection) {
node.textSelectionBase = node.textSelectionExtent;
}
}
/**
* Handles the responsibilities of {@link #performAction(int, int, Bundle)} for the specific
* scenario of cursor movement.
*/
@TargetApi(21)
@RequiresApi(21)
private boolean performSetText(int virtualViewId, @NonNull Bundle arguments) {
private boolean performSetText(SemanticsNode node, int virtualViewId, @NonNull Bundle arguments) {
String newText = "";
if (arguments != null
&& arguments.containsKey(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE)) {
newText = arguments.getString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
}
accessibilityChannel.dispatchSemanticsAction(virtualViewId, Action.SET_TEXT, newText);
// The voice access expects the semantics node to update immediately. We update the semantics
// node based on prediction. If the result is incorrect, it will be updated in the next frame.
node.value = newText;
return true;
}
......
......@@ -6,6 +6,7 @@ package io.flutter.view;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
......@@ -372,6 +373,357 @@ public class AccessibilityBridgeTest {
.dispatchSemanticsAction(1, AccessibilityBridge.Action.SET_TEXT, expectedText);
}
@TargetApi(21)
@Test
public void itCanPredictSetText() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
AccessibilityBridge accessibilityBridge =
setUpBridge(
/*rootAccessibilityView=*/ mockRootView,
/*accessibilityChannel=*/ mockChannel,
/*accessibilityManager=*/ mockManager,
/*contentResolver=*/ null,
/*accessibilityViewEmbedder=*/ mockViewEmbedder,
/*platformViewsAccessibilityDelegate=*/ null);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);
TestSemanticsNode root = new TestSemanticsNode();
root.id = 0;
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD);
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
Bundle bundle = new Bundle();
String expectedText = "some string";
bundle.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, expectedText);
accessibilityBridge.performAction(1, AccessibilityNodeInfo.ACTION_SET_TEXT, bundle);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
assertEquals(nodeInfo.getText(), expectedText);
}
@TargetApi(21)
@Test
public void itCanCreateAccessibilityNodeInfoWithSetText() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
AccessibilityBridge accessibilityBridge =
setUpBridge(
/*rootAccessibilityView=*/ mockRootView,
/*accessibilityChannel=*/ mockChannel,
/*accessibilityManager=*/ mockManager,
/*contentResolver=*/ null,
/*accessibilityViewEmbedder=*/ mockViewEmbedder,
/*platformViewsAccessibilityDelegate=*/ null);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);
TestSemanticsNode root = new TestSemanticsNode();
root.id = 0;
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD);
node1.addAction(AccessibilityBridge.Action.SET_TEXT);
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
List<AccessibilityNodeInfo.AccessibilityAction> actions = nodeInfo.getActionList();
assertTrue(actions.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT));
}
@Test
public void itCanPredictSetSelection() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
AccessibilityBridge accessibilityBridge =
setUpBridge(
/*rootAccessibilityView=*/ mockRootView,
/*accessibilityChannel=*/ mockChannel,
/*accessibilityManager=*/ mockManager,
/*contentResolver=*/ null,
/*accessibilityViewEmbedder=*/ mockViewEmbedder,
/*platformViewsAccessibilityDelegate=*/ null);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);
TestSemanticsNode root = new TestSemanticsNode();
root.id = 0;
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.value = "some text";
node1.textSelectionBase = -1;
node1.textSelectionExtent = -1;
node1.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD);
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
Bundle bundle = new Bundle();
int expectedStart = 1;
int expectedEnd = 3;
bundle.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, expectedStart);
bundle.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, expectedEnd);
accessibilityBridge.performAction(1, AccessibilityNodeInfo.ACTION_SET_SELECTION, bundle);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
assertEquals(nodeInfo.getTextSelectionStart(), expectedStart);
assertEquals(nodeInfo.getTextSelectionEnd(), expectedEnd);
}
@Test
public void itCanPredictCursorMovementsWithGranularityWord() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
AccessibilityBridge accessibilityBridge =
setUpBridge(
/*rootAccessibilityView=*/ mockRootView,
/*accessibilityChannel=*/ mockChannel,
/*accessibilityManager=*/ mockManager,
/*contentResolver=*/ null,
/*accessibilityViewEmbedder=*/ mockViewEmbedder,
/*platformViewsAccessibilityDelegate=*/ null);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);
TestSemanticsNode root = new TestSemanticsNode();
root.id = 0;
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.value = "some text";
node1.textSelectionBase = 0;
node1.textSelectionExtent = 0;
node1.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD);
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
Bundle bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, bundle);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
// The seletction should be at the end of 'text'
assertEquals(nodeInfo.getTextSelectionStart(), 9);
assertEquals(nodeInfo.getTextSelectionEnd(), 9);
bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, bundle);
nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
// The seletction should be go to beginning of 'text'.
assertEquals(nodeInfo.getTextSelectionStart(), 5);
assertEquals(nodeInfo.getTextSelectionEnd(), 5);
}
@Test
public void itCanPredictCursorMovementsWithGranularityWordUnicode() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
AccessibilityBridge accessibilityBridge =
setUpBridge(
/*rootAccessibilityView=*/ mockRootView,
/*accessibilityChannel=*/ mockChannel,
/*accessibilityManager=*/ mockManager,
/*contentResolver=*/ null,
/*accessibilityViewEmbedder=*/ mockViewEmbedder,
/*platformViewsAccessibilityDelegate=*/ null);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);
TestSemanticsNode root = new TestSemanticsNode();
root.id = 0;
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.value = "你 好 嗎";
node1.textSelectionBase = 0;
node1.textSelectionExtent = 0;
node1.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD);
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
Bundle bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, bundle);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
// The seletction should be at the end of '好'
assertEquals(nodeInfo.getTextSelectionStart(), 3);
assertEquals(nodeInfo.getTextSelectionEnd(), 3);
bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, bundle);
nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
// The seletction should be go to beginning of '好'.
assertEquals(nodeInfo.getTextSelectionStart(), 2);
assertEquals(nodeInfo.getTextSelectionEnd(), 2);
}
@Test
public void itCanPredictCursorMovementsWithGranularityLine() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
AccessibilityBridge accessibilityBridge =
setUpBridge(
/*rootAccessibilityView=*/ mockRootView,
/*accessibilityChannel=*/ mockChannel,
/*accessibilityManager=*/ mockManager,
/*contentResolver=*/ null,
/*accessibilityViewEmbedder=*/ mockViewEmbedder,
/*platformViewsAccessibilityDelegate=*/ null);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);
TestSemanticsNode root = new TestSemanticsNode();
root.id = 0;
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.value = "How are you\nI am fine\nThank you";
// Selection is at the second line.
node1.textSelectionBase = 14;
node1.textSelectionExtent = 14;
node1.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD);
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
Bundle bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, bundle);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
// The seletction should be at the beginning of the third line.
assertEquals(nodeInfo.getTextSelectionStart(), 21);
assertEquals(nodeInfo.getTextSelectionEnd(), 21);
bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, bundle);
nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
// The seletction should be at the beginning of the second line.
assertEquals(nodeInfo.getTextSelectionStart(), 11);
assertEquals(nodeInfo.getTextSelectionEnd(), 11);
}
@Test
public void itCanPredictCursorMovementsWithGranularityCharacter() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
AccessibilityManager mockManager = mock(AccessibilityManager.class);
View mockRootView = mock(View.class);
Context context = mock(Context.class);
when(mockRootView.getContext()).thenReturn(context);
when(context.getPackageName()).thenReturn("test");
AccessibilityBridge accessibilityBridge =
setUpBridge(
/*rootAccessibilityView=*/ mockRootView,
/*accessibilityChannel=*/ mockChannel,
/*accessibilityManager=*/ mockManager,
/*contentResolver=*/ null,
/*accessibilityViewEmbedder=*/ mockViewEmbedder,
/*platformViewsAccessibilityDelegate=*/ null);
ViewParent mockParent = mock(ViewParent.class);
when(mockRootView.getParent()).thenReturn(mockParent);
when(mockManager.isEnabled()).thenReturn(true);
TestSemanticsNode root = new TestSemanticsNode();
root.id = 0;
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.value = "some text";
node1.textSelectionBase = 0;
node1.textSelectionExtent = 0;
node1.addFlag(AccessibilityBridge.Flag.IS_TEXT_FIELD);
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings);
Bundle bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, bundle);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
assertEquals(nodeInfo.getTextSelectionStart(), 1);
assertEquals(nodeInfo.getTextSelectionEnd(), 1);
bundle = new Bundle();
bundle.putInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
bundle.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, false);
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, bundle);
nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1);
assertEquals(nodeInfo.getTextSelectionStart(), 0);
assertEquals(nodeInfo.getTextSelectionEnd(), 0);
}
@Test
public void itAnnouncesWhiteSpaceWhenNoNamesRoute() {
AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class);
......@@ -600,6 +952,10 @@ public class AccessibilityBridgeTest {
flags |= flag.value;
}
void addAction(AccessibilityBridge.Action action) {
actions |= action.value;
}
// These fields are declared in the order they should be
// encoded.
int id = 0;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册