Skip to content

Commit

Permalink
Android accessibility bridge also fire selection change event when it…
Browse files Browse the repository at this point in the history
… predict selection change. (flutter#30199)
  • Loading branch information
chunhtai authored Dec 10, 2021
1 parent 806d8bc commit 1ed10bb
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 0 deletions.
16 changes: 16 additions & 0 deletions shell/platform/android/io/flutter/view/AccessibilityBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -1208,7 +1208,23 @@ private boolean performCursorMoveAction(
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.
final int previousTextSelectionBase = semanticsNode.textSelectionBase;
final int previousTextSelectionExtent = semanticsNode.textSelectionExtent;
predictCursorMovement(semanticsNode, granularity, forward, extendSelection);

if (previousTextSelectionBase != semanticsNode.textSelectionBase
|| previousTextSelectionExtent != semanticsNode.textSelectionExtent) {
final String value = semanticsNode.value != null ? semanticsNode.value : "";
final AccessibilityEvent selectionEvent =
obtainAccessibilityEvent(
semanticsNode.id, AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
selectionEvent.getText().add(value);
selectionEvent.setFromIndex(semanticsNode.textSelectionBase);
selectionEvent.setToIndex(semanticsNode.textSelectionExtent);
selectionEvent.setItemCount(value.length());
sendAccessibilityEvent(selectionEvent);
}

switch (granularity) {
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
Expand Down Expand Up @@ -1106,6 +1107,107 @@ public void itCanPredictCursorMovementsWithGranularityWord() {
assertEquals(nodeInfo.getTextSelectionEnd(), 5);
}

@Test
public void itAlsoFireSelectionEventWhenPredictCursorMovements() {
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();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);
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);
ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(2))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
AccessibilityEvent event = eventCaptor.getAllValues().get(1);
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
assertEquals(event.getText().toString(), "[" + node1.value + "]");
assertEquals(event.getFromIndex(), 1);
assertEquals(event.getToIndex(), 1);
assertEquals(event.getItemCount(), node1.value.length());
}

@Test
public void itDoesNotFireSelectionEventWhenPredictCursorMovementsDoesNotChangeSelection() {
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();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);
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_PREVIOUS_AT_MOVEMENT_GRANULARITY, bundle);
ArgumentCaptor<AccessibilityEvent> eventCaptor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
verify(mockParent, times(1))
.requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture());
assertEquals(eventCaptor.getAllValues().size(), 1);
AccessibilityEvent event = eventCaptor.getAllValues().get(0);
assertNotEquals(event.getEventType(), AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
}

@Test
public void itCanPredictCursorMovementsWithGranularityWordUnicode() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
Expand Down

0 comments on commit 1ed10bb

Please sign in to comment.