Flutter Engine
The Flutter Engine
AccessibilityViewEmbedder.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.view;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.annotation.SuppressLint;
10import android.graphics.Rect;
11import android.os.Build;
12import android.os.Bundle;
13import android.os.Parcel;
14import android.util.SparseArray;
15import android.view.MotionEvent;
16import android.view.View;
17import android.view.accessibility.AccessibilityEvent;
18import android.view.accessibility.AccessibilityNodeInfo;
19import android.view.accessibility.AccessibilityNodeProvider;
20import android.view.accessibility.AccessibilityRecord;
21import androidx.annotation.Keep;
22import androidx.annotation.NonNull;
23import androidx.annotation.Nullable;
24import io.flutter.Log;
25import java.lang.reflect.Field;
26import java.lang.reflect.InvocationTargetException;
27import java.lang.reflect.Method;
28import java.util.HashMap;
29import java.util.Map;
30
31/**
32 * Facilitates embedding of platform views in the accessibility tree generated by the accessibility
33 * bridge.
34 *
35 * <p>Embedding is done by mirroring the accessibility tree of the platform view as a subtree of the
36 * flutter accessibility tree.
37 *
38 * <p>This class relies on hidden system APIs to extract the accessibility information and does not
39 * work starting Android P; If the reflection accessors are not available we fail silently by
40 * embedding a null node, the app continues working but the accessibility information for the
41 * platform view will not be embedded.
42 *
43 * <p>We use the term `flutterId` for virtual accessibility node IDs in the FlutterView tree, and
44 * the term `originId` for the virtual accessibility node IDs in the platform view's tree.
45 * Internally this class maintains a bidirectional mapping between `flutterId`s and the
46 * corresponding platform view and `originId`.
47 */
48@Keep
50 private static final String TAG = "AccessibilityBridge";
51
52 private final ReflectionAccessors reflectionAccessors;
53
54 // The view to which the platform view is embedded, this is typically FlutterView.
55 private final View rootAccessibilityView;
56
57 // Maps a flutterId to the corresponding platform view and originId.
58 private final SparseArray<ViewAndId> flutterIdToOrigin;
59
60 // Maps a platform view and originId to a corresponding flutterID.
61 private final Map<ViewAndId, Integer> originToFlutterId;
62
63 // Maps an embedded view to it's screen bounds.
64 // This is used to translate the coordinates of the accessibility node subtree to the main
65 // display's coordinate
66 // system.
67 private final Map<View, Rect> embeddedViewToDisplayBounds;
68
69 private int nextFlutterId;
70
71 AccessibilityViewEmbedder(@NonNull View rootAccessibiiltyView, int firstVirtualNodeId) {
72 reflectionAccessors = new ReflectionAccessors();
73 flutterIdToOrigin = new SparseArray<>();
74 this.rootAccessibilityView = rootAccessibiiltyView;
75 nextFlutterId = firstVirtualNodeId;
76 originToFlutterId = new HashMap<>();
77 embeddedViewToDisplayBounds = new HashMap<>();
78 }
79
80 /**
81 * Returns the root accessibility node for an embedded platform view.
82 *
83 * @param flutterId the virtual accessibility ID for the node in flutter accessibility tree
84 * @param displayBounds the display bounds for the node in screen coordinates
85 */
86 public AccessibilityNodeInfo getRootNode(
87 @NonNull View embeddedView, int flutterId, @NonNull Rect displayBounds) {
88 AccessibilityNodeInfo originNode = embeddedView.createAccessibilityNodeInfo();
89 Long originPackedId = reflectionAccessors.getSourceNodeId(originNode);
90 if (originPackedId == null) {
91 return null;
92 }
93 embeddedViewToDisplayBounds.put(embeddedView, displayBounds);
94 int originId = ReflectionAccessors.getVirtualNodeId(originPackedId);
95 cacheVirtualIdMappings(embeddedView, originId, flutterId);
96 return convertToFlutterNode(originNode, flutterId, embeddedView);
97 }
98
99 /** Creates the accessibility node info for the node identified with `flutterId`. */
100 @Nullable
101 public AccessibilityNodeInfo createAccessibilityNodeInfo(int flutterId) {
102 ViewAndId origin = flutterIdToOrigin.get(flutterId);
103 if (origin == null) {
104 return null;
105 }
106 if (!embeddedViewToDisplayBounds.containsKey(origin.view)) {
107 // This might happen if the embedded view is sending accessibility event before the first
108 // Flutter semantics
109 // tree was sent to the accessibility bridge. In this case we don't return a node as we do not
110 // know the
111 // bounds yet.
112 // https://github.com/flutter/flutter/issues/30068
113 return null;
114 }
115 AccessibilityNodeProvider provider = origin.view.getAccessibilityNodeProvider();
116 if (provider == null) {
117 // The provider is null for views that don't have a virtual accessibility tree.
118 // We currently only support embedding virtual hierarchies in the Flutter tree.
119 // TODO(amirh): support embedding non virtual hierarchies.
120 // https://github.com/flutter/flutter/issues/29717
121 return null;
122 }
123 AccessibilityNodeInfo originNode =
124 origin.view.getAccessibilityNodeProvider().createAccessibilityNodeInfo(origin.id);
125 if (originNode == null) {
126 return null;
127 }
128 return convertToFlutterNode(originNode, flutterId, origin.view);
129 }
130
131 /*
132 * Creates an AccessibilityNodeInfo that can be attached to the Flutter accessibility tree and is equivalent to
133 * originNode(which belongs to embeddedView). The virtual ID for the created node will be flutterId.
134 */
135 @NonNull
136 private AccessibilityNodeInfo convertToFlutterNode(
137 @NonNull AccessibilityNodeInfo originNode, int flutterId, @NonNull View embeddedView) {
138 AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(rootAccessibilityView, flutterId);
139 result.setPackageName(rootAccessibilityView.getContext().getPackageName());
140 result.setSource(rootAccessibilityView, flutterId);
141 result.setClassName(originNode.getClassName());
142
143 Rect displayBounds = embeddedViewToDisplayBounds.get(embeddedView);
144
145 copyAccessibilityFields(originNode, result);
146 setFlutterNodesTranslateBounds(originNode, displayBounds, result);
147 addChildrenToFlutterNode(originNode, embeddedView, result);
148 setFlutterNodeParent(originNode, embeddedView, result);
149
150 return result;
151 }
152
153 private void setFlutterNodeParent(
154 @NonNull AccessibilityNodeInfo originNode,
155 @NonNull View embeddedView,
156 @NonNull AccessibilityNodeInfo result) {
157 Long parentOriginPackedId = reflectionAccessors.getParentNodeId(originNode);
158 if (parentOriginPackedId == null) {
159 return;
160 }
161 int parentOriginId = ReflectionAccessors.getVirtualNodeId(parentOriginPackedId);
162 Integer parentFlutterId = originToFlutterId.get(new ViewAndId(embeddedView, parentOriginId));
163 if (parentFlutterId != null) {
164 result.setParent(rootAccessibilityView, parentFlutterId);
165 }
166 }
167
168 private void addChildrenToFlutterNode(
169 @NonNull AccessibilityNodeInfo originNode,
170 @NonNull View embeddedView,
171 @NonNull AccessibilityNodeInfo resultNode) {
172 for (int i = 0; i < originNode.getChildCount(); i++) {
173 Long originPackedId = reflectionAccessors.getChildId(originNode, i);
174 if (originPackedId == null) {
175 continue;
176 }
177 int originId = ReflectionAccessors.getVirtualNodeId(originPackedId);
178 ViewAndId origin = new ViewAndId(embeddedView, originId);
179 int childFlutterId;
180 if (originToFlutterId.containsKey(origin)) {
181 childFlutterId = originToFlutterId.get(origin);
182 } else {
183 childFlutterId = nextFlutterId++;
184 cacheVirtualIdMappings(embeddedView, originId, childFlutterId);
185 }
186 resultNode.addChild(rootAccessibilityView, childFlutterId);
187 }
188 }
189
190 // Caches a bidirectional mapping of (embeddedView, originId)<-->flutterId.
191 // Where originId is a virtual node ID in the embeddedView's tree, and flutterId is the ID
192 // of the corresponding node in the Flutter virtual accessibility nodes tree.
193 private void cacheVirtualIdMappings(@NonNull View embeddedView, int originId, int flutterId) {
194 ViewAndId origin = new ViewAndId(embeddedView, originId);
195 originToFlutterId.put(origin, flutterId);
196 flutterIdToOrigin.put(flutterId, origin);
197 }
198
199 // Suppressing deprecation warning for AccessibilityNodeInfo#getBoundsinParent and
200 // AccessibilityNodeInfo#getBoundsinParent as we are copying the platform view's
201 // accessibility node and we should not lose any available bounds information.
202 @SuppressWarnings("deprecation")
203 private void setFlutterNodesTranslateBounds(
204 @NonNull AccessibilityNodeInfo originNode,
205 @NonNull Rect displayBounds,
206 @NonNull AccessibilityNodeInfo resultNode) {
207 Rect boundsInParent = new Rect();
208 originNode.getBoundsInParent(boundsInParent);
209 resultNode.setBoundsInParent(boundsInParent);
210
211 Rect boundsInScreen = new Rect();
212 originNode.getBoundsInScreen(boundsInScreen);
213 boundsInScreen.offset(displayBounds.left, displayBounds.top);
214 resultNode.setBoundsInScreen(boundsInScreen);
215 }
216
217 private void copyAccessibilityFields(
218 @NonNull AccessibilityNodeInfo input, @NonNull AccessibilityNodeInfo output) {
219 output.setAccessibilityFocused(input.isAccessibilityFocused());
220 output.setCheckable(input.isCheckable());
221 output.setChecked(input.isChecked());
222 output.setContentDescription(input.getContentDescription());
223 output.setEnabled(input.isEnabled());
224 output.setClickable(input.isClickable());
225 output.setFocusable(input.isFocusable());
226 output.setFocused(input.isFocused());
227 output.setLongClickable(input.isLongClickable());
228 output.setMovementGranularities(input.getMovementGranularities());
229 output.setPassword(input.isPassword());
230 output.setScrollable(input.isScrollable());
231 output.setSelected(input.isSelected());
232 output.setText(input.getText());
233 output.setVisibleToUser(input.isVisibleToUser());
234
235 output.setEditable(input.isEditable());
236 output.setCanOpenPopup(input.canOpenPopup());
237 output.setCollectionInfo(input.getCollectionInfo());
238 output.setCollectionItemInfo(input.getCollectionItemInfo());
239 output.setContentInvalid(input.isContentInvalid());
240 output.setDismissable(input.isDismissable());
241 output.setInputType(input.getInputType());
242 output.setLiveRegion(input.getLiveRegion());
243 output.setMultiLine(input.isMultiLine());
244 output.setRangeInfo(input.getRangeInfo());
245 output.setError(input.getError());
246 output.setMaxTextLength(input.getMaxTextLength());
247 if (Build.VERSION.SDK_INT >= API_LEVELS.API_23) {
248 output.setContextClickable(input.isContextClickable());
249 // TODO(amirh): copy traversal before and after.
250 // https://github.com/flutter/flutter/issues/29718
251 }
252 if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
253 output.setDrawingOrder(input.getDrawingOrder());
254 output.setImportantForAccessibility(input.isImportantForAccessibility());
255 }
256 if (Build.VERSION.SDK_INT >= API_LEVELS.API_26) {
257 output.setAvailableExtraData(input.getAvailableExtraData());
258 output.setHintText(input.getHintText());
259 output.setShowingHintText(input.isShowingHintText());
260 }
261 }
262
263 /**
264 * Delegates an AccessibilityNodeProvider#requestSendAccessibilityEvent from the
265 * AccessibilityBridge to the embedded view.
266 *
267 * @return True if the event was sent.
268 */
270 @NonNull View embeddedView, @NonNull View eventOrigin, @NonNull AccessibilityEvent event) {
271 AccessibilityEvent translatedEvent = AccessibilityEvent.obtain(event);
272 Long originPackedId = reflectionAccessors.getRecordSourceNodeId(event);
273 if (originPackedId == null) {
274 return false;
275 }
276 int originVirtualId = ReflectionAccessors.getVirtualNodeId(originPackedId);
277 Integer flutterId = originToFlutterId.get(new ViewAndId(embeddedView, originVirtualId));
278 if (flutterId == null) {
279 flutterId = nextFlutterId++;
280 cacheVirtualIdMappings(embeddedView, originVirtualId, flutterId);
281 }
282 translatedEvent.setSource(rootAccessibilityView, flutterId);
283 translatedEvent.setClassName(event.getClassName());
284 translatedEvent.setPackageName(event.getPackageName());
285
286 for (int i = 0; i < translatedEvent.getRecordCount(); i++) {
287 AccessibilityRecord record = translatedEvent.getRecord(i);
288 Long recordOriginPackedId = reflectionAccessors.getRecordSourceNodeId(record);
289 if (recordOriginPackedId == null) {
290 return false;
291 }
292 int recordOriginVirtualID = ReflectionAccessors.getVirtualNodeId(recordOriginPackedId);
293 ViewAndId originViewAndId = new ViewAndId(embeddedView, recordOriginVirtualID);
294 if (!originToFlutterId.containsKey(originViewAndId)) {
295 return false;
296 }
297 int recordFlutterId = originToFlutterId.get(originViewAndId);
298 record.setSource(rootAccessibilityView, recordFlutterId);
299 }
300
301 return rootAccessibilityView
302 .getParent()
303 .requestSendAccessibilityEvent(eventOrigin, translatedEvent);
304 }
305
306 /**
307 * Delegates an @{link AccessibilityNodeProvider#performAction} from the AccessibilityBridge to
308 * the embedded view's accessibility node provider.
309 *
310 * @return True if the action was performed.
311 */
312 public boolean performAction(int flutterId, int accessibilityAction, @Nullable Bundle arguments) {
313 ViewAndId origin = flutterIdToOrigin.get(flutterId);
314 if (origin == null) {
315 return false;
316 }
317 View embeddedView = origin.view;
318 AccessibilityNodeProvider provider = embeddedView.getAccessibilityNodeProvider();
319 if (provider == null) {
320 return false;
321 }
322 return provider.performAction(origin.id, accessibilityAction, arguments);
323 }
324
325 /**
326 * Returns a flutterID for an accessibility record, or null if no mapping exists.
327 *
328 * @param embeddedView the embedded view that the record is associated with.
329 */
330 @Nullable
331 public Integer getRecordFlutterId(
332 @NonNull View embeddedView, @NonNull AccessibilityRecord record) {
333 Long originPackedId = reflectionAccessors.getRecordSourceNodeId(record);
334 if (originPackedId == null) {
335 return null;
336 }
337 int originVirtualId = ReflectionAccessors.getVirtualNodeId(originPackedId);
338 return originToFlutterId.get(new ViewAndId(embeddedView, originVirtualId));
339 }
340
341 /**
342 * Delegates a View#onHoverEvent event from the AccessibilityBridge to an embedded view.
343 *
344 * <p>The pointer coordinates are translated to the embedded view's coordinate system.
345 */
346 public boolean onAccessibilityHoverEvent(int rootFlutterId, @NonNull MotionEvent event) {
347 ViewAndId origin = flutterIdToOrigin.get(rootFlutterId);
348 if (origin == null) {
349 return false;
350 }
351 Rect displayBounds = embeddedViewToDisplayBounds.get(origin.view);
352 int pointerCount = event.getPointerCount();
353 MotionEvent.PointerProperties[] pointerProperties =
354 new MotionEvent.PointerProperties[pointerCount];
355 MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
356 for (int i = 0; i < event.getPointerCount(); i++) {
357 pointerProperties[i] = new MotionEvent.PointerProperties();
358 event.getPointerProperties(i, pointerProperties[i]);
359
360 MotionEvent.PointerCoords originCoords = new MotionEvent.PointerCoords();
361 event.getPointerCoords(i, originCoords);
362
363 pointerCoords[i] = new MotionEvent.PointerCoords(originCoords);
364 pointerCoords[i].x -= displayBounds.left;
365 pointerCoords[i].y -= displayBounds.top;
366 }
367 MotionEvent translatedEvent =
368 MotionEvent.obtain(
369 event.getDownTime(),
370 event.getEventTime(),
371 event.getAction(),
372 event.getPointerCount(),
373 pointerProperties,
374 pointerCoords,
375 event.getMetaState(),
376 event.getButtonState(),
377 event.getXPrecision(),
378 event.getYPrecision(),
379 event.getDeviceId(),
380 event.getEdgeFlags(),
381 event.getSource(),
382 event.getFlags());
383 return origin.view.dispatchGenericMotionEvent(translatedEvent);
384 }
385
386 /**
387 * Returns the View that contains the accessibility node identified by the provided flutterId or
388 * null if it doesn't belong to a view.
389 */
390 public View platformViewOfNode(int flutterId) {
391 ViewAndId viewAndId = flutterIdToOrigin.get(flutterId);
392 if (viewAndId == null) {
393 return null;
394 }
395 return viewAndId.view;
396 }
397
398 private static class ViewAndId {
399 final View view;
400 final int id;
401
402 private ViewAndId(View view, int id) {
403 this.view = view;
404 this.id = id;
405 }
406
407 @Override
408 public boolean equals(Object o) {
409 if (this == o) return true;
410 if (!(o instanceof ViewAndId)) return false;
411 ViewAndId viewAndId = (ViewAndId) o;
412 return id == viewAndId.id && view.equals(viewAndId.view);
413 }
414
415 @Override
416 public int hashCode() {
417 final int prime = 31;
418 int result = 1;
419 result = prime * result + view.hashCode();
420 result = prime * result + id;
421 return result;
422 }
423 }
424
425 private static class ReflectionAccessors {
426 private @Nullable final Method getSourceNodeId;
427 private @Nullable final Method getParentNodeId;
428 private @Nullable final Method getRecordSourceNodeId;
429 private @Nullable final Method getChildId;
430 private @Nullable final Field childNodeIdsField;
431 private @Nullable final Method longArrayGetIndex;
432
433 @SuppressLint("DiscouragedPrivateApi,PrivateApi")
434 private ReflectionAccessors() {
435 Method getSourceNodeId = null;
436 Method getParentNodeId = null;
437 Method getRecordSourceNodeId = null;
438 Method getChildId = null;
439 Field childNodeIdsField = null;
440 Method longArrayGetIndex = null;
441 try {
442 getSourceNodeId = AccessibilityNodeInfo.class.getMethod("getSourceNodeId");
443 } catch (NoSuchMethodException e) {
444 Log.w(TAG, "can't invoke AccessibilityNodeInfo#getSourceNodeId with reflection");
445 }
446 try {
447 getRecordSourceNodeId = AccessibilityRecord.class.getMethod("getSourceNodeId");
448 } catch (NoSuchMethodException e) {
449 Log.w(TAG, "can't invoke AccessibiiltyRecord#getSourceNodeId with reflection");
450 }
451 // Reflection access is not allowed starting Android P on these methods.
452 if (Build.VERSION.SDK_INT <= API_LEVELS.API_26) {
453 try {
454 getParentNodeId = AccessibilityNodeInfo.class.getMethod("getParentNodeId");
455 } catch (NoSuchMethodException e) {
456 Log.w(TAG, "can't invoke getParentNodeId with reflection");
457 }
458 // Starting P we extract the child id from the mChildNodeIds field (see getChildId
459 // below).
460 try {
461 getChildId = AccessibilityNodeInfo.class.getMethod("getChildId", int.class);
462 } catch (NoSuchMethodException e) {
463 Log.w(TAG, "can't invoke getChildId with reflection");
464 }
465 } else {
466 try {
467 childNodeIdsField = AccessibilityNodeInfo.class.getDeclaredField("mChildNodeIds");
468 childNodeIdsField.setAccessible(true);
469 // The private member is a private utility class to Android. We need to use
470 // reflection to actually handle the data too.
471 longArrayGetIndex = Class.forName("android.util.LongArray").getMethod("get", int.class);
472 } catch (NoSuchFieldException
473 | ClassNotFoundException
474 | NoSuchMethodException
475 | NullPointerException e) {
476 Log.w(TAG, "can't access childNodeIdsField with reflection");
477 childNodeIdsField = null;
478 }
479 }
480 this.getSourceNodeId = getSourceNodeId;
481 this.getParentNodeId = getParentNodeId;
482 this.getRecordSourceNodeId = getRecordSourceNodeId;
483 this.getChildId = getChildId;
484 this.childNodeIdsField = childNodeIdsField;
485 this.longArrayGetIndex = longArrayGetIndex;
486 }
487
488 /** Returns virtual node ID given packed node ID used internally in accessibility API. */
489 private static int getVirtualNodeId(long nodeId) {
490 return (int) (nodeId >> 32);
491 }
492
493 @Nullable
494 private Long getSourceNodeId(@NonNull AccessibilityNodeInfo node) {
495 if (getSourceNodeId == null) {
496 return null;
497 }
498 try {
499 return (Long) getSourceNodeId.invoke(node);
500 } catch (IllegalAccessException e) {
501 Log.w(TAG, "Failed to access getSourceNodeId method.", e);
502 } catch (InvocationTargetException e) {
503 Log.w(TAG, "The getSourceNodeId method threw an exception when invoked.", e);
504 }
505 return null;
506 }
507
508 @Nullable
509 private Long getChildId(@NonNull AccessibilityNodeInfo node, int child) {
510 if (getChildId == null && (childNodeIdsField == null || longArrayGetIndex == null)) {
511 return null;
512 }
513 if (getChildId != null) {
514 try {
515 return (Long) getChildId.invoke(node, child);
516 // Using identical separate catch blocks to comply with the following lint:
517 // Error: Multi-catch with these reflection exceptions requires API level 19
518 // (current min is 16) because they get compiled to the common but new super
519 // type ReflectiveOperationException. As a workaround either create individual
520 // catch statements, or catch Exception. [NewApi]
521 } catch (IllegalAccessException e) {
522 Log.w(TAG, "Failed to access getChildId method.", e);
523 } catch (InvocationTargetException e) {
524 Log.w(TAG, "The getChildId method threw an exception when invoked.", e);
525 }
526 } else {
527 try {
528 return (long) longArrayGetIndex.invoke(childNodeIdsField.get(node), child);
529 // Using identical separate catch blocks to comply with the following lint:
530 // Error: Multi-catch with these reflection exceptions requires API level 19
531 // (current min is 16) because they get compiled to the common but new super
532 // type ReflectiveOperationException. As a workaround either create individual
533 // catch statements, or catch Exception. [NewApi]
534 } catch (IllegalAccessException e) {
535 Log.w(TAG, "Failed to access longArrayGetIndex method or the childNodeId field.", e);
536 } catch (InvocationTargetException | ArrayIndexOutOfBoundsException e) {
537 Log.w(TAG, "The longArrayGetIndex method threw an exception when invoked.", e);
538 }
539 }
540 return null;
541 }
542
543 @Nullable
544 private Long getParentNodeId(@NonNull AccessibilityNodeInfo node) {
545 if (getParentNodeId != null) {
546 try {
547 return (long) getParentNodeId.invoke(node);
548 // Using identical separate catch blocks to comply with the following lint:
549 // Error: Multi-catch with these reflection exceptions requires API level 19
550 // (current min is 16) because they get compiled to the common but new super
551 // type ReflectiveOperationException. As a workaround either create individual
552 // catch statements, or catch Exception. [NewApi]
553 } catch (IllegalAccessException e) {
554 Log.w(TAG, "Failed to access getParentNodeId method.", e);
555 } catch (InvocationTargetException e) {
556 Log.w(TAG, "The getParentNodeId method threw an exception when invoked.", e);
557 }
558 }
559
560 // Fall back on reading the ID from a serialized data if we absolutely have to.
561 return yoinkParentIdFromParcel(node);
562 }
563
564 // If this looks like it's failing, that's because it probably is. This method is relying on
565 // the implementation details of `AccessibilityNodeInfo#writeToParcel` in order to find the
566 // particular bit in the opaque parcel that represents mParentNodeId. If the implementation
567 // details change from our assumptions in this method, this will silently break.
568 @Nullable
569 private static Long yoinkParentIdFromParcel(AccessibilityNodeInfo node) {
570 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
571 Log.w(TAG, "Unexpected Android version. Unable to find the parent ID.");
572 return null;
573 }
574
575 // We're creating a copy here because writing a node to a parcel recycles it. Objects
576 // are passed by reference in Java. So even though this method doesn't seem to use the
577 // node again, it's really used in other methods that would throw exceptions if we
578 // recycle it here.
579 AccessibilityNodeInfo copy = AccessibilityNodeInfo.obtain(node);
580 final Parcel parcel = Parcel.obtain();
581 parcel.setDataPosition(0);
582 copy.writeToParcel(parcel, /*flags=*/ 0);
583 Long parentNodeId = null;
584 // Match the internal logic that sets where mParentId actually ends up finally living.
585 // This logic should match
586 // https://android.googlesource.com/platform/frameworks/base/+/0b5ca24a4/core/java/android/view/accessibility/AccessibilityNodeInfo.java#3524.
587 parcel.setDataPosition(0);
588 long nonDefaultFields = parcel.readLong();
589 int fieldIndex = 0;
590 if (isBitSet(nonDefaultFields, fieldIndex++)) {
591 parcel.readInt(); // mIsSealed
592 }
593 if (isBitSet(nonDefaultFields, fieldIndex++)) {
594 parcel.readLong(); // mSourceNodeId
595 }
596 if (isBitSet(nonDefaultFields, fieldIndex++)) {
597 parcel.readInt(); // mWindowId
598 }
599 if (isBitSet(nonDefaultFields, fieldIndex++)) {
600 parentNodeId = parcel.readLong();
601 }
602
603 parcel.recycle();
604 return parentNodeId;
605 }
606
607 private static boolean isBitSet(long flags, int bitIndex) {
608 return (flags & (1L << bitIndex)) != 0;
609 }
610
611 @Nullable
612 private Long getRecordSourceNodeId(@NonNull AccessibilityRecord node) {
613 if (getRecordSourceNodeId == null) {
614 return null;
615 }
616 try {
617 return (Long) getRecordSourceNodeId.invoke(node);
618 } catch (IllegalAccessException e) {
619 Log.w(TAG, "Failed to access the getRecordSourceNodeId method.", e);
620 } catch (InvocationTargetException e) {
621 Log.w(TAG, "The getRecordSourceNodeId method threw an exception when invoked.", e);
622 }
623 return null;
624 }
625 }
626}
bool equals(SkDrawable *a, SkDrawable *b)
AccessibilityViewEmbedder(@NonNull View rootAccessibiiltyView, int firstVirtualNodeId)
boolean onAccessibilityHoverEvent(int rootFlutterId, @NonNull MotionEvent event)
Integer getRecordFlutterId( @NonNull View embeddedView, @NonNull AccessibilityRecord record)
AccessibilityNodeInfo getRootNode( @NonNull View embeddedView, int flutterId, @NonNull Rect displayBounds)
boolean performAction(int flutterId, int accessibilityAction, @Nullable Bundle arguments)
AccessibilityNodeInfo createAccessibilityNodeInfo(int flutterId)
boolean requestSendAccessibilityEvent( @NonNull View embeddedView, @NonNull View eventOrigin, @NonNull AccessibilityEvent event)
FlutterSemanticsFlag flags
FlKeyEvent * event
GAsyncResult * result
void Log(const char *format,...) SK_PRINTF_LIKE(1
Definition: TestRunner.cpp:137
def Build(configs, env, options)
Definition: build.py:232
Definition: copy.py:1
TRect< Scalar > Rect
Definition: rect.h:769
const uintptr_t id