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

Issues/79528 reland (#28117)

上级 90c9a915
......@@ -1020,28 +1020,39 @@ public class AccessibilityBridge extends AccessibilityNodeProvider {
}
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
{
// Focused semantics node must be reset before sending the
// TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED event. Otherwise,
// TalkBack may think the node is still focused.
if (accessibilityFocusedSemanticsNode != null
&& accessibilityFocusedSemanticsNode.id == virtualViewId) {
accessibilityFocusedSemanticsNode = null;
}
if (embeddedAccessibilityFocusedNodeId != null
&& embeddedAccessibilityFocusedNodeId == virtualViewId) {
embeddedAccessibilityFocusedNodeId = null;
}
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.DID_LOSE_ACCESSIBILITY_FOCUS);
sendAccessibilityEvent(
virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
accessibilityFocusedSemanticsNode = null;
embeddedAccessibilityFocusedNodeId = null;
return true;
}
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
{
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.DID_GAIN_ACCESSIBILITY_FOCUS);
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
if (accessibilityFocusedSemanticsNode == null) {
// When Android focuses a node, it doesn't invalidate the view.
// (It does when it sends ACTION_CLEAR_ACCESSIBILITY_FOCUS, so
// we only have to worry about this when the focused node is null.)
rootAccessibilityView.invalidate();
}
// Focused semantics node must be set before sending the TYPE_VIEW_ACCESSIBILITY_FOCUSED
// event. Otherwise, TalkBack may think the node is not focused yet.
accessibilityFocusedSemanticsNode = semanticsNode;
accessibilityChannel.dispatchSemanticsAction(
virtualViewId, Action.DID_GAIN_ACCESSIBILITY_FOCUS);
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
if (semanticsNode.hasAction(Action.INCREASE)
|| semanticsNode.hasAction(Action.DECREASE)) {
// SeekBars only announce themselves after this event.
......
......@@ -5,6 +5,7 @@
package io.flutter.view;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
......@@ -46,6 +47,7 @@ import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
......@@ -798,6 +800,170 @@ public class AccessibilityBridgeTest {
assertEquals(nodeInfo.getTextSelectionEnd(), expectedEnd);
}
@Test
public void itPerformsClearAccessibilityFocusCorrectly() {
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;
root.label = "root";
TestSemanticsNode node1 = new TestSemanticsNode();
node1.id = 1;
node1.value = "some text";
root.children.add(node1);
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);
accessibilityBridge.performAction(0, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0);
assertTrue(nodeInfo.isAccessibilityFocused());
// Clear focus on non-focused node shouldn't do anything
accessibilityBridge.performAction(
1, AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0);
assertTrue(nodeInfo.isAccessibilityFocused());
// Now, clear the focus for real.
accessibilityBridge.performAction(
0, AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0);
assertFalse(nodeInfo.isAccessibilityFocused());
}
@Test
public void itSetsFocusedNodeBeforeSendingEvent() {
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;
root.label = "root";
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);
class Verifier {
public Verifier(AccessibilityBridge accessibilityBridge) {
this.accessibilityBridge = accessibilityBridge;
}
public AccessibilityBridge accessibilityBridge;
public boolean verified = false;
public boolean verify(InvocationOnMock invocation) {
AccessibilityEvent event = (AccessibilityEvent) invocation.getArguments()[1];
assertEquals(event.getEventType(), AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
// The accessibility focus must be set before sending out
// the TYPE_VIEW_ACCESSIBILITY_FOCUSED event.
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0);
assertTrue(nodeInfo.isAccessibilityFocused());
verified = true;
return true;
}
};
Verifier verifier = new Verifier(accessibilityBridge);
when(mockParent.requestSendAccessibilityEvent(eq(mockRootView), any(AccessibilityEvent.class)))
.thenAnswer(invocation -> verifier.verify(invocation));
accessibilityBridge.performAction(0, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
assertTrue(verifier.verified);
}
@Test
public void itClearsFocusedNodeBeforeSendingEvent() {
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;
root.label = "root";
TestSemanticsUpdate testSemanticsUpdate = root.toUpdate();
testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge);
// Set the focus on root.
accessibilityBridge.performAction(0, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0);
assertTrue(nodeInfo.isAccessibilityFocused());
class Verifier {
public Verifier(AccessibilityBridge accessibilityBridge) {
this.accessibilityBridge = accessibilityBridge;
}
public AccessibilityBridge accessibilityBridge;
public boolean verified = false;
public boolean verify(InvocationOnMock invocation) {
AccessibilityEvent event = (AccessibilityEvent) invocation.getArguments()[1];
assertEquals(
event.getEventType(), AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
// The accessibility focus must be cleared before sending out
// the TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED event.
AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0);
assertFalse(nodeInfo.isAccessibilityFocused());
verified = true;
return true;
}
};
Verifier verifier = new Verifier(accessibilityBridge);
when(mockParent.requestSendAccessibilityEvent(eq(mockRootView), any(AccessibilityEvent.class)))
.thenAnswer(invocation -> verifier.verify(invocation));
accessibilityBridge.performAction(
0, AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
assertTrue(verifier.verified);
}
@Test
public void itCanPredictCursorMovementsWithGranularityWord() {
AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册