|
5 | 5 | package io.flutter.view;
|
6 | 6 |
|
7 | 7 | import static org.junit.Assert.assertEquals;
|
| 8 | +import static org.mockito.Matchers.eq; |
8 | 9 | import static org.mockito.Mockito.mock;
|
| 10 | +import static org.mockito.Mockito.times; |
| 11 | +import static org.mockito.Mockito.verify; |
9 | 12 | import static org.mockito.Mockito.when;
|
10 | 13 |
|
11 | 14 | import android.content.ContentResolver;
|
12 | 15 | import android.content.Context;
|
13 | 16 | import android.view.View;
|
| 17 | +import android.view.ViewParent; |
| 18 | +import android.view.accessibility.AccessibilityEvent; |
14 | 19 | import android.view.accessibility.AccessibilityManager;
|
15 | 20 | import android.view.accessibility.AccessibilityNodeInfo;
|
16 | 21 | import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
|
17 | 22 | import io.flutter.plugin.platform.PlatformViewsAccessibilityDelegate;
|
18 | 23 | import java.nio.ByteBuffer;
|
19 | 24 | import java.util.ArrayList;
|
| 25 | +import java.util.List; |
20 | 26 | import org.junit.Test;
|
21 | 27 | import org.junit.runner.RunWith;
|
| 28 | +import org.mockito.ArgumentCaptor; |
22 | 29 | import org.robolectric.RobolectricTestRunner;
|
23 | 30 | import org.robolectric.annotation.Config;
|
24 | 31 |
|
@@ -73,24 +80,103 @@ public void itDoesNotContainADescriptionIfScopesRoute() {
|
73 | 80 | assertEquals(nodeInfo.getText(), null);
|
74 | 81 | }
|
75 | 82 |
|
76 |
| - AccessibilityBridge setUpBridge() { |
77 |
| - View view = mock(View.class); |
| 83 | + @Test |
| 84 | + public void itUnfocusesPlatformViewWhenPlatformViewGoesAway() { |
| 85 | + AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); |
| 86 | + AccessibilityManager mockManager = mock(AccessibilityManager.class); |
| 87 | + View mockRootView = mock(View.class); |
78 | 88 | Context context = mock(Context.class);
|
79 |
| - when(view.getContext()).thenReturn(context); |
| 89 | + when(mockRootView.getContext()).thenReturn(context); |
80 | 90 | when(context.getPackageName()).thenReturn("test");
|
81 |
| - AccessibilityChannel accessibilityChannel = mock(AccessibilityChannel.class); |
82 |
| - AccessibilityManager accessibilityManager = mock(AccessibilityManager.class); |
83 |
| - ContentResolver contentResolver = mock(ContentResolver.class); |
84 |
| - PlatformViewsAccessibilityDelegate platformViewsAccessibilityDelegate = |
85 |
| - mock(PlatformViewsAccessibilityDelegate.class); |
86 | 91 | AccessibilityBridge accessibilityBridge =
|
87 |
| - new AccessibilityBridge( |
88 |
| - view, |
89 |
| - accessibilityChannel, |
90 |
| - accessibilityManager, |
91 |
| - contentResolver, |
92 |
| - platformViewsAccessibilityDelegate); |
93 |
| - return accessibilityBridge; |
| 92 | + setUpBridge(mockRootView, mockManager, mockViewEmbedder); |
| 93 | + |
| 94 | + // Sent a11y tree with platform view. |
| 95 | + TestSemanticsNode root = new TestSemanticsNode(); |
| 96 | + root.id = 0; |
| 97 | + TestSemanticsNode platformView = new TestSemanticsNode(); |
| 98 | + platformView.id = 1; |
| 99 | + platformView.platformViewId = 42; |
| 100 | + root.children.add(platformView); |
| 101 | + TestSemanticsUpdate testSemanticsUpdate = root.toUpdate(); |
| 102 | + accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings); |
| 103 | + |
| 104 | + // Set a11y focus to platform view. |
| 105 | + View mockView = mock(View.class); |
| 106 | + AccessibilityEvent focusEvent = mock(AccessibilityEvent.class); |
| 107 | + when(mockViewEmbedder.requestSendAccessibilityEvent(mockView, mockView, focusEvent)) |
| 108 | + .thenReturn(true); |
| 109 | + when(mockViewEmbedder.getRecordFlutterId(mockView, focusEvent)).thenReturn(42); |
| 110 | + when(focusEvent.getEventType()).thenReturn(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); |
| 111 | + accessibilityBridge.externalViewRequestSendAccessibilityEvent(mockView, mockView, focusEvent); |
| 112 | + |
| 113 | + // Replace the platform view. |
| 114 | + TestSemanticsNode node = new TestSemanticsNode(); |
| 115 | + node.id = 2; |
| 116 | + root.children.clear(); |
| 117 | + root.children.add(node); |
| 118 | + testSemanticsUpdate = root.toUpdate(); |
| 119 | + when(mockManager.isEnabled()).thenReturn(true); |
| 120 | + ViewParent mockParent = mock(ViewParent.class); |
| 121 | + when(mockRootView.getParent()).thenReturn(mockParent); |
| 122 | + accessibilityBridge.updateSemantics(testSemanticsUpdate.buffer, testSemanticsUpdate.strings); |
| 123 | + |
| 124 | + // Check that unfocus event was sent. |
| 125 | + ArgumentCaptor<AccessibilityEvent> eventCaptor = |
| 126 | + ArgumentCaptor.forClass(AccessibilityEvent.class); |
| 127 | + verify(mockParent, times(2)) |
| 128 | + .requestSendAccessibilityEvent(eq(mockRootView), eventCaptor.capture()); |
| 129 | + AccessibilityEvent event = eventCaptor.getAllValues().get(0); |
| 130 | + assertEquals(event.getEventType(), AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); |
| 131 | + } |
| 132 | + |
| 133 | + AccessibilityBridge setUpBridge() { |
| 134 | + return setUpBridge(null, null, null, null, null, null); |
| 135 | + } |
| 136 | + |
| 137 | + AccessibilityBridge setUpBridge( |
| 138 | + View rootAccessibilityView, |
| 139 | + AccessibilityManager accessibilityManager, |
| 140 | + AccessibilityViewEmbedder accessibilityViewEmbedder) { |
| 141 | + return setUpBridge( |
| 142 | + rootAccessibilityView, null, accessibilityManager, null, accessibilityViewEmbedder, null); |
| 143 | + } |
| 144 | + |
| 145 | + AccessibilityBridge setUpBridge( |
| 146 | + View rootAccessibilityView, |
| 147 | + AccessibilityChannel accessibilityChannel, |
| 148 | + AccessibilityManager accessibilityManager, |
| 149 | + ContentResolver contentResolver, |
| 150 | + AccessibilityViewEmbedder accessibilityViewEmbedder, |
| 151 | + PlatformViewsAccessibilityDelegate platformViewsAccessibilityDelegate) { |
| 152 | + if (rootAccessibilityView == null) { |
| 153 | + rootAccessibilityView = mock(View.class); |
| 154 | + Context context = mock(Context.class); |
| 155 | + when(rootAccessibilityView.getContext()).thenReturn(context); |
| 156 | + when(context.getPackageName()).thenReturn("test"); |
| 157 | + } |
| 158 | + if (accessibilityChannel == null) { |
| 159 | + accessibilityChannel = mock(AccessibilityChannel.class); |
| 160 | + } |
| 161 | + if (accessibilityManager == null) { |
| 162 | + accessibilityManager = mock(AccessibilityManager.class); |
| 163 | + } |
| 164 | + if (contentResolver == null) { |
| 165 | + contentResolver = mock(ContentResolver.class); |
| 166 | + } |
| 167 | + if (accessibilityViewEmbedder == null) { |
| 168 | + accessibilityViewEmbedder = mock(AccessibilityViewEmbedder.class); |
| 169 | + } |
| 170 | + if (platformViewsAccessibilityDelegate == null) { |
| 171 | + platformViewsAccessibilityDelegate = mock(PlatformViewsAccessibilityDelegate.class); |
| 172 | + } |
| 173 | + return new AccessibilityBridge( |
| 174 | + rootAccessibilityView, |
| 175 | + accessibilityChannel, |
| 176 | + accessibilityManager, |
| 177 | + contentResolver, |
| 178 | + accessibilityViewEmbedder, |
| 179 | + platformViewsAccessibilityDelegate); |
94 | 180 | }
|
95 | 181 |
|
96 | 182 | /// The encoding for semantics is described in platform_view_android.cc
|
@@ -136,11 +222,18 @@ void addFlag(AccessibilityBridge.Flag flag) {
|
136 | 222 | float top = 0.0f;
|
137 | 223 | float right = 0.0f;
|
138 | 224 | float bottom = 0.0f;
|
139 |
| - // children and custom actions not supported. |
| 225 | + final List<TestSemanticsNode> children = new ArrayList<TestSemanticsNode>(); |
| 226 | + // custom actions not supported. |
140 | 227 |
|
141 | 228 | TestSemanticsUpdate toUpdate() {
|
142 | 229 | ArrayList<String> strings = new ArrayList<String>();
|
143 | 230 | ByteBuffer bytes = ByteBuffer.allocate(1000);
|
| 231 | + addToBuffer(bytes, strings); |
| 232 | + bytes.flip(); |
| 233 | + return new TestSemanticsUpdate(bytes, strings.toArray(new String[strings.size()])); |
| 234 | + } |
| 235 | + |
| 236 | + protected void addToBuffer(ByteBuffer bytes, ArrayList<String> strings) { |
144 | 237 | bytes.putInt(id);
|
145 | 238 | bytes.putInt(flags);
|
146 | 239 | bytes.putInt(actions);
|
@@ -169,11 +262,20 @@ TestSemanticsUpdate toUpdate() {
|
169 | 262 | bytes.putFloat(0);
|
170 | 263 | }
|
171 | 264 | // children in traversal order.
|
172 |
| - bytes.putInt(0); |
| 265 | + bytes.putInt(children.size()); |
| 266 | + for (TestSemanticsNode node : children) { |
| 267 | + bytes.putInt(node.id); |
| 268 | + } |
| 269 | + // children in hit test order. |
| 270 | + for (TestSemanticsNode node : children) { |
| 271 | + bytes.putInt(node.id); |
| 272 | + } |
173 | 273 | // custom actions
|
174 | 274 | bytes.putInt(0);
|
175 |
| - bytes.flip(); |
176 |
| - return new TestSemanticsUpdate(bytes, strings.toArray(new String[strings.size()])); |
| 275 | + // child nodes |
| 276 | + for (TestSemanticsNode node : children) { |
| 277 | + node.addToBuffer(bytes, strings); |
| 278 | + } |
177 | 279 | }
|
178 | 280 | }
|
179 | 281 |
|
|
0 commit comments